PenDatas.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. <template>
  2. <div class="props">
  3. <div class="px-16 py-16" v-if="pen.realTimes && pen.realTimes.length">
  4. <div class="grid" style="line-height: 30px">
  5. <div class="title">数据名</div>
  6. <div class="title ml-8">值</div>
  7. <div>
  8. <div style="text-align: right; padding-right: 2px">
  9. <t-icon name="more" />
  10. </div>
  11. </div>
  12. </div>
  13. <div class="grid" v-for="(item, i) in pen.realTimes">
  14. <t-tooltip :content="item.key" placement="top">
  15. <label class="label">{{ item.label }}</label>
  16. </t-tooltip>
  17. <div class="value">
  18. <t-input v-model="item.value" />
  19. </div>
  20. <div class="actions">
  21. <t-tooltip content="变量绑定" placement="top">
  22. <t-icon name="link" class="hover" @click="onBind(item)" />
  23. </t-tooltip>
  24. <t-dropdown
  25. :options="moreOptions"
  26. @click="onMenuMore($event, item, i)"
  27. :minColumnWidth="80"
  28. >
  29. <t-icon name="more" class="more hover" />
  30. </t-dropdown>
  31. </div>
  32. </div>
  33. <div class="mt-8">
  34. <t-dropdown
  35. :options="options"
  36. @click="addRealTime"
  37. :minColumnWidth="150"
  38. >
  39. <a> <t-icon name="add-rectangle" /> 添加动态数据 </a>
  40. </t-dropdown>
  41. </div>
  42. </div>
  43. <div class="flex column center blank" v-else>
  44. <img src="/img/blank.png" />
  45. <div class="gray center">还没有动态数据</div>
  46. <div class="mt-8">
  47. <t-dropdown
  48. :options="options"
  49. @click="addRealTime"
  50. :minColumnWidth="150"
  51. >
  52. <t-button style="height: 30px"> 添加动态数据 </t-button>
  53. </t-dropdown>
  54. </div>
  55. </div>
  56. </div>
  57. <t-dialog
  58. v-if="addDataDialog.show"
  59. :visible="true"
  60. class="data-dialog"
  61. :header="addDataDialog.header"
  62. @close="addDataDialog.show = false"
  63. @confirm="onConfirmData"
  64. >
  65. <div class="form-item mt-16">
  66. <label>数据名</label>
  67. <t-input
  68. v-model="addDataDialog.data.label"
  69. placeholder="简短描述"
  70. :disabled="!!addDataDialog.data.keywords"
  71. @blur="onChangeLabel"
  72. />
  73. </div>
  74. <div class="form-item mt-16">
  75. <label>属性名</label>
  76. <t-input
  77. v-model="addDataDialog.data.key"
  78. placeholder="关键字"
  79. :disabled="!!addDataDialog.data.keywords"
  80. />
  81. </div>
  82. <div class="form-item mt-16">
  83. <label>类型</label>
  84. <t-select
  85. class="w-full"
  86. :options="typeOptions"
  87. v-model="addDataDialog.data.type"
  88. placeholder="字符串"
  89. :disabled="!!addDataDialog.data.keywords"
  90. @change="addDataDialog.data.value = null"
  91. />
  92. </div>
  93. <div class="form-item mt-16">
  94. <label>值</label>
  95. <div class="flex-grow" v-if="addDataDialog.data.type === 'number'">
  96. <t-input
  97. class="w-full"
  98. v-model="addDataDialog.data.value"
  99. placeholder="数字"
  100. />
  101. <div class="desc mt-8">
  102. 固定数字:直接输入数字。例如:5<br />
  103. 随机范围数字:最小值-最大值。例如:0-1 或 0-100<br />
  104. 随机指定数字:数字1,数字2,数字3... 。 例如:1,5,10,20<br />
  105. </div>
  106. </div>
  107. <t-select
  108. v-else-if="addDataDialog.data.type === 'bool'"
  109. v-model="addDataDialog.data.value"
  110. >
  111. <t-option :key="true" :value="true" label="true"></t-option>
  112. <t-option :key="false" :value="false" label="false"></t-option>
  113. <t-option key="随机" label="随机"></t-option>
  114. </t-select>
  115. <div class="flex-grow" v-else>
  116. <t-input
  117. class="w-full"
  118. v-model="addDataDialog.data.value"
  119. placeholder="字符串"
  120. />
  121. <div class="desc mt-8">
  122. 固定文字:直接输入。例如:大屏可视化<br />
  123. 随机文本:[文本长度]。例如:[8] 或 [16]<br />
  124. 随机指定文本:{文本1,文本2,文本3...} 。 例如:{大屏,可视化}
  125. <br />
  126. </div>
  127. </div>
  128. </div>
  129. </t-dialog>
  130. <t-dialog
  131. v-if="dataBindDialog.show"
  132. :visible="true"
  133. class="data-link-dialog"
  134. header="变量绑定"
  135. @cancel="
  136. dataBindDialog.data.binds = dataBindDialog.bkBinds;
  137. dataBindDialog.show = false;
  138. "
  139. @confirm="dataBindDialog.show = false"
  140. :width="700"
  141. >
  142. <div class="form-item">
  143. <label>当前绑定:</label>
  144. <div class="label" v-if="dataBindDialog.data.binds?.length">
  145. <t-tooltip
  146. v-for="(tag, index) in dataBindDialog.data.binds"
  147. :key="index"
  148. :content="tag.id"
  149. trigger="click"
  150. >
  151. <t-tag class="mr-8 mb-8" closable @close="onRemoveBind(index)">
  152. {{ tag.label }}
  153. </t-tag>
  154. </t-tooltip>
  155. </div>
  156. <div class="label gray" v-else>无</div>
  157. </div>
  158. <div class="form-item mt-8">
  159. <t-input
  160. placeholder="搜索"
  161. v-model="dataBindDialog.input"
  162. @change="onSearchDataSet"
  163. @enter="onSearchDataSet"
  164. >
  165. <template #suffixIcon>
  166. <t-icon name="search" class="hover" @click="onSearchDataSet" />
  167. </template>
  168. </t-input>
  169. </div>
  170. <t-table
  171. class="mt-12 data-list"
  172. row-key="id"
  173. :data="dataBindDialog.dataSet"
  174. :columns="dataSetColumns"
  175. size="small"
  176. bordered
  177. :loading="dataBindDialog.loading"
  178. :pagination="query"
  179. @page-change="onChangePagination"
  180. :selected-row-keys="dataBindDialog.selectedIds"
  181. @select-change="onSelectBindsChange"
  182. >
  183. </t-table>
  184. </t-dialog>
  185. </template>
  186. <script lang="ts" setup>
  187. import { onBeforeMount, reactive, ref, toRaw } from 'vue';
  188. import { useRoute, useRouter } from 'vue-router';
  189. import { MessagePlugin } from 'tdesign-vue-next';
  190. import axios from 'axios';
  191. import { debounce } from '@/services/debouce';
  192. const route = useRoute();
  193. const router = useRouter();
  194. const { pen } = defineProps<{
  195. pen: any;
  196. }>();
  197. const options = ref<any>([
  198. {
  199. value: '',
  200. content: '自定义',
  201. divider: true,
  202. },
  203. {
  204. value: 'x',
  205. content: 'X',
  206. type: 'number',
  207. keywords: true,
  208. },
  209. {
  210. value: 'y',
  211. content: 'Y',
  212. type: 'number',
  213. keywords: true,
  214. },
  215. {
  216. value: 'width',
  217. content: '宽',
  218. type: 'number',
  219. keywords: true,
  220. },
  221. {
  222. value: 'height',
  223. content: '高',
  224. type: 'number',
  225. keywords: true,
  226. },
  227. {
  228. value: 'visible',
  229. content: '显示',
  230. type: 'bool',
  231. keywords: true,
  232. },
  233. {
  234. value: 'text',
  235. content: '文字',
  236. keywords: true,
  237. },
  238. {
  239. value: 'progress',
  240. content: '进度',
  241. keywords: true,
  242. },
  243. {
  244. value: 'showChild',
  245. content: '状态',
  246. keywords: true,
  247. },
  248. {
  249. value: 'rotate',
  250. content: '旋转',
  251. type: 'number',
  252. keywords: true,
  253. },
  254. ]);
  255. const moreOptions = ref<any>([
  256. {
  257. value: 'edit',
  258. content: '编辑',
  259. },
  260. {
  261. value: 'delete',
  262. content: '移除',
  263. },
  264. ]);
  265. const typeOptions = [
  266. {
  267. label: '字符串',
  268. },
  269. {
  270. label: '数字',
  271. value: 'number',
  272. },
  273. {
  274. label: '布尔',
  275. value: 'bool',
  276. },
  277. ];
  278. const addDataDialog = reactive<any>({
  279. show: false,
  280. data: undefined,
  281. });
  282. const dataBindDialog = reactive<any>({
  283. show: false,
  284. data: undefined,
  285. });
  286. const dataSetColumns = [
  287. {
  288. colKey: 'row-select',
  289. type: 'multiple',
  290. width: 50,
  291. },
  292. {
  293. colKey: 'id',
  294. title: '编号',
  295. width: 150,
  296. ellipsis: { theme: 'light', trigger: 'context-menu' },
  297. },
  298. {
  299. colKey: 'label',
  300. title: '变量名称',
  301. width: 220,
  302. ellipsis: { theme: 'light', trigger: 'context-menu' },
  303. },
  304. {
  305. colKey: 'case',
  306. title: '场景',
  307. ellipsis: { theme: 'light', trigger: 'context-menu' },
  308. },
  309. ];
  310. const query = reactive<{
  311. current: number;
  312. pageSize: number;
  313. total: number;
  314. range: any[];
  315. }>({
  316. current: 1,
  317. pageSize: 10,
  318. total: 0,
  319. range: [],
  320. });
  321. onBeforeMount(() => {
  322. if (pen.realTimesOptions) {
  323. options.value[options.value.length - 1].divider = true;
  324. options.value.push(...pen.realTimesOptions);
  325. }
  326. });
  327. const addRealTime = (e: any) => {
  328. if (e.keywords) {
  329. if (!pen.realTimes) {
  330. pen.realTimes = [];
  331. }
  332. pen.realTimes.push({
  333. label: e.content,
  334. key: e.value,
  335. type: e.type,
  336. keywords: e.keywords,
  337. });
  338. return;
  339. }
  340. addDataDialog.header = '添加动态数据';
  341. addDataDialog.data = {
  342. label: e.content,
  343. key: e.value,
  344. type: e.type,
  345. keywords: e.keywords,
  346. };
  347. if (e.keywords) {
  348. addDataDialog.data.label = '';
  349. }
  350. addDataDialog.show = true;
  351. };
  352. const onChangeLabel = () => {
  353. if (!addDataDialog.data.key) {
  354. addDataDialog.data.key = addDataDialog.data.label;
  355. }
  356. };
  357. const onConfirmData = () => {
  358. if (!pen.realTimes) {
  359. pen.realTimes = [];
  360. }
  361. if (!addDataDialog.data.label || !addDataDialog.data.key) {
  362. MessagePlugin.error('数据名或属性名不能为空!');
  363. return;
  364. }
  365. if (addDataDialog.header === '添加动态数据') {
  366. const found = pen.realTimes.findIndex((item: any) => {
  367. return item.key === addDataDialog.data.key;
  368. });
  369. if (found > -1) {
  370. MessagePlugin.error('已经存在相同属性数据!');
  371. return;
  372. }
  373. pen.realTimes.push(addDataDialog.data);
  374. }
  375. addDataDialog.show = false;
  376. };
  377. const onMenuMore = (e: any, item: any, i: number) => {
  378. switch (e.value) {
  379. case 'edit':
  380. addDataDialog.header = '编辑动态数据';
  381. addDataDialog.data = item;
  382. addDataDialog.show = true;
  383. break;
  384. case 'delete':
  385. pen.realTimes.splice(i, 1);
  386. break;
  387. default:
  388. break;
  389. }
  390. };
  391. const onBind = (item: any) => {
  392. if (!item.binds) {
  393. item.binds = [];
  394. }
  395. dataBindDialog.data = item;
  396. dataBindDialog.input = '';
  397. dataBindDialog.selectedIds = [];
  398. for (const i of item.binds) {
  399. dataBindDialog.selectedIds.push(i.id);
  400. }
  401. dataBindDialog.bkBinds = [];
  402. dataBindDialog.bkBinds.push(...item.binds);
  403. dataBindDialog.show = true;
  404. getDataSet();
  405. };
  406. const onSearchDataSet = () => {
  407. debounce(getDataSet, 300);
  408. };
  409. const getDataSet = async () => {
  410. // @ts-ignore
  411. const data: Meta2dBackData = meta2d.data();
  412. dataBindDialog.loading = true;
  413. // 应该从data获取url或结果列表
  414. const ret: any = await axios.get(
  415. `/api/device/data/set?mock=1&q=${dataBindDialog.input}&current=${query.current}&pageSize=${query.pageSize}`
  416. );
  417. dataBindDialog.dataSet = ret.list;
  418. query.total = ret.total;
  419. dataBindDialog.loading = false;
  420. };
  421. const onChangePagination = (pageInfo: any) => {
  422. router.push({
  423. path: route.path,
  424. query: { current: pageInfo.current, pageSize: pageInfo.pageSize },
  425. });
  426. query.current = pageInfo.current;
  427. query.pageSize = pageInfo.pageSize;
  428. getDataSet();
  429. };
  430. const onSelectBindsChange = (value: string[], options: any) => {
  431. dataBindDialog.selectedIds = value;
  432. if (options.type === 'check') {
  433. for (const item of options.selectedRowData) {
  434. const found = dataBindDialog.data.binds.findIndex((elem: any) => {
  435. return elem.id === item.id;
  436. });
  437. if (found < 0) {
  438. dataBindDialog.data.binds.push(toRaw(item));
  439. }
  440. }
  441. } else if (options.type === 'uncheck') {
  442. if (options.currentRowKey === 'CHECK_ALL_BOX') {
  443. for (const data of dataBindDialog.dataSet) {
  444. const found = dataBindDialog.data.binds.findIndex((elem: any) => {
  445. return elem.id === data.id;
  446. });
  447. if (found > -1) {
  448. dataBindDialog.data.binds.splice(found, 1);
  449. }
  450. }
  451. } else {
  452. const found = dataBindDialog.data.binds.findIndex((elem: any) => {
  453. return elem.id === options.currentRowKey;
  454. });
  455. if (found > -1) {
  456. dataBindDialog.data.binds.splice(found, 1);
  457. }
  458. }
  459. }
  460. };
  461. const onRemoveBind = (index: number) => {
  462. dataBindDialog.data.binds.splice(index, 1);
  463. dataBindDialog.selectedIds = [];
  464. for (const i of dataBindDialog.data.binds) {
  465. dataBindDialog.selectedIds.push(i.id);
  466. }
  467. };
  468. </script>
  469. <style lang="postcss" scoped>
  470. .props {
  471. height: 100%;
  472. .grid {
  473. grid-template-columns: 80px 154px 40px;
  474. }
  475. .blank {
  476. height: 70%;
  477. img {
  478. padding: 16px;
  479. opacity: 0.9;
  480. }
  481. }
  482. .label {
  483. width: fit-content;
  484. font-size: 10px;
  485. line-height: 28px;
  486. color: var(--color-desc);
  487. }
  488. .value {
  489. padding-right: 8px;
  490. :deep(.t-input) {
  491. height: 26px;
  492. border-color: transparent;
  493. &:hover {
  494. border-color: var(--color-primary);
  495. }
  496. }
  497. }
  498. .actions {
  499. svg {
  500. margin-left: 6px;
  501. }
  502. }
  503. .data-list {
  504. height: 300px;
  505. overflow: auto;
  506. }
  507. }
  508. </style>