PenDatas.vue 38 KB

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