PenDatas.vue 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462
  1. <template>
  2. <div class="props">
  3. <t-collapse
  4. :defaultValue="['1', '2', '3']"
  5. expandIconPlacement="right"
  6. :borderless="true"
  7. >
  8. <t-collapse-panel value="1" header="通信">
  9. <t-space direction="vertical" size="small" class="w-full">
  10. <div class="form-item">
  11. <label title="http地址">http地址 </label>
  12. <t-input
  13. class="w-full"
  14. v-model="props.pen.apiUrl"
  15. @change="changeValue('apiUrl')"
  16. />
  17. </div>
  18. <div class="form-item">
  19. <label title="请求方式">请求方式</label>
  20. <t-select
  21. v-model="props.pen.apiMethod"
  22. @change="changeValue('apiMethod')"
  23. >
  24. <t-option key="GET" value="GET" label="GET" />
  25. <t-option key="POST" value="POST" label="POST" />
  26. </t-select>
  27. </div>
  28. <div class="form-item">
  29. <label title="请求头">请求头</label>
  30. <!-- <CodeEditor
  31. :json="true"
  32. :key="props.pen.id"
  33. v-model="props.pen.apiHeaders"
  34. class="mt-4"
  35. style="height: 50px"
  36. /> -->
  37. <t-button
  38. class="ml-8"
  39. shape="square"
  40. variant="outline"
  41. style="width: 24px"
  42. @click="preShowPropsEdit('apiHeaders')"
  43. >
  44. <ellipsis-icon slot="icon"/>
  45. <!-- <t-icon name="ellipsis" slot="icon"
  46. /> -->
  47. </t-button>
  48. </div>
  49. <div class="form-item" v-if="props.pen.apiMethod === 'POST'">
  50. <label title="请求体">请求体</label>
  51. <!-- <CodeEditor
  52. :json="true"
  53. :key="props.pen.id"
  54. v-model="props.pen.apiBody"
  55. class="mt-4"
  56. style="height: 50px"
  57. /> -->
  58. <t-button
  59. class="ml-8"
  60. shape="square"
  61. variant="outline"
  62. style="width: 24px"
  63. @click="preShowPropsEdit('apiBody')"
  64. >
  65. <ellipsis-icon slot="icon"/>
  66. <!-- <t-icon name="ellipsis" slot="icon"
  67. /> -->
  68. </t-button>
  69. </div>
  70. <div class="form-item">
  71. <label title="开启轮询">开启轮询</label>
  72. <t-switch
  73. class="mt-8 ml-8"
  74. v-model="props.pen.apiEnable"
  75. size="small"
  76. @change="changeValue('apiEnable')"
  77. />
  78. </div>
  79. </t-space>
  80. </t-collapse-panel>
  81. <t-collapse-panel v-if="props.pen.props.custom" value="2" header="属性">
  82. <t-space direction="vertical" size="small" class="w-full">
  83. <div v-for="item in props.pen.props.custom" class="form-item">
  84. <label :title="item.label">{{ item.label }}</label>
  85. <t-checkbox
  86. class="ml-8"
  87. v-if="item.type === 'bool'"
  88. v-model="props.pen[item.key]"
  89. @change="changeValue(item.key)"
  90. />
  91. <t-input-number
  92. class="w-full"
  93. v-else-if="item.type === 'number'"
  94. v-model.number="props.pen[item.key]"
  95. theme="column"
  96. :max="item.max"
  97. :min="item.min"
  98. @change="changeValue(item.key)"
  99. :placeholder="item.placeholder"
  100. />
  101. <t-color-picker
  102. class="w-full"
  103. v-else-if="item.type === 'color'"
  104. :enable-alpha="true"
  105. :recent-colors="null"
  106. format="CSS"
  107. :swatch-colors="defaultPureColor"
  108. :color-modes="['monochrome']"
  109. :show-primary-color-preview="false"
  110. v-model="props.pen[item.key]"
  111. @change="changeValue(item.key)"
  112. :placeholder="item.placeholder"
  113. />
  114. <t-select
  115. class="w-full"
  116. v-else-if="item.type === 'select'"
  117. size="small"
  118. :options="item.options"
  119. v-model="props.pen[item.key]"
  120. @change="changeValue(item.key)"
  121. :placeholder="item.placeholder"
  122. />
  123. <t-button
  124. class="ml-8"
  125. v-else-if="item.type === 'code'"
  126. shape="square"
  127. variant="outline"
  128. style="width: 24px"
  129. @click="showPropsEdit(item)"
  130. >
  131. <ellipsis-icon slot="icon"/>
  132. <!-- <t-icon name="ellipsis" slot="icon"
  133. /> -->
  134. </t-button>
  135. <t-slider
  136. v-else-if="item.type === 'slider'"
  137. v-model="props.pen[item.key]"
  138. :min="0"
  139. :max="1"
  140. :step="0.01"
  141. @change="changeValue(item.key)"
  142. />
  143. <t-switch
  144. size="small"
  145. class="mt-8 ml-8"
  146. v-else-if="item.type === 'switch'"
  147. v-model="props.pen[item.key]"
  148. @change="changeValue(item.key)"
  149. />
  150. <t-input
  151. class="w-full"
  152. v-else
  153. v-model="props.pen[item.key]"
  154. @change="changeValue(item.key)"
  155. :placeholder="item.placeholder"
  156. />
  157. </div>
  158. </t-space>
  159. </t-collapse-panel>
  160. <t-collapse-panel value="3" header="数据">
  161. <div
  162. class="real-times"
  163. v-if="props.pen.realTimes && props.pen.realTimes.length"
  164. >
  165. <div class="grid head">
  166. <div class="title">数据名</div>
  167. <div class="title">值</div>
  168. <div class="title">触发器</div>
  169. <div class="actions">
  170. <more-icon />
  171. <!-- <t-icon name="more" /> -->
  172. </div>
  173. </div>
  174. <div class="grid" v-for="(item, i) in props.pen.realTimes">
  175. <t-tooltip :content="item.key" placement="top">
  176. <label class="label">{{ item.label }}</label>
  177. </t-tooltip>
  178. <div class="value">
  179. <t-input
  180. v-if="item.type === 'integer'"
  181. v-model.number="props.pen[item.key]"
  182. placeholder="整数"
  183. @change="changeValue(item.key)"
  184. />
  185. <t-input-number
  186. v-else-if="item.type === 'float'"
  187. v-model="props.pen[item.key]"
  188. placeholder="浮点数"
  189. theme="normal"
  190. @change="changeValue(item.key)"
  191. />
  192. <t-switch
  193. v-else-if="item.type === 'bool'"
  194. v-model="props.pen[item.key]"
  195. class="ml-8"
  196. size="small"
  197. @change="changeValue(item.key)"
  198. />
  199. <t-button
  200. v-else-if="item.type === 'array' || item.type === 'object'"
  201. variant="outline"
  202. style="padding: 0px 2px 0 4px; margin: 0 4px"
  203. @click="editObject(item)"
  204. >
  205. <ellipsis-icon />
  206. <!-- <t-icon name="ellipsis" /> -->
  207. </t-button>
  208. <t-input
  209. v-else
  210. v-model="props.pen[item.key]"
  211. placeholder="字符串"
  212. @change="changeValue(item.key)"
  213. />
  214. <t-tooltip :content="getBindsDesc(item)" placement="top">
  215. <link-icon class="hover ml-4"
  216. :class="{ primary: item.enableMock || item.bind?.id }"
  217. @click="onBind(item)"/>
  218. <!-- <t-icon
  219. name="link"
  220. class="hover ml-4"
  221. :class="{ primary: item.enableMock || item.bind?.id }"
  222. @click="onBind(item)"
  223. /> -->
  224. </t-tooltip>
  225. </div>
  226. <div>
  227. <t-tooltip :content="item.triggers?.length || '触发器'">
  228. <t-badge
  229. :count="item.triggers?.length"
  230. size="small"
  231. dot
  232. :offset="[0, 5]"
  233. >
  234. <relativity-icon class="hover"
  235. @click="onTrigger(item)"/>
  236. <!-- <t-icon
  237. name="relativity"
  238. class="hover"
  239. @click="onTrigger(item)"
  240. /> -->
  241. </t-badge>
  242. </t-tooltip>
  243. </div>
  244. <div class="actions">
  245. <t-dropdown
  246. :options="moreOptions"
  247. @click="onMenuMore($event, item, i)"
  248. :minColumnWidth="80"
  249. >
  250. <more-icon class="more hover" />
  251. <!-- <t-icon name="more" class="more hover" /> -->
  252. </t-dropdown>
  253. </div>
  254. </div>
  255. <div class="mt-8 pb-16">
  256. <t-dropdown
  257. :options="options"
  258. @click="addRealTime"
  259. :minColumnWidth="150"
  260. >
  261. <a class="ml-12">
  262. <add-rectangle-icon />
  263. <!-- <t-icon name="add-rectangle" /> -->
  264. 添加动态数据
  265. </a>
  266. </t-dropdown>
  267. </div>
  268. </div>
  269. <div class="flex column center blank" v-else>
  270. <img src="/img/blank.png" />
  271. <div class="gray center">还没有动态数据</div>
  272. <div class="mt-8">
  273. <t-dropdown
  274. :options="options"
  275. @click="addRealTime"
  276. :minColumnWidth="150"
  277. >
  278. <t-button style="height: 30px"> 添加动态数据 </t-button>
  279. </t-dropdown>
  280. </div>
  281. </div>
  282. </t-collapse-panel>
  283. </t-collapse>
  284. </div>
  285. <t-dialog
  286. v-if="addDataDialog.show"
  287. :visible="true"
  288. class="data-dialog"
  289. :header="addDataDialog.header"
  290. @close="addDataDialog.show = false"
  291. @confirm="onConfirmData"
  292. >
  293. <div class="form-item mt-16">
  294. <label>数据名</label>
  295. <t-input
  296. v-model="addDataDialog.data.label"
  297. placeholder="简短描述"
  298. :disabled="!!addDataDialog.data.keywords"
  299. @blur="onChangeLabel"
  300. />
  301. </div>
  302. <div class="form-item mt-16">
  303. <label>属性名</label>
  304. <t-input
  305. v-model="addDataDialog.data.key"
  306. placeholder="关键字"
  307. @blur="onKeyBlur"
  308. :disabled="!!addDataDialog.data.keywords"
  309. />
  310. </div>
  311. <div class="form-item mt-16">
  312. <label>类型</label>
  313. <t-select
  314. class="w-full"
  315. :options="typeOptions"
  316. v-model="addDataDialog.data.type"
  317. placeholder="字符串"
  318. :disabled="!!addDataDialog.data.keywords"
  319. @change="onKeyBlur"
  320. />
  321. </div>
  322. </t-dialog>
  323. <t-dialog
  324. v-if="dataBindDialog.show"
  325. :visible="true"
  326. class="data-link-dialog"
  327. header="动态数据设置"
  328. @close="
  329. dataBindDialog.datasetList = undefined;
  330. dataBindDialog.show = false;
  331. "
  332. @confirm="dataBindonConfirm"
  333. :top="70"
  334. :width="700"
  335. >
  336. <t-tabs :defaultValue="1">
  337. <t-tab-panel
  338. :value="1"
  339. label="绑定数据点"
  340. :destroy-on-hide="false"
  341. style="height: 420px"
  342. >
  343. <div class="form-item mt-12">
  344. <label>当前绑定:</label>
  345. <div class="label" v-if="dataBindDialog.data.bind?.id">
  346. <t-tooltip :content="dataBindDialog.data.bind?.id">
  347. <t-tag class="mr-8 mb-8" closable @close="onRemoveBind()">
  348. {{ dataBindDialog.data.bind?.label }}
  349. </t-tag>
  350. </t-tooltip>
  351. </div>
  352. <div class="label gray" v-else>无</div>
  353. </div>
  354. <div class="form-item flex middle mt-8">
  355. <t-input
  356. v-if="dataBindDialog.url"
  357. v-model="dataBindDialog.device"
  358. placeholder="设备"
  359. style="width: 200px"
  360. />
  361. <t-select
  362. v-else
  363. class="mr-16"
  364. style="width: 200px"
  365. v-model="dataBindDialog.device"
  366. clearable
  367. placeholder="设备"
  368. @change="getDataset"
  369. >
  370. <t-option
  371. v-for="(item, i) in dataBindDialog.devices"
  372. :key="item"
  373. :value="item"
  374. :label="item"
  375. />
  376. </t-select>
  377. <t-input
  378. placeholder="搜索"
  379. v-model="dataBindDialog.input"
  380. @change="onSearchDataset"
  381. @enter="onSearchDataset"
  382. >
  383. <template #suffixIcon>
  384. <search-icon class="hover" @click="onSearchDataset"/>
  385. <!-- <t-icon name="search" class="hover" @click="onSearchDataset" /> -->
  386. </template>
  387. </t-input>
  388. </div>
  389. <t-table
  390. class="mt-12 data-list"
  391. row-key="id"
  392. :data="dataBindDialog.dataset"
  393. :columns="dataSetColumns"
  394. size="small"
  395. bordered
  396. :loading="dataBindDialog.loading"
  397. :pagination="query"
  398. @page-change="onChangePagination"
  399. :selected-row-keys="dataBindDialog.selectedIds"
  400. @select-change="onSelectBindsChange"
  401. :max-height="270"
  402. >
  403. </t-table>
  404. </t-tab-panel>
  405. <t-tab-panel
  406. :value="2"
  407. label="数据模拟"
  408. :destroy-on-hide="false"
  409. style="height: 420px"
  410. >
  411. <div class="form-item mt-20">
  412. <label>模拟值:</label>
  413. <t-input v-model="dataBindDialog.data.mock" />
  414. </div>
  415. <div class="form-item mt-20">
  416. <label>类型:</label>
  417. <t-select
  418. class="w-full"
  419. :options="typeOptions"
  420. v-model="dataBindDialog.data.type"
  421. placeholder="字符串"
  422. />
  423. </div>
  424. <div class="form-item mt-12">
  425. <label>开启:</label>
  426. <t-switch
  427. class="mt-8"
  428. size="small"
  429. v-model="dataBindDialog.data.enableMock"
  430. />
  431. </div>
  432. <h6 class="desc mt-20">模拟值说明</h6>
  433. <ul class="desc mt-4">
  434. <li class="mt-4">
  435. <label class="inline" style="width: 80px">固定值:</label>
  436. 直接填写,例如:10
  437. </li>
  438. <li class="mt-4">
  439. <label class="inline" style="width: 80px">随机值:</label>
  440. 值1,值2,...。例如:1,2,3,4,5
  441. </li>
  442. <li class="mt-4">
  443. <label class="inline" style="width: 80px">范围数字:</label>
  444. 最小值-最大值。例如:0-1.0 或 0-100
  445. </li>
  446. <li class="mt-4">
  447. <label class="inline" style="width: 80px">随机字符串:</label>
  448. [长度]。例如:[8]
  449. </li>
  450. </ul>
  451. </t-tab-panel>
  452. </t-tabs>
  453. </t-dialog>
  454. <t-dialog
  455. v-if="triggersDialog.show"
  456. :visible="true"
  457. class="data-events-dialog"
  458. header="数据触发器"
  459. @confirm="triggersDialog.show = false"
  460. @close="triggersDialog.show = false"
  461. :width="700"
  462. >
  463. <div class="body">
  464. <t-collapse
  465. v-model="triggersDialog.openedCollapses"
  466. :borderless="true"
  467. :expand-on-row-click="false"
  468. >
  469. <t-collapse-panel
  470. v-for="(trigger, i) in triggersDialog.data.triggers"
  471. :value="i"
  472. >
  473. <template #header>
  474. <t-input v-model="trigger.name" class="mr-12" />
  475. </template>
  476. <template #headerRightContent>
  477. <t-popconfirm
  478. content="确认删除该触发器吗?"
  479. @confirm="triggersDialog.data.triggers.splice(i, 1)"
  480. >
  481. <delete-icon class="hover"/>
  482. <!-- <t-icon name="delete" class="hover" /> -->
  483. </t-popconfirm>
  484. </template>
  485. <section>
  486. <div class="form-item banner">
  487. <label>触发条件</label>
  488. <div class="w-full flex middle between">
  489. <div></div>
  490. <t-radio-group v-model="trigger.conditionType">
  491. <t-radio value="and"> 满足全部条件 </t-radio>
  492. <t-radio value="or"> 满足任意条件 </t-radio>
  493. </t-radio-group>
  494. </div>
  495. </div>
  496. <div v-for="(c, index) in trigger.conditions" class="mb-12">
  497. <div class="flex middle between head">
  498. <div class="flex middle">
  499. <arrow-right-icon class="mr-4"/>
  500. <!-- <t-icon name="arrow-right" class="mr-4" /> -->
  501. 条件{{ index + 1 }}
  502. </div>
  503. <close-icon class="hover"
  504. @click="trigger.conditions.splice(index, 1)"/>
  505. <!-- <t-icon
  506. name="close"
  507. class="hover"
  508. @click="trigger.conditions.splice(index, 1)"
  509. /> -->
  510. </div>
  511. <div class="px-16 py-4">
  512. <div class="form-item mt-4">
  513. <label>条件类型</label>
  514. <t-radio-group v-model="c.type">
  515. <t-radio value=""> 关系条件 </t-radio>
  516. <t-radio value="fn"> 高级条件 </t-radio>
  517. </t-radio-group>
  518. </div>
  519. <template v-if="!c.type">
  520. <div class="form-item mt-8">
  521. <label>比较条件</label>
  522. <div class="flex middle">
  523. <label class="shrink-0 mr-8">数据</label>
  524. <t-select
  525. v-model="c.operator"
  526. placeholder="关系运算"
  527. :options="operatorOptions"
  528. class="shrink-0 mr-8"
  529. style="width: 80px"
  530. />
  531. <t-select
  532. v-model="c.valueType"
  533. class="shrink-0 mr-8"
  534. style="width: 110px"
  535. placeholder="固定值"
  536. >
  537. <t-option key="" value="" label="固定值">
  538. 固定值
  539. </t-option>
  540. <t-option key="prop" value="prop" label="对象属性值">
  541. 对象属性值
  542. </t-option>
  543. </t-select>
  544. <template v-if="!c.valueType">
  545. <t-input
  546. @change="valueChange($event,c)"
  547. v-model="c.value"
  548. class="shrink-0"
  549. style="width: 320px"
  550. />
  551. </template>
  552. <template v-else>
  553. <t-tree-select
  554. v-model="c.target"
  555. :data="penTree"
  556. filterable
  557. placeholder="对象"
  558. class="shrink-0 mr-8"
  559. style="width: 160px"
  560. @change="onChangeTriggerTarget(c)"
  561. />
  562. <t-select-input
  563. v-model:inputValue="c.value"
  564. :value="c.label"
  565. v-model:popupVisible="c.popupVisible"
  566. allow-input
  567. clearable
  568. @clear="c.label = undefined"
  569. @focus="c.popupVisible = true"
  570. @blur="c.popupVisible = false"
  571. @input-change="onInput(c)"
  572. class="shrink-0"
  573. style="width: 152px"
  574. >
  575. <template #panel>
  576. <ul style="padding: 8px 12px">
  577. <li
  578. v-for="item in cProps"
  579. :key="item.value"
  580. @click="
  581. c.value = item.value;
  582. c.label = item.label;
  583. c.popupVisible = false;
  584. "
  585. >
  586. {{ item.label }}
  587. </li>
  588. </ul>
  589. </template>
  590. </t-select-input>
  591. </template>
  592. </div>
  593. </div>
  594. </template>
  595. <template v-else>
  596. <div>function condition(pen) {</div>
  597. <CodeEditor class="mt-4" @change="codeChange($event,c)" v-model="c.fnJs" />
  598. <div class="mt-4">}</div>
  599. </template>
  600. </div>
  601. </div>
  602. <div class="mt-8">
  603. <a @click="addTriggerCondition(trigger)"> + 添加条件 </a>
  604. </div>
  605. <div class="form-item banner mt-16">
  606. <label>执行动作</label>
  607. </div>
  608. <Actions class="mt-8" :data="trigger" />
  609. </section>
  610. </t-collapse-panel>
  611. </t-collapse>
  612. <div class="mt-8">
  613. <a @click="onAddTrigger"> + 添加触发器 </a>
  614. </div>
  615. </div>
  616. </t-dialog>
  617. <t-dialog
  618. v-if="ojbectDialog.show"
  619. :visible="true"
  620. header="数据编辑"
  621. @confirm="onOkEditOjbect"
  622. @close="ojbectDialog.show = false"
  623. :width="700"
  624. >
  625. <div class="py-8">
  626. <CodeEditor
  627. :json="true"
  628. v-model="ojbectDialog.data"
  629. style="height: 300px"
  630. />
  631. </div>
  632. </t-dialog>
  633. <t-dialog
  634. v-if="propsDialog.show"
  635. :visible="true"
  636. :header="propsDialog.header"
  637. @confirm="onOkPropsEdit"
  638. @close="propsDialog.show = false"
  639. :width="700"
  640. >
  641. <div class="py-8">
  642. <CodeEditor
  643. :json="true"
  644. :language="'json'"
  645. v-model="propsDialog.value"
  646. style="height: 300px"
  647. />
  648. </div>
  649. <div class="gray" style="font-size: 12px">
  650. {{ propsDialog.placeholder }}
  651. </div>
  652. </t-dialog>
  653. </template>
  654. <script lang="ts" setup>
  655. import {
  656. getCurrentInstance,
  657. onBeforeMount,
  658. onUnmounted,
  659. reactive,
  660. ref,
  661. toRaw,
  662. } from 'vue';
  663. import { useRoute, useRouter } from 'vue-router';
  664. import { MessagePlugin } from 'tdesign-vue-next';
  665. import axios from 'axios';
  666. import { debounce } from '@/services/debouce';
  667. import { getPenTree, typeOptions, changeType } from '@/services/common';
  668. import { searchPinyin } from '@/services/pinyin';
  669. import { updatePen } from './pen';
  670. import { getter, setter, queryURLParams, deepClone } from '@meta2d/core';
  671. import CodeEditor from '@/views/components/common/CodeEditor.vue';
  672. import Actions from './Actions.vue';
  673. import { useSelection } from '@/services/selections';
  674. import { defaultGradientColor, defaultPureColor } from '@/services/defaults';
  675. import { getLe5le3d, getLe5leV, getLe5le2d } from '@/services/api';
  676. import { EllipsisIcon, MoreIcon, LinkIcon, RelativityIcon, AddRectangleIcon, SearchIcon ,DeleteIcon, ArrowRightIcon, CloseIcon } from 'tdesign-icons-vue-next';
  677. const route = useRoute();
  678. const router = useRouter();
  679. const {
  680. proxy: { $forceUpdate },
  681. }: any = getCurrentInstance();
  682. const props = defineProps<{
  683. pen: any;
  684. }>();
  685. const options = ref<any>([
  686. {
  687. value: '',
  688. content: '自定义',
  689. divider: true,
  690. },
  691. {
  692. value: 'x',
  693. content: 'X',
  694. type: 'float',
  695. keywords: true,
  696. },
  697. {
  698. value: 'y',
  699. content: 'Y',
  700. type: 'float',
  701. keywords: true,
  702. },
  703. {
  704. value: 'width',
  705. content: '宽',
  706. type: 'float',
  707. keywords: true,
  708. },
  709. {
  710. value: 'height',
  711. content: '高',
  712. type: 'float',
  713. keywords: true,
  714. },
  715. {
  716. value: 'visible',
  717. content: '显示',
  718. type: 'bool',
  719. keywords: true,
  720. },
  721. {
  722. value: 'text',
  723. content: '文字',
  724. // keywords: true,
  725. },
  726. {
  727. value: 'progress',
  728. content: '进度',
  729. type: 'float',
  730. keywords: true,
  731. },
  732. {
  733. value: 'showChild',
  734. content: '状态',
  735. type: 'integer',
  736. keywords: true,
  737. },
  738. {
  739. value: 'rotate',
  740. content: '旋转',
  741. type: 'integer',
  742. keywords: true,
  743. },
  744. ]);
  745. const moreOptions = ref<any>([
  746. {
  747. value: 'edit',
  748. content: '编辑',
  749. },
  750. {
  751. value: 'delete',
  752. content: '移除',
  753. },
  754. ]);
  755. const addDataDialog = reactive<any>({
  756. show: false,
  757. data: undefined,
  758. });
  759. const dataBindDialog = reactive<any>({
  760. show: false,
  761. data: undefined,
  762. });
  763. const ojbectDialog = reactive<any>({
  764. show: false,
  765. data: undefined,
  766. });
  767. const dataSetColumns = [
  768. {
  769. colKey: 'row-select',
  770. type: 'single',
  771. width: 50,
  772. },
  773. {
  774. colKey: 'device',
  775. title: '设备',
  776. ellipsis: { theme: 'light', trigger: 'context-menu' },
  777. },
  778. {
  779. colKey: 'label',
  780. title: '数据点名称',
  781. ellipsis: { theme: 'light', trigger: 'context-menu' },
  782. },
  783. {
  784. colKey: 'id',
  785. title: '数据点ID',
  786. ellipsis: { theme: 'light', trigger: 'context-menu' },
  787. },
  788. {
  789. colKey: 'type',
  790. title: '类型',
  791. width: 100,
  792. },
  793. {
  794. colKey: 'mock',
  795. title: '值范围',
  796. ellipsis: true,
  797. },
  798. ];
  799. const operatorOptions = ref<any>([
  800. { label: '=', value: '=' },
  801. { label: '!=', value: '!=' },
  802. { label: '>', value: '>' },
  803. { label: '<', value: '<' },
  804. { label: '>=', value: '>=' },
  805. { label: '<=', value: '<=' },
  806. { label: '包含', value: '[)' },
  807. { label: '不包含', value: '![)' },
  808. ]);
  809. const cProps = [
  810. {
  811. value: 'x',
  812. label: 'X',
  813. },
  814. {
  815. value: 'y',
  816. label: 'Y',
  817. },
  818. {
  819. value: 'width',
  820. label: '宽',
  821. },
  822. {
  823. value: 'height',
  824. label: '高',
  825. },
  826. {
  827. value: 'visible',
  828. label: '显示',
  829. },
  830. {
  831. value: 'text',
  832. label: '文字',
  833. },
  834. {
  835. value: 'progress',
  836. label: '进度',
  837. },
  838. {
  839. value: 'showChild',
  840. label: '状态',
  841. },
  842. {
  843. value: 'rotate',
  844. label: '旋转',
  845. },
  846. ];
  847. const query = reactive<{
  848. current: number;
  849. pageSize: number;
  850. total: number;
  851. range: any[];
  852. }>({
  853. current: 1,
  854. pageSize: 10,
  855. total: 0,
  856. range: [],
  857. });
  858. const triggersDialog = reactive<any>({
  859. show: false,
  860. data: undefined,
  861. });
  862. const penTree: any = ref([]);
  863. let timer: any;
  864. onBeforeMount(() => {
  865. // realTimesOptions - 扩展的动态数据下拉列表
  866. if (props.pen.realTimesOptions) {
  867. options.value[options.value.length - 1].divider = true;
  868. options.value.push(...props.pen.realTimesOptions);
  869. }
  870. timer = setInterval($forceUpdate, 1000);
  871. });
  872. const addRealTime = (e: any) => {
  873. if (e.keywords || e.value === 'text') {
  874. if (!props.pen.realTimes) {
  875. props.pen.realTimes = [];
  876. }
  877. props.pen.realTimes.push({
  878. label: e.content,
  879. key: e.value,
  880. type: e.type,
  881. keywords: e.keywords,
  882. });
  883. return;
  884. }
  885. addDataDialog.header = '添加动态数据';
  886. addDataDialog.data = {
  887. label: e.content,
  888. key: e.value,
  889. type: e.type,
  890. keywords: e.keywords,
  891. };
  892. if (e.keywords) {
  893. addDataDialog.data.label = '';
  894. }
  895. addDataDialog.show = true;
  896. };
  897. const onKeyBlur = () => {
  898. if (addDataDialog.data) {
  899. let value = getter(props.pen, addDataDialog.data.key);
  900. if (value) {
  901. setter(addDataDialog.data, 'value', value);
  902. } else {
  903. addDataDialog.data.value = null;
  904. }
  905. }
  906. };
  907. const onChangeLabel = () => {
  908. if (!addDataDialog.data.key) {
  909. addDataDialog.data.key = addDataDialog.data.label;
  910. }
  911. };
  912. const onConfirmData = () => {
  913. if (!props.pen.realTimes) {
  914. props.pen.realTimes = [];
  915. }
  916. if (!addDataDialog.data.label || !addDataDialog.data.key) {
  917. MessagePlugin.error('数据名或属性名不能为空!');
  918. return;
  919. }
  920. if (addDataDialog.header === '添加动态数据') {
  921. const found = props.pen.realTimes.findIndex((item: any) => {
  922. return item.key === addDataDialog.data.key;
  923. });
  924. if (found > -1) {
  925. MessagePlugin.error('已经存在相同属性数据!');
  926. return;
  927. }
  928. props.pen.realTimes.push(addDataDialog.data);
  929. }
  930. addDataDialog.show = false;
  931. };
  932. const onMenuMore = (e: any, item: any, i: number) => {
  933. switch (e.value) {
  934. case 'edit':
  935. addDataDialog.header = '编辑动态数据';
  936. setter(item, 'value', getter(props.pen, item.key));
  937. addDataDialog.data = item;
  938. addDataDialog.show = true;
  939. break;
  940. case 'delete':
  941. props.pen.realTimes.splice(i, 1);
  942. break;
  943. default:
  944. break;
  945. }
  946. };
  947. const onBind = (item: any) => {
  948. dataBindDialog.data = item;
  949. dataBindDialog.input = '';
  950. dataBindDialog.selectedIds = [];
  951. if (item.bind && item.bind.id) {
  952. dataBindDialog.selectedIds.push(item.bind.id);
  953. }
  954. dataBindDialog.show = true;
  955. getDataset();
  956. };
  957. const onSearchDataset = () => {
  958. debounce(getDataset, 300);
  959. };
  960. const getDataset = async () => {
  961. // @ts-ignore
  962. const data: Meta2dBackData = meta2d.data();
  963. if (!data.dataset) {
  964. return;
  965. }
  966. dataBindDialog.loading = true;
  967. if (data.dataset.data?.url) {
  968. const ret: any = await axios.get(data.dataset.data.url, {
  969. params: {
  970. device: dataBindDialog.device || '',
  971. q: dataBindDialog.input,
  972. current: query.current,
  973. pageSize: query.pageSize,
  974. },
  975. });
  976. dataBindDialog.dataset = ret;
  977. dataBindDialog.url = true;
  978. query.total = ret.total;
  979. } else {
  980. dataBindDialog.url = false;
  981. if (!dataBindDialog.datasetList) {
  982. const ret = await axios.post(`/api/data/datasource/get`, {
  983. id: data.dataset.id,
  984. });
  985. if (ret?.data) {
  986. data.dataset.data = ret.data;
  987. dataBindDialog.datasetList = data.dataset.data.devices;
  988. const set = new Set<string>();
  989. for (const item of dataBindDialog.datasetList) {
  990. set.add(item.device);
  991. }
  992. dataBindDialog.devices = Array.from(set);
  993. } else {
  994. dataBindDialog.dataset = [];
  995. dataBindDialog.devices = [];
  996. return;
  997. }
  998. }
  999. if (dataBindDialog.device || dataBindDialog.input) {
  1000. dataBindDialog.dataset = dataBindDialog.datasetList.filter(
  1001. (item: any) => {
  1002. if (dataBindDialog.device && item.device !== dataBindDialog.device) {
  1003. return;
  1004. }
  1005. return (
  1006. searchPinyin(item.label, dataBindDialog.input) ||
  1007. item.id.indexOf(dataBindDialog.input) > -1
  1008. );
  1009. }
  1010. );
  1011. query.total = dataBindDialog.datasetList.length;
  1012. } else {
  1013. dataBindDialog.dataset = [...dataBindDialog.datasetList];
  1014. query.total = dataBindDialog.datasetList.length;
  1015. }
  1016. }
  1017. dataBindDialog.loading = false;
  1018. };
  1019. const onChangePagination = (pageInfo: any) => {
  1020. router.push({
  1021. path: route.path,
  1022. query: { current: pageInfo.current, pageSize: pageInfo.pageSize },
  1023. });
  1024. query.current = pageInfo.current;
  1025. query.pageSize = pageInfo.pageSize;
  1026. getDataset();
  1027. };
  1028. const onSelectBindsChange = (value: string[], options: any) => {
  1029. if (options.type === 'check') {
  1030. dataBindDialog.selectedIds = value;
  1031. dataBindDialog.data.bind = toRaw(options.selectedRowData[0]);
  1032. dataBindDialog.data.mock = dataBindDialog.data.bind.mock;
  1033. dataBindDialog.data.type = dataBindDialog.data.bind.type;
  1034. doBindInit();
  1035. } else if (options.type === 'uncheck') {
  1036. dataBindDialog.selectedIds = [];
  1037. dataBindDialog.data.bind = {};
  1038. }
  1039. };
  1040. const doBindInit = () => {
  1041. let { id } = dataBindDialog.data;
  1042. if (props.pen.name === 'echarts' && id.includes('echarts.option.series')) {
  1043. const { replaceMode } = props.pen.echarts;
  1044. const { xAxis } = props.pen.echarts.option;
  1045. let beforeV = getter(props.pen, id);
  1046. if (Array.isArray(beforeV) && replaceMode === 0) {
  1047. //追加
  1048. setter(props.pen, id, []);
  1049. let _key = 'echarts.option.xAxis.data';
  1050. if (Array.isArray(xAxis) && xAxis.length) {
  1051. _key = 'echarts.option.xAxis.0.data';
  1052. }
  1053. setter(props.pen, _key, []);
  1054. }
  1055. }
  1056. };
  1057. const dataBindonConfirm = () => {
  1058. dataBindDialog.show = false;
  1059. dataBindDialog.datasetList = undefined;
  1060. meta2d.initBinds();
  1061. };
  1062. const onRemoveBind = () => {
  1063. dataBindDialog.selectedIds = [];
  1064. dataBindDialog.data.bind = undefined;
  1065. };
  1066. const getBindsDesc = (item: any) => {
  1067. if (item.bind?.label) {
  1068. return item.bind.label;
  1069. }
  1070. if (item.enableMock && item.mock) {
  1071. return item.mock;
  1072. }
  1073. return '动态数据';
  1074. };
  1075. const { selections } = useSelection();
  1076. const changeValue = (prop: string) => {
  1077. updatePen(props.pen, prop);
  1078. selections.pen[prop] = getter(props.pen, prop);
  1079. if (prop === 'iframe') {
  1080. getThumbImg();
  1081. }
  1082. };
  1083. const onTrigger = (item: any) => {
  1084. if (!item.triggers) {
  1085. item.triggers = [];
  1086. }
  1087. triggersDialog.openedCollapses = [0];
  1088. triggersDialog.data = item;
  1089. triggersDialog.show = true;
  1090. penTree.value = getPenTree();
  1091. };
  1092. const onAddTrigger = () => {
  1093. const i = triggersDialog.data.triggers.length;
  1094. triggersDialog.data.triggers.push({
  1095. name: `触发器${i + 1}`,
  1096. conditionType: 'and',
  1097. conditions: [],
  1098. actions: [],
  1099. });
  1100. triggersDialog.openedCollapses.push(i);
  1101. };
  1102. const addTriggerCondition = (trigger: any) => {
  1103. trigger.conditions.push({
  1104. type: '',
  1105. operator: '=',
  1106. valueType: '',
  1107. });
  1108. };
  1109. const onChangeTriggerTarget = (c: any) => {
  1110. /*
  1111. c.targetProps = [
  1112. {
  1113. value: 'x',
  1114. label: 'X',
  1115. },
  1116. {
  1117. value: 'y',
  1118. label: 'Y',
  1119. },
  1120. {
  1121. value: 'width',
  1122. label: '宽',
  1123. },
  1124. {
  1125. value: 'height',
  1126. label: '高',
  1127. },
  1128. {
  1129. value: 'visible',
  1130. label: '显示',
  1131. },
  1132. {
  1133. value: 'text',
  1134. label: '文字',
  1135. },
  1136. {
  1137. value: 'progress',
  1138. label: '进度',
  1139. },
  1140. {
  1141. value: 'showChild',
  1142. label: '状态',
  1143. },
  1144. {
  1145. value: 'rotate',
  1146. label: '旋转',
  1147. },
  1148. ];*/
  1149. const target: any = meta2d.findOne(c.target);
  1150. if (target && target.realTimes) {
  1151. for (const item of target.realTimes) {
  1152. const found = cProps.findIndex((elem: any) => elem.value === item.key);
  1153. if (found < 0) {
  1154. cProps.push({
  1155. value: item.key,
  1156. label: item.label,
  1157. });
  1158. }
  1159. }
  1160. }
  1161. };
  1162. const onInput = (item: any) => {
  1163. item.label = item.value;
  1164. };
  1165. const editObject = (item: any) => {
  1166. ojbectDialog.data = props.pen[item.key];
  1167. ojbectDialog.item = item;
  1168. ojbectDialog.show = true;
  1169. };
  1170. const onOkEditOjbect = () => {
  1171. if (ojbectDialog.data) {
  1172. if (
  1173. ojbectDialog.item.type === 'array' &&
  1174. !Array.isArray(ojbectDialog.data)
  1175. ) {
  1176. MessagePlugin.error('请输入数组格式数据');
  1177. return;
  1178. }
  1179. props.pen[ojbectDialog.item.key] = ojbectDialog.data;
  1180. props.pen[ojbectDialog.item.key] = updatePen(
  1181. props.pen,
  1182. ojbectDialog.item.key
  1183. );
  1184. ojbectDialog.show = false;
  1185. } else {
  1186. MessagePlugin.error('请输入严格的JSON格式数据');
  1187. }
  1188. };
  1189. const propsDialog = reactive<any>({
  1190. show: false,
  1191. });
  1192. // const changeValue = (prop: string) => {
  1193. // updatePen(props.pen, prop);
  1194. // selections.pen[prop] = getter(props.pen, prop);
  1195. // if (prop === 'iframe') {
  1196. // getThumbImg();
  1197. // }
  1198. // };
  1199. const getThumbImg = async () => {
  1200. //改iframe地址后
  1201. let arr = props.pen.iframe.split('?');
  1202. let id = queryURLParams(arr[1]).id;
  1203. if (!id) {
  1204. return;
  1205. }
  1206. // let projection = {
  1207. // image: 1,
  1208. // _id: 1,
  1209. // name: 1,
  1210. // };
  1211. let projection = 'image,_id,name';
  1212. let res: any;
  1213. if (arr[0].indexOf('2d.le5le') !== -1) {
  1214. res = await getLe5le2d(id, projection);
  1215. } else if (arr[0].indexOf('v.le5le') !== -1) {
  1216. res = await getLe5leV(id, projection);
  1217. } else if (arr[0].indexOf('3d.le5le') !== -1) {
  1218. res = await getLe5le3d(id, projection);
  1219. }
  1220. if (res) {
  1221. props.pen.thumbImg = res.image;
  1222. }
  1223. props.pen.onRenderPenRaw?.(props.pen);
  1224. };
  1225. const preShowPropsEdit = (key: string) => {
  1226. showPropsEdit({
  1227. key,
  1228. // item: {
  1229. // key,
  1230. type: 'code',
  1231. label: key,
  1232. placeholder: '支持设置动态参数,例如:{"Authorization": "Bearer ${token}"}',
  1233. // },
  1234. });
  1235. };
  1236. const showPropsEdit = (item: any) => {
  1237. propsDialog.key = item.key;
  1238. propsDialog.header = `${item.label}(${item.key})`;
  1239. propsDialog.value = props.pen[item.key];
  1240. propsDialog.placeholder = item.placeholder;
  1241. propsDialog.show = true;
  1242. };
  1243. const onOkPropsEdit = () => {
  1244. if (!propsDialog.value) {
  1245. MessagePlugin.error('数据不满足json格式');
  1246. return;
  1247. }
  1248. props.pen[propsDialog.key] = propsDialog.value;
  1249. updatePen(props.pen, propsDialog.key);
  1250. propsDialog.show = false;
  1251. };
  1252. const codeChange = (e:any,a:any)=>{
  1253. a.fn = null;
  1254. }
  1255. const valueChange = (e,c:any)=>{
  1256. c.value= changeType(e);
  1257. }
  1258. onUnmounted(() => {
  1259. clearInterval(timer);
  1260. });
  1261. </script>
  1262. <style lang="postcss" scoped>
  1263. .props {
  1264. height: 100%;
  1265. .grid {
  1266. grid-template-columns: 70px 100px 44px 30px;
  1267. padding: 0 12px;
  1268. &.head {
  1269. background: var(--color-background-input);
  1270. line-height: 36px;
  1271. margin-bottom: 6px;
  1272. .title {
  1273. line-height: 36px;
  1274. }
  1275. }
  1276. }
  1277. .blank {
  1278. height: 70%;
  1279. img {
  1280. padding: 16px;
  1281. opacity: 0.9;
  1282. }
  1283. }
  1284. .label {
  1285. width: fix-content;
  1286. font-size: 10px;
  1287. line-height: 28px;
  1288. color: var(--color-desc);
  1289. }
  1290. .value {
  1291. padding-right: 8px;
  1292. display: flex;
  1293. align-items: center;
  1294. justify-content: space-between;
  1295. svg {
  1296. flex-shrink: 0;
  1297. margin-right: 4px;
  1298. }
  1299. & > div {
  1300. width: 72px;
  1301. &.t-switch {
  1302. width: fit-content;
  1303. margin-left: 4px;
  1304. }
  1305. }
  1306. :deep(.t-input) {
  1307. padding-left: 4px;
  1308. height: 26px;
  1309. border-color: transparent;
  1310. &:hover {
  1311. border-color: var(--color-primary);
  1312. }
  1313. }
  1314. }
  1315. .actions {
  1316. text-align: right;
  1317. padding-right: 2px;
  1318. }
  1319. .data-list {
  1320. :deep(.t-table__header--fixed:not(.t-table__header--multiple) > tr > th) {
  1321. background: none;
  1322. }
  1323. :deep(.t-table__pagination) {
  1324. padding-bottom: 0;
  1325. }
  1326. }
  1327. }
  1328. .body {
  1329. :deep(.t-collapse.t--border-less) {
  1330. .t-collapse-panel__header {
  1331. border-top: none;
  1332. border-bottom: 1px solid var(--td-border-level-1-color);
  1333. padding: 8px 0;
  1334. .t-input {
  1335. border: none;
  1336. padding-left: 0;
  1337. font-size: 14px;
  1338. }
  1339. }
  1340. .t-collapse-panel__content {
  1341. padding: 8px 0;
  1342. }
  1343. }
  1344. .title {
  1345. position: relative;
  1346. margin: 8px 0;
  1347. :deep(.t-input) {
  1348. border-color: var(--color-background-input);
  1349. border-radius: 0;
  1350. border-left: none;
  1351. border-top: none;
  1352. border-right: none;
  1353. padding-left: 0;
  1354. padding-bottom: 8px;
  1355. font-size: 14px;
  1356. &:hover {
  1357. border-color: var(--color-border-input);
  1358. }
  1359. }
  1360. }
  1361. .head {
  1362. margin-top: 10px;
  1363. }
  1364. .banner {
  1365. background-color: var(--color-background-input);
  1366. padding: 0 12px;
  1367. }
  1368. }
  1369. </style>