前端修改
@ -218,6 +218,9 @@ public class DatasetGroupManage {
|
|||||||
if (ObjectUtils.isNotEmpty(request.getLeaf())) {
|
if (ObjectUtils.isNotEmpty(request.getLeaf())) {
|
||||||
queryWrapper.eq("node_type", request.getLeaf() ? "dataset" : "folder");
|
queryWrapper.eq("node_type", request.getLeaf() ? "dataset" : "folder");
|
||||||
}
|
}
|
||||||
|
if (ObjectUtils.isNotEmpty(request.getAppId())) {
|
||||||
|
queryWrapper.eq("app_id", request.getAppId());
|
||||||
|
}
|
||||||
String info = CommunityUtils.getInfo();
|
String info = CommunityUtils.getInfo();
|
||||||
if (StringUtils.isNotBlank(info)) {
|
if (StringUtils.isNotBlank(info)) {
|
||||||
queryWrapper.notExists(String.format(info, "core_dataset_group.id"));
|
queryWrapper.notExists(String.format(info, "core_dataset_group.id"));
|
||||||
|
@ -2,13 +2,13 @@ export default {
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/f': {
|
'/api/f': {
|
||||||
target: 'http://192.168.1.16:8100',
|
target: 'http://192.168.1.38:8100',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: path => path.replace(/^\/api\/f/, '')
|
rewrite: path => path.replace(/^\/api\/f/, '')
|
||||||
},
|
},
|
||||||
// 使用 proxy 实例
|
// 使用 proxy 实例
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://192.168.1.16:8100',
|
target: 'http://192.168.1.38:8100',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: path => path.replace(/^\/api/, '')
|
rewrite: path => path.replace(/^\/api/, '')
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_OPTIONS=--max_old_space_size=4096 vite --mode dev --host 0.0.0.0",
|
"dev": "cross-env NODE_OPTIONS=--max_old_space_size=8196 vite --mode dev --host 0.0.0.0",
|
||||||
"build:flush": "cd ./flushbonading && rimraf ./demo.html && npm i && node ./index.js",
|
"build:flush": "cd ./flushbonading && rimraf ./demo.html && npm i && node ./index.js",
|
||||||
"ts:check": "vue-tsc --noEmit",
|
"ts:check": "vue-tsc --noEmit",
|
||||||
"build:base": "cross-env NODE_OPTIONS=--max_old_space_size=4096 vite build --mode base && npm run build:flush",
|
"build:base": "cross-env NODE_OPTIONS=--max_old_space_size=4096 vite build --mode base && npm run build:flush",
|
||||||
@ -26,6 +26,7 @@
|
|||||||
"@vueuse/core": "^9.13.0",
|
"@vueuse/core": "^9.13.0",
|
||||||
"ace-builds": "^1.15.3",
|
"ace-builds": "^1.15.3",
|
||||||
"axios": "^1.3.3",
|
"axios": "^1.3.3",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
|
@ -7,6 +7,7 @@ export interface DatasetOrFolder {
|
|||||||
action?: string
|
action?: string
|
||||||
id?: number | string
|
id?: number | string
|
||||||
pid?: number | string
|
pid?: number | string
|
||||||
|
appId?: number | string
|
||||||
nodeType: 'folder' | 'dataset'
|
nodeType: 'folder' | 'dataset'
|
||||||
union?: Array<{}>
|
union?: Array<{}>
|
||||||
allFields?: Array<{}>
|
allFields?: Array<{}>
|
||||||
@ -151,8 +152,11 @@ export const perDelete = async (id): Promise<boolean> => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDatasourceList = async (weight?: number): Promise<IResponse> => {
|
export const getDatasourceList = async (weight: number,appId:any): Promise<IResponse> => {
|
||||||
const data = { busiFlag: 'datasource' }
|
const data = {
|
||||||
|
busiFlag: 'datasource',
|
||||||
|
appId:appId
|
||||||
|
}
|
||||||
if (weight) {
|
if (weight) {
|
||||||
data['weight'] = weight
|
data['weight'] = weight
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744253031439" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10299" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M42.666667 42.666667a42.666667 42.666667 0 0 1 42.666666-42.666667h853.333334a42.666667 42.666667 0 0 1 42.666666 42.666667v938.666666a42.666667 42.666667 0 0 1-42.666666 42.666667H85.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V42.666667z m85.333333 42.666666v853.333334h768V85.333333H128z" p-id="10300"></path><path d="M213.333333 341.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h512a42.666667 42.666667 0 1 1 0 85.333333H256a42.666667 42.666667 0 0 1-42.666667-42.666667zM213.333333 597.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h341.333333a42.666667 42.666667 0 1 1 0 85.333333H256a42.666667 42.666667 0 0 1-42.666667-42.666667z" p-id="10301"></path></svg>
|
After Width: | Height: | Size: 1020 B |
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744252975601" class="icon" viewBox="0 0 1117 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2708" xmlns:xlink="http://www.w3.org/1999/xlink" width="218.1640625" height="200"><path d="M930.909091 139.636364H407.272727C377.018182 58.181818 300.218182 0 209.454545 0 109.381818 0 23.272727 72.145455 4.654545 167.563636 2.327273 181.527273 0 195.490909 0 209.454545v628.363637c0 102.4 83.781818 186.181818 186.181818 186.181818h744.727273c102.4 0 186.181818-83.781818 186.181818-186.181818V325.818182c0-102.4-83.781818-186.181818-186.181818-186.181818z m93.090909 186.181818v337.454545l-283.927273-100.072727L826.181818 232.727273H930.909091c51.2 0 93.090909 41.890909 93.090909 93.090909z m-293.236364-93.090909L651.636364 535.272727l-232.727273-81.454545V232.727273h311.854545zM93.090909 232.727273V209.454545c0-25.6 9.309091-51.2 23.272727-69.818181 20.945455-27.927273 53.527273-46.545455 93.090909-46.545455s72.145455 18.618182 93.09091 46.545455c13.963636 18.618182 23.272727 44.218182 23.272727 69.818181v449.163637c-32.581818-23.272727-74.472727-34.909091-116.363637-34.909091s-83.781818 13.963636-116.363636 34.909091V232.727273z m837.818182 698.181818H186.181818c-51.2 0-93.090909-41.890909-93.090909-93.090909v-6.981818c0-65.163636 51.2-116.363636 116.363636-116.363637s116.363636 51.2 116.363637 116.363637c0 25.6 20.945455 46.545455 46.545454 46.545454s46.545455-20.945455 46.545455-46.545454v-279.272728l605.090909 211.781819V837.818182c0 51.2-41.890909 93.090909-93.090909 93.090909z" fill="currentColor" p-id="2709"></path></svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744253069658" class="icon" viewBox="0 0 1073 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13333" xmlns:xlink="http://www.w3.org/1999/xlink" width="209.5703125" height="200"><path d="M881.784845 0H191.570683A191.697888 191.697888 0 0 0 0 191.570683v640.858634A191.697888 191.697888 0 0 0 191.570683 1024h690.214162a191.825093 191.825093 0 0 0 191.570683-191.570683V191.570683A191.825093 191.825093 0 0 0 881.784845 0z m24.677764 333.785839h79.503105V394.335404h-79.503105a43.631304 43.631304 0 1 0 0 87.262608h79.503105v60.549566h-79.503105a43.758509 43.758509 0 1 0 0 87.389813h79.503105v60.549566h-79.503105a43.631304 43.631304 0 1 0 0 87.262608h79.503105v54.952547a104.308075 104.308075 0 0 1-104.180869 104.308074H191.570683a104.43528 104.43528 0 0 1-104.308074-104.308074v-54.825342h79.63031a43.631304 43.631304 0 1 0 0-87.262609H87.262609V629.664596h79.63031a43.758509 43.758509 0 0 0 0-87.389813H87.262609v-60.549566h79.63031a43.631304 43.631304 0 1 0 0-87.262608H87.262609v-60.67677h79.63031a43.631304 43.631304 0 1 0 0-87.262609H87.262609v-54.952547a104.43528 104.43528 0 0 1 104.308074-104.308074h690.214162a104.308075 104.308075 0 0 1 104.180869 104.308074v54.952547h-79.503105a43.631304 43.631304 0 1 0 0 87.262609z" fill="currentColor" p-id="13334"></path><path d="M718.199255 437.076273l-233.929938-152.645962a75.559752 75.559752 0 0 0-78.103851-4.579379 88.280248 88.280248 0 0 0-43.631305 78.994285v305.291926a89.043478 89.043478 0 0 0 44.52174 79.63031 73.524472 73.524472 0 0 0 35.617391 9.158758 78.994286 78.994286 0 0 0 42.995279-12.720497l231.894659-152.645962a89.933913 89.933913 0 0 0 40.19677-76.322982 87.008199 87.008199 0 0 0-39.560745-74.160497z m-48.337889 73.906087a7.250683 7.250683 0 0 1 0 2.925715l-220.064596 144.504844v-292.571428l219.937391 143.86882a3.180124 3.180124 0 0 1 0.127205 1.272049z" fill="currentColor" p-id="13335"></path></svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744253004677" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5674" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M864 98H162a64.19 64.19 0 0 0-64 64v158a64.19 64.19 0 0 0 64 64h702a64.19 64.19 0 0 0 64-64V162a64.19 64.19 0 0 0-64-64z m-32 214H194a24 24 0 0 1-24-24v-94a24 24 0 0 1 24-24h638a24 24 0 0 1 24 24v94a24 24 0 0 1-24 24zM384 448H162a64.19 64.19 0 0 0-64 64v352a64.19 64.19 0 0 0 64 64h222a64.19 64.19 0 0 0 64-64V512a64.19 64.19 0 0 0-64-64z m-32 408H194a24 24 0 0 1-24-24V544a24 24 0 0 1 24-24h158a24 24 0 0 1 24 24v288a24 24 0 0 1-24 24z m539-387H533.71c-20.19 0-37.09 16.52-36.7 36.7A36 36 0 0 0 533 541h357.29c20.19 0 37.09-16.52 36.7-36.7A36 36 0 0 0 891 469z m0 367H533.71c-20.19 0-37.09 16.52-36.7 36.7A36 36 0 0 0 533 908h357.29c20.19 0 37.09-16.52 36.7-36.7A36 36 0 0 0 891 836z m0-183.5H533.71c-20.19 0-37.09 16.52-36.7 36.7a36 36 0 0 0 36 35.3h357.28c20.19 0 37.09-16.52 36.7-36.7A36 36 0 0 0 891 652.5z" p-id="5675"></path></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744253055932" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12270" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M206.791111 76.572444c38.058667 0 68.892444 30.890667 68.892445 68.949334v2.844444h428.828444V217.315556H275.683556v4.835555c0 38.058667-30.833778 68.892444-68.892445 68.892445h-15.36v398.222222h15.36c38.058667 0 68.892444 30.890667 68.892445 68.949333v4.721778h428.828444v68.949333H275.683556v2.901334c0 38.115556-30.833778 68.949333-68.892445 68.949333H130.161778c-38.058667 0-68.892444-30.890667-68.892445-68.949333v-76.572445c0-38.115556 30.833778-68.949333 68.892445-68.949333h-7.623111v-398.222222h7.623111c-38.058667 0-68.892444-30.833778-68.892445-68.892445V145.521778c0-38.115556 30.833778-68.949333 68.892445-68.949334h76.629333z m566.670222 214.471112c-38.058667 0-68.892444-30.890667-68.892444-68.949334V145.521778c0-38.115556 30.833778-68.949333 68.892444-68.949334h76.572445c38.115556 0 68.949333 30.890667 68.949333 68.949334v76.572444c0 38.058667-30.890667 68.892444-68.949333 68.892445h7.68v398.222222h-7.68c38.115556 0 68.949333 30.890667 68.949333 68.949333v76.572445c0 38.115556-30.890667 68.949333-68.949333 68.949333h-76.572445c-38.058667 0-68.892444-30.890667-68.892444-68.949333v-76.572445c0-38.115556 30.833778-68.949333 68.892444-68.949333h15.303111v-398.222222h-15.303111z m-566.670222 467.057777H130.161778v76.629334h76.629333v-76.572445z m643.242667 0h-76.572445v76.629334h76.572445v-76.572445z m-241.208889-424.96a34.474667 34.474667 0 0 1 0 68.892445h-84.764445v237.397333a34.474667 34.474667 0 0 1-68.835555 0L455.111111 402.033778H371.427556a34.474667 34.474667 0 0 1 0-68.892445h237.397333zM206.791111 145.521778H130.161778v76.572444h76.629333V145.521778z m643.242667 0h-76.572445v76.572444h76.572445V145.521778z" fill="currentColor" p-id="12271"></path></svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744253082830" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14326" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1008.241778 940.714667c0 20.992-18.432 38.115556-40.049778 38.115555H62.179556A38.912 38.912 0 0 1 22.755556 940.714667C22.755556 919.665778 40.561778 902.542222 62.179556 902.542222H968.248889c21.617778 0 40.049778 17.180444 40.049778 38.115556" fill="currentColor" p-id="14327"></path><path d="M896.341333 190.407111c22.243556 0 40.049778 17.180444 40.049778 38.115556v578.56c0 21.048889-17.806222 38.229333-40.049778 38.229333h-166.570666a38.968889 38.968889 0 0 1-39.424-38.172444V228.579556c0-21.048889 17.806222-38.172444 39.424-38.172445h166.570666z m-127.146666 578.56h87.722666v-502.328889h-87.722666v502.328889zM598.812444 323.925333c21.617778 0 39.424 17.180444 39.424 38.115556v445.098667a38.968889 38.968889 0 0 1-39.424 38.115555h-167.253333a38.968889 38.968889 0 0 1-39.367111-38.115555V362.097778c0-21.048889 17.806222-38.172444 39.367111-38.172445h167.253333z m-127.146666 445.098667h87.096889V400.270222H471.665778v368.753778zM300.600889 56.888889c21.617778 0 40.049778 17.180444 40.049778 38.115555v712.135112c0 20.992-18.432 38.115556-40.049778 38.115555H133.404444a38.968889 38.968889 0 0 1-39.424-38.115555V95.004444c0-21.048889 17.806222-38.172444 39.424-38.172444h167.196445zM173.454222 769.024H260.551111V133.12H173.454222v635.847111z" fill="currentColor" p-id="14328"></path></svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744253044063" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11273" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M841.34 959.36H182.66c-65.06 0-117.99-52.94-117.99-118.02V182.69c0-65.08 52.94-118.04 117.99-118.04h658.68c65.06 0 117.99 52.96 117.99 118.04v658.65c0 65.08-52.93 118.02-117.99 118.02zM182.66 142.17c-22.31 0-40.51 18.18-40.51 40.51v658.65c0 22.34 18.2 40.49 40.51 40.49h658.68c22.31 0 40.51-18.15 40.51-40.49V182.69c0-22.34-18.2-40.51-40.51-40.51H182.66z" fill="#6C6D6E" p-id="11274"></path></svg>
|
After Width: | Height: | Size: 731 B |
BIN
core/core-frontend/src/assets/newimg/dvCanvas/u101.png
Normal file
After Width: | Height: | Size: 557 B |
BIN
core/core-frontend/src/assets/newimg/dvCanvas/u4860.png
Normal file
After Width: | Height: | Size: 536 B |
BIN
core/core-frontend/src/assets/newimg/dvCanvas/u4867.png
Normal file
After Width: | Height: | Size: 543 B |
BIN
core/core-frontend/src/assets/newimg/dvCanvas/u4876.png
Normal file
After Width: | Height: | Size: 401 B |
BIN
core/core-frontend/src/assets/newimg/dvCanvas/u4884.png
Normal file
After Width: | Height: | Size: 626 B |
BIN
core/core-frontend/src/assets/newimg/dvCanvas/u4891.png
Normal file
After Width: | Height: | Size: 547 B |
BIN
core/core-frontend/src/assets/newimg/dvCanvas/u4905.png
Normal file
After Width: | Height: | Size: 403 B |
BIN
core/core-frontend/src/assets/newimg/dvCanvas/u4912.png
Normal file
After Width: | Height: | Size: 262 B |
BIN
core/core-frontend/src/assets/newimg/dvCanvas/u4919.png
Normal file
After Width: | Height: | Size: 314 B |
@ -1,16 +1,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import dvFilter from '@/assets/svg/dv-filter.svg'
|
import dvFilter from '@/assets/svg/dv-filter.svg'
|
||||||
import dvMaterial from '@/assets/svg/dv-material.svg'
|
import dvMaterial from '@/assets/svg/dv-material.svg'
|
||||||
import dvMedia from '@/assets/svg/dv-media.svg'
|
import dvMedia from '@/assets/newimg/camvassvg/dv-media.svg' // 媒体
|
||||||
|
|
||||||
|
import dvMap from '@/assets/newimg/camvassvg/dv-map.svg' // 地图
|
||||||
|
|
||||||
|
|
||||||
import dvMoreCom from '@/assets/svg/dv-more-com.svg'
|
import dvMoreCom from '@/assets/svg/dv-more-com.svg'
|
||||||
import dvTab from '@/assets/svg/dv-tab.svg'
|
import dvTab from '@/assets/svg/dv-tab.svg'
|
||||||
import dvText from '@/assets/svg/dv-text.svg'
|
import dvText from '@/assets/newimg/camvassvg/dv-text.svg' // 文本
|
||||||
import dvView from '@/assets/svg/dv-view.svg'
|
import dvView from '@/assets/newimg/camvassvg/dv-view.svg' // echarts图表
|
||||||
|
|
||||||
|
import dvForm from '@/assets/newimg/camvassvg/dv-form.svg' // echarts图表
|
||||||
|
|
||||||
|
import dvTemplate from '@/assets/newimg/camvassvg/dv-template.svg' // 模版
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// import dvView from '@/assets/newimg/dvCanvas/u4876.png'
|
||||||
|
|
||||||
import icon_params_setting from '@/assets/svg/icon_params_setting.svg'
|
import icon_params_setting from '@/assets/svg/icon_params_setting.svg'
|
||||||
import icon_copy_filled from '@/assets/svg/icon_copy_filled.svg'
|
import icon_copy_filled from '@/assets/svg/icon_copy_filled.svg'
|
||||||
import icon_left_outlined from '@/assets/svg/icon_left_outlined.svg'
|
import icon_left_outlined from '@/assets/svg/icon_left_outlined.svg'
|
||||||
import icon_undo_outlined from '@/assets/svg/icon_undo_outlined.svg'
|
// import icon_undo_outlined from '@/assets/svg/icon_undo_outlined.svg'
|
||||||
import icon_redo_outlined from '@/assets/svg/icon_redo_outlined.svg'
|
// import icon_redo_outlined from '@/assets/svg/icon_redo_outlined.svg'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||||
import eventBus from '@/utils/eventBus'
|
import eventBus from '@/utils/eventBus'
|
||||||
import { ref, nextTick, computed, toRefs, onBeforeUnmount, onMounted } from 'vue'
|
import { ref, nextTick, computed, toRefs, onBeforeUnmount, onMounted } from 'vue'
|
||||||
@ -327,7 +341,7 @@ const fullScreenPreview = () => {
|
|||||||
<span id="dv-canvas-name" class="name-area" @dblclick="editCanvasName">
|
<span id="dv-canvas-name" class="name-area" @dblclick="editCanvasName">
|
||||||
{{ dvInfo.name }}
|
{{ dvInfo.name }}
|
||||||
</span>
|
</span>
|
||||||
<div class="opt-area">
|
<!-- <div class="opt-area">
|
||||||
<el-tooltip effect="ndark" :content="$t('visualization.undo')" placement="bottom">
|
<el-tooltip effect="ndark" :content="$t('visualization.undo')" placement="bottom">
|
||||||
<el-icon
|
<el-icon
|
||||||
class="toolbar-hover-icon"
|
class="toolbar-hover-icon"
|
||||||
@ -348,11 +362,22 @@ const fullScreenPreview = () => {
|
|||||||
<Icon name="icon_redo_outlined"><icon_redo_outlined class="svg-icon" /></Icon>
|
<Icon name="icon_redo_outlined"><icon_redo_outlined class="svg-icon" /></Icon>
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="middle-area">
|
<div class="middle-area">
|
||||||
|
<div class="undo-redo" @click="undo()">
|
||||||
|
<img src="@/assets/newimg/dvCanvas/u4860.png" alt="" srcset="">
|
||||||
|
<div class="undo-redo-text">撤销</div>
|
||||||
|
</div>
|
||||||
|
<div class="undo-redo" @click="redo()">
|
||||||
|
<img src="@/assets/newimg/dvCanvas/u4867.png" alt="" srcset="">
|
||||||
|
<div class="undo-redo-text">重做</div>
|
||||||
|
</div>
|
||||||
|
<div class="tabline"></div>
|
||||||
|
|
||||||
|
|
||||||
<component-group
|
<component-group
|
||||||
show-split-line
|
|
||||||
is-label
|
is-label
|
||||||
:base-width="410"
|
:base-width="410"
|
||||||
:icon-name="dvView"
|
:icon-name="dvView"
|
||||||
@ -360,9 +385,35 @@ const fullScreenPreview = () => {
|
|||||||
>
|
>
|
||||||
<user-view-group></user-view-group>
|
<user-view-group></user-view-group>
|
||||||
</component-group>
|
</component-group>
|
||||||
|
|
||||||
|
<component-group
|
||||||
|
is-label
|
||||||
|
placement="bottom"
|
||||||
|
:base-width="328"
|
||||||
|
:icon-name="dvMedia"
|
||||||
|
:title="t('visualization.media')"
|
||||||
|
>
|
||||||
|
<media-group></media-group>
|
||||||
|
</component-group>
|
||||||
|
|
||||||
|
|
||||||
|
<component-group
|
||||||
|
is-label
|
||||||
|
placement="bottom"
|
||||||
|
:base-width="328"
|
||||||
|
:icon-name="dvMap"
|
||||||
|
:title="'地图'"
|
||||||
|
>
|
||||||
|
<media-group></media-group>
|
||||||
|
</component-group>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<component-group
|
<component-group
|
||||||
:base-width="115"
|
:base-width="115"
|
||||||
:show-split-line="true"
|
|
||||||
is-label
|
is-label
|
||||||
:icon-name="dvFilter"
|
:icon-name="dvFilter"
|
||||||
:title="t('visualization.query_component')"
|
:title="t('visualization.query_component')"
|
||||||
@ -377,15 +428,7 @@ const fullScreenPreview = () => {
|
|||||||
>
|
>
|
||||||
<text-group></text-group>
|
<text-group></text-group>
|
||||||
</component-group>
|
</component-group>
|
||||||
<component-group
|
|
||||||
is-label
|
|
||||||
placement="bottom"
|
|
||||||
:base-width="328"
|
|
||||||
:icon-name="dvMedia"
|
|
||||||
:title="t('visualization.media')"
|
|
||||||
>
|
|
||||||
<media-group></media-group>
|
|
||||||
</component-group>
|
|
||||||
<component-group is-label :base-width="115" :icon-name="dvTab" title="Tab">
|
<component-group is-label :base-width="115" :icon-name="dvTab" title="Tab">
|
||||||
<tabs-group :dv-model="dvModel"></tabs-group>
|
<tabs-group :dv-model="dvModel"></tabs-group>
|
||||||
</component-group>
|
</component-group>
|
||||||
@ -406,12 +449,33 @@ const fullScreenPreview = () => {
|
|||||||
>
|
>
|
||||||
<common-group></common-group>
|
<common-group></common-group>
|
||||||
</component-group>
|
</component-group>
|
||||||
|
|
||||||
<component-button-label
|
<component-button-label
|
||||||
:icon-name="icon_copy_filled"
|
:icon-name="icon_copy_filled"
|
||||||
:title="t('visualization.multiplexing')"
|
:title="t('visualization.multiplexing')"
|
||||||
is-label
|
is-label
|
||||||
@customClick="multiplexingCanvasOpen"
|
@customClick="multiplexingCanvasOpen"
|
||||||
></component-button-label>
|
></component-button-label>
|
||||||
|
<component-group
|
||||||
|
is-label
|
||||||
|
placement="bottom"
|
||||||
|
:base-width="328"
|
||||||
|
:icon-name="dvForm"
|
||||||
|
:title="'表单'"
|
||||||
|
>
|
||||||
|
<media-group></media-group>
|
||||||
|
</component-group>
|
||||||
|
<component-group
|
||||||
|
is-label
|
||||||
|
placement="bottom"
|
||||||
|
:base-width="328"
|
||||||
|
:icon-name="dvTemplate"
|
||||||
|
:title="'模版'"
|
||||||
|
>
|
||||||
|
<media-group></media-group>
|
||||||
|
</component-group>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="right-area">
|
<div class="right-area">
|
||||||
@ -508,7 +572,7 @@ const fullScreenPreview = () => {
|
|||||||
height: @top-bar-height;
|
height: @top-bar-height;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
background: #1a1a1a;
|
background: rgb(37,38,38);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
box-shadow: 0px 2px 4px 0px rgba(31, 35, 41, 0.12);
|
box-shadow: 0px 2px 4px 0px rgba(31, 35, 41, 0.12);
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -526,13 +590,18 @@ const fullScreenPreview = () => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.name-area {
|
.name-area {
|
||||||
position: relative;
|
position: relative;
|
||||||
line-height: 24px;
|
line-height: 46px;
|
||||||
height: 24px;
|
height: 46px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: @dv-canvas-main-font-color;
|
color: @dv-canvas-main-font-color;
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #F2F4F5;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -603,4 +672,35 @@ const fullScreenPreview = () => {
|
|||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
.undo-redo{
|
||||||
|
width: 47px;
|
||||||
|
height: 47px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
// items-content: center;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
.undo-redo-text{
|
||||||
|
width: 100%;
|
||||||
|
font-family: '微软雅黑';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #F2F4F5;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.undo-redo:hover{
|
||||||
|
background-color: rgba(54, 55, 56, 1);
|
||||||
|
}
|
||||||
|
.tabline{
|
||||||
|
width: 1px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: rgba(24, 24, 24, 1);
|
||||||
|
margin: 0px 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -20,7 +20,7 @@ withDefaults(
|
|||||||
inTable?: boolean
|
inTable?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
placement: 'bottom-end',
|
placement: 'right-start',
|
||||||
iconSize: '16px',
|
iconSize: '16px',
|
||||||
inTable: false
|
inTable: false
|
||||||
}
|
}
|
||||||
@ -53,9 +53,9 @@ const emit = defineEmits(['handleCommand'])
|
|||||||
:key="ele.label"
|
:key="ele.label"
|
||||||
:disabled="ele.disabled"
|
:disabled="ele.disabled"
|
||||||
>
|
>
|
||||||
<el-icon class="handle-icon" :style="{ fontSize: iconSize }" v-if="ele.svgName">
|
<!-- <el-icon class="handle-icon" :style="{ fontSize: iconSize }" v-if="ele.svgName">
|
||||||
<Icon><component class="svg-icon" :is="ele.svgName"></component></Icon>
|
<Icon><component class="svg-icon" :is="ele.svgName"></component></Icon>
|
||||||
</el-icon>
|
</el-icon> -->
|
||||||
{{ ele.label }}
|
{{ ele.label }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
|
@ -21,6 +21,8 @@ const emits = defineEmits(['customClick'])
|
|||||||
<el-row class="group_icon" :title="tips" @click="emits('customClick')">
|
<el-row class="group_icon" :title="tips" @click="emits('customClick')">
|
||||||
<el-col :span="24" class="group_inner" :class="{ 'inner-active': active }">
|
<el-col :span="24" class="group_inner" :class="{ 'inner-active': active }">
|
||||||
<Icon><component class="svg-icon toolbar-icon" :is="iconName"></component></Icon>
|
<Icon><component class="svg-icon toolbar-icon" :is="iconName"></component></Icon>
|
||||||
|
|
||||||
|
<!-- <img src="@/assets/newimg/avatar.png" alt="" srcset=""> -->
|
||||||
<span >{{ title }}</span>
|
<span >{{ title }}</span>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -34,18 +36,18 @@ const emits = defineEmits(['customClick'])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.group_inner {
|
.group_inner {
|
||||||
padding: 8px;
|
padding: 0px 10px 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: #a6a6a6;
|
color: #ffffff;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
float: left;
|
float: left;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 8px;
|
margin-top: 5px;
|
||||||
line-height: 12px;
|
line-height: 12px;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -3308,7 +3308,8 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
.query-condition-list {
|
.query-condition-list {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #f5f6f7;
|
// background: #f5f6f7;
|
||||||
|
background: rgb(38,38,38);
|
||||||
border-right: 1px solid #dee0e3;
|
border-right: 1px solid #dee0e3;
|
||||||
width: 208px;
|
width: 208px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -3377,7 +3378,7 @@ defineExpose({
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: #fff;
|
background: transparent;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
.ed-radio {
|
.ed-radio {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@ -3497,7 +3498,7 @@ defineExpose({
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: #fff;
|
background: transparent;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
.ed-checkbox {
|
.ed-checkbox {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export interface BusiTreeNode {
|
export interface BusiTreeNode {
|
||||||
id: string | number
|
id: string | number
|
||||||
pid: string | number
|
pid: string | number
|
||||||
|
appId: string | number
|
||||||
name: string
|
name: string
|
||||||
leaf?: boolean
|
leaf?: boolean
|
||||||
weight: number
|
weight: number
|
||||||
@ -10,6 +11,7 @@ export interface BusiTreeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BusiTreeRequest {
|
export interface BusiTreeRequest {
|
||||||
|
appId?: string
|
||||||
busiFlag?: string
|
busiFlag?: string
|
||||||
leaf?: boolean
|
leaf?: boolean
|
||||||
weight?: number
|
weight?: number
|
||||||
|
@ -25,7 +25,7 @@ const { loadStart, loadDone } = usePageLoading()
|
|||||||
|
|
||||||
const whiteList = ['/login', '/de-link', '/chart-view', '/admin-login', '/401'] // 不重定向白名单
|
const whiteList = ['/login', '/de-link', '/chart-view', '/admin-login', '/401'] // 不重定向白名单
|
||||||
const embeddedWindowWhiteList = ['/dvCanvas', '/dashboard', '/preview', '/dataset-embedded-form']
|
const embeddedWindowWhiteList = ['/dvCanvas', '/dashboard', '/preview', '/dataset-embedded-form']
|
||||||
const embeddedRouteWhiteList = ['/dataset-embedded', '/dataset-form', '/dataset-embedded-form']
|
const embeddedRouteWhiteList = ['/dataset-embedded', '/dataset-form','/datasetForm', '/dataset-embedded-form']
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
start()
|
start()
|
||||||
loadStart()
|
loadStart()
|
||||||
@ -138,6 +138,10 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
next({ path: '/dataset-embedded-form', query: to.query })
|
next({ path: '/dataset-embedded-form', query: to.query })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (to.path.includes('/datasetForm')) {
|
||||||
|
next({ path: '/datasetEmbeddedForm', query: to.query })
|
||||||
|
return
|
||||||
|
}
|
||||||
permissionStore.setCurrentPath(to.path)
|
permissionStore.setCurrentPath(to.path)
|
||||||
next()
|
next()
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import { isExternal } from '@/utils/validate'
|
import { isExternal } from '@/utils/validate'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import { XpackComponent } from '@/components/plugin'
|
import { XpackComponent } from '@/components/plugin'
|
||||||
|
// const modules = import.meta.glob('../viewsnew/**/*.vue')
|
||||||
|
// export const Layout = () => import('@/viewsnew/layout/index.vue')
|
||||||
|
|
||||||
const modules = import.meta.glob('../views/**/*.vue')
|
const modules = import.meta.glob('../views/**/*.vue')
|
||||||
export const Layout = () => import('@/layout/index.vue')
|
export const Layout = () => import('@/layout/index.vue')
|
||||||
|
|
||||||
const xpackComName = 'components/plugin'
|
const xpackComName = 'components/plugin'
|
||||||
export const LayoutTransition = () => import('@/layout/components/LayoutTransition.vue')
|
export const LayoutTransition = () => import('@/layout/components/LayoutTransition.vue')
|
||||||
// 后端控制路由生成
|
// 后端控制路由生成
|
||||||
@ -33,13 +37,13 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
|
|||||||
meta: route.meta,
|
meta: route.meta,
|
||||||
props: route.props as Recordable
|
props: route.props as Recordable
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.component) {
|
if (route.component) {
|
||||||
let comModule = null
|
let comModule = null
|
||||||
if (route.component === xpackComName) {
|
if (route.component === xpackComName) {
|
||||||
comModule = XpackComponent
|
comModule = XpackComponent
|
||||||
} else if (!route.component.startsWith('Layout')) {
|
} else if (!route.component.startsWith('Layout')) {
|
||||||
comModule = modules[`../views/${route.component}/index.vue`]
|
comModule = modules[`../views/${route.component}/index.vue`]
|
||||||
|
// comModule = modules[`../viewsnew/${route.component}/index.vue`]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.component === 'Layout') {
|
if (route.component === 'Layout') {
|
||||||
|
@ -177,6 +177,27 @@ export const routes: AppRouteRecordRaw[] = [
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
meta: {},
|
meta: {},
|
||||||
component: () => import('@/viewsnew/application/index.vue')
|
component: () => import('@/viewsnew/application/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/datasetnew',
|
||||||
|
name: 'datasetnew',
|
||||||
|
hidden: true,
|
||||||
|
meta: {},
|
||||||
|
component: () => import('@/viewsnew/application/service/dataset/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/datasetForm',
|
||||||
|
name: 'datasetForm',
|
||||||
|
hidden: true,
|
||||||
|
meta: {},
|
||||||
|
component: () => import('@/viewsnew/application/service/dataset/form/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/datasourcenew',
|
||||||
|
name: 'datasourcenew',
|
||||||
|
hidden: true,
|
||||||
|
meta: {},
|
||||||
|
component: () => import('@/viewsnew/application/service/datasource/index.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
|
import { ref} from 'vue'
|
||||||
|
|
||||||
import { queryTreeApi, queryBusiTreeApi } from '@/api/visualization/dataVisualization'
|
import { queryTreeApi, queryBusiTreeApi } from '@/api/visualization/dataVisualization'
|
||||||
import { getDatasetTree } from '@/api/dataset'
|
import { getDatasetTree } from '@/api/dataset'
|
||||||
import { listDatasources } from '@/api/datasource'
|
import { listDatasources } from '@/api/datasource'
|
||||||
@ -7,6 +9,8 @@ import type { BusiTreeRequest, BusiTreeNode } from '@/models/tree/TreeNode'
|
|||||||
import { pathValid } from '@/store/modules/permission'
|
import { pathValid } from '@/store/modules/permission'
|
||||||
import { useCache } from '@/hooks/web/useCache'
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const appStore = useAppStoreWithOut()
|
const appStore = useAppStoreWithOut()
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
export interface InnerInteractive {
|
export interface InnerInteractive {
|
||||||
@ -58,6 +62,7 @@ export const interactiveStore = defineStore('interactive', {
|
|||||||
menuAuth: false
|
menuAuth: false
|
||||||
}
|
}
|
||||||
this.data[flag] = tempData
|
this.data[flag] = tempData
|
||||||
|
|
||||||
if (flag === 0) {
|
if (flag === 0) {
|
||||||
wsCache.set('panel-weight', {})
|
wsCache.set('panel-weight', {})
|
||||||
}
|
}
|
||||||
@ -68,7 +73,13 @@ export const interactiveStore = defineStore('interactive', {
|
|||||||
}
|
}
|
||||||
let res = resParam
|
let res = resParam
|
||||||
if (!resParam) {
|
if (!resParam) {
|
||||||
|
const route = useRoute()
|
||||||
|
const appId:any = ref('')
|
||||||
|
if (route.query.id) {
|
||||||
|
appId.value = route.query.id
|
||||||
|
}
|
||||||
const method = apiMap[flag]
|
const method = apiMap[flag]
|
||||||
|
param.appId = appId.value
|
||||||
res = await method(param)
|
res = await method(param)
|
||||||
}
|
}
|
||||||
this.data[flag] = convertInteractive(res)
|
this.data[flag] = convertInteractive(res)
|
||||||
|
@ -46,9 +46,38 @@ export const usePermissionStore = defineStore('permission', {
|
|||||||
},
|
},
|
||||||
generateRoutes(routers?: AppCustomRouteRecordRaw[] | string[]): Promise<unknown> {
|
generateRoutes(routers?: AppCustomRouteRecordRaw[] | string[]): Promise<unknown> {
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>(resolve => {
|
||||||
|
let aaaa= [{
|
||||||
|
children: null,
|
||||||
|
component: "application",
|
||||||
|
hidden: false,
|
||||||
|
inLayout: true,
|
||||||
|
meta:{
|
||||||
|
icon: null,
|
||||||
|
title: "平台项目"
|
||||||
|
} ,
|
||||||
|
name: "application",
|
||||||
|
path: "/application",
|
||||||
|
plugin: false,
|
||||||
|
redirect: null,
|
||||||
|
top: true
|
||||||
|
},{
|
||||||
|
children: null,
|
||||||
|
component: "visualized/view/panel",
|
||||||
|
hidden: false,
|
||||||
|
inLayout: true,
|
||||||
|
meta:{
|
||||||
|
icon: null,
|
||||||
|
title: "仪表板"
|
||||||
|
} ,
|
||||||
|
name: "panel",
|
||||||
|
path: "/panel",
|
||||||
|
plugin: false,
|
||||||
|
redirect: null,
|
||||||
|
top: true
|
||||||
|
}]
|
||||||
|
|
||||||
let routerMap: AppRouteRecordRaw[] = []
|
let routerMap: AppRouteRecordRaw[] = []
|
||||||
routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[]) || []
|
routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[]) || []
|
||||||
|
|
||||||
this.addRouters = routerMap.concat([
|
this.addRouters = routerMap.concat([
|
||||||
{
|
{
|
||||||
path: '/:catchAll(.*)',
|
path: '/:catchAll(.*)',
|
||||||
|
@ -194,3 +194,32 @@
|
|||||||
.vjs-custom-skin > .video-js .vjs-control-bar .vjs-fullscreen-control {
|
.vjs-custom-skin > .video-js .vjs-control-bar .vjs-fullscreen-control {
|
||||||
order: 6;
|
order: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ed-dialog {
|
||||||
|
--ed-dialog-bg-color: rgba(33, 33, 33, 1);
|
||||||
|
}
|
||||||
|
.ed-dialog__title{
|
||||||
|
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #F2F4F5;
|
||||||
|
}
|
||||||
|
.ed-form-item__label{
|
||||||
|
font-family: 'Arial Normal', 'Arial';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #D2D2D2;
|
||||||
|
}
|
||||||
|
.ed-pagination__total{
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.ed-dialog__body{
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ body {
|
|||||||
width: 24px !important;
|
width: 24px !important;
|
||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: #646a73 !important;
|
color: #ffffff !important;
|
||||||
|
|
||||||
&[aria-expanded='true'] {
|
&[aria-expanded='true'] {
|
||||||
background: rgba(31, 35, 41, 0.1);
|
background: rgba(31, 35, 41, 0.1);
|
||||||
@ -671,3 +671,28 @@ strong {
|
|||||||
.ed-main{
|
.ed-main{
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新样式
|
||||||
|
|
||||||
|
.ed-drawer{
|
||||||
|
background: rgba(33, 33, 33, 1) !important;
|
||||||
|
}
|
||||||
|
.ed-drawer__footer{
|
||||||
|
background: rgba(33, 33, 33, 1) !important;
|
||||||
|
}
|
||||||
|
.ed-dialog__body{
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.ed-radio__label{
|
||||||
|
color: #fff ;
|
||||||
|
}
|
||||||
|
.ed-radio__input.is-checked+.ed-radio__label{
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.ed-checkbox__label{
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ed-checkbox__label:hover{
|
||||||
|
color: #fff ;
|
||||||
|
}
|
@ -17,3 +17,33 @@
|
|||||||
right: -@width;
|
right: -@width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:deep(.ed-pagination__total){
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.ed-pagination__goto){
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.ed-dialog__body){
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
:deep(.ed-checkbox__label){
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:deep(.ed-radio__label){
|
||||||
|
color: #fff ;
|
||||||
|
}
|
||||||
|
// :deep(.ed-checkbox__label){
|
||||||
|
// color: #fff !important;
|
||||||
|
// }
|
||||||
|
// :deep(.is-checked){
|
||||||
|
// :deep(.ed-radio__label){
|
||||||
|
// color: #fff;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
@ -5250,7 +5250,7 @@ span {
|
|||||||
position: relative;
|
position: relative;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
font-size: 14px !important;
|
font-size: 14px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
input {
|
input {
|
||||||
|
@ -263,6 +263,7 @@ const getNodeField = ({ datasourceId, tableName }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getDatasource = () => {
|
const getDatasource = () => {
|
||||||
|
debugger
|
||||||
getDatasourceList().then(res => {
|
getDatasourceList().then(res => {
|
||||||
const _list = (res as unknown as DataSource[]) || []
|
const _list = (res as unknown as DataSource[]) || []
|
||||||
if (_list && _list.length > 0 && _list[0].id === '0') {
|
if (_list && _list.length > 0 && _list[0].id === '0') {
|
||||||
|
@ -66,6 +66,11 @@ import { cloneDeep, debounce } from 'lodash-es'
|
|||||||
import { XpackComponent } from '@/components/plugin'
|
import { XpackComponent } from '@/components/plugin'
|
||||||
import { iconFieldMap } from '@/components/icon-group/field-list'
|
import { iconFieldMap } from '@/components/icon-group/field-list'
|
||||||
import { iconDatasourceMap } from '@/components/icon-group/datasource-list'
|
import { iconDatasourceMap } from '@/components/icon-group/datasource-list'
|
||||||
|
const route = useRoute()
|
||||||
|
const appId:any = ref('')
|
||||||
|
if (route.query.appId) {
|
||||||
|
appId.value = route.query.appId
|
||||||
|
}
|
||||||
interface DragEvent extends MouseEvent {
|
interface DragEvent extends MouseEvent {
|
||||||
dataTransfer: DataTransfer
|
dataTransfer: DataTransfer
|
||||||
}
|
}
|
||||||
@ -81,7 +86,6 @@ const { wsCache } = useCache()
|
|||||||
const appStore = useAppStoreWithOut()
|
const appStore = useAppStoreWithOut()
|
||||||
const embeddedStore = useEmbedded()
|
const embeddedStore = useEmbedded()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const route = useRoute()
|
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
const quotaTableHeight = ref(238)
|
const quotaTableHeight = ref(238)
|
||||||
const creatDsFolder = ref()
|
const creatDsFolder = ref()
|
||||||
@ -1332,7 +1336,7 @@ const getSqlResultHeight = () => {
|
|||||||
sqlResultHeight.value = (document.querySelector('.sql-result') as HTMLElement).offsetHeight
|
sqlResultHeight.value = (document.querySelector('.sql-result') as HTMLElement).offsetHeight
|
||||||
}
|
}
|
||||||
const getDatasource = (weight?: number) => {
|
const getDatasource = (weight?: number) => {
|
||||||
getDatasourceList(weight).then(res => {
|
getDatasourceList(weight,appId.value).then(res => {
|
||||||
const _list = (res as unknown as DataSource[]) || []
|
const _list = (res as unknown as DataSource[]) || []
|
||||||
if (_list && _list.length > 0 && _list[0].id === '0') {
|
if (_list && _list.length > 0 && _list[0].id === '0') {
|
||||||
state.dataSourceList = dfsChild(_list[0].children)
|
state.dataSourceList = dfsChild(_list[0].children)
|
||||||
|
@ -66,7 +66,7 @@ const handleChange = (val: boolean) => {
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<p class="name">{{ $t('auth.dataset') }}</p>
|
<p class="name">{{ $t('auth.dataset') }}</p>
|
||||||
<p class="size">{{ t('data_source.or_large_screen') }}</p>
|
<p class="size">{{ t('data_source.or_large_screen') }}1</p>
|
||||||
</div>
|
</div>
|
||||||
<el-button class="create" secondary :disabled="disabled" @click="createDataset">
|
<el-button class="create" secondary :disabled="disabled" @click="createDataset">
|
||||||
{{ t('data_source.go_to_create') }}
|
{{ t('data_source.go_to_create') }}
|
||||||
|
@ -46,22 +46,22 @@ function updateClick(row:any){
|
|||||||
paramsObj.value = row
|
paramsObj.value = row
|
||||||
isAppPopUp.value = true
|
isAppPopUp.value = true
|
||||||
}
|
}
|
||||||
function routerClick(item){
|
function routerClick(item,path){
|
||||||
router.push({
|
router.push({
|
||||||
path: '/module',
|
path: path,
|
||||||
query: {
|
query: {
|
||||||
id: item.id
|
id: item.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function delClick(id){
|
function delClick(row){
|
||||||
ElMessageBox.confirm('您确定删除该项目模块及内容吗?', {
|
ElMessageBox.confirm('您确定删除'+ row.name+'项目及所有模块吗?', {
|
||||||
confirmButtonType: 'primary',
|
confirmButtonType: 'primary',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
applicationDel(id).then(res => {
|
applicationDel(row.id).then(res => {
|
||||||
if(res.data.code == '0'){
|
if(res.data.code == '0'){
|
||||||
getDatasetList()
|
getDatasetList()
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
@ -107,16 +107,17 @@ function delClick(id){
|
|||||||
<div class="mask_box_img">
|
<div class="mask_box_img">
|
||||||
<img src="@/assets/newimg/icon/edit.png" @click="updateClick(item)" title="编辑项目">
|
<img src="@/assets/newimg/icon/edit.png" @click="updateClick(item)" title="编辑项目">
|
||||||
<img src="@/assets/newimg/icon/caidan.png" alt="" title="菜单配置">
|
<img src="@/assets/newimg/icon/caidan.png" alt="" title="菜单配置">
|
||||||
<img src="@/assets/newimg/icon/edit.png" alt="" title="编辑数据">
|
<img src="@/assets/newimg/icon/edit.png" alt="" title="编辑数据集" @click="routerClick(item,'/datasetnew')">
|
||||||
|
<img src="@/assets/newimg/icon/edit.png" alt="" title="编辑数据源" @click="routerClick(item,'/datasourcenew')">
|
||||||
<img src="@/assets/newimg/icon/fuwu.png" alt="" title="服务配置">
|
<img src="@/assets/newimg/icon/fuwu.png" alt="" title="服务配置">
|
||||||
<img src="@/assets/newimg/icon/permission.png" alt="" title="权限设置">
|
<img src="@/assets/newimg/icon/permission.png" alt="" title="权限设置">
|
||||||
<img src="@/assets/newimg/icon/export.png" alt="" title="导出">
|
<img src="@/assets/newimg/icon/export.png" alt="" title="导出">
|
||||||
<img src="@/assets/newimg/icon/release.png" alt="" title="发布">
|
<img src="@/assets/newimg/icon/release.png" alt="" title="发布">
|
||||||
<img src="@/assets/newimg/icon/del.png" alt="" title="删除" @click="delClick(item.id)">
|
<img src="@/assets/newimg/icon/del.png" alt="" title="删除" @click="delClick(item)">
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex;justify-content: center;">
|
<div style="display: flex;justify-content: center;">
|
||||||
<div class="yulan">预览</div>
|
<div class="yulan">预览</div>
|
||||||
<div class="mokuaipeizhi" @click="routerClick(item)">模块配置</div>
|
<div class="mokuaipeizhi" @click="routerClick(item,'/module')">模块配置</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -284,3 +285,9 @@ function delClick(id){
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<style>
|
||||||
|
.ed-message-box__headerbtn{
|
||||||
|
top: -15px;
|
||||||
|
right: -10px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
export interface Info {
|
||||||
|
name: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
defineProps({
|
||||||
|
creator: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
createTime: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-title">
|
||||||
|
{{ t('dataset.create_by') }}
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
{{ creator }}
|
||||||
|
</div>
|
||||||
|
<div class="info-title">
|
||||||
|
{{ t('dataset.create_time') }}
|
||||||
|
</div>
|
||||||
|
<div class="info-content">
|
||||||
|
{{ createTime }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.info-card {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-style: normal;
|
||||||
|
padding-left: 4px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
|
||||||
|
.info-title {
|
||||||
|
color: #646a73;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.info-content {
|
||||||
|
color: #1f2329;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,582 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import dvPreviewDownload from '@/assets/svg/icon_download_outlined.svg'
|
||||||
|
import deDelete from '@/assets/svg/de-delete.svg'
|
||||||
|
import icon_fileExcel_colorful from '@/assets/svg/icon_file-excel_colorful.svg'
|
||||||
|
import icon_refresh_outlined from '@/assets/svg/icon_refresh_outlined.svg'
|
||||||
|
import { ref, h, onUnmounted, computed } from 'vue'
|
||||||
|
import { EmptyBackground } from '@/components/empty-background'
|
||||||
|
import { ElButton, ElMessage, ElMessageBox, ElTabPane, ElTabs } from 'element-plus-secondary'
|
||||||
|
import { RefreshLeft } from '@element-plus/icons-vue'
|
||||||
|
import {
|
||||||
|
exportTasks,
|
||||||
|
exportRetry,
|
||||||
|
exportDelete,
|
||||||
|
exportDeleteAll,
|
||||||
|
exportDeletePost
|
||||||
|
} from '@/api/dataset'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||||
|
import Icon from '@/components/icon-custom/src/Icon.vue'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { useLinkStoreWithOut } from '@/store/modules/link'
|
||||||
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const tableData = ref([])
|
||||||
|
const drawerLoading = ref(false)
|
||||||
|
const drawer = ref(false)
|
||||||
|
const msgDialogVisible = ref(false)
|
||||||
|
const msg = ref('')
|
||||||
|
const activeName = ref('ALL')
|
||||||
|
const multipleSelection = ref([])
|
||||||
|
const description = ref(t('data_set.no_tasks_yet'))
|
||||||
|
const tabList = ref([
|
||||||
|
{
|
||||||
|
label: t('data_set.exporting') + '(0)',
|
||||||
|
name: 'IN_PROGRESS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.success') + '(0)',
|
||||||
|
name: 'SUCCESS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.fail') + '(0)',
|
||||||
|
name: 'FAILED'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.waiting') + '(0)',
|
||||||
|
name: 'PENDING'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.all') + '(0)',
|
||||||
|
name: 'ALL'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
let timer
|
||||||
|
const handleClose = () => {
|
||||||
|
drawer.value = false
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
|
||||||
|
const xpack = wsCache.get('xpack-model-distributed')
|
||||||
|
const desktop = wsCache.get('app.desktop')
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(timer)
|
||||||
|
})
|
||||||
|
const handleClick = tab => {
|
||||||
|
if (tab) {
|
||||||
|
activeName.value = tab.paneName
|
||||||
|
}
|
||||||
|
if (activeName.value === 'ALL') {
|
||||||
|
description.value = t('data_export.no_file')
|
||||||
|
} else if (activeName.value === 'FAILED') {
|
||||||
|
description.value = t('data_export.no_failed_file')
|
||||||
|
} else {
|
||||||
|
description.value = t('data_export.no_task')
|
||||||
|
}
|
||||||
|
drawerLoading.value = true
|
||||||
|
exportTasks(activeName.value)
|
||||||
|
.then(res => {
|
||||||
|
tabList.value.forEach(item => {
|
||||||
|
if (item.name === 'ALL') {
|
||||||
|
item.label = t('data_set.all') + '(' + res.data.length + ')'
|
||||||
|
}
|
||||||
|
if (item.name === 'IN_PROGRESS') {
|
||||||
|
item.label =
|
||||||
|
t('data_set.exporting') +
|
||||||
|
'(' +
|
||||||
|
res.data.filter(task => task.exportStatus === 'IN_PROGRESS').length +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
if (item.name === 'SUCCESS') {
|
||||||
|
item.label =
|
||||||
|
t('data_set.success') +
|
||||||
|
'(' +
|
||||||
|
res.data.filter(task => task.exportStatus === 'SUCCESS').length +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
if (item.name === 'FAILED') {
|
||||||
|
item.label =
|
||||||
|
t('data_set.fail') +
|
||||||
|
'(' +
|
||||||
|
res.data.filter(task => task.exportStatus === 'FAILED').length +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
if (item.name === 'PENDING') {
|
||||||
|
item.label =
|
||||||
|
t('data_set.waiting') +
|
||||||
|
'(' +
|
||||||
|
res.data.filter(task => task.exportStatus === 'PENDING').length +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (activeName.value === 'ALL') {
|
||||||
|
tableData.value = res.data
|
||||||
|
} else {
|
||||||
|
tableData.value = res.data.filter(task => task.exportStatus === activeName.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
drawerLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = params => {
|
||||||
|
drawer.value = true
|
||||||
|
if (params && params.activeName !== undefined) {
|
||||||
|
activeName.value = params.activeName
|
||||||
|
}
|
||||||
|
handleClick()
|
||||||
|
timer = setInterval(() => {
|
||||||
|
if (activeName.value === 'IN_PROGRESS') {
|
||||||
|
exportTasks(activeName.value).then(res => {
|
||||||
|
tabList.value.forEach(item => {
|
||||||
|
if (item.name === 'ALL') {
|
||||||
|
item.label = t('data_set.all') + '(' + res.data.length + ')'
|
||||||
|
}
|
||||||
|
if (item.name === 'IN_PROGRESS') {
|
||||||
|
item.label =
|
||||||
|
t('data_set.exporting') +
|
||||||
|
'(' +
|
||||||
|
res.data.filter(task => task.exportStatus === 'IN_PROGRESS').length +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
if (item.name === 'SUCCESS') {
|
||||||
|
item.label =
|
||||||
|
t('data_set.success') +
|
||||||
|
'(' +
|
||||||
|
res.data.filter(task => task.exportStatus === 'SUCCESS').length +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
if (item.name === 'FAILED') {
|
||||||
|
item.label =
|
||||||
|
t('data_set.fail') +
|
||||||
|
'(' +
|
||||||
|
res.data.filter(task => task.exportStatus === 'FAILED').length +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
if (item.name === 'PENDING') {
|
||||||
|
item.label =
|
||||||
|
t('data_set.waiting') +
|
||||||
|
'(' +
|
||||||
|
res.data.filter(task => task.exportStatus === 'PENDING').length +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (activeName.value === 'ALL') {
|
||||||
|
tableData.value = res.data
|
||||||
|
} else {
|
||||||
|
tableData.value = res.data.filter(task => task.exportStatus === activeName.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
const linkStore = useLinkStoreWithOut()
|
||||||
|
const appStore = useAppStoreWithOut()
|
||||||
|
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
|
||||||
|
|
||||||
|
const taskExportTopicCall = task => {
|
||||||
|
if (!linkStore.getLinkToken && !isDataEaseBi.value && !appStore.getIsIframe) {
|
||||||
|
if (JSON.parse(task).exportStatus === 'SUCCESS') {
|
||||||
|
openMessageLoading(
|
||||||
|
JSON.parse(task).exportFromName + ` ${t('data_set.successful_go_to')}`,
|
||||||
|
'success',
|
||||||
|
callbackExportSuc
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (JSON.parse(task).exportStatus === 'FAILED') {
|
||||||
|
openMessageLoading(
|
||||||
|
JSON.parse(task).exportFromName + ` ${t('data_set.failed_go_to')}`,
|
||||||
|
'error',
|
||||||
|
callbackExportError
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openMessageLoading = (text, type = 'success', cb) => {
|
||||||
|
// success error loading
|
||||||
|
const customClass = `de-message-${type || 'success'} de-message-export`
|
||||||
|
ElMessage({
|
||||||
|
message: h('p', null, [
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
title: t(text),
|
||||||
|
class: 'ellipsis m50-export'
|
||||||
|
},
|
||||||
|
t(text)
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
ElButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
size: 'small',
|
||||||
|
class: 'btn-text',
|
||||||
|
onClick: () => {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
t('data_export.export_center')
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
icon: type === 'loading' ? h(RefreshLeft) : '',
|
||||||
|
type,
|
||||||
|
showClose: true,
|
||||||
|
customClass
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const callbackExportError = () => {
|
||||||
|
useEmitt().emitter.emit('data-export-center', { activeName: 'FAILED' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const callbackExportSuc = () => {
|
||||||
|
useEmitt().emitter.emit('data-export-center', { activeName: 'SUCCESS' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const downLoadAll = () => {
|
||||||
|
if (multipleSelection.value.length === 0) {
|
||||||
|
tableData.value.forEach(item => {
|
||||||
|
window.open(PATH_URL + '/exportCenter/download/' + item.id)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
multipleSelection.value.map(ele => {
|
||||||
|
window.open(PATH_URL + '/exportCenter/download/' + ele.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const showMsg = item => {
|
||||||
|
msg.value = ''
|
||||||
|
msg.value = item.msg
|
||||||
|
msgDialogVisible.value = true
|
||||||
|
}
|
||||||
|
const timestampFormatDate = value => {
|
||||||
|
if (!value) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
return new Date(value).toLocaleString()
|
||||||
|
}
|
||||||
|
import { PATH_URL } from '@/config/axios/service'
|
||||||
|
const downloadClick = item => {
|
||||||
|
window.open(PATH_URL + '/exportCenter/download/' + item.id, openType)
|
||||||
|
}
|
||||||
|
|
||||||
|
const retry = item => {
|
||||||
|
exportRetry(item.id).then(() => {
|
||||||
|
handleClick()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteField = item => {
|
||||||
|
ElMessageBox.confirm(t('data_export.sure_del'), {
|
||||||
|
confirmButtonType: 'danger',
|
||||||
|
type: 'warning',
|
||||||
|
autofocus: false,
|
||||||
|
showClose: false
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
exportDelete(item.id).then(() => {
|
||||||
|
ElMessage.success(t('commons.delete_success'))
|
||||||
|
handleClick()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// info(t('commons.delete_cancel'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelectionChange = val => {
|
||||||
|
multipleSelection.value = val
|
||||||
|
}
|
||||||
|
|
||||||
|
const delAll = () => {
|
||||||
|
if (multipleSelection.value.length === 0) {
|
||||||
|
ElMessageBox.confirm(t('data_export.sure_del_all'), {
|
||||||
|
confirmButtonType: 'danger',
|
||||||
|
type: 'warning',
|
||||||
|
autofocus: false,
|
||||||
|
showClose: false
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
exportDeleteAll(
|
||||||
|
activeName.value,
|
||||||
|
multipleSelection.value.map(ele => ele.id)
|
||||||
|
).then(() => {
|
||||||
|
ElMessage.success(t('commons.delete_success'))
|
||||||
|
handleClick()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// info(t('commons.delete_cancel'))
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessageBox.confirm(t('data_export.sure_del'), {
|
||||||
|
confirmButtonType: 'danger',
|
||||||
|
type: 'warning',
|
||||||
|
autofocus: false,
|
||||||
|
showClose: false
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
exportDeletePost(multipleSelection.value.map(ele => ele.id)).then(() => {
|
||||||
|
ElMessage.success(t('commons.delete_success'))
|
||||||
|
handleClick()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// info(t('commons.delete_cancel'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEmitt({ name: 'task-export-topic-call', callback: taskExportTopicCall })
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
init
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
v-loading="drawerLoading"
|
||||||
|
custom-class="de-export-excel"
|
||||||
|
:title="$t('data_export.export_center')"
|
||||||
|
v-model="drawer"
|
||||||
|
direction="rtl"
|
||||||
|
size="1000px"
|
||||||
|
append-to-body
|
||||||
|
:before-close="handleClose"
|
||||||
|
>
|
||||||
|
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||||
|
<el-tab-pane v-for="tab in tabList" :key="tab.name" :label="tab.label" :name="tab.name" />
|
||||||
|
</el-tabs>
|
||||||
|
<el-button
|
||||||
|
v-if="activeName === 'SUCCESS' && multipleSelection.length === 0"
|
||||||
|
secondary
|
||||||
|
@click="downLoadAll"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Icon name="dv-preview-download"><dvPreviewDownload class="svg-icon" /></Icon>
|
||||||
|
</template>
|
||||||
|
{{ $t('data_export.download_all') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="activeName === 'SUCCESS' && multipleSelection.length !== 0"
|
||||||
|
secondary
|
||||||
|
@click="downLoadAll"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Icon name="dv-preview-download"><dvPreviewDownload class="svg-icon" /></Icon>
|
||||||
|
</template>
|
||||||
|
{{ $t('data_export.download') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="multipleSelection.length === 0" secondary @click="delAll"
|
||||||
|
><template #icon>
|
||||||
|
<Icon name="de-delete"><deDelete class="svg-icon" /></Icon> </template
|
||||||
|
>{{ $t('data_export.del_all') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="multipleSelection.length !== 0" secondary @click="delAll"
|
||||||
|
><template #icon>
|
||||||
|
<Icon name="de-delete"><deDelete class="svg-icon" /></Icon> </template
|
||||||
|
>{{ $t('commons.delete') }}
|
||||||
|
</el-button>
|
||||||
|
<div class="table-container" :class="!tableData.length && 'hidden-bottom'">
|
||||||
|
<el-table
|
||||||
|
ref="multipleTable"
|
||||||
|
:data="tableData"
|
||||||
|
height="100%"
|
||||||
|
style="width: 100%"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="50" />
|
||||||
|
<el-table-column prop="fileName" :label="$t('driver.file_name')" width="332">
|
||||||
|
<template #default="scope">
|
||||||
|
<div class="name-excel">
|
||||||
|
<el-icon style="font-size: 24px">
|
||||||
|
<Icon name="icon_file-excel_colorful"
|
||||||
|
><icon_fileExcel_colorful class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<div class="name-content">
|
||||||
|
<div class="fileName">{{ scope.row.fileName }}</div>
|
||||||
|
<div
|
||||||
|
v-if="scope.row.exportStatus === 'FAILED'"
|
||||||
|
class="failed"
|
||||||
|
@click="showMsg(scope.row)"
|
||||||
|
>
|
||||||
|
{{ $t('data_export.export_failed') }}
|
||||||
|
</div>
|
||||||
|
<div v-if="scope.row.exportStatus === 'SUCCESS'" class="success">
|
||||||
|
{{ scope.row.fileSize }}{{ scope.row.fileSizeUnit }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="scope.row.exportStatus === 'FAILED'" class="red-line" />
|
||||||
|
<el-progress
|
||||||
|
v-if="scope.row.exportStatus === 'IN_PROGRESS'"
|
||||||
|
:percentage="+scope.row.exportProgress"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="exportFromName" :label="$t('data_export.export_obj')" width="200" />
|
||||||
|
<el-table-column prop="exportTime" width="180" :label="$t('data_export.export_time')">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ timestampFormatDate(scope.row.exportTime) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="exportFromType" width="120" :label="$t('data_export.export_from')">
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-if="scope.row.exportFromType === 'dataset'">{{ t('data_set.data_set') }}</span>
|
||||||
|
<span v-if="scope.row.exportFromType === 'chart'">{{ t('data_set.view') }}</span>
|
||||||
|
<span v-if="scope.row.exportFromType === 'data_filling'">{{
|
||||||
|
t('data_fill.data_fill')
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-if="!desktop"
|
||||||
|
prop="orgName"
|
||||||
|
:label="t('data_set.organization')"
|
||||||
|
width="200"
|
||||||
|
/>
|
||||||
|
<el-table-column fixed="right" prop="operate" width="90" :label="$t('commons.operating')">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tooltip effect="dark" :content="t('data_set.download')" placement="top">
|
||||||
|
<el-button
|
||||||
|
v-if="scope.row.exportStatus === 'SUCCESS'"
|
||||||
|
text
|
||||||
|
@click="downloadClick(scope.row)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="dv-preview-download"><dvPreviewDownload class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip effect="dark" :content="t('data_set.re_export')" placement="top">
|
||||||
|
<el-button v-if="scope.row.exportStatus === 'FAILED'" text @click="retry(scope.row)">
|
||||||
|
<template #icon>
|
||||||
|
<Icon name="icon_refresh_outlined"
|
||||||
|
><icon_refresh_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip effect="dark" :content="t('data_set.delete')" placement="top">
|
||||||
|
<el-button text @click="deleteField(scope.row)">
|
||||||
|
<template #icon>
|
||||||
|
<Icon name="de-delete"><deDelete class="svg-icon" /></Icon>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<template #empty>
|
||||||
|
<empty-background :description="description" img-type="noneWhite" />
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<el-dialog :title="t('data_set.reason_for_failure')" v-model="msgDialogVisible" width="30%">
|
||||||
|
<span>{{ msg }}</span>
|
||||||
|
<template v-slot:footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="msgDialogVisible = false">{{
|
||||||
|
t('data_set.closure')
|
||||||
|
}}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.de-export-excel {
|
||||||
|
.ed-drawer__body {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
.ed-drawer__header {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.ed-tabs {
|
||||||
|
margin-top: -25px;
|
||||||
|
.ed-tabs__header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
margin-top: 16px;
|
||||||
|
height: calc(100vh - 190px);
|
||||||
|
|
||||||
|
.ed-table .cell {
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hidden-bottom {
|
||||||
|
.ed-table::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-excel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.name-content {
|
||||||
|
max-width: 280px;
|
||||||
|
margin-left: 4px;
|
||||||
|
.fileName {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #f54a45;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #8f959e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ed-table__header {
|
||||||
|
border-top: 1px solid #1f232926;
|
||||||
|
}
|
||||||
|
|
||||||
|
th.ed-table__cell.is-leaf {
|
||||||
|
border-color: #1f232926;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-line {
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
background: #f54a45;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,245 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import icon_down_outlined from '@/assets/svg/icon_down_outlined.svg'
|
||||||
|
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||||
|
export default {
|
||||||
|
name: 'logic-relation'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { PropType, computed, toRefs } from 'vue'
|
||||||
|
import FilterFiled from './FilterFiled.vue'
|
||||||
|
import type { Item } from './FilterFiled.vue'
|
||||||
|
export type Logic = 'or' | 'and'
|
||||||
|
export type Relation = {
|
||||||
|
child?: Relation[]
|
||||||
|
logic: Logic
|
||||||
|
x: number
|
||||||
|
} & Item
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
relationList: {
|
||||||
|
type: Array as PropType<Relation[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
logic: {
|
||||||
|
type: String as PropType<Logic>,
|
||||||
|
default: 'or'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const marginLeft = computed(() => {
|
||||||
|
return {
|
||||||
|
marginLeft: props.x ? '20px' : 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits([
|
||||||
|
'addCondReal',
|
||||||
|
'changeAndOrDfs',
|
||||||
|
'update:logic',
|
||||||
|
'removeRelationList',
|
||||||
|
'del'
|
||||||
|
])
|
||||||
|
|
||||||
|
const { relationList } = toRefs(props)
|
||||||
|
|
||||||
|
const handleCommand = type => {
|
||||||
|
emits('update:logic', type)
|
||||||
|
emits('changeAndOrDfs', type)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeRelationList = index => {
|
||||||
|
relationList.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
const addCondReal = type => {
|
||||||
|
emits('addCondReal', type, props.logic === 'or' ? 'and' : 'or')
|
||||||
|
}
|
||||||
|
const add = (type, child, logic) => {
|
||||||
|
child.push(
|
||||||
|
type === 'condition'
|
||||||
|
? {
|
||||||
|
fieldId: '',
|
||||||
|
value: '',
|
||||||
|
enumValue: '',
|
||||||
|
term: '',
|
||||||
|
filterType: 'logic',
|
||||||
|
name: '',
|
||||||
|
deType: ''
|
||||||
|
}
|
||||||
|
: { child: [], logic }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const del = (index, child) => {
|
||||||
|
child.splice(index, 1)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="logic" :style="marginLeft">
|
||||||
|
<div class="logic-left">
|
||||||
|
<div class="operate-title">
|
||||||
|
<span style="color: #bfbfbf" class="mrg-title" v-if="x">
|
||||||
|
{{ logic === 'or' ? 'OR' : 'AND' }}
|
||||||
|
</span>
|
||||||
|
<el-dropdown @command="handleCommand" trigger="click" v-else>
|
||||||
|
<span style="color: rgba(0 0 0 / 65%)" class="mrg-title fir">
|
||||||
|
{{ logic === 'or' ? 'OR' : 'AND' }}
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_down_outlined"><icon_down_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item command="and">AND</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="or">OR</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
<span class="operate-icon" v-if="x">
|
||||||
|
<el-icon @click="emits('removeRelationList')">
|
||||||
|
<Icon name="icon_delete-trash_outlined"
|
||||||
|
><icon_deleteTrash_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="logic-right">
|
||||||
|
<template :key="index" v-for="(item, index) in relationList">
|
||||||
|
<logic-relation
|
||||||
|
v-if="item.child"
|
||||||
|
:x="item.x"
|
||||||
|
@del="idx => del(idx, item.child)"
|
||||||
|
@addCondReal="(type, logic) => add(type, item.child, logic)"
|
||||||
|
:logic="item.logic"
|
||||||
|
@removeRelationList="removeRelationList(index)"
|
||||||
|
:relationList="item.child"
|
||||||
|
>
|
||||||
|
</logic-relation>
|
||||||
|
<filter-filed v-else :item="item" @del="emits('del', index)" :index="index"></filter-filed>
|
||||||
|
</template>
|
||||||
|
<div class="logic-right-add">
|
||||||
|
<button @click="addCondReal('condition')" class="operand-btn">
|
||||||
|
+ {{ t('auth.add_condition') }}
|
||||||
|
</button>
|
||||||
|
<button v-if="x < 2" @click="addCondReal('relation')" class="operand-btn">
|
||||||
|
+ {{ t('auth.add_relationship') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.logic {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.logic-left {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 48px;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.operate-title {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
word-wrap: break-word;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 65px;
|
||||||
|
background-color: #f8f8fa;
|
||||||
|
line-height: 28px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
height: 28px;
|
||||||
|
|
||||||
|
.mrg-title {
|
||||||
|
text-align: left;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
margin-left: 11px;
|
||||||
|
margin-right: 11px;
|
||||||
|
line-height: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.operate-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate-title {
|
||||||
|
.mrg-title:not(.fir) {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
line-height: 28px;
|
||||||
|
background-color: #f8f8fa;
|
||||||
|
z-index: 1;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
display: unset;
|
||||||
|
padding: 5px 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logic-right-add {
|
||||||
|
display: flex;
|
||||||
|
height: 41.4px;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 26px;
|
||||||
|
|
||||||
|
.operand-btn {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
|
||||||
|
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||||
|
outline: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #246dff;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #246dff;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,327 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import AuthTree from './AuthTree.vue'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const errorMessage = ref('')
|
||||||
|
const logic = ref<'or' | 'and'>('or')
|
||||||
|
const relationList = ref([])
|
||||||
|
|
||||||
|
const svgRealinePath = computed(() => {
|
||||||
|
const lg = relationList.value.length
|
||||||
|
let a = { x: 0, y: 0, child: relationList.value }
|
||||||
|
a.y = Math.floor(dfsXY(a, 0) / 2)
|
||||||
|
if (!lg) return ''
|
||||||
|
let path = calculateDepth(a)
|
||||||
|
return path
|
||||||
|
})
|
||||||
|
|
||||||
|
const svgDashinePath = computed(() => {
|
||||||
|
const lg = relationList.value.length
|
||||||
|
let a = { x: 0, y: 0, child: relationList.value }
|
||||||
|
a.y = Math.floor(dfsXY(a, 0) / 2)
|
||||||
|
if (!lg) return `M48 20 L68 20`
|
||||||
|
let path = calculateDepthDash(a)
|
||||||
|
return path
|
||||||
|
})
|
||||||
|
|
||||||
|
const init = expressionTree => {
|
||||||
|
const { items } = expressionTree
|
||||||
|
logic.value = expressionTree.logic || 'or'
|
||||||
|
relationList.value = dfsInit(items || [])
|
||||||
|
}
|
||||||
|
const submit = () => {
|
||||||
|
errorMessage.value = ''
|
||||||
|
emits('save', {
|
||||||
|
logic: logic.value,
|
||||||
|
items: dfsSubmit(relationList.value),
|
||||||
|
errorMessage: errorMessage.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const errorDetected = ({ enumValue, deType, filterType, term, value, name }) => {
|
||||||
|
if (!name) {
|
||||||
|
errorMessage.value = t('data_set.cannot_be_empty_')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (filterType === 'logic') {
|
||||||
|
if (!term) {
|
||||||
|
errorMessage.value = t('data_set.cannot_be_empty_de_ruler')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!term.includes('null') && !term.includes('empty') && value === '') {
|
||||||
|
errorMessage.value = t('chart.filter_value_can_null')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ([2, 3].includes(deType)) {
|
||||||
|
if (parseFloat(value).toString() === 'NaN') {
|
||||||
|
errorMessage.value = t('chart.filter_value_can_not_str')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filterType === 'enum') {
|
||||||
|
if (enumValue.length < 1) {
|
||||||
|
errorMessage.value = t('chart.enum_value_can_not_null')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dfsInit = arr => {
|
||||||
|
const elementList = []
|
||||||
|
arr.forEach(ele => {
|
||||||
|
const { subTree } = ele
|
||||||
|
if (subTree) {
|
||||||
|
const { items = [], logic } = subTree
|
||||||
|
const child = dfsInit(items)
|
||||||
|
elementList.push({ logic, child })
|
||||||
|
} else {
|
||||||
|
const { enumValue, fieldId, filterType, term, value, field } = ele
|
||||||
|
const { name, deType } = field || {}
|
||||||
|
elementList.push({
|
||||||
|
enumValue: enumValue.join(','),
|
||||||
|
fieldId,
|
||||||
|
filterType,
|
||||||
|
term,
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
deType
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return elementList
|
||||||
|
}
|
||||||
|
const dfsSubmit = arr => {
|
||||||
|
const items = []
|
||||||
|
arr.forEach(ele => {
|
||||||
|
const { child = [] } = ele
|
||||||
|
if (child.length) {
|
||||||
|
const { logic } = ele
|
||||||
|
const subTree = dfsSubmit(child)
|
||||||
|
items.push({
|
||||||
|
enumValue: [],
|
||||||
|
fieldId: '',
|
||||||
|
filterType: '',
|
||||||
|
term: '',
|
||||||
|
type: 'tree',
|
||||||
|
value: '',
|
||||||
|
subTree: { logic, items: subTree }
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const { enumValue, fieldId, filterType, deType, term, value, name } = ele
|
||||||
|
errorDetected({ deType, enumValue, filterType, term, value, name })
|
||||||
|
if (fieldId) {
|
||||||
|
items.push({
|
||||||
|
enumValue: enumValue ? enumValue.split(',') : [],
|
||||||
|
fieldId,
|
||||||
|
filterType,
|
||||||
|
term,
|
||||||
|
value,
|
||||||
|
type: 'item',
|
||||||
|
subTree: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
const removeRelationList = () => {
|
||||||
|
relationList.value = []
|
||||||
|
}
|
||||||
|
const getY = arr => {
|
||||||
|
const [a] = arr
|
||||||
|
if (a.child?.length) {
|
||||||
|
return getY(a.child)
|
||||||
|
}
|
||||||
|
return a.y
|
||||||
|
}
|
||||||
|
const calculateDepthDash = obj => {
|
||||||
|
const lg = obj.child?.length
|
||||||
|
let path = ''
|
||||||
|
if (!lg && Array.isArray(obj.child)) {
|
||||||
|
const { x, y } = obj
|
||||||
|
path += `M${48 + x * 68} ${y * 41.4 + 20} L${88 + x * 68} ${y * 41.4 + 20}`
|
||||||
|
} else if (obj.child?.length) {
|
||||||
|
let y = Math.max(dfsY(obj, 0), dfs(obj.child, 0) + getY(obj.child) - 1)
|
||||||
|
let parent = (dfs(obj.child, 0) * 41.4) / 2 + (getY(obj.child) || 0) * 41.4
|
||||||
|
const { x } = obj
|
||||||
|
path += `M${24 + x * 68} ${parent} L${24 + x * 68} ${y * 41.4 + 20} L${64 + x * 68} ${
|
||||||
|
y * 41.4 + 20
|
||||||
|
}`
|
||||||
|
obj.child.forEach(item => {
|
||||||
|
path += calculateDepthDash(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
const calculateDepth = obj => {
|
||||||
|
const lg = obj.child.length
|
||||||
|
if (!lg) return ''
|
||||||
|
let path = ''
|
||||||
|
const { x: depth, y } = obj
|
||||||
|
obj.child.forEach((item, index) => {
|
||||||
|
const { y: sibingLg, z } = item
|
||||||
|
if (item.child?.length) {
|
||||||
|
let parent = (dfs(obj.child, 0) * 41.4) / 2 + (getY(obj.child) || 0) * 41.4
|
||||||
|
let children = (dfs(item.child, 0) * 41.4) / 2 + getY(item.child) * 41.4
|
||||||
|
let path1 = 0
|
||||||
|
let path2 = 0
|
||||||
|
if (parent < children) {
|
||||||
|
path1 = parent
|
||||||
|
path2 = children
|
||||||
|
} else {
|
||||||
|
;[path1, path2] = [children, parent]
|
||||||
|
}
|
||||||
|
if (y >= sibingLg) {
|
||||||
|
path1 = parent
|
||||||
|
path2 = children
|
||||||
|
}
|
||||||
|
path += `M${24 + depth * 68} ${path1} L${24 + depth * 68} ${path2} L${
|
||||||
|
68 + depth * 68
|
||||||
|
} ${path2}`
|
||||||
|
path += calculateDepth(item)
|
||||||
|
}
|
||||||
|
if (!item.child?.length) {
|
||||||
|
if (sibingLg >= y) {
|
||||||
|
path += `M${24 + depth * 68} ${y * 40} L${24 + depth * 68} ${
|
||||||
|
(sibingLg + 1) * 41.4 - 20.69921875
|
||||||
|
} L${68 + depth * 68} ${(sibingLg + 1) * 41.4 - 20.69921875}`
|
||||||
|
} else {
|
||||||
|
path += `M${24 + depth * 68} ${
|
||||||
|
(sibingLg +
|
||||||
|
(lg === 1 && index === 0 ? 0 : 1) +
|
||||||
|
(obj.child[index + 1]?.child?.length ? y - sibingLg - 1 : 0)) *
|
||||||
|
41.4 +
|
||||||
|
20 +
|
||||||
|
(lg === 1 && index === 0 ? 26 : 0)
|
||||||
|
} L${24 + depth * 68} ${
|
||||||
|
(sibingLg + 1) * 41.4 - 20.69921875 - (lg === 1 && index === 0 ? (z || 0) * 1.4 : 0)
|
||||||
|
} L${68 + depth * 68} ${
|
||||||
|
(sibingLg + 1) * 41.4 - 20.69921875 - (lg === 1 && index === 0 ? (z || 0) * 1.4 : 0)
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
const changeAndOrDfs = (arr, logic) => {
|
||||||
|
arr.forEach(ele => {
|
||||||
|
if (ele.child) {
|
||||||
|
ele.logic = logic === 'and' ? 'or' : 'and'
|
||||||
|
changeAndOrDfs(ele.child, ele.logic)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const dfs = (arr, count) => {
|
||||||
|
arr.forEach(ele => {
|
||||||
|
if (ele.child?.length) {
|
||||||
|
count = dfs(ele.child, count)
|
||||||
|
} else {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
const dfsY = (obj, count) => {
|
||||||
|
obj.child.forEach(ele => {
|
||||||
|
if (ele.child?.length) {
|
||||||
|
count = dfsY(ele, count)
|
||||||
|
} else {
|
||||||
|
count = Math.max(count, ele.y, obj.y)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
const dfsXY = (obj, count) => {
|
||||||
|
obj.child.forEach(ele => {
|
||||||
|
ele.x = obj.x + 1
|
||||||
|
if (ele.child?.length) {
|
||||||
|
let l = dfs(ele.child, 0)
|
||||||
|
ele.y = Math.floor(l / 2) + count
|
||||||
|
count = dfsXY(ele, count)
|
||||||
|
} else {
|
||||||
|
count += 1
|
||||||
|
ele.y = count - 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
const addCondReal = (type, logic) => {
|
||||||
|
relationList.value.push(
|
||||||
|
type === 'condition'
|
||||||
|
? {
|
||||||
|
fieldId: '',
|
||||||
|
value: '',
|
||||||
|
enumValue: '',
|
||||||
|
term: '',
|
||||||
|
filterType: 'logic',
|
||||||
|
name: '',
|
||||||
|
deType: ''
|
||||||
|
}
|
||||||
|
: { child: [], logic }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const del = index => {
|
||||||
|
relationList.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
init,
|
||||||
|
submit
|
||||||
|
})
|
||||||
|
const emits = defineEmits(['save'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rowAuth">
|
||||||
|
<auth-tree
|
||||||
|
@del="idx => del(idx)"
|
||||||
|
@addCondReal="addCondReal"
|
||||||
|
@removeRelationList="removeRelationList"
|
||||||
|
@changeAndOrDfs="type => changeAndOrDfs(relationList, type)"
|
||||||
|
:relationList="relationList"
|
||||||
|
v-model:logic="logic"
|
||||||
|
/>
|
||||||
|
<svg width="388" height="100%" class="real-line">
|
||||||
|
<path
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
:d="svgRealinePath"
|
||||||
|
fill="none"
|
||||||
|
stroke="#CCCCCC"
|
||||||
|
stroke-width="0.5"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<svg width="388" height="100%" class="dash-line">
|
||||||
|
<path
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
:d="svgDashinePath"
|
||||||
|
fill="none"
|
||||||
|
stroke="#CCCCCC"
|
||||||
|
stroke-width="0.5"
|
||||||
|
stroke-dasharray="4,4"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.rowAuth {
|
||||||
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #2c3e50;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.real-line,
|
||||||
|
.dash-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,897 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
|
||||||
|
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||||
|
import icon_adjustment_outlined from '@/assets/svg/icon_adjustment_outlined.svg'
|
||||||
|
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||||
|
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||||
|
import { ref, reactive, onMounted, onBeforeUnmount, watch, unref, computed, nextTick } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import CodeMirror from './CodeMirror.vue'
|
||||||
|
import { getFunction } from '@/api/dataset'
|
||||||
|
import { fieldType } from '@/utils/attr'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import { guid } from './util'
|
||||||
|
import { iconFieldMap } from '@/components/icon-group/field-list'
|
||||||
|
export interface CalcFieldType {
|
||||||
|
id?: string
|
||||||
|
datasourceId?: string // 数据源id
|
||||||
|
datasetTableId?: string // union node id
|
||||||
|
datasetGroupId?: string // 有就传,没有null
|
||||||
|
originName: string // 物理字段名
|
||||||
|
name: string // 字段显示名
|
||||||
|
dataeaseName?: string // 字段别名
|
||||||
|
groupType: 'd' | 'q' // d=维度,q=指标
|
||||||
|
type: string
|
||||||
|
params?: Array<{ id: string; name: string; value: number }>
|
||||||
|
checked: boolean
|
||||||
|
deType: number // 字段类型
|
||||||
|
deExtractType?: number // 字段原始类型
|
||||||
|
extField?: number // 0=原始字段,2=复制或计算字段
|
||||||
|
fieldShortName?: string // 字段别名
|
||||||
|
}
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const myCm = ref()
|
||||||
|
const searchField = ref('')
|
||||||
|
const searchFunction = ref('')
|
||||||
|
|
||||||
|
const mirror = ref()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
crossDs: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
{ label: t('dataset.text'), value: 0 },
|
||||||
|
{ label: t('dataset.time'), value: 1 },
|
||||||
|
{ label: t('dataset.value'), value: 2 },
|
||||||
|
{
|
||||||
|
label: t('dataset.value') + '(' + t('dataset.float') + ')',
|
||||||
|
value: 3
|
||||||
|
},
|
||||||
|
{ label: t('dataset.location'), value: 5 },
|
||||||
|
{ label: 'URL', value: 7 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
originName: '', // 物理字段名
|
||||||
|
name: '', // 字段显示名
|
||||||
|
groupType: 'd', // d=维度,q=指标
|
||||||
|
type: 'VARCHAR',
|
||||||
|
deType: 0, // 字段类型
|
||||||
|
extField: 2,
|
||||||
|
id: '',
|
||||||
|
params: [],
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
functionData: [],
|
||||||
|
dimensionData: [],
|
||||||
|
dimensionList: [],
|
||||||
|
quotaData: []
|
||||||
|
})
|
||||||
|
const formQuotaRef = ref()
|
||||||
|
const formQuota = reactive({
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
value: null
|
||||||
|
})
|
||||||
|
const dialogFormVisible = ref(false)
|
||||||
|
const formQuotaRules = {
|
||||||
|
name: [
|
||||||
|
{ required: true, message: t('data_set.enter_parameter_name'), trigger: 'blur' },
|
||||||
|
{ min: 1, max: 50, message: t('data_set.enter_1_50_characters'), trigger: 'blur' }
|
||||||
|
],
|
||||||
|
value: [{ required: true, message: t('data_set.parameter_default_value'), trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const formQuotaClose = () => {
|
||||||
|
formQuotaRef.value.resetFields()
|
||||||
|
dialogFormVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const formQuotaConfirm = () => {
|
||||||
|
formQuotaRef.value.validate(val => {
|
||||||
|
if (val) {
|
||||||
|
if (!formQuota.id) {
|
||||||
|
formQuota.id = `params_${guid()}`
|
||||||
|
}
|
||||||
|
const q = cloneDeep(unref(formQuota))
|
||||||
|
fieldForm.params = [q]
|
||||||
|
const i = state.quotaData.find(ele => ele.id === formQuota.id)
|
||||||
|
if (i) {
|
||||||
|
const str = mirror.value.state.doc.toString()
|
||||||
|
const name2Auto = []
|
||||||
|
fieldForm.originName = setNameIdTrans('name', 'id', str, name2Auto)
|
||||||
|
Object.assign(i, cloneDeep(unref(formQuota)))
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
mirror.value.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: mirror.value.viewState.state.doc.length,
|
||||||
|
insert: setNameIdTrans('id', 'name', fieldForm.originName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
state.quotaData.push(q)
|
||||||
|
}
|
||||||
|
formQuotaClose()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldForm = reactive<CalcFieldType>({ ...(defaultForm as CalcFieldType) })
|
||||||
|
|
||||||
|
const setFieldForm = () => {
|
||||||
|
const str = mirror.value.state.doc.toString()
|
||||||
|
const name2Auto = []
|
||||||
|
fieldForm.originName = setNameIdTrans('name', 'id', str, name2Auto)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setNameIdTrans = (from, to, originName, name2Auto?: string[]) => {
|
||||||
|
let name2Id = originName
|
||||||
|
const nameIdMap = [...state.dimensionData, ...state.quotaData].reduce((pre, next) => {
|
||||||
|
pre[next[from]] = next[to]
|
||||||
|
return pre
|
||||||
|
}, {})
|
||||||
|
const on = originName.match(/\[(.+?)\]/g)
|
||||||
|
if (on) {
|
||||||
|
on.forEach(itm => {
|
||||||
|
const ele = itm.slice(1, -1)
|
||||||
|
if (name2Auto) {
|
||||||
|
name2Auto.push(nameIdMap[ele])
|
||||||
|
}
|
||||||
|
name2Id = name2Id.replace(`[${ele}]`, `[${nameIdMap[ele]}]`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return name2Id
|
||||||
|
}
|
||||||
|
|
||||||
|
let quotaDataList = []
|
||||||
|
let dimensionDataList = []
|
||||||
|
const initEdit = (obj, dimensionData, quotaData) => {
|
||||||
|
formQuota.id = null
|
||||||
|
Object.assign(fieldForm, { ...defaultForm, ...obj })
|
||||||
|
state.dimensionData = dimensionData
|
||||||
|
state.quotaData = quotaData.concat(fieldForm.params || [])
|
||||||
|
quotaDataList = cloneDeep(quotaData.concat(fieldForm.params || []))
|
||||||
|
dimensionDataList = cloneDeep(dimensionData)
|
||||||
|
setTimeout(() => {
|
||||||
|
formField.value.clearValidate()
|
||||||
|
}, 100)
|
||||||
|
if (!obj.originName) {
|
||||||
|
mirror.value.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: mirror.value.viewState.state.doc.length,
|
||||||
|
insert: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
mirror.value.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: mirror.value.viewState.state.doc.length,
|
||||||
|
insert: setNameIdTrans('id', 'name', obj.originName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertFieldToCodeMirror = (value: string) => {
|
||||||
|
mirror.value.dispatch({
|
||||||
|
changes: { from: mirror.value.viewState.state.selection.ranges[0].from, insert: value },
|
||||||
|
selection: { anchor: mirror.value.viewState.state.selection.ranges[0].from }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
mirror.value = myCm.value.codeComInit()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
mirror.value.destroy?.()
|
||||||
|
})
|
||||||
|
|
||||||
|
const insertParamToCodeMirror = (value: string) => {
|
||||||
|
mirror.value.dispatch({
|
||||||
|
changes: { from: mirror.value.viewState.state.selection.ranges[0].from, insert: value },
|
||||||
|
selection: { anchor: mirror.value.viewState.state.selection.ranges[0].from }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let functions = []
|
||||||
|
const initFunction = () => {
|
||||||
|
getFunction().then(res => {
|
||||||
|
functions = cloneDeep(res)
|
||||||
|
state.functionData = cloneDeep(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
() => searchField.value,
|
||||||
|
val => {
|
||||||
|
if (val && val !== '') {
|
||||||
|
state.dimensionData = JSON.parse(
|
||||||
|
JSON.stringify(
|
||||||
|
dimensionDataList.filter(
|
||||||
|
ele =>
|
||||||
|
ele.name.toLocaleLowerCase().includes(val.toLocaleLowerCase()) && ele.extField === 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
state.quotaData = JSON.parse(
|
||||||
|
JSON.stringify(
|
||||||
|
quotaDataList.filter(
|
||||||
|
ele =>
|
||||||
|
ele.name.toLocaleLowerCase().includes(val.toLocaleLowerCase()) && ele.extField === 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
state.dimensionData = JSON.parse(JSON.stringify(dimensionDataList)).filter(
|
||||||
|
ele => ele.extField === 0
|
||||||
|
)
|
||||||
|
state.quotaData = JSON.parse(JSON.stringify(quotaDataList)).filter(ele => ele.extField === 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => searchFunction.value,
|
||||||
|
val => {
|
||||||
|
if (val && val !== '') {
|
||||||
|
state.functionData = JSON.parse(
|
||||||
|
JSON.stringify(
|
||||||
|
functions.filter(ele => {
|
||||||
|
return ele.func.toLocaleLowerCase().includes(val.toLocaleLowerCase())
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
state.functionData = cloneDeep(functions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const formField = ref()
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
initEdit,
|
||||||
|
setFieldForm,
|
||||||
|
fieldForm,
|
||||||
|
formField
|
||||||
|
})
|
||||||
|
const parmasTitle = ref('')
|
||||||
|
const addParmasToQuota = () => {
|
||||||
|
if (disableCaParams.value) return
|
||||||
|
parmasTitle.value = t('data_set.add_calculation_parameters')
|
||||||
|
if (!fieldForm.params) {
|
||||||
|
fieldForm.params = []
|
||||||
|
}
|
||||||
|
dialogFormVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateParmasToQuota = () => {
|
||||||
|
const [o] = fieldForm.params
|
||||||
|
parmasTitle.value = t('data_set.edit_calculation_parameters')
|
||||||
|
Object.assign(formQuota, o || {})
|
||||||
|
dialogFormVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const disableCaParams = computed(() => {
|
||||||
|
return !!fieldForm.params?.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const delParmasToQuota = () => {
|
||||||
|
const [o] = fieldForm.params
|
||||||
|
fieldForm.params = []
|
||||||
|
const str = mirror.value.state.doc.toString()
|
||||||
|
const name2Auto = []
|
||||||
|
fieldForm.originName = setNameIdTrans('name', 'id', str, name2Auto).replaceAll(`[${o.id}]`, '')
|
||||||
|
state.quotaData = state.quotaData.filter(ele => ele.id !== o.id)
|
||||||
|
mirror.value.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: mirror.value.viewState.state.doc.length,
|
||||||
|
insert: setNameIdTrans('id', 'name', fieldForm.originName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
initFunction()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div @keydown.stop @keyup.stop class="calcu-field">
|
||||||
|
<div class="calcu-cont">
|
||||||
|
<div style="flex: 1">
|
||||||
|
<div style="max-width: 488px">
|
||||||
|
<el-form
|
||||||
|
require-asterisk-position="right"
|
||||||
|
ref="formField"
|
||||||
|
@keydown.stop.prevent.enter
|
||||||
|
:model="fieldForm"
|
||||||
|
label-position="top"
|
||||||
|
>
|
||||||
|
<el-form-item
|
||||||
|
prop="name"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('dataset.input_edit_name')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
max: 50,
|
||||||
|
message: t('commons.char_can_not_more_50')
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
:label="t('dataset.field_edit_name')"
|
||||||
|
>
|
||||||
|
<el-input v-model="fieldForm.name" :placeholder="t('dataset.input_edit_name')" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div>
|
||||||
|
<el-form label-position="top" ref="form" inline :model="fieldForm">
|
||||||
|
<el-form-item class="mr12" :label="t('dataset.data_type')">
|
||||||
|
<div class="btn-select">
|
||||||
|
<el-button
|
||||||
|
@click="fieldForm.groupType = 'd'"
|
||||||
|
:class="[fieldForm.groupType === 'd' && 'is-active']"
|
||||||
|
text
|
||||||
|
>
|
||||||
|
{{ t('chart.dimension_abb') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
@click="fieldForm.groupType = 'q'"
|
||||||
|
:class="[fieldForm.groupType === 'q' && 'is-active']"
|
||||||
|
text
|
||||||
|
>
|
||||||
|
{{ t('chart.quota_abb') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="mr0" :label="t('dataset.field_type')">
|
||||||
|
<el-select v-model="fieldForm.deType" style="width: 376px">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
class="svg-icon"
|
||||||
|
:class="`field-icon-${fieldType[fieldForm.deType]}`"
|
||||||
|
:is="iconFieldMap[fieldType[fieldForm.deType]]"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
<el-option
|
||||||
|
v-for="item in fields"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
<span style="display: flex; align-items: center">
|
||||||
|
<el-icon>
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
class="svg-icon"
|
||||||
|
:class="`field-icon-${fieldType[item.value]}`"
|
||||||
|
:is="iconFieldMap[fieldType[item.value]]"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
<span style="margin-left: 5px; font-size: 12px; color: #8492a6">{{
|
||||||
|
item.label
|
||||||
|
}}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<div class="mb8 field-exp">
|
||||||
|
<span>{{ t('dataset.field_exp') }}</span>
|
||||||
|
<span>*</span>
|
||||||
|
<el-tooltip class="item" effect="dark" placement="top">
|
||||||
|
<template #content>
|
||||||
|
<div v-if="props.crossDs">{{ t('dataset.calc_tips.tip1') }}</div>
|
||||||
|
<div v-else>{{ t('dataset.calc_tips.tip1_1') }}</div>
|
||||||
|
<div>{{ t('dataset.calc_tips.tip2') }}</div>
|
||||||
|
</template>
|
||||||
|
<el-icon size="16px">
|
||||||
|
<Icon name="icon_info_outlined"><icon_info_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<code-mirror
|
||||||
|
:quotaMap="state.quotaData.map(ele => ele.name)"
|
||||||
|
:dimensionMap="state.dimensionData.map(ele => ele.name)"
|
||||||
|
ref="myCm"
|
||||||
|
height="318px"
|
||||||
|
dom-id="calcField"
|
||||||
|
></code-mirror>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="padding-lr">
|
||||||
|
<span class="mb8">
|
||||||
|
{{ t('dataset.click_ref_field') }}
|
||||||
|
<el-tooltip class="item" effect="dark" placement="bottom">
|
||||||
|
<template #content>
|
||||||
|
{{ t('dataset.calc_tips.tip3') }}
|
||||||
|
<br />
|
||||||
|
{{ t('dataset.calc_tips.tip4') }}
|
||||||
|
<br />
|
||||||
|
{{ t('dataset.calc_tips.tip5') }}
|
||||||
|
</template>
|
||||||
|
<el-icon size="16px">
|
||||||
|
<Icon name="icon_info_outlined"><icon_info_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
<div class="padding-lr-content">
|
||||||
|
<el-input v-model="searchField" :placeholder="t('dataset.edit_search')" clearable>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_search-outline_outlined"
|
||||||
|
><icon_searchOutline_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<div class="field-height">
|
||||||
|
<el-scrollbar>
|
||||||
|
<span>{{ t('chart.dimension') }}</span>
|
||||||
|
<div v-if="state.dimensionData.length" class="field-list">
|
||||||
|
<span
|
||||||
|
v-for="item in state.dimensionData"
|
||||||
|
:key="item.id"
|
||||||
|
class="item-dimension flex-align-center"
|
||||||
|
:title="item.name"
|
||||||
|
@click="insertFieldToCodeMirror('[' + item.name + ']')"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
class="svg-icon"
|
||||||
|
:class="`field-icon-${fieldType[item.deType]}`"
|
||||||
|
:is="iconFieldMap[fieldType[item.deType]]"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span class="ellipsis" :title="item.name">{{ item.name }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="class-na">{{ t('dataset.na') }}</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<div class="quota-btn_de">
|
||||||
|
<span>{{ t('chart.quota') }}</span>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
:content="
|
||||||
|
disableCaParams
|
||||||
|
? t('data_set.parameter_is_supported')
|
||||||
|
: t('data_set.add_calculation_parameters')
|
||||||
|
"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-icon class="hover-icon_quota" @click="addParmasToQuota">
|
||||||
|
<Icon
|
||||||
|
:class="[`field-icon-${fieldType[0]}`, disableCaParams && 'not-allow']"
|
||||||
|
style="color: #646a73"
|
||||||
|
name="icon_adjustment_outlined"
|
||||||
|
><icon_adjustment_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="field-height">
|
||||||
|
<el-scrollbar>
|
||||||
|
<div v-if="state.quotaData.length" class="field-list">
|
||||||
|
<span
|
||||||
|
v-for="item in state.quotaData"
|
||||||
|
:key="item.id"
|
||||||
|
class="item-quota flex-align-center"
|
||||||
|
@click="insertFieldToCodeMirror('[' + item.name + ']')"
|
||||||
|
>
|
||||||
|
<el-icon v-if="!item.groupType">
|
||||||
|
<Icon name="icon_adjustment_outlined"
|
||||||
|
><icon_adjustment_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<el-icon v-else>
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
class="svg-icon"
|
||||||
|
:class="`field-icon-${fieldType[item.deType]}`"
|
||||||
|
:is="iconFieldMap[fieldType[item.deType]]"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span class="ellipsis" :title="item.name">{{ item.name }}</span>
|
||||||
|
<div v-if="!item.groupType" class="icon-right">
|
||||||
|
<el-icon @click.stop="updateParmasToQuota" class="hover-icon">
|
||||||
|
<Icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<el-icon @click.stop="delParmasToQuota" class="hover-icon">
|
||||||
|
<Icon name="icon_delete-trash_outlined"
|
||||||
|
><icon_deleteTrash_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-else class="class-na">{{ t('dataset.na') }}</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="padding-lr">
|
||||||
|
<span class="mb8">
|
||||||
|
{{ t('dataset.click_ref_function') }}
|
||||||
|
<el-tooltip class="item" effect="dark" placement="bottom">
|
||||||
|
<template #content>
|
||||||
|
<div v-if="props.crossDs">
|
||||||
|
{{ t('dataset.calc_tips.tip6') }}
|
||||||
|
<br />
|
||||||
|
{{ t('dataset.calc_tips.tip8') }}
|
||||||
|
<br />
|
||||||
|
https://calcite.apache.org/docs/reference.html
|
||||||
|
</div>
|
||||||
|
<div v-else>{{ t('dataset.calc_tips.tip7') }}</div>
|
||||||
|
</template>
|
||||||
|
<el-icon size="16px">
|
||||||
|
<Icon name="icon_info_outlined"><icon_info_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
<div class="padding-lr-content">
|
||||||
|
<el-input
|
||||||
|
v-model="searchFunction"
|
||||||
|
style="margin-bottom: 8px"
|
||||||
|
:placeholder="t('dataset.edit_search')"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_search-outline_outlined"
|
||||||
|
><icon_searchOutline_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-row class="function-height">
|
||||||
|
<div v-if="state.functionData.length" style="width: 100%">
|
||||||
|
<el-popover
|
||||||
|
v-for="(item, index) in state.functionData"
|
||||||
|
:key="index"
|
||||||
|
class="function-pop"
|
||||||
|
placement="right"
|
||||||
|
width="200"
|
||||||
|
trigger="hover"
|
||||||
|
:open-delay="500"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<span
|
||||||
|
class="function-style flex-align-center"
|
||||||
|
@click="insertParamToCodeMirror(item.func)"
|
||||||
|
>{{ item.func }}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<p class="pop-title">{{ item.name }}</p>
|
||||||
|
<p class="pop-info">{{ item.func }}</p>
|
||||||
|
<p class="pop-info">{{ item.desc }}</p>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
<div v-else class="class-na">{{ t('chart.no_function') }}</div>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-dialog
|
||||||
|
:before-close="formQuotaClose"
|
||||||
|
v-model="dialogFormVisible"
|
||||||
|
append-to-body
|
||||||
|
class="create-dialog"
|
||||||
|
:title="t('data_set.add_calculation_parameters')"
|
||||||
|
width="500"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
@keydown.stop.prevent.enter
|
||||||
|
label-position="top"
|
||||||
|
ref="formQuotaRef"
|
||||||
|
:model="formQuota"
|
||||||
|
:rules="formQuotaRules"
|
||||||
|
>
|
||||||
|
<el-form-item :label="t('data_set.parameter_name')" prop="name">
|
||||||
|
<el-input
|
||||||
|
style="width: 100%"
|
||||||
|
v-model="formQuota.name"
|
||||||
|
:placeholder="t('data_set.enter_1_50_characters')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('data_set.parameter_default_value_de')" prop="value">
|
||||||
|
<el-input-number
|
||||||
|
style="width: 100%"
|
||||||
|
v-model="formQuota.value"
|
||||||
|
:placeholder="t('data_set.enter_a_number')"
|
||||||
|
controls-position="right"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="formQuotaClose">{{ t('chart.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="formQuotaConfirm"> {{ t('chart.confirm') }} </el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.calcu-field {
|
||||||
|
.calcu-cont {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr12 {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr0 {
|
||||||
|
margin-right: 0;
|
||||||
|
|
||||||
|
:deep(.ed-select__prefix--light) {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-select {
|
||||||
|
width: 100px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgb(38,38,38);
|
||||||
|
border: 1px solid #bbbfc4;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.is-active {
|
||||||
|
background: var(--ed-color-primary-1a, rgba(95, 95, 95, 0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ed-button:not(.is-active) {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.ed-button.is-text {
|
||||||
|
height: 24px;
|
||||||
|
width: 44px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
.ed-button + .ed-button {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb8 {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #1f2329;
|
||||||
|
|
||||||
|
&.field-exp {
|
||||||
|
& > :nth-child(2) {
|
||||||
|
margin: 0 -0.67px 0 2px;
|
||||||
|
color: #f54a45;
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ed-icon {
|
||||||
|
color: #646a73;
|
||||||
|
margin-left: 4.67px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding-lr {
|
||||||
|
margin-left: 12px;
|
||||||
|
width: 214px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
.padding-lr-content {
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--deCardStrokeColor, #dee0e3);
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 500px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hover-icon_quota {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&[aria-expanded='true'] {
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: rgba(31, 35, 41, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: rgba(31, 35, 41, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: rgba(31, 35, 41, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quota-btn_de {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: -12px;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.field-height {
|
||||||
|
height: calc(50% - 41px);
|
||||||
|
margin-top: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
& > :nth-child(1) {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-allow {
|
||||||
|
cursor: not-allowed;
|
||||||
|
// color: #bbbfc4 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-dimension,
|
||||||
|
.item-quota {
|
||||||
|
padding: 1px 8px;
|
||||||
|
border: solid 1px #dee0e3;
|
||||||
|
background-color: rgb(37, 38, 38);
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
.ed-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
height: 28px;
|
||||||
|
margin-top: 4px;
|
||||||
|
word-break: break-all;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.icon-right {
|
||||||
|
display: none;
|
||||||
|
margin-left: auto;
|
||||||
|
align-items: center;
|
||||||
|
.ed-icon {
|
||||||
|
margin: 0 0 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-dimension:hover {
|
||||||
|
border-color: var(--ed-color-primary, #3370ff);
|
||||||
|
background: var(--ed-color-primary-1a, rgba(51, 112, 255, 0.1));
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-quota {
|
||||||
|
.ed-icon {
|
||||||
|
color: #04b49c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-quota:hover {
|
||||||
|
background: rgba(4, 180, 156, 0.1);
|
||||||
|
border-color: #04b49c;
|
||||||
|
cursor: pointer;
|
||||||
|
.icon-right {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-style {
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 0px 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #ffffff;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(31, 35, 41, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-style:hover {
|
||||||
|
border-color: var(--ed-color-primary, #3370ff);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.function-height {
|
||||||
|
height: calc(100% - 29px);
|
||||||
|
overflow: auto;
|
||||||
|
width: calc(100% + 16px);
|
||||||
|
margin-left: -8px;
|
||||||
|
}
|
||||||
|
.function-pop :deep(.ed-popover) {
|
||||||
|
padding: 6px !important;
|
||||||
|
}
|
||||||
|
.pop-title {
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.pop-info {
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-na {
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--deTextDisable);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.calcu-field {
|
||||||
|
.cm-scroller {
|
||||||
|
height: 320px;
|
||||||
|
border: 1px solid #bbbfc4;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-focused {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,156 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { sql } from '@codemirror/lang-sql'
|
||||||
|
import { basicSetup } from 'codemirror'
|
||||||
|
import { indentWithTab } from '@codemirror/commands'
|
||||||
|
import {
|
||||||
|
Decoration,
|
||||||
|
EditorView,
|
||||||
|
ViewPlugin,
|
||||||
|
WidgetType,
|
||||||
|
MatchDecorator,
|
||||||
|
keymap
|
||||||
|
} from '@codemirror/view'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
domId: propTypes.string.def('editor'),
|
||||||
|
height: propTypes.string.def('250px'),
|
||||||
|
quotaMap: propTypes.arrayOf(String).def(() => []),
|
||||||
|
dimensionMap: propTypes.arrayOf(String).def(() => [])
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['change'])
|
||||||
|
|
||||||
|
const codeComInit = (doc: string, sqlMode?: boolean) => {
|
||||||
|
function _optionalChain(ops) {
|
||||||
|
let lastAccessLHS = undefined
|
||||||
|
let value = ops[0]
|
||||||
|
let i = 1
|
||||||
|
while (i < ops.length) {
|
||||||
|
const op = ops[i]
|
||||||
|
const fn = ops[i + 1]
|
||||||
|
i += 2
|
||||||
|
if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (op === 'access' || op === 'optionalAccess') {
|
||||||
|
lastAccessLHS = value
|
||||||
|
value = fn(value)
|
||||||
|
} else if (op === 'call' || op === 'optionalCall') {
|
||||||
|
value = fn((...args) => value.call(lastAccessLHS, ...args))
|
||||||
|
lastAccessLHS = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
} //!placeholderMatcher
|
||||||
|
|
||||||
|
const placeholderMatcher = new MatchDecorator({
|
||||||
|
regexp: /\[(.*?)\]/g,
|
||||||
|
decoration: match =>
|
||||||
|
Decoration.replace({
|
||||||
|
widget: new PlaceholderWidget(match[1])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
//!placeholderPlugin
|
||||||
|
|
||||||
|
const placeholders = ViewPlugin.fromClass(
|
||||||
|
class {
|
||||||
|
placeholders
|
||||||
|
constructor(view) {
|
||||||
|
this.placeholders = placeholderMatcher.createDeco(view)
|
||||||
|
}
|
||||||
|
update(update) {
|
||||||
|
this.placeholders = placeholderMatcher.updateDeco(update, this.placeholders)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
decorations: instance => instance.placeholders,
|
||||||
|
provide: plugin =>
|
||||||
|
EditorView.atomicRanges.of(view => {
|
||||||
|
return (
|
||||||
|
_optionalChain([
|
||||||
|
view,
|
||||||
|
'access',
|
||||||
|
_ => _.plugin,
|
||||||
|
'call',
|
||||||
|
_2 => _2(plugin),
|
||||||
|
'optionalAccess',
|
||||||
|
_3 => _3.placeholders
|
||||||
|
]) || Decoration.none
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
//!placeholderWidget
|
||||||
|
|
||||||
|
class PlaceholderWidget extends WidgetType {
|
||||||
|
name: string
|
||||||
|
constructor(name: string) {
|
||||||
|
super()
|
||||||
|
this.name = name
|
||||||
|
}
|
||||||
|
eq(other) {
|
||||||
|
return this.name == other.name
|
||||||
|
}
|
||||||
|
toDOM() {
|
||||||
|
let elt = document.createElement('span')
|
||||||
|
elt.textContent = `[${this.name}]`
|
||||||
|
const { dimensionMap, quotaMap } = props
|
||||||
|
if (!dimensionMap?.length && !quotaMap?.length) {
|
||||||
|
return elt
|
||||||
|
}
|
||||||
|
const isQuota = quotaMap.includes(this.name)
|
||||||
|
elt.style.borderRadius = '2px'
|
||||||
|
elt.style.margin = '0 4px'
|
||||||
|
elt.style.padding = '0 6px'
|
||||||
|
elt.style.background = isQuota ? 'rgba(0, 214, 185, 0.20)' : 'rgba(51, 112, 255, 0.20)'
|
||||||
|
elt.style.color = isQuota ? '#04B49C' : '#2B5FD9'
|
||||||
|
return elt
|
||||||
|
}
|
||||||
|
ignoreEvent() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const extensionsAttach = sqlMode
|
||||||
|
? [
|
||||||
|
basicSetup,
|
||||||
|
sql(),
|
||||||
|
placeholders,
|
||||||
|
keymap.of([indentWithTab]),
|
||||||
|
EditorView.updateListener.of(v => {
|
||||||
|
if (v.docChanged) {
|
||||||
|
emits('change')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
: [basicSetup, placeholders, keymap.of([indentWithTab])]
|
||||||
|
return new EditorView({
|
||||||
|
doc,
|
||||||
|
extensions: extensionsAttach,
|
||||||
|
parent: document.querySelector(`#${props.domId}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
codeComInit
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :style="{ height: height }" class="editor-placeholder" :id="domId"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.editor-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="less">
|
||||||
|
.cm-editor,
|
||||||
|
.cm-scroller {
|
||||||
|
height: 100% !important;
|
||||||
|
background: #eff0f1;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,454 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import dvFolder from '@/assets/svg/dv-folder.svg'
|
||||||
|
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||||
|
import { ref, reactive, computed, watch, nextTick, unref } from 'vue'
|
||||||
|
import treeSort from '@/utils/treeSortUtils'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { ElMessage } from 'element-plus-secondary'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import {
|
||||||
|
getDatasetTree,
|
||||||
|
moveDatasetTree,
|
||||||
|
createDatasetTree,
|
||||||
|
renameDatasetTree
|
||||||
|
} from '@/api/dataset'
|
||||||
|
import type { DatasetOrFolder } from '@/api/dataset'
|
||||||
|
import nothingTree from '@/assets/img/nothing-tree.png'
|
||||||
|
import { BusiTreeRequest } from '@/models/tree/TreeNode'
|
||||||
|
import { filterFreeFolder } from '@/utils/utils'
|
||||||
|
export interface Tree {
|
||||||
|
name: string
|
||||||
|
value?: string | number
|
||||||
|
id: string | number
|
||||||
|
nodeType: string
|
||||||
|
appId: string | number
|
||||||
|
createBy?: string
|
||||||
|
level: number
|
||||||
|
leaf?: boolean
|
||||||
|
pid: string | number
|
||||||
|
union?: Array<{}>
|
||||||
|
createTime: number
|
||||||
|
allfields?: Array<{}>
|
||||||
|
children?: Tree[]
|
||||||
|
}
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
const state = reactive({
|
||||||
|
tData: [],
|
||||||
|
nameList: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const placeholder = ref('')
|
||||||
|
const nodeType = ref()
|
||||||
|
const pid = ref()
|
||||||
|
const appId = ref()
|
||||||
|
const id = ref()
|
||||||
|
const cmd = ref('')
|
||||||
|
const treeRef = ref()
|
||||||
|
const filterText = ref('')
|
||||||
|
let union = []
|
||||||
|
let allfields = []
|
||||||
|
const datasetForm = reactive({
|
||||||
|
pid: '',
|
||||||
|
name: ''
|
||||||
|
})
|
||||||
|
const searchEmpty = ref(false)
|
||||||
|
|
||||||
|
const filterNode = (value: string, data: Tree) => {
|
||||||
|
nextTick(() => {
|
||||||
|
searchEmpty.value = treeRef.value.isEmpty
|
||||||
|
})
|
||||||
|
if (!value) return true
|
||||||
|
return data.name.includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(filterText, val => {
|
||||||
|
showAll.value = !val
|
||||||
|
treeRef.value.filter(val)
|
||||||
|
nextTick(() => {
|
||||||
|
document.querySelectorAll('.node-text').forEach(ele => {
|
||||||
|
const content = ele.getAttribute('title')
|
||||||
|
ele.innerHTML = content.replace(val, `<span class="highLight">${val}</span>`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const showPid = computed(() => {
|
||||||
|
if (nodeType.value === 'folder' && !!pid.value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !['rename', 'move'].includes(cmd.value) && !!pid.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const labelName = computed(() => {
|
||||||
|
return nodeType.value === 'folder' ? t('deDataset.folder_name') : t('dataset.name')
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogTitle = computed(() => {
|
||||||
|
let title = ''
|
||||||
|
|
||||||
|
switch (nodeType.value) {
|
||||||
|
case 'folder':
|
||||||
|
title = t('deDataset.new_folder')
|
||||||
|
break
|
||||||
|
case 'dataset':
|
||||||
|
title = t('common.save') + t('auth.dataset')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch (cmd.value) {
|
||||||
|
case 'move':
|
||||||
|
title = t('chart.move_to')
|
||||||
|
break
|
||||||
|
case 'rename':
|
||||||
|
title = t('chart.rename')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
})
|
||||||
|
|
||||||
|
const showName = computed(() => {
|
||||||
|
return cmd.value !== 'move'
|
||||||
|
})
|
||||||
|
|
||||||
|
const datasetFormRules = ref()
|
||||||
|
const activeAll = ref(false)
|
||||||
|
const showAll = ref(true)
|
||||||
|
const dataset = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
const createDataset = ref(false)
|
||||||
|
const filterMethod = (value, data) => data.name.includes(value)
|
||||||
|
const resetForm = () => {
|
||||||
|
createDataset.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const dfs = (arr: Tree[]) => {
|
||||||
|
arr?.forEach(ele => {
|
||||||
|
ele.value = ele.id
|
||||||
|
if (ele.children?.length) {
|
||||||
|
dfs(ele.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const formatRootMiss = (id: string | number, treeData: Tree[]) => {
|
||||||
|
if (!treeData?.length) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (id === '0' && treeData[0].id !== '0') {
|
||||||
|
return treeData[0].id
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
const originResourceTree = ref([])
|
||||||
|
const sortList = ['time_asc', 'time_desc', 'name_asc', 'name_desc']
|
||||||
|
const createInit = (type, data: Tree, exec, name: string) => {
|
||||||
|
appId.value = ''
|
||||||
|
pid.value = ''
|
||||||
|
id.value = ''
|
||||||
|
cmd.value = ''
|
||||||
|
datasetForm.pid = ''
|
||||||
|
datasetForm.name = ''
|
||||||
|
filterText.value = ''
|
||||||
|
nodeType.value = type
|
||||||
|
if(type === 'folder' && data !=null && data.appId !=null){
|
||||||
|
appId.value = data.appId
|
||||||
|
}
|
||||||
|
placeholder.value =
|
||||||
|
type === 'folder' ? t('data_set.a_folder_name') : t('data_set.the_dataset_name')
|
||||||
|
if (type === 'dataset') {
|
||||||
|
union = data.union
|
||||||
|
allfields = data.allfields
|
||||||
|
}
|
||||||
|
if (data.id) {
|
||||||
|
const request = { leaf: false, weight: 7 } as BusiTreeRequest
|
||||||
|
getDatasetTree(request).then(res => {
|
||||||
|
filterFreeFolder(res, 'dataset')
|
||||||
|
dfs(res as unknown as Tree[])
|
||||||
|
state.tData = (res as unknown as Tree[]) || []
|
||||||
|
let curSortType = sortList[Number(wsCache.get('TreeSort-backend')) ?? 1]
|
||||||
|
curSortType = wsCache.get('TreeSort-dataset') ?? curSortType
|
||||||
|
originResourceTree.value = cloneDeep(unref(state.tData))
|
||||||
|
state.tData = treeSort(originResourceTree.value, curSortType)
|
||||||
|
if (state.tData.length && state.tData[0].name === 'root' && state.tData[0].id === '0') {
|
||||||
|
state.tData[0].name = t('data_set.data_set')
|
||||||
|
}
|
||||||
|
data.id = formatRootMiss(data.id, state.tData)
|
||||||
|
if (exec) {
|
||||||
|
pid.value = data.pid
|
||||||
|
id.value = data.id
|
||||||
|
datasetForm.pid = data.pid as string
|
||||||
|
datasetForm.name = data.name
|
||||||
|
} else {
|
||||||
|
datasetForm.pid = data.id as string
|
||||||
|
pid.value = data.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
cmd.value = exec
|
||||||
|
}
|
||||||
|
name && (datasetForm.name = name)
|
||||||
|
createDataset.value = true
|
||||||
|
datasetFormRules.value = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: placeholder.value,
|
||||||
|
trigger: 'change'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: placeholder.value,
|
||||||
|
trigger: 'blur'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
min: 1,
|
||||||
|
max: 64,
|
||||||
|
message: t('datasource.input_limit_1_64', [1, 64]),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pid: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('common.please_select'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
dataset.value.clearValidate()
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
const editeInit = (param: Tree) => {
|
||||||
|
pid.value = param.pid
|
||||||
|
id.value = param.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
label: 'name',
|
||||||
|
children: 'children',
|
||||||
|
isLeaf: node => !node.children?.length
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeClick = (data: Tree) => {
|
||||||
|
activeAll.value = false
|
||||||
|
datasetForm.pid = data.id as string
|
||||||
|
}
|
||||||
|
const checkPid = pid => {
|
||||||
|
if (pid !== 0 && !pid) {
|
||||||
|
ElMessage.error(t('data_set.the_destination_folder'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const saveDataset = () => {
|
||||||
|
dataset.value.validate(result => {
|
||||||
|
if (result) {
|
||||||
|
const params: DatasetOrFolder = {
|
||||||
|
nodeType: nodeType.value as 'folder' | 'dataset',
|
||||||
|
name: datasetForm.name,
|
||||||
|
appId: appId.value
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cmd.value) {
|
||||||
|
case 'move':
|
||||||
|
params.pid = activeAll.value ? '0' : (datasetForm.pid as string)
|
||||||
|
params.id = id.value
|
||||||
|
break
|
||||||
|
case 'rename':
|
||||||
|
params.pid = pid.value as string
|
||||||
|
params.id = id.value
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
params.pid = datasetForm.pid || pid.value || '0'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (nodeType.value === 'dataset') {
|
||||||
|
params.union = union
|
||||||
|
params.allFields = allfields
|
||||||
|
}
|
||||||
|
if (cmd.value === 'move' && !checkPid(params.pid)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
const req =
|
||||||
|
cmd.value === 'move' ? moveDatasetTree : params.id ? renameDatasetTree : createDatasetTree
|
||||||
|
req(params)
|
||||||
|
.then(res => {
|
||||||
|
dataset.value.resetFields()
|
||||||
|
createDataset.value = false
|
||||||
|
emits('finish', res)
|
||||||
|
switch (cmd.value) {
|
||||||
|
case 'move':
|
||||||
|
ElMessage.success(t('data_set.moved_successfully'))
|
||||||
|
break
|
||||||
|
case 'rename':
|
||||||
|
ElMessage.success(t('data_set.rename_successful'))
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
emits('onDatasetSave')
|
||||||
|
ElMessage.success(t('common.save_success'))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
createInit,
|
||||||
|
editeInit
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['finish', 'onDatasetSave'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="dialogTitle"
|
||||||
|
v-model="createDataset"
|
||||||
|
class="create-dialog"
|
||||||
|
:width="cmd === 'move' ? '600px' : '420px'"
|
||||||
|
:before-close="resetForm"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
ref="dataset"
|
||||||
|
@keydown.stop.prevent.enter
|
||||||
|
:model="datasetForm"
|
||||||
|
:rules="datasetFormRules"
|
||||||
|
>
|
||||||
|
<el-form-item v-if="showName" :label="labelName" prop="name">
|
||||||
|
<el-input :placeholder="placeholder" v-model="datasetForm.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="showPid" :label="t('deDataset.folder')" prop="pid">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="datasetForm.pid"
|
||||||
|
:data="state.tData"
|
||||||
|
popper-class="dataset-tree-select"
|
||||||
|
:render-after-expand="false"
|
||||||
|
style="width: 100%"
|
||||||
|
:props="props"
|
||||||
|
@node-click="nodeClick"
|
||||||
|
:filter-node-method="filterMethod"
|
||||||
|
filterable
|
||||||
|
>
|
||||||
|
<template #default="{ data: { name } }">
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span :title="name">{{ name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-tree-select>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-if="cmd === 'move'">
|
||||||
|
<el-input style="margin-bottom: 12px" v-model="filterText" clearable>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_search-outline_outlined"
|
||||||
|
><icon_searchOutline_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<div class="tree-content">
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:filter-node-method="filterNode"
|
||||||
|
filterable
|
||||||
|
v-model="datasetForm.pid"
|
||||||
|
menu
|
||||||
|
empty-text=""
|
||||||
|
:data="state.tData"
|
||||||
|
:props="props"
|
||||||
|
@node-click="nodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ data }">
|
||||||
|
<span class="custom-tree-node">
|
||||||
|
<el-icon style="font-size: 18px">
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span class="node-text" :title="data.name">{{ data.name }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
<div v-if="searchEmpty" class="empty-search">
|
||||||
|
<img :src="nothingTree" />
|
||||||
|
<span>{{ t('data_set.relevant_content_found') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button secondary @click="resetForm">{{ t('dataset.cancel') }} </el-button>
|
||||||
|
<el-button v-loading="loading" type="primary" @click="saveDataset"
|
||||||
|
>{{ t('dataset.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tree-content {
|
||||||
|
width: 552px;
|
||||||
|
height: 380px;
|
||||||
|
border: 1px solid #dee0e3;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
.custom-tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.node-text {
|
||||||
|
margin-left: 8.75px;
|
||||||
|
width: 120px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
:deep(.highLight) {
|
||||||
|
color: var(--el-color-primary, #3370ff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-search {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 57px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
img {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
color: #646a73;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="less">
|
||||||
|
.dataset-tree-select {
|
||||||
|
.ed-select-dropdown__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.ed-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,251 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import custom_sort from '@/assets/svg/custom_sort.svg'
|
||||||
|
import dvRename from '@/assets/svg/dv-rename.svg'
|
||||||
|
import icon_calendar_outlined from '@/assets/svg/icon_calendar_outlined.svg'
|
||||||
|
import icon_copy_outlined from '@/assets/svg/icon_copy_outlined.svg'
|
||||||
|
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||||
|
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||||
|
import icon_local_outlined from '@/assets/svg/icon_local_outlined.svg'
|
||||||
|
import icon_number_outlined from '@/assets/svg/icon_number_outlined.svg'
|
||||||
|
import icon_url_outlined from '@/assets/svg/icon_url_outlined.svg'
|
||||||
|
import icon_switch_outlined from '@/assets/svg/icon_switch_outlined.svg'
|
||||||
|
import icon_text_outlined from '@/assets/svg/icon_text_outlined.svg'
|
||||||
|
import more_v from '@/assets/svg/more_v.svg'
|
||||||
|
import { ref, computed, nextTick } from 'vue'
|
||||||
|
import { ElCascaderPanel } from 'element-plus-secondary'
|
||||||
|
import { timeTypes } from './util'
|
||||||
|
import { fieldType } from '@/utils/attr'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
|
export interface Menu {
|
||||||
|
svgName: string
|
||||||
|
label?: string
|
||||||
|
command: string
|
||||||
|
divided?: boolean
|
||||||
|
}
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
extField: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
showTime: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
transType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const timeTypesChildren = timeTypes.map(ele => {
|
||||||
|
return {
|
||||||
|
label: ele === 'custom' ? t('data_set.customize') : ele,
|
||||||
|
value: ele
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const options = computed(() => {
|
||||||
|
const optionArr = [
|
||||||
|
{
|
||||||
|
label: props.transType,
|
||||||
|
value: 'translate',
|
||||||
|
icon: icon_switch_outlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.change_field_type'),
|
||||||
|
value: 'translateType',
|
||||||
|
icon: custom_sort,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
label: t('data_set.text'),
|
||||||
|
icon: icon_text_outlined,
|
||||||
|
value: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.time'),
|
||||||
|
icon: icon_calendar_outlined,
|
||||||
|
value: 'time',
|
||||||
|
children: props.showTime ? timeTypesChildren : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.geographical_location'),
|
||||||
|
icon: icon_local_outlined,
|
||||||
|
value: 'location'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.numerical_value'),
|
||||||
|
icon: icon_number_outlined,
|
||||||
|
value: 'value'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.numeric_value_decimal'),
|
||||||
|
icon: icon_number_outlined,
|
||||||
|
value: 'float'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'URL',
|
||||||
|
icon: icon_url_outlined,
|
||||||
|
value: 'url'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.edit'),
|
||||||
|
value: 'editor',
|
||||||
|
icon: icon_edit_outlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.rename'),
|
||||||
|
value: 'rename',
|
||||||
|
icon: dvRename
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.copy'),
|
||||||
|
value: 'copy',
|
||||||
|
icon: icon_copy_outlined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_set.delete'),
|
||||||
|
value: 'delete',
|
||||||
|
icon: icon_deleteTrash_outlined
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if (![2, 3].includes(props.extField)) {
|
||||||
|
optionArr.splice(2, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([3].includes(props.extField)) {
|
||||||
|
optionArr.splice(0, 1)
|
||||||
|
optionArr.splice(0, 1)
|
||||||
|
}
|
||||||
|
return optionArr
|
||||||
|
})
|
||||||
|
const deTypeArr = ref([])
|
||||||
|
const popover = ref()
|
||||||
|
const level = ref(1)
|
||||||
|
const cascaderPanel = ref()
|
||||||
|
const handleExpand = val => {
|
||||||
|
level.value = val.left + 1
|
||||||
|
}
|
||||||
|
const emit = defineEmits(['handleCommand'])
|
||||||
|
const handleCommand = () => {
|
||||||
|
const [translate, fieldType, timeType] = deTypeArr.value
|
||||||
|
popover.value.hide()
|
||||||
|
emit('handleCommand', timeType || fieldType || translate)
|
||||||
|
nextTick(() => {
|
||||||
|
deTypeArr.value = []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleChange = () => {
|
||||||
|
handleCommand()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-popover
|
||||||
|
:popper-class="
|
||||||
|
options.length === 6
|
||||||
|
? 'menu-more_popper_one menu-more_popper_six'
|
||||||
|
: extField === 3
|
||||||
|
? 'menu-more_popper_one menu-more_popper_three'
|
||||||
|
: 'menu-more_popper_one'
|
||||||
|
"
|
||||||
|
:persistent="false"
|
||||||
|
ref="popover"
|
||||||
|
placement="right"
|
||||||
|
:width="level * 175"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-icon class="menu-more">
|
||||||
|
<Icon name="more_v"><more_v class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
<ElCascaderPanel
|
||||||
|
v-model="deTypeArr"
|
||||||
|
@expand-change="handleExpand"
|
||||||
|
ref="cascaderPanel"
|
||||||
|
:border="false"
|
||||||
|
:options="options"
|
||||||
|
@change="handleChange"
|
||||||
|
>
|
||||||
|
<template #default="{ data }">
|
||||||
|
<div class="flex-align-center icon">
|
||||||
|
<el-icon v-if="data.icon">
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
class="svg-icon"
|
||||||
|
:class="
|
||||||
|
['text', 'location', 'value', 'float', 'time', 'url'].includes(data.value) &&
|
||||||
|
`field-icon-${fieldType[['float', 'value'].includes(data.value) ? 2 : 0]}`
|
||||||
|
"
|
||||||
|
:is="data.icon"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span>
|
||||||
|
{{ data.label }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ElCascaderPanel>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.menu-more {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(31, 35, 41, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.menu-more_popper_six > :first-child > :first-child > :first-child {
|
||||||
|
height: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-more_popper_three > :first-child > :first-child > :first-child {
|
||||||
|
height: 145px;
|
||||||
|
}
|
||||||
|
.menu-more_popper_one {
|
||||||
|
padding: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: none !important;
|
||||||
|
|
||||||
|
.ed-cascader-node.in-active-path {
|
||||||
|
color: #1f2329;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.ed-cascader-panel {
|
||||||
|
.ed-cascader-menu {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #dee0e3;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.1);
|
||||||
|
border-right: none;
|
||||||
|
&:nth-child(2) {
|
||||||
|
> div {
|
||||||
|
height: 210px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:nth-child(3) {
|
||||||
|
margin-top: 64px;
|
||||||
|
}
|
||||||
|
.arrow-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 11px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,167 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import UnionFieldList from './UnionFieldList.vue'
|
||||||
|
import UnionItemEdit from './UnionItemEdit.vue'
|
||||||
|
import type { Field, NodeType, UnionType, Node } from './util'
|
||||||
|
import { getTableField } from '@/api/dataset'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
|
||||||
|
const changeParentFields = val => {
|
||||||
|
parent.currentDsFields = val
|
||||||
|
}
|
||||||
|
const changeNodeFields = val => {
|
||||||
|
node.currentDsFields = val
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeUnionFields = (index?: number) => {
|
||||||
|
if (index !== undefined) {
|
||||||
|
node.unionFields.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
node.unionFields.push({
|
||||||
|
parentField: null,
|
||||||
|
currentField: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const defaultNode = {
|
||||||
|
info: '',
|
||||||
|
tableName: '',
|
||||||
|
type: 'db' as NodeType,
|
||||||
|
datasourceId: '',
|
||||||
|
id: '',
|
||||||
|
unionType: 'left' as UnionType,
|
||||||
|
unionFields: [],
|
||||||
|
currentDsFields: [],
|
||||||
|
sqlVariableDetails: null,
|
||||||
|
confirm: false,
|
||||||
|
isShadow: false,
|
||||||
|
flag: ''
|
||||||
|
}
|
||||||
|
const parentField = ref<Field[]>([])
|
||||||
|
const nodeField = ref<Field[]>([])
|
||||||
|
const node = reactive<Node>(cloneDeep(defaultNode))
|
||||||
|
const parent = reactive<Node>(cloneDeep(defaultNode))
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
editArr: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const clearState = () => {
|
||||||
|
Object.assign(node, cloneDeep(defaultNode))
|
||||||
|
Object.assign(parent, cloneDeep(defaultNode))
|
||||||
|
parentField.value = []
|
||||||
|
nodeField.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const initState = () => {
|
||||||
|
node.confirm = false
|
||||||
|
node.isShadow = false
|
||||||
|
node.flag = ''
|
||||||
|
parent.confirm = false
|
||||||
|
parent.isShadow = false
|
||||||
|
parent.flag = ''
|
||||||
|
Object.assign(node, cloneDeep(props.editArr[0]))
|
||||||
|
Object.assign(parent, cloneDeep(props.editArr[1]))
|
||||||
|
getFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getParams = (obj: Node) => {
|
||||||
|
return ['datasourceId', 'id', 'info', 'tableName', 'type'].reduce((pre, next) => {
|
||||||
|
pre[next] = obj[next]
|
||||||
|
return pre
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
const getFields = async () => {
|
||||||
|
const [n, p] = props.editArr as Node[]
|
||||||
|
const [nr, pr] = await Promise.all([getTableField(getParams(n)), getTableField(getParams(p))])
|
||||||
|
parentField.value = pr as unknown as Field[]
|
||||||
|
parentField.value.forEach(ele => {
|
||||||
|
ele.checked = p.currentDsFields.map(ele => ele.originName).includes(ele.originName)
|
||||||
|
})
|
||||||
|
nodeField.value = nr as unknown as Field[]
|
||||||
|
nodeField.value.forEach(ele => {
|
||||||
|
ele.checked = n.currentDsFields.map(ele => ele.originName).includes(ele.originName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
node,
|
||||||
|
parent,
|
||||||
|
clearState,
|
||||||
|
initState
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div style="height: 100%; overflow-y: auto">
|
||||||
|
<div class="field-style">
|
||||||
|
<div class="fields" v-loading="!parentField.length">
|
||||||
|
<p :title="parent.tableName">
|
||||||
|
{{ parent.tableName }}
|
||||||
|
</p>
|
||||||
|
<union-field-list
|
||||||
|
:field-list="parentField"
|
||||||
|
v-if="parentField.length"
|
||||||
|
:node="parent"
|
||||||
|
@checkedFields="changeParentFields"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="fields" v-loading="!nodeField.length">
|
||||||
|
<p :title="node.tableName">
|
||||||
|
{{ node.tableName }}
|
||||||
|
</p>
|
||||||
|
<union-field-list
|
||||||
|
:field-list="nodeField"
|
||||||
|
:node="node"
|
||||||
|
v-if="nodeField.length"
|
||||||
|
@checkedFields="changeNodeFields"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<union-item-edit
|
||||||
|
:parent-field-list="parentField"
|
||||||
|
:node-field-list="nodeField"
|
||||||
|
:node="node"
|
||||||
|
@change-union-type="val => (node.unionType = val)"
|
||||||
|
v-if="node.tableName"
|
||||||
|
@change-union-fields="changeUnionFields"
|
||||||
|
:table-name="parent.tableName"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.field-style {
|
||||||
|
height: 430px;
|
||||||
|
border: 1px solid var(--deCardStrokeColor, #dee0e3);
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 36px;
|
||||||
|
}
|
||||||
|
.fields {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px;
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
& > p {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--deTextPrimary, #1f2329);
|
||||||
|
}
|
||||||
|
&:nth-child(1) {
|
||||||
|
border-right: 1px solid var(--deCardStrokeColor, #dee0e3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,200 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||||
|
import { ref, PropType, watch } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { ElTable } from 'element-plus-secondary'
|
||||||
|
import { fieldType } from '@/utils/attr'
|
||||||
|
import { type Field } from './util'
|
||||||
|
import { iconFieldMap } from '@/components/icon-group/field-list'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const props = defineProps({
|
||||||
|
fieldList: {
|
||||||
|
type: Array as PropType<Field[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
node: propTypes.object.def({})
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['checkedFields'])
|
||||||
|
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
|
||||||
|
const search = ref('')
|
||||||
|
const checkAll = ref(false)
|
||||||
|
const isIndeterminate = ref(true)
|
||||||
|
const handleCheckAllChange = (val: boolean) => {
|
||||||
|
fieldSearchList.value.forEach(ele => {
|
||||||
|
ele.checked = val
|
||||||
|
})
|
||||||
|
const org = fieldSearchList.value.map(ele => ele.originName)
|
||||||
|
if (val) {
|
||||||
|
multipleSelection.value = [
|
||||||
|
...multipleSelection.value.filter(ele => !org.includes(ele.originName)),
|
||||||
|
...fieldSearchList.value
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
multipleSelection.value = multipleSelection.value.filter(ele => !org.includes(ele.originName))
|
||||||
|
}
|
||||||
|
isIndeterminate.value = false
|
||||||
|
emit('checkedFields', multipleSelection.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldSearchList = ref([])
|
||||||
|
|
||||||
|
const multipleSelection = ref<Field[]>([])
|
||||||
|
const checkChange = () => {
|
||||||
|
handleSelectionChange(fieldSearchList.value.filter(ele => ele.checked))
|
||||||
|
}
|
||||||
|
const handleSelectionChange = val => {
|
||||||
|
const checkedCount = val.length
|
||||||
|
checkAll.value = checkedCount === fieldSearchList.value.length
|
||||||
|
isIndeterminate.value = checkedCount > 0 && checkedCount < fieldSearchList.value.length
|
||||||
|
multipleSelection.value = props.fieldList.filter(ele => ele.checked)
|
||||||
|
emit('checkedFields', multipleSelection.value)
|
||||||
|
}
|
||||||
|
watch(
|
||||||
|
search,
|
||||||
|
val => {
|
||||||
|
if (val.trim() !== '') {
|
||||||
|
fieldSearchList.value = props.fieldList.filter(ele =>
|
||||||
|
ele.originName.toLocaleLowerCase().includes(val.trim().toLocaleLowerCase())
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fieldSearchList.value = props.fieldList
|
||||||
|
}
|
||||||
|
handleSelectionChange(fieldSearchList.value.filter(ele => ele.checked))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.fieldList,
|
||||||
|
() => {
|
||||||
|
fieldSearchList.value = props.fieldList
|
||||||
|
handleSelectionChange(fieldSearchList.value.filter(ele => ele.checked))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="field-block-style">
|
||||||
|
<div class="field-block-option">
|
||||||
|
<span class="option-field"
|
||||||
|
>{{ $t('dataset.field_select') }}({{ multipleSelection.length }}/{{
|
||||||
|
fieldList.length
|
||||||
|
}})</span
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="search"
|
||||||
|
:placeholder="$t('auth.search_by_field')"
|
||||||
|
clearable
|
||||||
|
class="option-input"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_search-outline_outlined"
|
||||||
|
><icon_searchOutline_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<div class="field-block-body">
|
||||||
|
<el-table
|
||||||
|
header-cell-class-name="header-cell"
|
||||||
|
ref="multipleTableRef"
|
||||||
|
:data="fieldSearchList"
|
||||||
|
style="width: 100%"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column align="center" width="55">
|
||||||
|
<template #header>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="checkAll"
|
||||||
|
:indeterminate="isIndeterminate"
|
||||||
|
@change="handleCheckAllChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-checkbox @change="checkChange" v-model="scope.row.checked" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('dataset.origin_name')">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-icon>
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
class="svg-icon"
|
||||||
|
:class="`field-icon-${fieldType[scope.row.deType]}`"
|
||||||
|
:is="iconFieldMap[fieldType[scope.row.deType]]"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
{{ scope.row.originName }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column property="description" :label="t('deDataset.description')" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.field-block-style {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
.field-block-body {
|
||||||
|
height: 327px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.field-origin-style {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 12px;
|
||||||
|
width: 140px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--deTextSecondary, #646a73);
|
||||||
|
}
|
||||||
|
.field-style {
|
||||||
|
width: 140px;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: pre;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--deTextSecondary, #646a73);
|
||||||
|
}
|
||||||
|
.field-block-option {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.option-field {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--deTextSecondary, #646a73);
|
||||||
|
}
|
||||||
|
.option-input {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.field-block-body {
|
||||||
|
.cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.ed-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 5.25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,297 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import noJoin from '@/assets/svg/no-join.svg'
|
||||||
|
import icon_fullAssociation from '@/assets/svg/icon_full-association.svg'
|
||||||
|
import icon_intersect from '@/assets/svg/icon_intersect.svg'
|
||||||
|
import icon_leftAssociation from '@/assets/svg/icon_left-association.svg'
|
||||||
|
import icon_rightAssociation from '@/assets/svg/icon_right-association.svg'
|
||||||
|
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||||
|
import joinJoin from '@/assets/svg/join-join.svg'
|
||||||
|
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||||
|
import { PropType, ref } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import type { Field } from '@/api/chart'
|
||||||
|
import { fieldType } from '@/utils/attr'
|
||||||
|
import { iconFieldMap } from '@/components/icon-group/field-list'
|
||||||
|
const unionTypeFromParent = ref('left')
|
||||||
|
const { t } = useI18n()
|
||||||
|
const iconName = {
|
||||||
|
left: icon_leftAssociation,
|
||||||
|
right: icon_rightAssociation,
|
||||||
|
inner: icon_intersect,
|
||||||
|
full: icon_fullAssociation
|
||||||
|
}
|
||||||
|
const props = defineProps({
|
||||||
|
tableName: {
|
||||||
|
type: String,
|
||||||
|
default: 'left'
|
||||||
|
},
|
||||||
|
parentFieldList: {
|
||||||
|
type: Array as PropType<Field[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
nodeFieldList: {
|
||||||
|
type: Array as PropType<Field[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const unionOptions = [
|
||||||
|
{ label: t('dataset.left_join'), value: 'left' },
|
||||||
|
{ label: t('dataset.right_join'), value: 'right' },
|
||||||
|
{ label: t('dataset.inner_join'), value: 'inner' },
|
||||||
|
{ label: t('dataset.full_join'), value: 'full' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
unionTypeFromParent.value = props.node.unionType
|
||||||
|
if (props.node.unionFields.length < 1) {
|
||||||
|
addUnion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const emit = defineEmits(['changeUnionFields', 'changeUnionType'])
|
||||||
|
const addUnion = () => {
|
||||||
|
emit('changeUnionFields')
|
||||||
|
}
|
||||||
|
const removeUnionItem = index => {
|
||||||
|
emit('changeUnionFields', index)
|
||||||
|
}
|
||||||
|
|
||||||
|
init()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="union-container">
|
||||||
|
<div class="union-header">
|
||||||
|
{{ t('dataset.union_relation') }}
|
||||||
|
<div class="union-header-operator">
|
||||||
|
<el-select
|
||||||
|
v-model="unionTypeFromParent"
|
||||||
|
class="union-selector"
|
||||||
|
@change="emit('changeUnionType', unionTypeFromParent)"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
class="svg-icon"
|
||||||
|
:is="iconName[unionTypeFromParent] || noJoin"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
<el-option
|
||||||
|
v-for="item in unionOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-button type="primary" class="union-add" @click="addUnion">
|
||||||
|
<template #icon>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('dataset.add_union_field') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="union-body">
|
||||||
|
<div class="union-body-header">
|
||||||
|
<span class="column" :title="tableName">{{ tableName }}</span>
|
||||||
|
<span class="column" :title="node.tableName">{{ node.tableName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="union-body-container">
|
||||||
|
<div v-for="(field, index) in node.unionFields" :key="index" class="union-body-item">
|
||||||
|
<!--左侧父级field-->
|
||||||
|
<span class="column">
|
||||||
|
<el-select
|
||||||
|
v-model="field.parentField"
|
||||||
|
:placeholder="t('dataset.pls_slc_union_field')"
|
||||||
|
filterable
|
||||||
|
value-key="originName"
|
||||||
|
clearable
|
||||||
|
class="select-field"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in parentFieldList"
|
||||||
|
:key="item.originName"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
class="svg-icon"
|
||||||
|
:class="`field-icon-${fieldType[item.deType]}`"
|
||||||
|
:is="iconFieldMap[fieldType[item.deType]]"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span>
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</span>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="join-join"><joinJoin class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<!--右侧孩子field-->
|
||||||
|
<span class="column">
|
||||||
|
<el-select
|
||||||
|
v-model="field.currentField"
|
||||||
|
:placeholder="t('dataset.pls_slc_union_field')"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
value-key="originName"
|
||||||
|
class="select-field"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in nodeFieldList"
|
||||||
|
:key="item.originName"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
class="svg-icon"
|
||||||
|
:class="`field-icon-${fieldType[item.deType]}`"
|
||||||
|
:is="iconFieldMap[fieldType[item.deType]]"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span>
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="column-last">
|
||||||
|
<el-button
|
||||||
|
v-if="node.unionFields && node.unionFields.length > 1"
|
||||||
|
text
|
||||||
|
@click="removeUnionItem(index)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Icon name="icon_delete-trash_outlined"
|
||||||
|
><icon_deleteTrash_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.union-container {
|
||||||
|
height: 275px;
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
}
|
||||||
|
.union-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--deTextPrimary, #1f2329);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.union-header-operator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.select-svg-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 50%;
|
||||||
|
z-index: 2;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.union-selector {
|
||||||
|
width: 180px;
|
||||||
|
:deep(.ed-select__prefix--light) {
|
||||||
|
border-right: none;
|
||||||
|
font-size: 22px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.union-add {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
.union-body {
|
||||||
|
height: 240px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.union-body-header {
|
||||||
|
height: 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 20px 0 8px 0;
|
||||||
|
color: var(--deTextSecondary, #1f2329);
|
||||||
|
}
|
||||||
|
.union-body-header .column {
|
||||||
|
width: 364px;
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
&:nth-child(2) {
|
||||||
|
margin-left: 37px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.union-body-header .column-last {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.union-body-container {
|
||||||
|
height: 180px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.select-field {
|
||||||
|
width: 364px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.union-body-item {
|
||||||
|
height: 32px;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
& > .ed-icon {
|
||||||
|
margin: 0 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.union-body-item .column {
|
||||||
|
width: 364px;
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.union-body-item .column-last {
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
:deep(.ed-icon) {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--deTextSecondary, #646a73);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,172 @@
|
|||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import SnowflakeId from 'snowflake-id'
|
||||||
|
const snowflake = new SnowflakeId()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const guid = () => {
|
||||||
|
return snowflake.generate()
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestampFormatDate = (timestamp, showMs?: boolean) => {
|
||||||
|
if (!timestamp || timestamp === -1) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(timestamp)
|
||||||
|
|
||||||
|
const y = date.getFullYear()
|
||||||
|
|
||||||
|
let MM = date.getMonth() + 1
|
||||||
|
MM = (MM < 10 ? '0' + MM : MM) as number
|
||||||
|
|
||||||
|
let d = date.getDate()
|
||||||
|
d = (d < 10 ? '0' + d : d) as number
|
||||||
|
|
||||||
|
let h = date.getHours()
|
||||||
|
h = (h < 10 ? '0' + h : h) as number
|
||||||
|
|
||||||
|
let m = date.getMinutes()
|
||||||
|
m = (m < 10 ? '0' + m : m) as number
|
||||||
|
|
||||||
|
let s = date.getSeconds()
|
||||||
|
s = (s < 10 ? '0' + s : s) as number
|
||||||
|
|
||||||
|
let format = y + '-' + MM + '-' + d + ' ' + h + ':' + m + ':' + s
|
||||||
|
|
||||||
|
if (showMs === true) {
|
||||||
|
const ms = date.getMilliseconds()
|
||||||
|
format += ':' + ms
|
||||||
|
}
|
||||||
|
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValueScopeList = [
|
||||||
|
{ label: t('dataset.scope_edit'), value: 'EDIT' },
|
||||||
|
{ label: t('dataset.scope_all'), value: 'ALLSCOPE' }
|
||||||
|
]
|
||||||
|
const fieldOptions = [
|
||||||
|
{ label: t('dataset.text'), value: 'TEXT' },
|
||||||
|
{ label: t('dataset.value'), value: 'LONG' },
|
||||||
|
{
|
||||||
|
label: t('dataset.value') + '(' + t('dataset.float') + ')',
|
||||||
|
value: 'DOUBLE'
|
||||||
|
},
|
||||||
|
{ label: t('dataset.time_year'), value: 'DATETIME-YEAR' },
|
||||||
|
{
|
||||||
|
label: t('dataset.time_year_month'),
|
||||||
|
value: 'DATETIME-YEAR-MONTH',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'YYYY-MM',
|
||||||
|
label: 'YYYY-MM'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'YYYY/MM',
|
||||||
|
label: 'YYYY/MM'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('dataset.time_year_month_day'),
|
||||||
|
value: 'DATETIME-YEAR-MONTH-DAY',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'YYYY-MM-DD',
|
||||||
|
label: 'YYYY-MM-DD'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'YYYY/MM/DD',
|
||||||
|
label: 'YYYY/MM/DD'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('dataset.time_all'),
|
||||||
|
value: 'DATETIME',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
label: 'YYYY-MM-DD HH:MI:SS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'YYYY/MM/DD HH:mm:ss',
|
||||||
|
label: 'YYYY/MM/DD HH:MI:SS'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const getFieldName = (fields, name) => {
|
||||||
|
let n = name
|
||||||
|
n = n + '_copy'
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
const field = fields[i]
|
||||||
|
if (field.name === n) {
|
||||||
|
n = getFieldName(fields, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeTypes = [
|
||||||
|
'yyyy-MM-dd',
|
||||||
|
'yyyy/MM/dd',
|
||||||
|
'yyyy-MM-dd HH:mm:ss',
|
||||||
|
'yyyy/MM/dd HH:mm:ss',
|
||||||
|
'custom'
|
||||||
|
]
|
||||||
|
|
||||||
|
type NodeType = 'db' | 'sql'
|
||||||
|
type UnionType = 'left' | 'right' | 'inner'
|
||||||
|
interface UnionField {
|
||||||
|
currentField: Field
|
||||||
|
parentField: Field
|
||||||
|
}
|
||||||
|
interface Node {
|
||||||
|
tableName: string
|
||||||
|
type: NodeType
|
||||||
|
datasourceId: string
|
||||||
|
id: string
|
||||||
|
unionType: UnionType
|
||||||
|
unionFields: UnionField[]
|
||||||
|
info: string
|
||||||
|
sqlVariableDetails: string
|
||||||
|
currentDsFields: Field[]
|
||||||
|
children?: Node[]
|
||||||
|
confirm?: boolean
|
||||||
|
isShadow?: boolean
|
||||||
|
flag?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Field {
|
||||||
|
checked: boolean
|
||||||
|
deExtractType: number
|
||||||
|
deType: number
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
originName: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataSource {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
children?: DataSource[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
NodeType,
|
||||||
|
UnionType,
|
||||||
|
UnionField,
|
||||||
|
DataSource,
|
||||||
|
Node,
|
||||||
|
Field,
|
||||||
|
timestampFormatDate,
|
||||||
|
defaultValueScopeList,
|
||||||
|
fieldOptions,
|
||||||
|
guid,
|
||||||
|
getFieldName,
|
||||||
|
timeTypes
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
function formatEnum(ele) {
|
||||||
|
return {
|
||||||
|
value: ele,
|
||||||
|
label: `chart.filter_${ele.replace(' ', '_')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toLine(name) {
|
||||||
|
return name.replace(/([A-Z])/g, '_$1').toLowerCase()
|
||||||
|
}
|
||||||
|
const textEnum = ['eq', 'not_eq', 'like', 'not like', 'null', 'not_null', 'empty', 'not_empty']
|
||||||
|
const textOptions = textEnum.map(formatEnum)
|
||||||
|
|
||||||
|
const dateEnum = ['eq', 'not_eq', 'lt', 'gt', 'le', 'ge']
|
||||||
|
const dateOptions = dateEnum.concat(['null', 'not_null']).map(formatEnum)
|
||||||
|
|
||||||
|
const valueEnum = [...dateEnum]
|
||||||
|
const valueOptions = valueEnum.map(formatEnum)
|
||||||
|
|
||||||
|
const sysParams = ['eq', 'not_eq', 'like', 'not like', 'in', 'not in']
|
||||||
|
const textOptionsForSysParams = sysParams.map(formatEnum)
|
||||||
|
|
||||||
|
const sysParamsEnum = ['userId', 'userName', 'userEmail']
|
||||||
|
|
||||||
|
const sysParamsIlns = sysParamsEnum.map(_ => {
|
||||||
|
return { value: `\${sysParams.${_}}`, label: `auth.sysParams_type.${toLine(_)}` }
|
||||||
|
})
|
||||||
|
|
||||||
|
const fieldEnums = ['text', 'time', 'value', 'value', 'value', 'location', 'binary', 'url']
|
||||||
|
|
||||||
|
export {
|
||||||
|
textOptions,
|
||||||
|
dateOptions,
|
||||||
|
valueOptions,
|
||||||
|
textOptionsForSysParams,
|
||||||
|
sysParamsIlns,
|
||||||
|
fieldEnums
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import icon_expandRight_filled from '@/assets/svg/icon_expand-right_filled.svg'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
defineProps({
|
||||||
|
name: propTypes.string.def('')
|
||||||
|
})
|
||||||
|
const active = ref(true)
|
||||||
|
defineExpose({
|
||||||
|
active
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="[active ? 'active' : 'deactivate', 'base-info-content']">
|
||||||
|
<p class="title" @click="active = !active">
|
||||||
|
<el-icon style="font-size: 10px">
|
||||||
|
<Icon name="icon_expand-right_filled"><icon_expandRight_filled class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span class="name">{{ name }}</span>
|
||||||
|
</p>
|
||||||
|
<slot :active="active"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.base-info-content {
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
// background: #fff;
|
||||||
|
margin: 24px 24px 0 24px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& + .base-info-content {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-records-time {
|
||||||
|
color: #646a73;
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: #fff;
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
.title {
|
||||||
|
.ed-icon {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
color: #B8BCBF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overflow: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.deactivate {
|
||||||
|
height: 72px;
|
||||||
|
overflow: hidden;
|
||||||
|
.title {
|
||||||
|
.ed-icon {
|
||||||
|
transform: rotate(0);
|
||||||
|
color: var(--ed-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
defineProps({
|
||||||
|
label: propTypes.string.def('')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="base-info-item">
|
||||||
|
<p class="label">{{ label }}</p>
|
||||||
|
<p class="value">
|
||||||
|
<slot></slot>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.base-info-item {
|
||||||
|
margin-top: 16px;
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
width: 100%;
|
||||||
|
.label {
|
||||||
|
color: #B8BCBF;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #F2F4F5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,78 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import icon_excel from '@/assets/svg/icon_excel.svg'
|
||||||
|
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
name?: string
|
||||||
|
size?: number
|
||||||
|
showDel?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
size: 0,
|
||||||
|
showDel: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const emits = defineEmits(['del'])
|
||||||
|
const del = () => {
|
||||||
|
emits('del')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="excel-info">
|
||||||
|
<el-icon class="excel">
|
||||||
|
<Icon name="icon_excel"><icon_excel class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<div class="info">
|
||||||
|
<p class="name ellipsis">{{ name || '-' }}</p>
|
||||||
|
<p class="size ellipsis">{{ size || '-' }}</p>
|
||||||
|
</div>
|
||||||
|
<el-icon v-if="showDel" @click="del" class="delete">
|
||||||
|
<Icon name="icon_delete-trash_outlined"><icon_deleteTrash_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.excel-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 58px;
|
||||||
|
padding: 0 16px 0 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #dee0e3;
|
||||||
|
.excel {
|
||||||
|
font-size: 32px;
|
||||||
|
margin-right: 14.67px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
width: 80%;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: #1f2329;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size {
|
||||||
|
color: #8f959e;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,176 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import icon_succeed_colorful from '@/assets/svg/icon_succeed_colorful.svg'
|
||||||
|
import icon_dataset from '@/assets/svg/icon_dataset.svg'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { setShowFinishPage } from '@/api/datasource'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
name: propTypes.string.def(''),
|
||||||
|
disabled: propTypes.bool.def(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
const emits = defineEmits(['createDataset', 'backToDatasourceList', 'continueCreating'])
|
||||||
|
const checked = ref(false)
|
||||||
|
const createDataset = () => {
|
||||||
|
emits('createDataset')
|
||||||
|
}
|
||||||
|
const backToDatasourceList = () => {
|
||||||
|
emits('backToDatasourceList')
|
||||||
|
}
|
||||||
|
const continueCreating = () => {
|
||||||
|
emits('continueCreating')
|
||||||
|
}
|
||||||
|
|
||||||
|
checked.value = wsCache.get('ds-create-success') || false
|
||||||
|
const handleChange = (val: boolean) => {
|
||||||
|
setShowFinishPage({})
|
||||||
|
wsCache.set('ds-create-success', val)
|
||||||
|
emits('backToDatasourceList')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="finish-page-content">
|
||||||
|
<div class="finish-page">
|
||||||
|
<el-icon class="succeed-icon">
|
||||||
|
<Icon name="icon_succeed_colorful"><icon_succeed_colorful class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
|
||||||
|
<div class="succeed-text">{{ t('data_source.successfully_created') }}</div>
|
||||||
|
<div class="btn-list">
|
||||||
|
<el-button @click="continueCreating" secondary>
|
||||||
|
{{ t('data_source.continue_to_create') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="backToDatasourceList" type="primary">
|
||||||
|
{{ t('data_source.data_source_list') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="nolonger-tips">
|
||||||
|
<el-checkbox
|
||||||
|
@change="handleChange"
|
||||||
|
v-model="checked"
|
||||||
|
:label="t('data_source.prompts_next_time')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="maybe-want" v-permission="['dataset']">
|
||||||
|
<div class="title">{{ t('data_source.also_want_to') }}</div>
|
||||||
|
<div class="ds-info">
|
||||||
|
<el-icon class="ds">
|
||||||
|
<Icon name="icon_dataset"><icon_dataset class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<div class="info">
|
||||||
|
<p class="name">{{ $t('auth.dataset') }}</p>
|
||||||
|
<p class="size">{{ t('data_source.or_large_screen') }}</p>
|
||||||
|
</div>
|
||||||
|
<el-button class="create" secondary :disabled="disabled" @click="createDataset">
|
||||||
|
{{ t('data_source.go_to_create') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.finish-page-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgb(0, 0, 0);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.ed-button,
|
||||||
|
:deep(.ed-checkbox__label) {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.finish-page {
|
||||||
|
width: 592px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 83px;
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
.succeed-icon {
|
||||||
|
font-size: 58px;
|
||||||
|
color: #34c724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.succeed-text {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 28px;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-list {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nolonger-tips {
|
||||||
|
margin-bottom: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maybe-want {
|
||||||
|
width: 100%;
|
||||||
|
.title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 22px;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ds-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 82px;
|
||||||
|
padding: 0 16px 0 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #dee0e3;
|
||||||
|
.ds {
|
||||||
|
font-size: 32px;
|
||||||
|
margin-right: 14.67px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
.name {
|
||||||
|
color: #e9e9e9;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size {
|
||||||
|
color: #8f959e;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.create {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: auto;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,204 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import icon_expandLeft_filled from '@/assets/svg/icon_expand-left_filled.svg'
|
||||||
|
import icon_expandRight_filled from '@/assets/svg/icon_expand-right_filled.svg'
|
||||||
|
import { toRefs, ref, watch, nextTick } from 'vue'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
const props = defineProps({
|
||||||
|
tabList: propTypes.arrayOf(
|
||||||
|
propTypes.shape({
|
||||||
|
label: String,
|
||||||
|
value: String
|
||||||
|
})
|
||||||
|
),
|
||||||
|
activeTab: propTypes.string.def('')
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeTabIndex = ref(0)
|
||||||
|
|
||||||
|
const emits = defineEmits(['TabClick'])
|
||||||
|
const { activeTab } = toRefs(props)
|
||||||
|
const handleTabClick = tab => {
|
||||||
|
let tabDom = document.getElementById(`tab-${tab.value}`)
|
||||||
|
if (tabDom.offsetLeft + tabDom.offsetWidth > tabWrapper.value.offsetWidth) {
|
||||||
|
tabWrapper.value.scrollLeft =
|
||||||
|
tabDom.offsetLeft + tabDom.offsetWidth - tabWrapper.value.offsetWidth
|
||||||
|
} else {
|
||||||
|
tabWrapper.value.scrollLeft = 0
|
||||||
|
}
|
||||||
|
emits('TabClick', tab)
|
||||||
|
}
|
||||||
|
const tabWrapper = ref()
|
||||||
|
const showBtn = ref(false)
|
||||||
|
watch(
|
||||||
|
() => activeTab.value,
|
||||||
|
val => {
|
||||||
|
activeTabIndex.value = props.tabList.findIndex(ele => ele.value === val)
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.tabList,
|
||||||
|
() => {
|
||||||
|
nextTick(() => {
|
||||||
|
showBtn.value = tabWrapper.value.scrollWidth > tabWrapper.value.offsetWidth
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const prevClick = () => {
|
||||||
|
let domWrapper = tabWrapper.value
|
||||||
|
if (!domWrapper.scrollLeft) return
|
||||||
|
domWrapper.scrollLeft -= 30
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextClick = () => {
|
||||||
|
let domWrapper = tabWrapper.value
|
||||||
|
domWrapper.scrollLeft += 30
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="sheet-tabs">
|
||||||
|
<div ref="tabWrapper" class="tab-wrapper">
|
||||||
|
<div
|
||||||
|
v-for="tab in tabList"
|
||||||
|
:key="tab.label"
|
||||||
|
:id="`tab-${tab.value}`"
|
||||||
|
:title="tab.label"
|
||||||
|
:class="[{ active: activeTab === tab.value }, 'sheet-tab']"
|
||||||
|
@click="handleTabClick(tab)"
|
||||||
|
>
|
||||||
|
<span class="ellipsis">
|
||||||
|
{{ tab.label }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-btn" v-if="showBtn">
|
||||||
|
<el-icon size="12px" @click="prevClick">
|
||||||
|
<Icon name="icon_expand-left_filled"><icon_expandLeft_filled class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<el-icon size="12px" @click="nextClick">
|
||||||
|
<Icon name="icon_expand-right_filled"><icon_expandRight_filled class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.sheet-tabs {
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 60px;
|
||||||
|
|
||||||
|
.tab-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn {
|
||||||
|
padding: 8px 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 60px;
|
||||||
|
height: 28px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 4px;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
.ed-icon {
|
||||||
|
color: #8d9199;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.disabled):hover {
|
||||||
|
color: var(--ed-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
& + .ed-icon {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-tab {
|
||||||
|
color: #1f2329;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 36px;
|
||||||
|
max-width: 200px;
|
||||||
|
border-bottom: 1px solid rgba(31, 35, 41, 0.15);
|
||||||
|
&:hover {
|
||||||
|
color: var(--ed-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ellipsis {
|
||||||
|
max-width: 200px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after,
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 24px;
|
||||||
|
width: 1px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(31, 35, 41, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + .active {
|
||||||
|
::before {
|
||||||
|
content: '';
|
||||||
|
left: -3px;
|
||||||
|
height: 30px;
|
||||||
|
width: 2px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.active {
|
||||||
|
box-shadow: 0px -1px 0px 0px #f5f6f7 inset;
|
||||||
|
color: var(--ed-color-primary);
|
||||||
|
border: 1px solid rgba(31, 35, 41, 0.15);
|
||||||
|
border-bottom: none;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
background: #f5f6f7;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + .sheet-tab {
|
||||||
|
&::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,75 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { PropType, toRefs } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
|
export interface AuthConfig {
|
||||||
|
verification: string
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
request: {
|
||||||
|
type: Object as PropType<{ authManager: AuthConfig }>,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const { request } = toRefs(props)
|
||||||
|
|
||||||
|
const options = [{ name: 'No Auth' }, { name: 'Basic Auth' }]
|
||||||
|
const change = () => {
|
||||||
|
const { username, password, verification } = request.value.authManager
|
||||||
|
const isBasic = verification === 'Basic Auth'
|
||||||
|
request.value.authManager.username = isBasic ? username : ''
|
||||||
|
request.value.authManager.password = isBasic ? password : ''
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-form label-position="top">
|
||||||
|
<el-form-item class="api-auth-config" :label="t('datasource.verification_method')">
|
||||||
|
<el-select
|
||||||
|
style="width: 100%"
|
||||||
|
v-model="request.authManager.verification"
|
||||||
|
@change="change"
|
||||||
|
:placeholder="t('datasource.verification_method')"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in options" :key="item.name" :label="item.name" :value="item.name" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item
|
||||||
|
v-if="request.authManager.verification === 'Basic Auth'"
|
||||||
|
:label="t('datasource.username')"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="request.authManager.username"
|
||||||
|
:placeholder="t('datasource.username')"
|
||||||
|
class="ms-http-input"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item
|
||||||
|
v-if="request.authManager.verification === 'Basic Auth'"
|
||||||
|
:label="t('datasource.password')"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="request.authManager.password"
|
||||||
|
type="password"
|
||||||
|
:placeholder="t('datasource.password')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.api-auth-config {
|
||||||
|
margin-bottom: 16px !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,286 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { onBeforeMount, watch, toRefs, PropType } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import ApiVariable from './ApiVariable.vue'
|
||||||
|
import CodeEdit from './CodeEdit.vue'
|
||||||
|
import Convert from './convert.js'
|
||||||
|
import { KeyValue, BODY_TYPE } from './ApiTestModel.js'
|
||||||
|
export interface ApiBodyItem {
|
||||||
|
raw?: string
|
||||||
|
typeChange: string
|
||||||
|
format?: string
|
||||||
|
jsonSchema?: string
|
||||||
|
type?: string
|
||||||
|
kvs: Item[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
name: string
|
||||||
|
value: string
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
const props = defineProps({
|
||||||
|
isReadOnly: propTypes.bool.def(false),
|
||||||
|
isShowEnable: propTypes.bool.def(false),
|
||||||
|
body: {
|
||||||
|
type: Object as PropType<ApiBodyItem>,
|
||||||
|
default: () => ({
|
||||||
|
raw: '',
|
||||||
|
typeChange: '',
|
||||||
|
format: '',
|
||||||
|
jsonSchema: '',
|
||||||
|
type: '',
|
||||||
|
kvs: []
|
||||||
|
})
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
type: Array as PropType<Item[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
valueList: {
|
||||||
|
type: Array as PropType<Item[]>,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { t } = useI18n()
|
||||||
|
const modes = ['text', 'json', 'xml', 'html']
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty
|
||||||
|
const propIsEnumerable = Object.prototype.propertyIsEnumerable
|
||||||
|
const { body: apiBody, headers } = toRefs(props)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => apiBody.value.raw,
|
||||||
|
() => {
|
||||||
|
if (apiBody.value.format !== 'JSON-SCHEMA' && apiBody.value.raw) {
|
||||||
|
try {
|
||||||
|
const MsConvert = new Convert()
|
||||||
|
const data = MsConvert.format(JSON.parse(apiBody.value.raw))
|
||||||
|
if (apiBody.value.jsonSchema) {
|
||||||
|
apiBody.value.jsonSchema = deepAssign(data)
|
||||||
|
} else {
|
||||||
|
apiBody.value.jsonSchema = data
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
apiBody.value.jsonSchema = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
if (!apiBody.value.type) {
|
||||||
|
apiBody.value.type = BODY_TYPE.FORM_DATA
|
||||||
|
}
|
||||||
|
if (apiBody.value.kvs) {
|
||||||
|
apiBody.value.kvs.forEach(param => {
|
||||||
|
if (!param.type) {
|
||||||
|
param.type = 'text'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const isObj = x => {
|
||||||
|
const type = typeof x
|
||||||
|
return x !== null && (type === 'object' || type === 'function')
|
||||||
|
}
|
||||||
|
|
||||||
|
const toObject = val => {
|
||||||
|
if (val === null || val === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return Object(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
const assignKey = (to, from, key) => {
|
||||||
|
const val = from[key]
|
||||||
|
if (val === undefined || val === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasOwnProperty.call(to, key) || !isObj(val)) {
|
||||||
|
to[key] = val
|
||||||
|
} else {
|
||||||
|
to[key] = assign(Object(to[key]), from[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const assign = (to, from) => {
|
||||||
|
if (to === from) {
|
||||||
|
return to
|
||||||
|
}
|
||||||
|
from = Object(from)
|
||||||
|
for (const key in from) {
|
||||||
|
if (hasOwnProperty.call(from, key)) {
|
||||||
|
assignKey(to, from, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.getOwnPropertySymbols) {
|
||||||
|
const symbols = Object.getOwnPropertySymbols(from)
|
||||||
|
|
||||||
|
for (let i = 0; i < symbols.length; i++) {
|
||||||
|
if (propIsEnumerable.call(from, symbols[i])) {
|
||||||
|
assignKey(to, from, symbols[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return to
|
||||||
|
}
|
||||||
|
|
||||||
|
const deepAssign = function (target) {
|
||||||
|
target = toObject(target)
|
||||||
|
for (let s = 1; s < arguments.length; s++) {
|
||||||
|
assign(target, arguments[s])
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
const modeChange = () => {
|
||||||
|
switch (apiBody.value.type) {
|
||||||
|
case 'JSON':
|
||||||
|
setContentType('application/json')
|
||||||
|
break
|
||||||
|
case 'XML':
|
||||||
|
setContentType('text/xml')
|
||||||
|
break
|
||||||
|
case 'WWW_FORM':
|
||||||
|
setContentType('application/x-www-form-urlencoded')
|
||||||
|
break
|
||||||
|
case 'BINARY':
|
||||||
|
setContentType('application/octet-stream')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
removeContentType()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setContentType = value => {
|
||||||
|
let isType = false
|
||||||
|
headers.value.forEach(item => {
|
||||||
|
if (item.name === 'Content-Type') {
|
||||||
|
item.value = value
|
||||||
|
isType = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!isType) {
|
||||||
|
headers.value.unshift(new KeyValue({ name: 'Content-Type', value: value }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const removeContentType = () => {
|
||||||
|
for (const index in headers.value) {
|
||||||
|
if (headers.value[index].name === 'Content-Type') {
|
||||||
|
headers.value.splice(parseInt(index), 1)
|
||||||
|
emits('headersChange')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const changeParameters = val => {
|
||||||
|
if (!isNaN(val)) {
|
||||||
|
apiBody.value.kvs.splice(val, 1)
|
||||||
|
} else {
|
||||||
|
apiBody.value.kvs.push(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const emits = defineEmits(['headersChange'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="radio-group_api">
|
||||||
|
<el-radio-group v-model="apiBody.type">
|
||||||
|
<el-radio :disabled="isReadOnly" :label="BODY_TYPE.FORM_DATA" @change="modeChange">
|
||||||
|
{{ t('datasource.body_form_data') }}
|
||||||
|
</el-radio>
|
||||||
|
|
||||||
|
<el-radio :disabled="isReadOnly" :label="BODY_TYPE.WWW_FORM" @change="modeChange">
|
||||||
|
{{ t('datasource.body_x_www_from_urlencoded') }}
|
||||||
|
</el-radio>
|
||||||
|
|
||||||
|
<el-radio :disabled="isReadOnly" :label="BODY_TYPE.JSON" @change="modeChange">
|
||||||
|
{{ t('datasource.body_json') }}
|
||||||
|
</el-radio>
|
||||||
|
|
||||||
|
<el-radio :disabled="isReadOnly" :label="BODY_TYPE.XML" @change="modeChange">
|
||||||
|
{{ t('datasource.body_xml') }}
|
||||||
|
</el-radio>
|
||||||
|
|
||||||
|
<el-radio :disabled="isReadOnly" :label="BODY_TYPE.RAW" @change="modeChange">
|
||||||
|
{{ t('datasource.body_raw') }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<div style="padding-top: 16px" v-if="apiBody.type == 'Form_Data' || apiBody.type == 'WWW_FORM'">
|
||||||
|
<api-variable
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
:parameters="apiBody.kvs"
|
||||||
|
:is-show-enable="isShowEnable"
|
||||||
|
type="body"
|
||||||
|
@change-parameters="changeParameters"
|
||||||
|
:value-list="valueList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="apiBody.type == 'JSON'" class="ms-body">
|
||||||
|
<code-edit
|
||||||
|
ref="codeEdit"
|
||||||
|
:read-only="isReadOnly"
|
||||||
|
v-model="apiBody.raw"
|
||||||
|
class="api-body-code"
|
||||||
|
:data="apiBody.raw"
|
||||||
|
:modes="modes"
|
||||||
|
mode="json"
|
||||||
|
height="200px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="apiBody.type == 'XML'" class="ms-body">
|
||||||
|
<code-edit
|
||||||
|
ref="codeEdit"
|
||||||
|
:read-only="isReadOnly"
|
||||||
|
class="api-body-code"
|
||||||
|
v-model="apiBody.raw"
|
||||||
|
:data="apiBody.raw"
|
||||||
|
height="200px"
|
||||||
|
mode="xml"
|
||||||
|
:modes="modes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="apiBody.type == 'Raw'" class="ms-body">
|
||||||
|
<code-edit
|
||||||
|
ref="codeEdit"
|
||||||
|
:read-only="isReadOnly"
|
||||||
|
v-model="apiBody.raw"
|
||||||
|
:data="apiBody.raw"
|
||||||
|
height="200px"
|
||||||
|
class="api-body-code"
|
||||||
|
:modes="modes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.radio-group_api {
|
||||||
|
:deep(.ed-radio) {
|
||||||
|
height: 22px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ms-body {
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.ace_print-margin {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-body-code {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #bbbfc4;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,249 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, onBeforeMount, PropType, toRefs } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import ApiKeyValue from './ApiKeyValue.vue'
|
||||||
|
import ApiBody from './ApiBody.vue'
|
||||||
|
import Pagination from './Pagination.vue'
|
||||||
|
import ApiVariable from './ApiVariable.vue'
|
||||||
|
import ApiAuthConfig from './ApiAuthConfig.vue'
|
||||||
|
import { Body } from './ApiTestModel.js'
|
||||||
|
import type { Item } from './ApiKeyValue.vue'
|
||||||
|
import type { AuthConfig } from './ApiAuthConfig.vue'
|
||||||
|
import type { ApiBodyItem } from './ApiBody.vue'
|
||||||
|
import { PageSetting } from '@/views/visualized/data/datasource/form/Pagination.vue'
|
||||||
|
export interface ApiRequest {
|
||||||
|
changeId: string
|
||||||
|
headers: Item[]
|
||||||
|
rest: Item[]
|
||||||
|
arguments: Item[]
|
||||||
|
authManager: AuthConfig
|
||||||
|
body: ApiBodyItem
|
||||||
|
page: PageSetting
|
||||||
|
}
|
||||||
|
const props = defineProps({
|
||||||
|
showScript: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
valueList: {
|
||||||
|
type: Array as PropType<Item[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
type: Object as PropType<ApiRequest>,
|
||||||
|
default: () => ({
|
||||||
|
changeId: '',
|
||||||
|
authManager: { verification: '', username: '', password: '' },
|
||||||
|
headers: [],
|
||||||
|
rest: [],
|
||||||
|
arguments: [],
|
||||||
|
body: {
|
||||||
|
typeChange: '',
|
||||||
|
kvs: []
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
pageType: 'empty',
|
||||||
|
requestData: [],
|
||||||
|
responseData: []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
referenced: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isShowEnable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const spanCount = ref(21)
|
||||||
|
const activeName = ref('headers')
|
||||||
|
const headerSuggestions = [
|
||||||
|
{ value: 'Accept' },
|
||||||
|
{ value: 'Accept-Charset' },
|
||||||
|
{ value: 'Accept-Language' },
|
||||||
|
{ value: 'Accept-Datetime' },
|
||||||
|
{ value: 'X-DE-TOKEN' },
|
||||||
|
{ value: 'Cache-Control' },
|
||||||
|
{ value: 'Connection' },
|
||||||
|
{ value: 'Cookie' },
|
||||||
|
{ value: 'Content-Length' },
|
||||||
|
{ value: 'Content-MD5' },
|
||||||
|
{ value: 'Content-Type' },
|
||||||
|
{ value: 'Date' },
|
||||||
|
{ value: 'Expect' },
|
||||||
|
{ value: 'From' },
|
||||||
|
{ value: 'Host' },
|
||||||
|
{ value: 'If-Match' },
|
||||||
|
{ value: 'If-Modified-Since' },
|
||||||
|
{ value: 'If-None-Match' },
|
||||||
|
{ value: 'If-Range' },
|
||||||
|
{ value: 'If-Unmodified-Since' },
|
||||||
|
{ value: 'Max-Forwards' },
|
||||||
|
{ value: 'Origin' },
|
||||||
|
{ value: 'Pragma' },
|
||||||
|
{ value: 'Proxy-Authorization' },
|
||||||
|
{ value: 'Range' },
|
||||||
|
{ value: 'Referer' },
|
||||||
|
{ value: 'TE' },
|
||||||
|
{ value: 'User-Agent' },
|
||||||
|
{ value: 'Upgrade' },
|
||||||
|
{ value: 'Via' },
|
||||||
|
{ value: 'Warning' }
|
||||||
|
]
|
||||||
|
const bodyRef = ref()
|
||||||
|
|
||||||
|
const { request: apiRequest } = toRefs(props)
|
||||||
|
onBeforeMount(() => {
|
||||||
|
if (!props.referenced && props.showScript) {
|
||||||
|
spanCount.value = 21
|
||||||
|
} else {
|
||||||
|
spanCount.value = 24
|
||||||
|
}
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => apiRequest.value.changeId,
|
||||||
|
() => {
|
||||||
|
if (apiRequest.value.headers?.length > 1) {
|
||||||
|
activeName.value = 'headers'
|
||||||
|
}
|
||||||
|
if (apiRequest.value.rest?.length > 1) {
|
||||||
|
activeName.value = 'rest'
|
||||||
|
}
|
||||||
|
if (apiRequest.value.arguments?.length > 1) {
|
||||||
|
activeName.value = 'parameters'
|
||||||
|
}
|
||||||
|
if (apiRequest.value.body) {
|
||||||
|
apiRequest.value.body.typeChange = apiRequest.value.changeId
|
||||||
|
emits('changeId', apiRequest.value.changeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
if (!apiRequest.value.body) {
|
||||||
|
apiRequest.value.body = new Body()
|
||||||
|
}
|
||||||
|
if (!apiRequest.value.body.kvs) {
|
||||||
|
apiRequest.value.body.kvs = []
|
||||||
|
}
|
||||||
|
if (!apiRequest.value.rest) {
|
||||||
|
apiRequest.value.rest = []
|
||||||
|
}
|
||||||
|
if (!apiRequest.value.arguments) {
|
||||||
|
apiRequest.value.arguments = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const emits = defineEmits(['changeId'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="request-content">
|
||||||
|
<el-tabs v-model="activeName" class="request-tabs">
|
||||||
|
<!-- 请求头-->
|
||||||
|
<el-tab-pane :label="t('datasource.headers')" name="headers" key="headers">
|
||||||
|
<api-key-value
|
||||||
|
:show-desc="true"
|
||||||
|
:suggestions="headerSuggestions"
|
||||||
|
:items="apiRequest.headers"
|
||||||
|
:value-list="valueList"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!--query 参数-->
|
||||||
|
<el-tab-pane key="parameters" :label="t('datasource.query_param')" name="parameters">
|
||||||
|
<api-variable
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
:isShowEnable="isShowEnable"
|
||||||
|
:parameters="apiRequest.arguments"
|
||||||
|
:value-list="valueList"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!--请求体-->
|
||||||
|
<el-tab-pane
|
||||||
|
key="body"
|
||||||
|
:label="t('datasource.request_body')"
|
||||||
|
name="body"
|
||||||
|
style="overflow: hidden"
|
||||||
|
>
|
||||||
|
<api-body
|
||||||
|
ref="bodyRef"
|
||||||
|
:headers="apiRequest.headers"
|
||||||
|
:body="apiRequest.body"
|
||||||
|
:is-show-enable="isShowEnable"
|
||||||
|
:value-list="valueList"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- 认证配置 -->
|
||||||
|
<el-tab-pane key="authConfig" :label="t('datasource.auth_config')" name="authConfig">
|
||||||
|
<el-tooltip
|
||||||
|
class="item-tabs"
|
||||||
|
effect="dark"
|
||||||
|
:content="t('datasource.auth_config_info')"
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>{{ t('datasource.auth_config') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-tooltip>
|
||||||
|
<api-auth-config :request="apiRequest" />
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane key="pagination" :label="t('api_pagination.paging_ettings')" name="pagination">
|
||||||
|
<Pagination :page="apiRequest.page" />
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.request-content {
|
||||||
|
border: 1px #dcdfe6 solid;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
.ms-query {
|
||||||
|
background: #409eff;
|
||||||
|
color: white;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 42%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-header {
|
||||||
|
background: #409eff;
|
||||||
|
color: white;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 42%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-tabs {
|
||||||
|
margin: 0 16px;
|
||||||
|
|
||||||
|
:deep(.ed-tabs__item) {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ed-tabs__content) {
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-el-link {
|
||||||
|
float: right;
|
||||||
|
margin-right: 45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,288 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import icon_drag_outlined from '@/assets/svg/icon_drag_outlined.svg'
|
||||||
|
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||||
|
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { computed, onBeforeMount, PropType, toRefs, inject, ref } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { KeyValue } from './ApiTestModel.js'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
name: string
|
||||||
|
value: string
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
const props = defineProps({
|
||||||
|
keyPlaceholder: propTypes.string.def(''),
|
||||||
|
valuePlaceholder: propTypes.string.def(''),
|
||||||
|
unShowSelect: propTypes.bool.def(false),
|
||||||
|
isReadOnly: propTypes.bool.def(false),
|
||||||
|
needMock: propTypes.bool.def(false),
|
||||||
|
showDesc: propTypes.bool.def(false),
|
||||||
|
items: {
|
||||||
|
type: Array as PropType<Item[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
valueList: {
|
||||||
|
type: Array as PropType<Item[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
suggestions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const keyText = computed(() => {
|
||||||
|
return props.keyPlaceholder || t('datasource.key')
|
||||||
|
})
|
||||||
|
const valueText = computed(() => {
|
||||||
|
return props.valuePlaceholder || t('datasource.value')
|
||||||
|
})
|
||||||
|
|
||||||
|
const { suggestions, items } = toRefs(props)
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
if (!items.value.length || items.value[items.value.length - 1].name) {
|
||||||
|
items.value.push(new KeyValue({ enable: true, name: '', value: '' }))
|
||||||
|
}
|
||||||
|
for (let i = 0; i < items.value.length; i++) {
|
||||||
|
if (!items.value[i].hasOwnProperty('nameType')) {
|
||||||
|
items.value[i].nameType = 'fixed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeName = inject('api-active-name')
|
||||||
|
|
||||||
|
const remove = (index: number) => {
|
||||||
|
if (isDisable()) return
|
||||||
|
// 移除整行输入控件及内容
|
||||||
|
items.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
const change = () => {
|
||||||
|
items.value.push(new KeyValue({ enable: true, nameType: 'fixed' }))
|
||||||
|
}
|
||||||
|
const isDisable = () => {
|
||||||
|
return items.value.length === 1
|
||||||
|
}
|
||||||
|
const querySearch = (queryString, cb) => {
|
||||||
|
const results = queryString
|
||||||
|
? suggestions.value.filter(createFilter(queryString))
|
||||||
|
: suggestions.value
|
||||||
|
cb(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeNameType = element => {
|
||||||
|
element.value = ''
|
||||||
|
}
|
||||||
|
const createFilter = (queryString: string) => {
|
||||||
|
return restaurant => {
|
||||||
|
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
label: t('data_source.parameter'),
|
||||||
|
value: 'params'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_source.fixed_value'),
|
||||||
|
value: 'fixed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_source.time_function'),
|
||||||
|
value: 'timeFun'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_source.customize'),
|
||||||
|
value: 'custom'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const timeFunLists = [
|
||||||
|
{
|
||||||
|
label: t('data_source.that_day') + '(yyyy-MM-dd)',
|
||||||
|
value: 'currentDay yyyy-MM-dd'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_source.that_day') + '(yyyy/MM/dd)',
|
||||||
|
value: 'currentDay yyyy/MM/dd'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="api-key-value">
|
||||||
|
<draggable tag="div" :list="items" handle=".handle" class="draggable-content_api">
|
||||||
|
<template #item="{ element, index }">
|
||||||
|
<div style="margin-bottom: 16px">
|
||||||
|
<el-row :gutter="8">
|
||||||
|
<el-icon class="drag handle">
|
||||||
|
<Icon name="icon_drag_outlined"><icon_drag_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<el-col :span="activeName === 'params' ? 8 : 6" v-if="!unShowSelect">
|
||||||
|
<el-input
|
||||||
|
v-if="!suggestions"
|
||||||
|
v-model="element.name"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
maxlength="200"
|
||||||
|
:placeholder="keyText"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
<el-autocomplete
|
||||||
|
v-else
|
||||||
|
v-model="element.name"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
:maxlength="400"
|
||||||
|
:fetch-suggestions="querySearch"
|
||||||
|
:placeholder="keyText"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3" v-if="activeName === 'table'">
|
||||||
|
<el-select v-model="element.nameType" @change="changeNameType(element)">
|
||||||
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8" v-if="unShowSelect">
|
||||||
|
<el-input
|
||||||
|
v-if="!!suggestions.length"
|
||||||
|
v-model="element.name"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
maxlength="200"
|
||||||
|
:placeholder="keyText"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="activeName === 'params' ? 7 : 6">
|
||||||
|
<el-input
|
||||||
|
v-if="!needMock && activeName === 'params'"
|
||||||
|
v-model="element.value"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
:placeholder="unShowSelect ? t('common.description') : valueText"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
<el-select
|
||||||
|
v-model="element.value"
|
||||||
|
v-if="!needMock && activeName === 'table' && element.nameType === 'params'"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in valueList"
|
||||||
|
:key="item.originName"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.originName"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-model="element.value"
|
||||||
|
v-if="!needMock && activeName === 'table' && element.nameType === 'timeFun'"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in timeFunLists"
|
||||||
|
:key="item.originName"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-input
|
||||||
|
v-if="
|
||||||
|
!needMock &&
|
||||||
|
activeName === 'table' &&
|
||||||
|
element.nameType !== 'params' &&
|
||||||
|
element.nameType !== 'timeFun'
|
||||||
|
"
|
||||||
|
v-model="element.value"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
:placeholder="
|
||||||
|
element.nameType === 'fixed'
|
||||||
|
? t('data_source.value')
|
||||||
|
: t('data_source.name_use_parameters')
|
||||||
|
"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="7" v-if="showDesc">
|
||||||
|
<el-input
|
||||||
|
v-model="element.description"
|
||||||
|
maxlength="200"
|
||||||
|
:placeholder="t('common.description')"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="1">
|
||||||
|
<el-button
|
||||||
|
class="api-variable_del"
|
||||||
|
text
|
||||||
|
:disabled="isDisable()"
|
||||||
|
@click="remove(index)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Icon name="icon_delete-trash_outlined"
|
||||||
|
><icon_deleteTrash_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
|
||||||
|
<el-button style="margin-top: 14px" @click="change" text>
|
||||||
|
<template #icon>
|
||||||
|
<icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></icon>
|
||||||
|
</template>
|
||||||
|
{{ t('data_source.add_parameters') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.api-key-value {
|
||||||
|
padding-bottom: 14px;
|
||||||
|
|
||||||
|
& > .ed-input,
|
||||||
|
.ed-autocomplete {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.api-variable_del {
|
||||||
|
color: #646a73;
|
||||||
|
:deep(.ed-icon) {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(31, 35, 41, 0.1) !important;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
background: rgba(31, 35, 41, 0.1) !important;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: rgba(31, 35, 41, 0.2) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drag {
|
||||||
|
margin-top: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.draggable-content_api) > :last-child {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,154 @@
|
|||||||
|
export class BaseConfig {
|
||||||
|
set(options, notUndefined) {
|
||||||
|
options = this.initOptions(options)
|
||||||
|
for (const name in options) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(options, name)) {
|
||||||
|
if (!(this[name] instanceof Array)) {
|
||||||
|
if (notUndefined === true) {
|
||||||
|
this[name] = options[name] === undefined ? this[name] : options[name]
|
||||||
|
} else {
|
||||||
|
this[name] = options[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sets(types, options) {
|
||||||
|
options = this.initOptions(options)
|
||||||
|
if (types) {
|
||||||
|
for (const name in types) {
|
||||||
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(types, name) &&
|
||||||
|
Object.prototype.hasOwnProperty.call(options, name)
|
||||||
|
) {
|
||||||
|
options[name].forEach(o => {
|
||||||
|
this[name].push(new types[name](o))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initOptions(options) {
|
||||||
|
return options || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KeyValue extends BaseConfig {
|
||||||
|
constructor(options) {
|
||||||
|
options = options || {}
|
||||||
|
options.enable = options.enable === undefined ? true : options.enable
|
||||||
|
|
||||||
|
super()
|
||||||
|
this.name = undefined
|
||||||
|
this.value = undefined
|
||||||
|
this.type = undefined
|
||||||
|
this.files = undefined
|
||||||
|
this.enable = undefined
|
||||||
|
this.uuid = undefined
|
||||||
|
this.time = undefined
|
||||||
|
this.contentType = undefined
|
||||||
|
this.set(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid() {
|
||||||
|
return (!!this.name || !!this.value) && this.type !== 'file'
|
||||||
|
}
|
||||||
|
|
||||||
|
isFile() {
|
||||||
|
return (!!this.name || !!this.value) && this.type === 'file'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Body extends BaseConfig {
|
||||||
|
constructor(options) {
|
||||||
|
super()
|
||||||
|
this.type = 'KeyValue'
|
||||||
|
this.raw = undefined
|
||||||
|
this.kvs = []
|
||||||
|
this.binary = []
|
||||||
|
this.set(options)
|
||||||
|
this.sets({ kvs: KeyValue }, { binary: KeyValue }, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid() {
|
||||||
|
if (this.isKV()) {
|
||||||
|
return this.kvs.some(kv => {
|
||||||
|
return kv.isValid()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return !!this.raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isKV() {
|
||||||
|
return [BODY_TYPE.FORM_DATA, BODY_TYPE.WWW_FORM, BODY_TYPE.BINARY].indexOf(this.type) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BODY_TYPE = {
|
||||||
|
KV: 'KeyValue',
|
||||||
|
FORM_DATA: 'Form_Data',
|
||||||
|
RAW: 'Raw',
|
||||||
|
WWW_FORM: 'WWW_FORM',
|
||||||
|
XML: 'XML',
|
||||||
|
JSON: 'JSON'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Scenario extends BaseConfig {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super()
|
||||||
|
this.id = undefined
|
||||||
|
this.name = undefined
|
||||||
|
this.url = undefined
|
||||||
|
this.variables = []
|
||||||
|
this.headers = []
|
||||||
|
this.requests = []
|
||||||
|
this.environmentId = undefined
|
||||||
|
this.dubboConfig = undefined
|
||||||
|
this.environment = undefined
|
||||||
|
this.enableCookieShare = false
|
||||||
|
this.enable = true
|
||||||
|
this.databaseConfigs = []
|
||||||
|
this.tcpConfig = undefined
|
||||||
|
this.set(options)
|
||||||
|
this.sets(
|
||||||
|
{
|
||||||
|
variables: KeyValue,
|
||||||
|
headers: KeyValue
|
||||||
|
},
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
initOptions(options = {}) {
|
||||||
|
options.databaseConfigs = options.databaseConfigs || []
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
const clone = new Scenario(this)
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid() {
|
||||||
|
if (this.enable) {
|
||||||
|
for (let i = 0; i < this.requests.length; i++) {
|
||||||
|
const validator = this.requests[i].isValid(this.environmentId, this.environment)
|
||||||
|
if (!validator.isValid) {
|
||||||
|
return validator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { isValid: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
isReference() {
|
||||||
|
return this.id.indexOf('#') !== -1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,345 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import icon_drag_outlined from '@/assets/svg/icon_drag_outlined.svg'
|
||||||
|
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||||
|
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { computed, onBeforeMount, PropType, toRefs, inject } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { KeyValue } from './ApiTestModel.js'
|
||||||
|
import { guid } from '@/views/visualized/data/dataset/form/util'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
name: string
|
||||||
|
value: string
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
const props = defineProps({
|
||||||
|
keyPlaceholder: propTypes.string.def(''),
|
||||||
|
valuePlaceholder: propTypes.string.def(''),
|
||||||
|
description: propTypes.string.def(''),
|
||||||
|
type: propTypes.string.def(''),
|
||||||
|
isReadOnly: propTypes.bool.def(false),
|
||||||
|
parameters: {
|
||||||
|
type: Array as PropType<Item[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
valueList: {
|
||||||
|
type: Array as PropType<Item[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
suggestions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const keyText = computed(() => {
|
||||||
|
return props.keyPlaceholder || t('datasource.key')
|
||||||
|
})
|
||||||
|
const valueText = computed(() => {
|
||||||
|
return props.valuePlaceholder || t('datasource.value')
|
||||||
|
})
|
||||||
|
|
||||||
|
const { parameters, suggestions } = toRefs(props)
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
if (parameters.value.length === 0 || parameters.value[parameters.value.length - 1].name) {
|
||||||
|
parameters.value.push(
|
||||||
|
new KeyValue({
|
||||||
|
type: 'text',
|
||||||
|
nameType: 'fixed',
|
||||||
|
enable: true,
|
||||||
|
required: true,
|
||||||
|
uuid: guid(),
|
||||||
|
contentType: 'text/plain'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const typeChange = item => {
|
||||||
|
if (item.type === 'file') {
|
||||||
|
item.contentType = 'application/octet-stream'
|
||||||
|
} else if (item.type === 'text') {
|
||||||
|
item.contentType = 'text/plain'
|
||||||
|
} else {
|
||||||
|
item.contentType = 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = (index: number) => {
|
||||||
|
if (isDisable()) return
|
||||||
|
// 移除整行输入控件及内容
|
||||||
|
parameters.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
const change = () => {
|
||||||
|
parameters.value.push(
|
||||||
|
new KeyValue({
|
||||||
|
type: 'text',
|
||||||
|
enable: true,
|
||||||
|
nameType: 'fixed',
|
||||||
|
uuid: guid(),
|
||||||
|
contentType: 'text/plain'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const isDisable = () => {
|
||||||
|
return parameters.value.length === 1
|
||||||
|
}
|
||||||
|
const querySearch = (queryString, cb) => {
|
||||||
|
const results = queryString
|
||||||
|
? suggestions.value.filter(createFilter(queryString))
|
||||||
|
: suggestions.value
|
||||||
|
cb(results)
|
||||||
|
}
|
||||||
|
const createFilter = (queryString: string) => {
|
||||||
|
return restaurant => {
|
||||||
|
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const changeNameType = element => {
|
||||||
|
element.value = ''
|
||||||
|
}
|
||||||
|
const activeName = inject('api-active-name')
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
label: t('data_source.parameter'),
|
||||||
|
value: 'params'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_source.page_parameter'),
|
||||||
|
value: 'pageParams'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_source.fixed_value'),
|
||||||
|
value: 'fixed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_source.time_function'),
|
||||||
|
value: 'timeFun'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_source.customize'),
|
||||||
|
value: 'custom'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const pageParams = [
|
||||||
|
{
|
||||||
|
label: '${pageNumber}',
|
||||||
|
value: '${pageNumber}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '${pageSize}',
|
||||||
|
value: '${pageSize}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '${pageToken}',
|
||||||
|
value: '${pageToken}'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const timeFunLists = [
|
||||||
|
{
|
||||||
|
label: t('data_source.that_day') + '(yyyy-MM-dd)',
|
||||||
|
value: 'currentDay yyyy-MM-dd'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('data_source.that_day') + '(yyyy/MM/dd)',
|
||||||
|
value: 'currentDay yyyy/MM/dd'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="api-variable">
|
||||||
|
<span v-if="description" class="kv-description">
|
||||||
|
{{ description }}
|
||||||
|
</span>
|
||||||
|
<draggable class="draggable-content_api" tag="div" :list="parameters" handle=".handle">
|
||||||
|
<template #item="{ element, index }">
|
||||||
|
<div :key="index" style="margin-bottom: 16px">
|
||||||
|
<el-row :gutter="8">
|
||||||
|
<el-icon class="drag handle">
|
||||||
|
<Icon name="icon_drag_outlined"><icon_drag_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-input
|
||||||
|
v-if="!suggestions"
|
||||||
|
v-model="element.name"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
maxlength="200"
|
||||||
|
:placeholder="keyText"
|
||||||
|
show-word-limit
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<el-select
|
||||||
|
v-if="type === 'body'"
|
||||||
|
v-model="element.type"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
class="kv-type"
|
||||||
|
@change="typeChange(item)"
|
||||||
|
>
|
||||||
|
<el-option value="text" />
|
||||||
|
<el-option value="json" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-autocomplete
|
||||||
|
v-else
|
||||||
|
v-model="element.name"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
:fetch-suggestions="querySearch"
|
||||||
|
:placeholder="keyText"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3" v-if="activeName === 'table'">
|
||||||
|
<el-select v-model="element.nameType" @change="changeNameType(element)">
|
||||||
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="element.type !== 'file'" :span="6">
|
||||||
|
<el-input
|
||||||
|
v-if="activeName === 'params'"
|
||||||
|
v-model="element.value"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
class="input-with-autocomplete"
|
||||||
|
:placeholder="valueText"
|
||||||
|
value-key="name"
|
||||||
|
highlight-first-item
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-select
|
||||||
|
v-model="element.value"
|
||||||
|
v-if="!needMock && activeName === 'table' && element.nameType === 'params'"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in valueList"
|
||||||
|
:key="item.originName"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.originName"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-model="element.value"
|
||||||
|
v-if="!needMock && activeName === 'table' && element.nameType === 'timeFun'"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in timeFunLists"
|
||||||
|
:key="item.originName"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-model="element.value"
|
||||||
|
v-if="!needMock && activeName === 'table' && element.nameType === 'pageParams'"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in pageParams"
|
||||||
|
:key="item.originName"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-if="
|
||||||
|
activeName === 'table' &&
|
||||||
|
element.nameType !== 'params' &&
|
||||||
|
element.nameType !== 'timeFun' &&
|
||||||
|
element.nameType !== 'pageParams'
|
||||||
|
"
|
||||||
|
v-model="element.value"
|
||||||
|
:disabled="isReadOnly"
|
||||||
|
class="input-with-autocomplete"
|
||||||
|
:placeholder="
|
||||||
|
element.nameType === 'fixed'
|
||||||
|
? t('data_source.value')
|
||||||
|
: t('data_source.name_use_parameters')
|
||||||
|
"
|
||||||
|
value-key="name"
|
||||||
|
highlight-first-item
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="activeName === 'params' ? 10 : 7">
|
||||||
|
<el-input
|
||||||
|
v-model="element.description"
|
||||||
|
maxlength="200"
|
||||||
|
:placeholder="$t('common.description')"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1">
|
||||||
|
<el-button
|
||||||
|
class="api-variable_del"
|
||||||
|
text
|
||||||
|
:disabled="isDisable() || isReadOnly"
|
||||||
|
@click="remove(index)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Icon><icon_deleteTrash_outlined class="svg-icon" /></Icon>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
|
||||||
|
<el-button style="margin-top: 14px" @click="change" text>
|
||||||
|
<template #icon>
|
||||||
|
<icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></icon>
|
||||||
|
</template>
|
||||||
|
{{ t('data_source.add_parameters') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.api-variable {
|
||||||
|
padding-bottom: 14px;
|
||||||
|
& > .ed-input,
|
||||||
|
:deep(.ed-autocomplete) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.drag {
|
||||||
|
margin-top: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
:deep(.draggable-content_api) > :last-child {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-variable_del {
|
||||||
|
color: #646a73;
|
||||||
|
:deep(.ed-icon) {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(31, 35, 41, 0.1) !important;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
background: rgba(31, 35, 41, 0.1) !important;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: rgba(31, 35, 41, 0.2) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.kv-description {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,70 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, PropType, watch, onMounted } from 'vue'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { VAceEditor } from 'vue3-ace-editor'
|
||||||
|
import { formatJson, formatXml } from './format-utils'
|
||||||
|
import './ace-config'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
height: [String, Number],
|
||||||
|
data: propTypes.string.def(''),
|
||||||
|
theme: propTypes.string.def('chrome'),
|
||||||
|
init: Function,
|
||||||
|
enableFormat: propTypes.bool.def(true),
|
||||||
|
readOnly: propTypes.bool.def(true),
|
||||||
|
modes: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => ['text', 'json', 'xml']
|
||||||
|
},
|
||||||
|
mode: propTypes.string.def('text')
|
||||||
|
})
|
||||||
|
|
||||||
|
const formatData = ref('')
|
||||||
|
watch(formatData, () => {
|
||||||
|
emits('update:modelValue', formatData.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch([props.theme], () => {
|
||||||
|
format()
|
||||||
|
})
|
||||||
|
onMounted(() => {
|
||||||
|
format()
|
||||||
|
})
|
||||||
|
const editorInit = editor => {
|
||||||
|
if (props.readOnly) {
|
||||||
|
editor.setReadOnly(true)
|
||||||
|
}
|
||||||
|
if (props.init) {
|
||||||
|
props.init(editor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const format = () => {
|
||||||
|
if (props.enableFormat) {
|
||||||
|
if (props.data) {
|
||||||
|
switch (props.mode) {
|
||||||
|
case 'json':
|
||||||
|
formatData.value = formatJson(props.data)
|
||||||
|
break
|
||||||
|
case 'xml':
|
||||||
|
formatData.value = formatXml(props.data)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
formatData.value = props.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formatData.value = props.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const emits = defineEmits(['update:modelValue'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-ace-editor
|
||||||
|
v-model:value="formatData"
|
||||||
|
:lang="mode"
|
||||||
|
:theme="theme"
|
||||||
|
:style="{ height }"
|
||||||
|
@init="editorInit"
|
||||||
|
/>
|
||||||
|
</template>
|
@ -0,0 +1,494 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import dvFolder from '@/assets/svg/dv-folder.svg'
|
||||||
|
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||||
|
import { ref, reactive, computed, watch, nextTick, shallowRef, unref } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { checkRepeat, listDatasources, save, update } from '@/api/datasource'
|
||||||
|
import { ElMessage, ElMessageBox, ElMessageBoxOptions } from 'element-plus-secondary'
|
||||||
|
import treeSort from '@/utils/treeSortUtils'
|
||||||
|
import type { DatasetOrFolder } from '@/api/dataset'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import nothingTree from '@/assets/img/nothing-tree.png'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { filterFreeFolder } from '@/utils/utils'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
const route = useRoute()
|
||||||
|
const appId:any = ref('')
|
||||||
|
if (route.query.id) {
|
||||||
|
appId.value = route.query.id
|
||||||
|
}
|
||||||
|
export interface Tree {
|
||||||
|
name: string
|
||||||
|
value?: string | number
|
||||||
|
id: string | number
|
||||||
|
nodeType: string
|
||||||
|
createBy?: string
|
||||||
|
level: number
|
||||||
|
leaf?: boolean
|
||||||
|
pid: string | number
|
||||||
|
type?: string
|
||||||
|
createTime: number
|
||||||
|
children?: Tree[]
|
||||||
|
request: any
|
||||||
|
}
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
tData: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const nodeType = ref()
|
||||||
|
const pid = ref()
|
||||||
|
const id = ref()
|
||||||
|
const oldName = ref()
|
||||||
|
const cmd = ref('')
|
||||||
|
const treeRef = ref()
|
||||||
|
const filterText = ref('')
|
||||||
|
const datasetForm = reactive({
|
||||||
|
pid: '',
|
||||||
|
name: ''
|
||||||
|
})
|
||||||
|
const searchEmpty = ref(false)
|
||||||
|
|
||||||
|
const filterNode = (value: string, data: Tree) => {
|
||||||
|
nextTick(() => {
|
||||||
|
searchEmpty.value = treeRef.value.isEmpty
|
||||||
|
})
|
||||||
|
if (!value) return true
|
||||||
|
return data.name.includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(filterText, val => {
|
||||||
|
showAll.value = !val
|
||||||
|
treeRef.value.filter(val)
|
||||||
|
nextTick(() => {
|
||||||
|
document.querySelectorAll('.node-text').forEach(ele => {
|
||||||
|
const content = ele.getAttribute('title')
|
||||||
|
ele.innerHTML = content.replace(val, `<span class="highLight">${val}</span>`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const showPid = computed(() => {
|
||||||
|
if (nodeType.value === 'folder' && !!pid.value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !['rename', 'move'].includes(cmd.value) && !!pid.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const labelName = computed(() => {
|
||||||
|
return nodeType.value === 'folder'
|
||||||
|
? t('deDataset.folder_name')
|
||||||
|
: t('data_source.data_source_name')
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogTitle = computed(() => {
|
||||||
|
let title = ''
|
||||||
|
switch (nodeType.value) {
|
||||||
|
case 'folder':
|
||||||
|
title = t('deDataset.new_folder')
|
||||||
|
break
|
||||||
|
case 'datasource':
|
||||||
|
title = t('deDataset.create') + t('auth.datasource')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch (cmd.value) {
|
||||||
|
case 'move':
|
||||||
|
title = t('chart.move_to')
|
||||||
|
break
|
||||||
|
case 'rename':
|
||||||
|
title = t('chart.rename')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
})
|
||||||
|
|
||||||
|
const showName = computed(() => {
|
||||||
|
return cmd.value !== 'move'
|
||||||
|
})
|
||||||
|
|
||||||
|
const placeholder = ref('')
|
||||||
|
const datasetFormRules = ref()
|
||||||
|
const activeAll = ref(false)
|
||||||
|
const showAll = ref(true)
|
||||||
|
const datasource = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
const createDataset = ref(false)
|
||||||
|
const filterMethod = (value, data) => {
|
||||||
|
if (!data) return false
|
||||||
|
data.name.includes(value)
|
||||||
|
}
|
||||||
|
const resetForm = () => {
|
||||||
|
createDataset.value = false
|
||||||
|
}
|
||||||
|
const originResourceTree = shallowRef([])
|
||||||
|
|
||||||
|
const sortTypeChange = sortType => {
|
||||||
|
state.tData = treeSort(originResourceTree.value, sortType)
|
||||||
|
}
|
||||||
|
const dfs = (arr: Tree[]) => {
|
||||||
|
arr.forEach(ele => {
|
||||||
|
ele.value = ele.id
|
||||||
|
if (ele.children?.length) {
|
||||||
|
dfs(ele.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let request = null
|
||||||
|
let dsType = ''
|
||||||
|
const sortList = ['time_asc', 'time_desc', 'name_asc', 'name_desc']
|
||||||
|
const createInit = (type, data: Tree, exec, name: string) => {
|
||||||
|
pid.value = ''
|
||||||
|
id.value = ''
|
||||||
|
cmd.value = ''
|
||||||
|
datasetForm.pid = ''
|
||||||
|
datasetForm.name = ''
|
||||||
|
nodeType.value = type
|
||||||
|
filterText.value = ''
|
||||||
|
placeholder.value =
|
||||||
|
type === 'folder' ? t('data_source.a_folder_name') : t('data_source.data_source_name_de')
|
||||||
|
dsType = data.type
|
||||||
|
if (type === 'datasource') {
|
||||||
|
request = data.request
|
||||||
|
}
|
||||||
|
if (data.id) {
|
||||||
|
if (exec !== 'rename') {
|
||||||
|
listDatasources({ leaf: false, id: data.id, weight: 7, appId: appId.value }).then(res => {
|
||||||
|
filterFreeFolder(res, 'datasource')
|
||||||
|
dfs(res as unknown as Tree[])
|
||||||
|
state.tData = (res as unknown as Tree[]) || []
|
||||||
|
if (state.tData.length && state.tData[0].name === 'root' && state.tData[0].id === '0') {
|
||||||
|
state.tData[0].name = t('data_source.data_source')
|
||||||
|
}
|
||||||
|
originResourceTree.value = cloneDeep(unref(state.tData))
|
||||||
|
let curSortType = sortList[Number(wsCache.get('TreeSort-backend')) ?? 1]
|
||||||
|
curSortType = wsCache.get('TreeSort-datasource') ?? curSortType
|
||||||
|
sortTypeChange(curSortType)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (exec) {
|
||||||
|
pid.value = data.pid
|
||||||
|
id.value = data.id
|
||||||
|
datasetForm.pid = data.pid as string
|
||||||
|
datasetForm.name = data.name
|
||||||
|
oldName.value = data.name
|
||||||
|
} else {
|
||||||
|
datasetForm.pid = data.id as string
|
||||||
|
pid.value = data.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.value = data.id ? exec : ''
|
||||||
|
name && (datasetForm.name = name)
|
||||||
|
createDataset.value = true
|
||||||
|
datasetFormRules.value = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: placeholder.value,
|
||||||
|
trigger: 'change'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: placeholder.value,
|
||||||
|
trigger: 'blur'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
min: 1,
|
||||||
|
max: 64,
|
||||||
|
message: t('datasource.input_limit_1_64', [1, 64]),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pid: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('common.please_select'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
datasource.value.clearValidate()
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
const editeInit = (param: Tree) => {
|
||||||
|
pid.value = param.pid
|
||||||
|
id.value = param.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
label: 'name',
|
||||||
|
children: 'children',
|
||||||
|
isLeaf: node => !node.children?.length
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeClick = (data: Tree) => {
|
||||||
|
activeAll.value = false
|
||||||
|
datasetForm.pid = data.id as string
|
||||||
|
}
|
||||||
|
|
||||||
|
const successCb = () => {
|
||||||
|
wsCache.set('ds-new-success', true)
|
||||||
|
datasource.value.resetFields()
|
||||||
|
request = null
|
||||||
|
datasetForm.pid = ''
|
||||||
|
datasetForm.name = ''
|
||||||
|
createDataset.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const finallyCb = () => {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
const checkPid = pid => {
|
||||||
|
if (pid !== 0 && !pid) {
|
||||||
|
ElMessage.error(t('data_source.the_destination_folder'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const saveDataset = () => {
|
||||||
|
datasource.value.validate(result => {
|
||||||
|
if (result) {
|
||||||
|
const params: Omit<DatasetOrFolder, 'nodeType'> & { nodeType: 'folder' | 'datasource' } = {
|
||||||
|
nodeType: nodeType.value as 'folder' | 'datasource',
|
||||||
|
name: datasetForm.name.trim()
|
||||||
|
}
|
||||||
|
switch (cmd.value) {
|
||||||
|
case 'move':
|
||||||
|
params.pid = activeAll.value ? '0' : (datasetForm.pid as string)
|
||||||
|
params.id = id.value
|
||||||
|
params.action = 'move'
|
||||||
|
break
|
||||||
|
case 'rename':
|
||||||
|
params.pid = pid.value as string
|
||||||
|
params.id = id.value
|
||||||
|
params.action = 'rename'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
params.pid = datasetForm.pid || pid.value || '0'
|
||||||
|
params.action = 'create'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (cmd.value === 'rename' && oldName.value === params.name) {
|
||||||
|
successCb()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (cmd.value === 'move' && !checkPid(params.pid)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (loading.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
if (request) {
|
||||||
|
let options = {
|
||||||
|
confirmButtonType: 'danger',
|
||||||
|
type: 'warning',
|
||||||
|
autofocus: false,
|
||||||
|
showClose: false,
|
||||||
|
tip: ''
|
||||||
|
}
|
||||||
|
request.apiConfiguration = ''
|
||||||
|
request.appId = appId.value
|
||||||
|
debugger
|
||||||
|
checkRepeat(request).then(res => {
|
||||||
|
let method = request.id === '' ? save : update
|
||||||
|
if (!request.type.startsWith('API')) {
|
||||||
|
request.syncSetting = null
|
||||||
|
}
|
||||||
|
if (res) {
|
||||||
|
ElMessageBox.confirm(t('datasource.has_same_ds'), options as ElMessageBoxOptions)
|
||||||
|
.then(() => {
|
||||||
|
method({ ...request, name: datasetForm.name, pid: params.pid })
|
||||||
|
.then(res => {
|
||||||
|
if (res !== undefined) {
|
||||||
|
wsCache.set('ds-new-success', true)
|
||||||
|
emits('handleShowFinishPage', { ...res, pid: params.pid })
|
||||||
|
ElMessage.success(t('data_source.source_saved_successfully'))
|
||||||
|
successCb()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false
|
||||||
|
createDataset.value = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
method({ ...request, name: datasetForm.name, pid: params.pid })
|
||||||
|
.then(res => {
|
||||||
|
if (res !== undefined) {
|
||||||
|
wsCache.set('ds-new-success', true)
|
||||||
|
emits('handleShowFinishPage', { ...res, pid: params.pid })
|
||||||
|
ElMessage.success(t('data_source.source_saved_successfully'))
|
||||||
|
successCb()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emits('finish', params, successCb, finallyCb, cmd.value, dsType)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
createInit,
|
||||||
|
editeInit
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['finish', 'handleShowFinishPage'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-loading="loading"
|
||||||
|
:title="dialogTitle"
|
||||||
|
v-model="createDataset"
|
||||||
|
:width="cmd === 'move' ? '600px' : '420px'"
|
||||||
|
class="create-dialog"
|
||||||
|
:before-close="resetForm"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
ref="datasource"
|
||||||
|
@keydown.stop.prevent.enter
|
||||||
|
:model="datasetForm"
|
||||||
|
:rules="datasetFormRules"
|
||||||
|
>
|
||||||
|
<el-form-item v-if="showName" :label="labelName" prop="name">
|
||||||
|
<el-input :placeholder="placeholder" v-model="datasetForm.name" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="showPid" :label="t('deDataset.folder')" prop="pid">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="datasetForm.pid"
|
||||||
|
:data="state.tData"
|
||||||
|
popper-class="dataset-tree-select"
|
||||||
|
style="width: 100%"
|
||||||
|
:render-after-expand="false"
|
||||||
|
:props="props"
|
||||||
|
@node-click="nodeClick"
|
||||||
|
:filter-method="filterMethod"
|
||||||
|
filterable
|
||||||
|
>
|
||||||
|
<template #default="{ data: { name } }">
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span :title="name">{{ name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-tree-select>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-if="cmd === 'move'">
|
||||||
|
<el-input style="margin-bottom: 12px" v-model="filterText" clearable>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_search-outline_outlined"
|
||||||
|
><icon_searchOutline_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<div class="tree-content">
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:filter-node-method="filterNode"
|
||||||
|
filterable
|
||||||
|
v-model="datasetForm.pid"
|
||||||
|
menu
|
||||||
|
empty-text=""
|
||||||
|
:data="state.tData"
|
||||||
|
:props="props"
|
||||||
|
@node-click="nodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ data }">
|
||||||
|
<span class="custom-tree-node">
|
||||||
|
<el-icon style="font-size: 18px">
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span class="node-text" :title="data.name">{{ data.name }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
<div v-if="searchEmpty" class="empty-search">
|
||||||
|
<img :src="nothingTree" />
|
||||||
|
<span>{{ t('data_source.relevant_content_found') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button secondary @click="resetForm">{{ t('dataset.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="saveDataset">{{ t('dataset.confirm') }} </el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tree-content {
|
||||||
|
width: 552px;
|
||||||
|
height: 380px;
|
||||||
|
border: 1px solid #dee0e3;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
.custom-tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.node-text {
|
||||||
|
margin-left: 8.75px;
|
||||||
|
width: 120px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
:deep(.highLight) {
|
||||||
|
color: var(--el-color-primary, #3370ff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-search {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 57px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
img {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
color: #646a73;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="less">
|
||||||
|
.dataset-tree-select {
|
||||||
|
.ed-select-dropdown__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.ed-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,187 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { shallowRef, PropType, computed } from 'vue'
|
||||||
|
import { dsTypes, typeList, nameMap } from './option'
|
||||||
|
import Icon from '@/components/icon-custom/src/Icon.vue'
|
||||||
|
import { XpackComponent } from '@/components/plugin'
|
||||||
|
import { iconDatasourceMap } from '@/components/icon-group/datasource-list'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
|
export type DsType = 'OLTP' | 'OLAP' | 'DL' | 'OTHER' | 'LOCAL' | 'latestUse' | 'all'
|
||||||
|
const props = defineProps({
|
||||||
|
currentType: {
|
||||||
|
type: String as PropType<DsType>,
|
||||||
|
default: 'OLTP'
|
||||||
|
},
|
||||||
|
filterText: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
latestUseTypes: {
|
||||||
|
type: Array
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const databaseList = shallowRef([])
|
||||||
|
const currentTypeList = computed(() => {
|
||||||
|
if (props.currentType == 'all') {
|
||||||
|
return typeList.map((ele, index) => {
|
||||||
|
return {
|
||||||
|
name: nameMap[ele],
|
||||||
|
dbList: databaseList.value[index].filter(ele =>
|
||||||
|
ele.name.toLowerCase().includes(props.filterText.trim())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (props.currentType === 'latestUse') {
|
||||||
|
let catalogList = []
|
||||||
|
let dstypes = []
|
||||||
|
props.latestUseTypes.forEach(type => {
|
||||||
|
dsTypes.forEach(item => {
|
||||||
|
if (item.type === type && catalogList.indexOf(item.catalog) === -1) {
|
||||||
|
catalogList.push(item.catalog)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let dbList = []
|
||||||
|
catalogList.forEach(catalog => {
|
||||||
|
props.latestUseTypes.forEach(type => {
|
||||||
|
dsTypes.forEach(item => {
|
||||||
|
if (item.type === type && item.catalog === catalog) {
|
||||||
|
dbList.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
dbList = dbList.filter(ele => ele.name.toLowerCase().includes(props.filterText.trim()))
|
||||||
|
dstypes.push({ name: t('data_source.recently_created'), dbList })
|
||||||
|
return dstypes
|
||||||
|
}
|
||||||
|
const index = typeList.findIndex(ele => props.currentType === ele)
|
||||||
|
return (
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: nameMap[props.currentType],
|
||||||
|
dbList: databaseList.value[index].filter(ele =>
|
||||||
|
ele.name.toLowerCase().includes(props.filterText.trim())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
] || []
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const getDatasourceTypes = () => {
|
||||||
|
const arr = [[], [], [], [], []]
|
||||||
|
dsTypes.forEach(item => {
|
||||||
|
const index = typeList.findIndex(ele => ele === item.catalog)
|
||||||
|
if (index !== -1) {
|
||||||
|
arr[index].push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
databaseList.value = arr.map(ele => {
|
||||||
|
return ele.sort((a, b) => {
|
||||||
|
return a.name.toLowerCase().charCodeAt(0) - b.name.toLowerCase().charCodeAt(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
getDatasourceTypes()
|
||||||
|
const loadDsPlugin = data => {
|
||||||
|
data.forEach(item => {
|
||||||
|
const { name, category, type, icon, extraParams, staticMap } = item
|
||||||
|
const node = {
|
||||||
|
name,
|
||||||
|
catalog: category,
|
||||||
|
type,
|
||||||
|
icon,
|
||||||
|
extraParams,
|
||||||
|
isPlugin: true,
|
||||||
|
staticMap
|
||||||
|
}
|
||||||
|
const index = typeList.findIndex(ele => ele === node.catalog)
|
||||||
|
if (index !== -1) {
|
||||||
|
let copiedArr = JSON.parse(JSON.stringify(databaseList.value))
|
||||||
|
copiedArr[index].push(node)
|
||||||
|
databaseList.value = copiedArr
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const emits = defineEmits(['selectDsType'])
|
||||||
|
const selectDs = ({ type }) => {
|
||||||
|
emits('selectDsType', type)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="ds-type-list">
|
||||||
|
<template v-for="ele in currentTypeList" :key="ele.name">
|
||||||
|
<div class="title-form_primary">
|
||||||
|
{{ ele.name }}
|
||||||
|
</div>
|
||||||
|
<div class="item-container">
|
||||||
|
<div v-for="db in ele.dbList" :key="db.type" class="db-card" @click="selectDs(db)">
|
||||||
|
<el-icon class="icon-border">
|
||||||
|
<Icon v-if="db['isPlugin']" :static-content="db.icon"></Icon>
|
||||||
|
<Icon v-else
|
||||||
|
><component :is="iconDatasourceMap[db.type]" class="svg-icon"></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<p class="db-name">{{ db.name }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<XpackComponent
|
||||||
|
jsname="L2NvbXBvbmVudC9wbHVnaW5zLWhhbmRsZXIvRHNDYXRlZ29yeUhhbmRsZXI="
|
||||||
|
@load-ds-plugin="loadDsPlugin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ds-type-list {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.title-form_primary {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-container {
|
||||||
|
display: flex;
|
||||||
|
width: calc(100% + 16px);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-left: -16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.db-card {
|
||||||
|
height: 64px;
|
||||||
|
width: 266px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #dee0e3;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
margin-left: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.icon-border {
|
||||||
|
margin-right: 12px;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 6px 24px rgba(31, 35, 41, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.marLeft {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,875 @@
|
|||||||
|
<script lang="tsx" setup>
|
||||||
|
import icon_upload_outlined from '@/assets/svg/icon_upload_outlined.svg'
|
||||||
|
import icon_refresh_outlined from '@/assets/svg/icon_refresh_outlined.svg'
|
||||||
|
import { Icon } from '@/components/icon-custom'
|
||||||
|
import { ElIcon } from 'element-plus-secondary'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import {
|
||||||
|
ref,
|
||||||
|
shallowRef,
|
||||||
|
reactive,
|
||||||
|
h,
|
||||||
|
computed,
|
||||||
|
toRefs,
|
||||||
|
onMounted,
|
||||||
|
onBeforeUnmount,
|
||||||
|
nextTick
|
||||||
|
} from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||||
|
import { save, update } from '@/api/datasource'
|
||||||
|
import type { Action } from 'element-plus-secondary'
|
||||||
|
import { Base64 } from 'js-base64'
|
||||||
|
import ExcelInfo from '../ExcelInfo.vue'
|
||||||
|
import SheetTabs from '../SheetTabs.vue'
|
||||||
|
import { cloneDeep, debounce } from 'lodash-es'
|
||||||
|
import { uploadFile } from '@/api/datasource'
|
||||||
|
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||||
|
import { iconFieldMap } from '@/components/icon-group/field-list'
|
||||||
|
import { boolean } from 'mathjs'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
const route = useRoute()
|
||||||
|
const appId:any = ref('')
|
||||||
|
if (route.query.id) {
|
||||||
|
appId.value = route.query.id
|
||||||
|
}
|
||||||
|
export interface Param {
|
||||||
|
editType: number
|
||||||
|
pid?: string
|
||||||
|
type?: string
|
||||||
|
id?: string
|
||||||
|
name?: string
|
||||||
|
creator?: string
|
||||||
|
isPlugin?: boolean
|
||||||
|
staticMap?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Field {
|
||||||
|
accuracy: number
|
||||||
|
originName: string
|
||||||
|
fieldSize: number
|
||||||
|
fieldType: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
const props = defineProps({
|
||||||
|
param: {
|
||||||
|
required: false,
|
||||||
|
default() {
|
||||||
|
return reactive<{
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
desc: string
|
||||||
|
type: string
|
||||||
|
editType: number
|
||||||
|
}>({
|
||||||
|
id: '0',
|
||||||
|
name: '',
|
||||||
|
desc: '',
|
||||||
|
type: 'Excel',
|
||||||
|
editType: 0
|
||||||
|
})
|
||||||
|
},
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
isSupportSetKey: {
|
||||||
|
type: boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { param, isSupportSetKey } = toRefs(props)
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const { emitter } = useEmitt()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const columns = shallowRef([])
|
||||||
|
const multipleSelection = shallowRef([])
|
||||||
|
const multipleTable = ref()
|
||||||
|
|
||||||
|
const defaultSheetObj = {
|
||||||
|
tableName: ' ',
|
||||||
|
sheetExcelId: '',
|
||||||
|
fields: [],
|
||||||
|
jsonArray: [],
|
||||||
|
nameExist: false,
|
||||||
|
empty: '',
|
||||||
|
overLength: false
|
||||||
|
}
|
||||||
|
const sheetObj = reactive(cloneDeep(defaultSheetObj))
|
||||||
|
const state = reactive({
|
||||||
|
excelData: [],
|
||||||
|
defaultExpandedKeys: [],
|
||||||
|
defaultCheckedKeys: [],
|
||||||
|
fileList: null,
|
||||||
|
sheets: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const sheetFile = computed(() => {
|
||||||
|
const [sheet = {}] = state.excelData
|
||||||
|
return {
|
||||||
|
name: sheet.excelLabel,
|
||||||
|
size: sheet.excelLabel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const uploading = ref(false)
|
||||||
|
|
||||||
|
const fieldType = {
|
||||||
|
TEXT: 'text',
|
||||||
|
DATETIME: 'time',
|
||||||
|
LONG: 'value',
|
||||||
|
DOUBLE: 'value'
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateColumns = (arr: Field[]) =>
|
||||||
|
arr.map(ele => ({
|
||||||
|
key: ele.originName,
|
||||||
|
fieldType: ele.fieldType,
|
||||||
|
dataKey: ele.originName,
|
||||||
|
title: ele.name,
|
||||||
|
checked: ele.checked,
|
||||||
|
primaryKey: ele.primaryKey,
|
||||||
|
length: ele.length,
|
||||||
|
width: 150,
|
||||||
|
headerCellRenderer: ({ column }) => (
|
||||||
|
<div class="flex-align-center icon">
|
||||||
|
<ElIcon>
|
||||||
|
<Icon>
|
||||||
|
{h(iconFieldMap[fieldType[column.fieldType]], {
|
||||||
|
class: `svg-icon field-icon-${fieldType[column.fieldType]}`
|
||||||
|
})}
|
||||||
|
</Icon>
|
||||||
|
</ElIcon>
|
||||||
|
<span class="ellipsis" title={column.title} style={{ width: '100px' }}>
|
||||||
|
{column.title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handleNodeClick = data => {
|
||||||
|
if (data.sheet) {
|
||||||
|
Object.assign(sheetObj, data)
|
||||||
|
columns.value = generateColumns(data.fields)
|
||||||
|
multipleSelection.value = columns.value.filter(item => item.checked)
|
||||||
|
currentMode.value = 'preview'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeUpload = () => {
|
||||||
|
uploading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTabClick = tab => {
|
||||||
|
activeTab.value = tab.value
|
||||||
|
const sheet = state.excelData[0]?.sheets.find(ele => ele.sheetId === tab.value)
|
||||||
|
handleNodeClick(sheet)
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadFail = response => {
|
||||||
|
state.excelData = []
|
||||||
|
activeTab.value = ''
|
||||||
|
tabList.value = []
|
||||||
|
Object.assign(sheetObj, cloneDeep(defaultSheetObj))
|
||||||
|
let myError = response.toString()
|
||||||
|
myError.replace('Error: ', '')
|
||||||
|
}
|
||||||
|
const tabList = shallowRef([])
|
||||||
|
const activeTab = ref('')
|
||||||
|
|
||||||
|
const handleExcelDel = () => {
|
||||||
|
state.excelData = []
|
||||||
|
activeTab.value = ''
|
||||||
|
tabList.value = []
|
||||||
|
Object.assign(sheetObj, cloneDeep(defaultSheetObj))
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadSuccess = response => {
|
||||||
|
if (!response) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (response?.code !== 0) {
|
||||||
|
state.excelData = []
|
||||||
|
activeTab.value = ''
|
||||||
|
tabList.value = []
|
||||||
|
Object.assign(sheetObj, cloneDeep(defaultSheetObj))
|
||||||
|
ElMessage.warning(response.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
columns.value = []
|
||||||
|
Object.assign(sheetObj, cloneDeep(defaultSheetObj))
|
||||||
|
multipleSelection.value = []
|
||||||
|
uploading.value = false
|
||||||
|
if (!param.value.name) {
|
||||||
|
param.value.name = response.data.excelLabel
|
||||||
|
}
|
||||||
|
tabList.value = response.data.sheets.map(ele => {
|
||||||
|
const { sheetId, tableName, newSheet } = ele
|
||||||
|
return {
|
||||||
|
value: sheetId,
|
||||||
|
label: tableName,
|
||||||
|
newSheet: newSheet
|
||||||
|
}
|
||||||
|
})
|
||||||
|
state.excelData = [response.data]
|
||||||
|
const [sheet] = tabList.value
|
||||||
|
|
||||||
|
sheet && handleTabClick(sheet)
|
||||||
|
}
|
||||||
|
const saveExcelDs = (params, successCb, finallyCb) => {
|
||||||
|
let validate = true
|
||||||
|
let selectedSheet = []
|
||||||
|
let sheetFileMd5 = []
|
||||||
|
let effectExtField = false
|
||||||
|
let changeFiled = false
|
||||||
|
let selectNode = state.excelData[0]?.sheets
|
||||||
|
for (let i = 0; i < selectNode.length; i++) {
|
||||||
|
if (selectNode[i].sheet) {
|
||||||
|
if (selectNode[i].effectExtField) {
|
||||||
|
effectExtField = true
|
||||||
|
}
|
||||||
|
if (selectNode[i].changeFiled) {
|
||||||
|
changeFiled = true
|
||||||
|
}
|
||||||
|
if (selectNode[i].fields.filter(field => field.checked).length == 0) {
|
||||||
|
ElMessage({
|
||||||
|
message: selectNode[i].excelLabel + t('datasource.api_field_not_empty'),
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
finallyCb?.()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (let j = 0; j < selectNode[i].fields.length; j++) {
|
||||||
|
if (
|
||||||
|
selectNode[i].fields[j].checked &&
|
||||||
|
selectNode[i].fields[j].primaryKey &&
|
||||||
|
!selectNode[i].fields[j].length &&
|
||||||
|
selectNode[i].fields[j].deExtractType === 0
|
||||||
|
) {
|
||||||
|
ElMessage({
|
||||||
|
message:
|
||||||
|
t('datasource.primary_key_length') +
|
||||||
|
selectNode[i].excelLabel +
|
||||||
|
': ' +
|
||||||
|
selectNode[i].fields[j].name,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
finallyCb?.()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedSheet.push(selectNode[i])
|
||||||
|
sheetFileMd5.push(selectNode[i].fieldsMd5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!selectedSheet.length) {
|
||||||
|
ElMessage({
|
||||||
|
message: t('dataset.ple_select_excel'),
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
finallyCb?.()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!validate) {
|
||||||
|
finallyCb?.()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = {}
|
||||||
|
if (params) {
|
||||||
|
param.value.name = params.name
|
||||||
|
}
|
||||||
|
if (!props.param.id) {
|
||||||
|
table = {
|
||||||
|
id: props.param.id,
|
||||||
|
name: props.param.name,
|
||||||
|
type: 'Excel',
|
||||||
|
sheets: selectedSheet,
|
||||||
|
editType: 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
table = {
|
||||||
|
id: props.param.id,
|
||||||
|
name: props.param.name,
|
||||||
|
type: 'Excel',
|
||||||
|
sheets: selectedSheet,
|
||||||
|
editType: props.param.editType ? props.param.editType : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.param.editType === 0 && props.param.id && (effectExtField || changeFiled)) {
|
||||||
|
ElMessageBox.confirm(t('deDataset.replace_the_data'), {
|
||||||
|
confirmButtonText: t('dataset.confirm'),
|
||||||
|
tip: t('data_source.to_replace_it'),
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
confirmButtonType: 'primary',
|
||||||
|
type: 'warning',
|
||||||
|
autofocus: false,
|
||||||
|
showClose: false,
|
||||||
|
callback: (action: Action) => {
|
||||||
|
if (action === 'confirm') {
|
||||||
|
saveExcelData(sheetFileMd5, table, params, successCb, finallyCb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
saveExcelData(sheetFileMd5, table, params, successCb, finallyCb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveExcelData = (sheetFileMd5, table, params, successCb, finallyCb) => {
|
||||||
|
for (let i = 0; i < table.sheets.length; i++) {
|
||||||
|
table.sheets[i].data = []
|
||||||
|
table.sheets[i].jsonArray = []
|
||||||
|
}
|
||||||
|
table.configuration = Base64.encode(JSON.stringify(table.sheets))
|
||||||
|
let method = save
|
||||||
|
debugger
|
||||||
|
if (!table.id || table.id === '0') {
|
||||||
|
delete table.id
|
||||||
|
table.pid = params.pid
|
||||||
|
} else {
|
||||||
|
method = update
|
||||||
|
}
|
||||||
|
if (new Set(sheetFileMd5).size !== sheetFileMd5.length && !props.param.id) {
|
||||||
|
ElMessageBox.confirm(t('dataset.merge_title'), {
|
||||||
|
confirmButtonText: t('dataset.merge'),
|
||||||
|
tip: t('dataset.task.excel_replace_msg'),
|
||||||
|
cancelButtonText: t('dataset.no_merge'),
|
||||||
|
confirmButtonType: 'primary',
|
||||||
|
type: 'warning',
|
||||||
|
autofocus: false,
|
||||||
|
callback: (action: Action) => {
|
||||||
|
if (action === 'close') return
|
||||||
|
loading.value = true
|
||||||
|
table.mergeSheet = action === 'confirm'
|
||||||
|
|
||||||
|
table.appId = appId.value
|
||||||
|
if (action === 'confirm') {
|
||||||
|
method(table)
|
||||||
|
.then(res => {
|
||||||
|
emitter.emit('showFinishPage', res)
|
||||||
|
successCb?.()
|
||||||
|
ElMessage({
|
||||||
|
message: t('commons.save_success'),
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
finallyCb?.()
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'cancel') {
|
||||||
|
method(table)
|
||||||
|
.then(res => {
|
||||||
|
emitter.emit('showFinishPage', res)
|
||||||
|
successCb?.()
|
||||||
|
ElMessage({
|
||||||
|
message: t('commons.save_success'),
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
finallyCb?.()
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (loading.value) return
|
||||||
|
loading.value = true
|
||||||
|
method(table)
|
||||||
|
.then(res => {
|
||||||
|
emitter.emit('showFinishPage', res)
|
||||||
|
successCb?.()
|
||||||
|
ElMessage({
|
||||||
|
message: t('commons.save_success'),
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
finallyCb?.()
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChange = file => {
|
||||||
|
state.fileList = file
|
||||||
|
}
|
||||||
|
const isResize = ref(true)
|
||||||
|
|
||||||
|
const handleResize = debounce(() => {
|
||||||
|
isResize.value = false
|
||||||
|
nextTick(() => {
|
||||||
|
isResize.value = true
|
||||||
|
})
|
||||||
|
}, 500)
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', handleResize)
|
||||||
|
})
|
||||||
|
|
||||||
|
const upload = ref()
|
||||||
|
const uploadAgain = ref()
|
||||||
|
|
||||||
|
const uploadExcel = () => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', state.fileList.raw)
|
||||||
|
formData.append('type', '')
|
||||||
|
formData.append('editType', param.value.editType)
|
||||||
|
formData.append('id', param.value.id || 0)
|
||||||
|
loading.value = true
|
||||||
|
return uploadFile(formData)
|
||||||
|
.then(res => {
|
||||||
|
upload.value?.clearFiles()
|
||||||
|
uploadAgain.value?.clearFiles()
|
||||||
|
uploadSuccess(res)
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
state.excelData = []
|
||||||
|
activeTab.value = ''
|
||||||
|
tabList.value = []
|
||||||
|
Object.assign(sheetObj, cloneDeep(defaultSheetObj))
|
||||||
|
if (error.code === 'ECONNABORTED') {
|
||||||
|
ElMessage({
|
||||||
|
type: 'error',
|
||||||
|
message: error.message,
|
||||||
|
showClose: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const excelForm = ref()
|
||||||
|
const submitForm = () => {
|
||||||
|
return excelForm.value.validate
|
||||||
|
}
|
||||||
|
|
||||||
|
const showName = ref(true)
|
||||||
|
|
||||||
|
const appendReplaceExcel = response => {
|
||||||
|
showName.value = false
|
||||||
|
uploadSuccess(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = ref(false)
|
||||||
|
const initMultipleTable = ref(false)
|
||||||
|
const currentMode = ref('preview')
|
||||||
|
const refreshData = () => {
|
||||||
|
currentMode.value = 'preview'
|
||||||
|
}
|
||||||
|
|
||||||
|
const lengthChange = val => {
|
||||||
|
const sheet = state.excelData[0]?.sheets.find(ele => ele.sheetId === activeTab.value)
|
||||||
|
sheet.fields.forEach(row => {
|
||||||
|
if (row.originName === val.dataKey) {
|
||||||
|
row.length = val.length
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const primaryKeyChange = val => {
|
||||||
|
const sheet = state.excelData[0]?.sheets.find(ele => ele.sheetId === activeTab.value)
|
||||||
|
sheet.fields.forEach(row => {
|
||||||
|
if (row.originName === val.dataKey) {
|
||||||
|
row.primaryKey = val.primaryKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelectionChange = val => {
|
||||||
|
if (!initMultipleTable.value) {
|
||||||
|
multipleSelection.value = val
|
||||||
|
multipleSelection.value.forEach(row => {
|
||||||
|
row.checked = true
|
||||||
|
})
|
||||||
|
columns.value.forEach(row => {
|
||||||
|
let item
|
||||||
|
for (let i = 0; i < multipleSelection.value.length; i++) {
|
||||||
|
if (row.dataKey === multipleSelection.value[i].dataKey) {
|
||||||
|
item = multipleSelection.value[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item) {
|
||||||
|
row.checked = item.checked
|
||||||
|
} else {
|
||||||
|
row.checked = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const sheet = state.excelData[0]?.sheets.find(ele => ele.sheetId === activeTab.value)
|
||||||
|
sheet.fields.forEach(row => {
|
||||||
|
let item
|
||||||
|
for (let i = 0; i < multipleSelection.value.length; i++) {
|
||||||
|
if (row.originName === multipleSelection.value[i].dataKey) {
|
||||||
|
item = multipleSelection.value[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item) {
|
||||||
|
row.checked = item.checked
|
||||||
|
} else {
|
||||||
|
row.checked = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const disabledFieldLength = item => {
|
||||||
|
if (!item.checked) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (item.fieldType !== 'TEXT') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeCurrentMode = val => {
|
||||||
|
currentMode.value = val
|
||||||
|
if (val === 'select') {
|
||||||
|
nextTick(() => {
|
||||||
|
initMultipleTable.value = true
|
||||||
|
for (let i = 0; i < columns.value.length; i++) {
|
||||||
|
if (columns.value[i].checked) {
|
||||||
|
multipleTable?.value?.toggleRowSelection(columns.value[i], true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initMultipleTable.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadStatus = val => {
|
||||||
|
status.value = val
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
saveExcelDs,
|
||||||
|
submitForm,
|
||||||
|
sheetFile,
|
||||||
|
appendReplaceExcel,
|
||||||
|
uploadStatus
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="excel-detail">
|
||||||
|
<div class="detail-inner">
|
||||||
|
<el-form
|
||||||
|
ref="excelForm"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
:model="param"
|
||||||
|
label-position="top"
|
||||||
|
v-loading="loading"
|
||||||
|
>
|
||||||
|
<el-form-item
|
||||||
|
v-if="sheetFile.name"
|
||||||
|
prop="id"
|
||||||
|
:label="t('data_source.document')"
|
||||||
|
key="sheetFile"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<ExcelInfo
|
||||||
|
@del="handleExcelDel"
|
||||||
|
show-del
|
||||||
|
:name="sheetFile.name"
|
||||||
|
:size="sheetFile.size"
|
||||||
|
></ExcelInfo>
|
||||||
|
<el-upload
|
||||||
|
action=""
|
||||||
|
:multiple="false"
|
||||||
|
ref="uploadAgain"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept=".xls,.xlsx,.csv"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:on-change="onChange"
|
||||||
|
:http-request="uploadExcel"
|
||||||
|
:on-error="uploadFail"
|
||||||
|
name="file"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<el-button text>{{ t('data_source.reupload') }}</el-button>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-else
|
||||||
|
prop="id"
|
||||||
|
key="sheetId"
|
||||||
|
:label="t('data_source.document')"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<el-upload
|
||||||
|
:multiple="false"
|
||||||
|
action=""
|
||||||
|
ref="upload"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept=".xls,.xlsx,.csv"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:on-change="onChange"
|
||||||
|
:http-request="uploadExcel"
|
||||||
|
:on-error="uploadFail"
|
||||||
|
name="file"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<el-button secondary>
|
||||||
|
<template #icon>
|
||||||
|
<Icon name="icon_upload_outlined"><icon_upload_outlined class="svg-icon" /></Icon>
|
||||||
|
</template>
|
||||||
|
{{ t('dataset.upload_file') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
<p class="upload-tip" style="width: 100%">{{ t('data_source.and_csv_formats') }}</p>
|
||||||
|
<div class="ed-form-item__error" v-if="status">
|
||||||
|
{{ t('data_source.please_upload_files') }}
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
:class="status && !sheetFile.name && 'error-status'"
|
||||||
|
prop="name"
|
||||||
|
key="name"
|
||||||
|
v-if="showName"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('common.please_input') + t('datasource.datasource') + t('common.name')
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
:label="t('visualization.custom') + t('datasource.datasource') + t('common.name')"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="param.name"
|
||||||
|
:placeholder="t('common.please_input') + t('datasource.datasource') + t('common.name')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template v-if="activeTab">
|
||||||
|
<div class="title-form_primary">
|
||||||
|
{{ t('chart.data_preview') }}
|
||||||
|
</div>
|
||||||
|
<SheetTabs
|
||||||
|
:activeTab="activeTab"
|
||||||
|
@tab-click="handleTabClick"
|
||||||
|
:tab-list="tabList"
|
||||||
|
></SheetTabs>
|
||||||
|
|
||||||
|
<div class="table-select_mode" v-if="param.editType === 0">
|
||||||
|
<div class="btn-select">
|
||||||
|
<el-button
|
||||||
|
@click="changeCurrentMode('preview')"
|
||||||
|
:class="[currentMode === 'preview' && 'is-active']"
|
||||||
|
text
|
||||||
|
>
|
||||||
|
{{ t('chart.data_preview') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
@click="changeCurrentMode('select')"
|
||||||
|
:class="[currentMode === 'select' && 'is-active']"
|
||||||
|
text
|
||||||
|
>
|
||||||
|
{{ t('data_set.field_selection') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="info-table"
|
||||||
|
:class="param.editType === 0 && 'info-table_height'"
|
||||||
|
v-if="isResize"
|
||||||
|
>
|
||||||
|
<el-auto-resizer v-if="currentMode === 'preview'">
|
||||||
|
<template #default="{ height, width }">
|
||||||
|
<el-table-v2
|
||||||
|
:columns="multipleSelection"
|
||||||
|
header-class="excel-header-cell"
|
||||||
|
:data="sheetObj.jsonArray"
|
||||||
|
:width="width"
|
||||||
|
:height="height"
|
||||||
|
fixed
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-auto-resizer>
|
||||||
|
<el-table
|
||||||
|
header-class="header-cell"
|
||||||
|
v-else
|
||||||
|
ref="multipleTable"
|
||||||
|
:data="columns"
|
||||||
|
style="width: 100%"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column :label="t('data_set.field_name')">
|
||||||
|
<template #default="scope">{{ scope.row.title }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('data_set.field_type')">
|
||||||
|
<template #default="scope">
|
||||||
|
<div class="flex-align-center">
|
||||||
|
<el-icon>
|
||||||
|
<Icon>
|
||||||
|
<component
|
||||||
|
:class="`svg-icon field-icon-${fieldType[scope.row.fieldType]}`"
|
||||||
|
:is="iconFieldMap[fieldType[scope.row.fieldType]]"
|
||||||
|
></component>
|
||||||
|
</Icon>
|
||||||
|
</el-icon>
|
||||||
|
|
||||||
|
{{ t(`dataset.${fieldType[scope.row.fieldType]}`) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="length"
|
||||||
|
:label="t('datasource.length')"
|
||||||
|
v-if="param.editType === 0"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input-number
|
||||||
|
:disabled="disabledFieldLength(scope.row)"
|
||||||
|
v-model="scope.row.length"
|
||||||
|
autocomplete="off"
|
||||||
|
step-strictly
|
||||||
|
class="text-left edit-all-line"
|
||||||
|
:min="1"
|
||||||
|
:max="512"
|
||||||
|
:placeholder="t('common.inputText')"
|
||||||
|
controls-position="right"
|
||||||
|
type="number"
|
||||||
|
@change="lengthChange(scope.row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
prop="primaryKey"
|
||||||
|
class-name="checkbox-table"
|
||||||
|
:label="t('datasource.set_key')"
|
||||||
|
width="100"
|
||||||
|
v-if="param.editType === 0 && isSupportSetKey"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-checkbox
|
||||||
|
:key="scope.row.dataKey"
|
||||||
|
v-model="scope.row.primaryKey"
|
||||||
|
:disabled="!scope.row.checked"
|
||||||
|
@change="primaryKeyChange(scope.row)"
|
||||||
|
>
|
||||||
|
</el-checkbox>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.excel-detail {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: calc(100% + 48px);
|
||||||
|
margin: -8px -24px 0 -24px;
|
||||||
|
.ed-form-item {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-select_mode {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #f5f6f7;
|
||||||
|
padding: 16px;
|
||||||
|
.btn-select {
|
||||||
|
width: 164px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #bbbfc4;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.is-active {
|
||||||
|
background: var(--ed-color-primary-1a, rgba(51, 112, 255, 0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ed-button:not(.is-active) {
|
||||||
|
color: #1f2329;
|
||||||
|
}
|
||||||
|
.ed-button.is-text {
|
||||||
|
height: 24px;
|
||||||
|
width: 74px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
.ed-button + .ed-button {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-operate {
|
||||||
|
height: 56px;
|
||||||
|
padding: 16px 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid rgba(31, 35, 41, 0.15);
|
||||||
|
}
|
||||||
|
.detail-inner {
|
||||||
|
width: 800px;
|
||||||
|
padding-top: 16px;
|
||||||
|
height: calc(100vh - 280px);
|
||||||
|
min-height: 700px;
|
||||||
|
|
||||||
|
.dropdown-icon {
|
||||||
|
.down-outlined {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
&[aria-expanded='true'] {
|
||||||
|
.down-outlined {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-status {
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-tip {
|
||||||
|
color: #8f959e;
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-form_primary {
|
||||||
|
margin: 16px 0;
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-table {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 200px);
|
||||||
|
&.info-table_height {
|
||||||
|
height: calc(100% - 379px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,229 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { onBeforeMount, PropType, ref, toRefs } from 'vue'
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
|
export interface PageSetting {
|
||||||
|
pageType: string
|
||||||
|
requestData: requestItem[]
|
||||||
|
responseData: responseItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface requestItem {
|
||||||
|
parameterName: string
|
||||||
|
builtInParameterName: string
|
||||||
|
requestParameterName: string
|
||||||
|
parameterDefaultValue: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface responseItem {
|
||||||
|
parameterName: string
|
||||||
|
resolutionPath: string
|
||||||
|
resolutionPathType: string
|
||||||
|
}
|
||||||
|
const props = defineProps({
|
||||||
|
page: {
|
||||||
|
type: Object as PropType<PageSetting>,
|
||||||
|
default: () => ({
|
||||||
|
pageType: '',
|
||||||
|
requestData: [],
|
||||||
|
responseData: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { page } = toRefs(props)
|
||||||
|
const { t } = useI18n()
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: 'pageNumber',
|
||||||
|
label: t('api_pagination.number__size')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'cursor',
|
||||||
|
label: t('api_pagination.cursor__size')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'empty',
|
||||||
|
label: t('chart.line_symbol_none')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const requestData = ref([
|
||||||
|
{
|
||||||
|
parameterName: t('api_pagination.page_number'),
|
||||||
|
builtInParameterName: '${pageToken}',
|
||||||
|
requestParameterName: '',
|
||||||
|
parameterDefaultValue: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parameterName: t('api_pagination.pagination_size'),
|
||||||
|
builtInParameterName: '${pageSize}',
|
||||||
|
requestParameterName: '',
|
||||||
|
parameterDefaultValue: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const defaultPathArr = [
|
||||||
|
{
|
||||||
|
value: 'totalNumber',
|
||||||
|
label: t('api_pagination.total_number_de')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'totalPage',
|
||||||
|
label: t('api_pagination.number_of_pages')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const cursorPathArr = [
|
||||||
|
{
|
||||||
|
value: 'cursor',
|
||||||
|
label: t('api_pagination.cursor')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const resolutionPathOptions = ref(cloneDeep(defaultPathArr))
|
||||||
|
|
||||||
|
const responseData = ref([
|
||||||
|
{
|
||||||
|
parameterName: t('api_pagination.total_number'),
|
||||||
|
resolutionPath: '',
|
||||||
|
resolutionPathType: 'number'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
if (!page.value.requestData || page.value.requestData.length === 0) {
|
||||||
|
page.value.requestData = requestData.value
|
||||||
|
}
|
||||||
|
if (!page.value.responseData || page.value.responseData.length === 0) {
|
||||||
|
page.value.responseData = responseData.value
|
||||||
|
}
|
||||||
|
if (page.value.pageType === '' || !page.value.pageType) {
|
||||||
|
page.value.pageType = 'empty'
|
||||||
|
}
|
||||||
|
handleNumberSizeChange()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleNumberSizeChange = () => {
|
||||||
|
if (page.value.pageType === 'pageNumber') {
|
||||||
|
page.value.responseData[0].resolutionPathType = 'totalNumber'
|
||||||
|
page.value.responseData[0].parameterName = t('api_pagination.total_number')
|
||||||
|
resolutionPathOptions.value = cloneDeep(defaultPathArr)
|
||||||
|
page.value.requestData[0].parameterName = t('api_pagination.page_number')
|
||||||
|
page.value.requestData[0].builtInParameterName = '${pageNumber}'
|
||||||
|
}
|
||||||
|
if (page.value.pageType === 'cursor') {
|
||||||
|
page.value.responseData[0].resolutionPathType = 'cursor'
|
||||||
|
page.value.responseData[0].parameterName = t('api_pagination.cursor')
|
||||||
|
resolutionPathOptions.value = cloneDeep(cursorPathArr)
|
||||||
|
page.value.requestData[0].parameterName = t('api_pagination.cursor')
|
||||||
|
page.value.requestData[0].builtInParameterName = '${pageToken}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="api-pagination">
|
||||||
|
<span class="type">{{ t('api_pagination.pagination_method') }}</span>
|
||||||
|
<el-select
|
||||||
|
v-model="page.pageType"
|
||||||
|
@change="handleNumberSizeChange"
|
||||||
|
style="width: 100%; margin-top: 8px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<template v-if="page.pageType !== 'empty'">
|
||||||
|
<div class="table-title request">{{ t('datasource.request') }}</div>
|
||||||
|
<el-table header-cell-class-name="header-cell" :data="page.requestData" style="width: 100%">
|
||||||
|
<el-table-column prop="parameterName" :label="t('api_pagination.parameter_name')" />
|
||||||
|
<el-table-column
|
||||||
|
prop="builtInParameterName"
|
||||||
|
:label="t('api_pagination.built_in_parameter_name')"
|
||||||
|
/>
|
||||||
|
<el-table-column :label="t('api_pagination.parameter_default_value')" width="220">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input
|
||||||
|
v-model="scope.row.parameterDefaultValue"
|
||||||
|
style="width: 100%"
|
||||||
|
:placeholder="
|
||||||
|
scope.row.builtInParameterName === '${pageNumber}'
|
||||||
|
? t('api_pagination.enter_first_page')
|
||||||
|
: t('api_pagination.enter_default_value')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="table-title response">{{ t('api_pagination.response') }}</div>
|
||||||
|
<el-table header-cell-class-name="header-cell" :data="page.responseData" style="width: 100%">
|
||||||
|
<el-table-column
|
||||||
|
prop="parameterName"
|
||||||
|
:label="t('api_pagination.parameter_name')"
|
||||||
|
width="160"
|
||||||
|
/>
|
||||||
|
<el-table-column prop="resolutionPath" :label="t('api_pagination.parsing_path')">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input
|
||||||
|
v-model="scope.row.resolutionPath"
|
||||||
|
style="width: 100%"
|
||||||
|
:placeholder="t('api_pagination.please_enter_jsonpath')"
|
||||||
|
><template #prepend>
|
||||||
|
<el-select
|
||||||
|
class="bg-white"
|
||||||
|
v-model="scope.row.resolutionPathType"
|
||||||
|
style="width: 89px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in resolutionPathOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select> </template
|
||||||
|
></el-input>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.api-pagination {
|
||||||
|
.type {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-title {
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
padding-left: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.request {
|
||||||
|
background: #ebf1ff;
|
||||||
|
margin-top: 16px;
|
||||||
|
border-top: 1px solid #dddedf;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.response {
|
||||||
|
background: #e6f7f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-white {
|
||||||
|
:deep(.ed-input__wrapper) {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,15 @@
|
|||||||
|
import ace from 'ace-builds'
|
||||||
|
import themeChromeUrl from 'ace-builds/src-noconflict/theme-chrome?url'
|
||||||
|
ace.config.setModuleUrl('ace/theme/chrome', themeChromeUrl)
|
||||||
|
|
||||||
|
import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url'
|
||||||
|
ace.config.setModuleUrl('ace/mode/json', modeJsonUrl)
|
||||||
|
|
||||||
|
import modeXmlUrl from 'ace-builds/src-noconflict/mode-xml?url'
|
||||||
|
ace.config.setModuleUrl('ace/mode/xml', modeXmlUrl)
|
||||||
|
|
||||||
|
import modeTextUrl from 'ace-builds/src-noconflict/mode-text?url'
|
||||||
|
ace.config.setModuleUrl('ace/mode/text', modeTextUrl)
|
||||||
|
|
||||||
|
import 'ace-builds/src-noconflict/ext-language_tools'
|
||||||
|
ace.require('ace/ext/language_tools')
|
@ -0,0 +1,184 @@
|
|||||||
|
import { isString, isObject, isNumber, isNull, isInteger, isEmpty, isBoolean } from 'lodash-es'
|
||||||
|
const isArray = Array.isArray
|
||||||
|
|
||||||
|
class Convert {
|
||||||
|
constructor() {
|
||||||
|
this._option = {
|
||||||
|
$id: 'http://example.com/root.json',
|
||||||
|
$schema: 'http://json-schema.org/draft-07/schema#'
|
||||||
|
}
|
||||||
|
this._object = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换函数
|
||||||
|
* @param {*} object 需要转换的对象
|
||||||
|
* @param {*} ?option 可选参数,目前只有能设置 root 节点的 $id 和 $schema
|
||||||
|
*/
|
||||||
|
format(object, option = {}) {
|
||||||
|
// 数据校验,确保传入的的object只能是对象或数组
|
||||||
|
if (!isObject(object)) {
|
||||||
|
throw new TypeError('传入参数只能是对象或数组')
|
||||||
|
}
|
||||||
|
// 合并属性
|
||||||
|
this._option = Object.assign(this._option, option)
|
||||||
|
// 需要转换的对象
|
||||||
|
this._object = object
|
||||||
|
let convertRes
|
||||||
|
// 数组类型和对象类型结构不一样
|
||||||
|
if (isArray(object)) {
|
||||||
|
convertRes = this._arrayToSchema()
|
||||||
|
} else {
|
||||||
|
convertRes = this._objectToSchema()
|
||||||
|
}
|
||||||
|
// 释放
|
||||||
|
this._object = null
|
||||||
|
return convertRes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数组类型转换成JSONSCHEMA
|
||||||
|
*/
|
||||||
|
_arrayToSchema() {
|
||||||
|
// root节点基本信息
|
||||||
|
const result = this._value2object(this._object, this._option.$id, '', true)
|
||||||
|
if (this._object.length > 0) {
|
||||||
|
const itemArr = []
|
||||||
|
for (let index = 0; index < this._object.length; index++) {
|
||||||
|
// 创建items对象的基本信息
|
||||||
|
const objectItem = this._object[index]
|
||||||
|
let item = this._value2object(objectItem, `#/items`, 'items')
|
||||||
|
if (isObject(objectItem) && !isEmpty(objectItem)) {
|
||||||
|
// 递归遍历
|
||||||
|
const objectItemSchema = this._json2schema(objectItem, `#/items`)
|
||||||
|
// 合并对象
|
||||||
|
item = Object.assign(item, objectItemSchema)
|
||||||
|
}
|
||||||
|
itemArr.push(item)
|
||||||
|
}
|
||||||
|
result['items'] = itemArr
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对象类型转换成JSONSCHEMA
|
||||||
|
*/
|
||||||
|
_objectToSchema() {
|
||||||
|
let baseResult = this._value2object(this._object, this._option.$id, '', true)
|
||||||
|
const objectSchema = this._json2schema(this._object)
|
||||||
|
baseResult = Object.assign(baseResult, objectSchema)
|
||||||
|
return baseResult
|
||||||
|
}
|
||||||
|
|
||||||
|
_json2schema(object, name = '') {
|
||||||
|
// 如果递归值不是对象,那么return掉
|
||||||
|
if (!isObject(object)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 处理当前路径$id
|
||||||
|
if (name === '' || name === undefined) {
|
||||||
|
name = '#'
|
||||||
|
}
|
||||||
|
const result = {}
|
||||||
|
// 判断传入object是对象还是数组。
|
||||||
|
if (isArray(object)) {
|
||||||
|
result.items = {}
|
||||||
|
} else {
|
||||||
|
result.properties = {}
|
||||||
|
}
|
||||||
|
// 遍历传入的对象
|
||||||
|
for (const key in object) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||||
|
const element = object[key]
|
||||||
|
// 如果只是undefined。跳过
|
||||||
|
if (element === undefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const $id = `${name}/properties/${key}`
|
||||||
|
// 判断当前 element 的值 是否也是对象,如果是就继续递归,不是就赋值给result
|
||||||
|
if (!result['properties']) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (isObject(element)) {
|
||||||
|
// 创建当前属性的基本信息
|
||||||
|
result['properties'][key] = this._value2object(element, $id, key)
|
||||||
|
if (isArray(element)) {
|
||||||
|
// 针对空数组和有值的数组做不同处理
|
||||||
|
if (element.length > 0) {
|
||||||
|
// 是数组
|
||||||
|
const itemArr = []
|
||||||
|
for (let index = 0; index < element.length; index++) {
|
||||||
|
const elementItem = element[index]
|
||||||
|
// 创建items对象的基本信息
|
||||||
|
let item = this._value2object(elementItem, `${$id}/items`, key + 'items')
|
||||||
|
// 判断第一项是否是对象,且对象属性不为空
|
||||||
|
if (isObject(elementItem) && !isEmpty(elementItem)) {
|
||||||
|
// 新增的properties才合并进来
|
||||||
|
item = Object.assign(item, this._json2schema(elementItem, `${$id}/items`))
|
||||||
|
}
|
||||||
|
itemArr.push(item)
|
||||||
|
}
|
||||||
|
result['properties'][key]['items'] = itemArr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 不是数组,递归遍历获取,然后合并对象属性
|
||||||
|
result['properties'][key] = Object.assign(
|
||||||
|
result['properties'][key],
|
||||||
|
this._json2schema(element, $id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 一般属性直接获取基本信息
|
||||||
|
if (result['properties']) {
|
||||||
|
result['properties'][key] = this._value2object(element, $id, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把json的值转换成对象类型
|
||||||
|
* @param {*} value
|
||||||
|
* @param {*} $id
|
||||||
|
* @param {*} key
|
||||||
|
*/
|
||||||
|
_value2object(value, $id, key = '', root = false) {
|
||||||
|
const objectTemplate = {
|
||||||
|
$id: $id,
|
||||||
|
title: `The ${key} Schema`,
|
||||||
|
mock: {
|
||||||
|
mock: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为初始化root数据
|
||||||
|
if (root) {
|
||||||
|
objectTemplate['$schema'] = this._option.$schema
|
||||||
|
objectTemplate['title'] = `The Root Schema`
|
||||||
|
objectTemplate['mock'] = undefined
|
||||||
|
}
|
||||||
|
if (isBoolean(value)) {
|
||||||
|
objectTemplate.type = 'boolean'
|
||||||
|
} else if (isInteger(value)) {
|
||||||
|
objectTemplate.type = 'integer'
|
||||||
|
} else if (isNumber(value)) {
|
||||||
|
objectTemplate.type = 'number'
|
||||||
|
} else if (isString(value)) {
|
||||||
|
objectTemplate.type = 'string'
|
||||||
|
} else if (isNull(value)) {
|
||||||
|
objectTemplate.type = 'null'
|
||||||
|
} else if (isArray(value)) {
|
||||||
|
objectTemplate.type = 'array'
|
||||||
|
objectTemplate['mock'] = undefined
|
||||||
|
} else if (isObject(value)) {
|
||||||
|
objectTemplate.type = 'object'
|
||||||
|
objectTemplate['mock'] = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectTemplate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default Convert
|
@ -0,0 +1,185 @@
|
|||||||
|
export function formatJson(json) {
|
||||||
|
let i = 0
|
||||||
|
let il = 0
|
||||||
|
const tab = ' '
|
||||||
|
let newJson = ''
|
||||||
|
let indentLevel = 0
|
||||||
|
let inString = false
|
||||||
|
let currentChar = null
|
||||||
|
let flag = false
|
||||||
|
for (i = 0, il = json.length; i < il; i += 1) {
|
||||||
|
currentChar = json.charAt(i)
|
||||||
|
switch (currentChar) {
|
||||||
|
case '{':
|
||||||
|
if (i !== 0 && json.charAt(i - 1) === '$') {
|
||||||
|
newJson += currentChar
|
||||||
|
flag = true
|
||||||
|
} else if (!inString) {
|
||||||
|
newJson += currentChar + '\n' + repeat(tab, indentLevel + 1)
|
||||||
|
indentLevel += 1
|
||||||
|
} else {
|
||||||
|
newJson += currentChar
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case '[':
|
||||||
|
if (!inString) {
|
||||||
|
newJson += currentChar + '\n' + repeat(tab, indentLevel + 1)
|
||||||
|
indentLevel += 1
|
||||||
|
} else {
|
||||||
|
newJson += currentChar
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case '}':
|
||||||
|
if (flag) {
|
||||||
|
newJson += currentChar
|
||||||
|
flag = false
|
||||||
|
} else if (!inString) {
|
||||||
|
indentLevel -= 1
|
||||||
|
newJson += '\n' + repeat(tab, indentLevel) + currentChar
|
||||||
|
} else {
|
||||||
|
newJson += currentChar
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case ']':
|
||||||
|
if (!inString) {
|
||||||
|
indentLevel -= 1
|
||||||
|
newJson += '\n' + repeat(tab, indentLevel) + currentChar
|
||||||
|
} else {
|
||||||
|
newJson += currentChar
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case ',':
|
||||||
|
if (!inString) {
|
||||||
|
newJson += ',\n' + repeat(tab, indentLevel)
|
||||||
|
} else {
|
||||||
|
newJson += currentChar
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case ':':
|
||||||
|
if (!inString) {
|
||||||
|
newJson += ': '
|
||||||
|
} else {
|
||||||
|
newJson += currentChar
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case ' ':
|
||||||
|
case '\n':
|
||||||
|
case '\t':
|
||||||
|
if (inString) {
|
||||||
|
newJson += currentChar
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case '"':
|
||||||
|
if (i > 0 && json.charAt(i - 1) !== '\\') {
|
||||||
|
inString = !inString
|
||||||
|
}
|
||||||
|
newJson += currentChar
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
newJson += currentChar
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newJson
|
||||||
|
}
|
||||||
|
|
||||||
|
function repeat(s, count) {
|
||||||
|
return new Array(count + 1).join(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatXml(text) {
|
||||||
|
// 去掉多余的空格
|
||||||
|
text =
|
||||||
|
'\n' +
|
||||||
|
text.replace(/(<\w+)(\s.*?>)/g, function ($0, name, props) {
|
||||||
|
return name + ' ' + props.replace(/\s+(\w+=)/g, ' $1')
|
||||||
|
})
|
||||||
|
// 把注释编码
|
||||||
|
text = text.replace(/<!--(.+?)-->/g, function ($0, text) {
|
||||||
|
var ret = '<!--' + escape(text) + '-->'
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
// 调整格式
|
||||||
|
var rgx = /\n(<(([^\?]).+?)(?:\s|\s*?>|\s*?(\/)>)(?:.*?(?:(?:(\/)>)|(?:<(\/)\2>)))?)/gm
|
||||||
|
var nodeStack = []
|
||||||
|
var output = text.replace(
|
||||||
|
rgx,
|
||||||
|
function ($0, all, name, isBegin, isCloseFull1, isCloseFull2, isFull1, isFull2) {
|
||||||
|
var isClosed =
|
||||||
|
isCloseFull1 === '/' || isCloseFull2 === '/' || isFull1 === '/' || isFull2 === '/'
|
||||||
|
var prefix = ''
|
||||||
|
if (isBegin === '!') {
|
||||||
|
prefix = getPrefix(nodeStack.length)
|
||||||
|
} else {
|
||||||
|
if (isBegin !== '/') {
|
||||||
|
prefix = getPrefix(nodeStack.length)
|
||||||
|
if (!isClosed) {
|
||||||
|
nodeStack.push(name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodeStack.pop()
|
||||||
|
prefix = getPrefix(nodeStack.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var ret = '\n' + prefix + all
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
)
|
||||||
|
var outputText = output.substring(1)
|
||||||
|
// 把注释还原并解码,调格式
|
||||||
|
outputText = outputText.replace(/(\s*)<!--(.+?)-->/g, function ($0, prefix, text) {
|
||||||
|
if (prefix.charAt(0) === '\r') {
|
||||||
|
prefix = prefix.substring(1)
|
||||||
|
}
|
||||||
|
text = unescape(text).replace(/\r/g, '\n')
|
||||||
|
var ret = '\n' + prefix + '<!--' + text.replace(/^\s*/gm, prefix) + '-->'
|
||||||
|
return ret
|
||||||
|
})
|
||||||
|
return outputText.replace(/\s+$/g, '').replace(/\r/g, '\r\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param time 时间
|
||||||
|
* @param cFormat 格式
|
||||||
|
* @returns {string|null} 字符串
|
||||||
|
* @example formatTime('2018-1-29', '{y}/{m}/{d} {h}:{i}:{s}') // -> 2018/01/29 00:00:00
|
||||||
|
*/
|
||||||
|
export function formatTime(time, cFormat) {
|
||||||
|
if (arguments.length === 0) return null
|
||||||
|
if ((time + '').length === 10) {
|
||||||
|
time = +time * 1000
|
||||||
|
}
|
||||||
|
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||||
|
let date
|
||||||
|
if (typeof time === 'object') {
|
||||||
|
date = time
|
||||||
|
} else {
|
||||||
|
date = new Date(time)
|
||||||
|
}
|
||||||
|
const formatObj = {
|
||||||
|
y: date.getFullYear(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
d: date.getDate(),
|
||||||
|
h: date.getHours(),
|
||||||
|
i: date.getMinutes(),
|
||||||
|
s: date.getSeconds(),
|
||||||
|
a: date.getDay()
|
||||||
|
}
|
||||||
|
return format.replace(/{([ymdhisa])+}/g, (result, key) => {
|
||||||
|
let value = formatObj[key]
|
||||||
|
if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
|
||||||
|
if (result.length > 0 && value < 10) {
|
||||||
|
value = '0' + value
|
||||||
|
}
|
||||||
|
return value || 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrefix(prefixIndex) {
|
||||||
|
var span = ' '
|
||||||
|
var output = []
|
||||||
|
for (var i = 0; i < prefixIndex; ++i) {
|
||||||
|
output.push(span)
|
||||||
|
}
|
||||||
|
return output.join('')
|
||||||
|
}
|
@ -0,0 +1,197 @@
|
|||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
export const dsTypes = [
|
||||||
|
{
|
||||||
|
type: 'db2',
|
||||||
|
name: 'Db2',
|
||||||
|
catalog: 'OLTP',
|
||||||
|
extraParams: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'mysql',
|
||||||
|
name: 'MySQL',
|
||||||
|
catalog: 'OLTP',
|
||||||
|
extraParams:
|
||||||
|
'characterEncoding=UTF-8&connectTimeout=5000&useSSL=false&allowPublicKeyRetrieval=true'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'TiDB',
|
||||||
|
name: 'TiDB',
|
||||||
|
catalog: 'OLTP',
|
||||||
|
extraParams:
|
||||||
|
'characterEncoding=UTF-8&connectTimeout=5000&useSSL=false&allowPublicKeyRetrieval=true'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'impala',
|
||||||
|
name: 'Apache Impala',
|
||||||
|
catalog: 'OLAP',
|
||||||
|
extraParams: 'AuthMech=0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'mariadb',
|
||||||
|
name: 'MariaDB',
|
||||||
|
catalog: 'OLTP',
|
||||||
|
extraParams:
|
||||||
|
'characterEncoding=UTF-8&connectTimeout=5000&useSSL=false&allowPublicKeyRetrieval=true'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'doris',
|
||||||
|
name: 'Apache Doris',
|
||||||
|
catalog: 'OLAP',
|
||||||
|
extraParams:
|
||||||
|
'characterEncoding=UTF-8&connectTimeout=5000&useSSL=false&allowPublicKeyRetrieval=true'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'es',
|
||||||
|
name: 'Elasticsearch',
|
||||||
|
catalog: 'OLAP',
|
||||||
|
extraParams: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'StarRocks',
|
||||||
|
name: 'StarRocks',
|
||||||
|
catalog: 'OLAP',
|
||||||
|
extraParams:
|
||||||
|
'characterEncoding=UTF-8&connectTimeout=5000&useSSL=false&allowPublicKeyRetrieval=true'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'pg',
|
||||||
|
name: 'PostgreSQL',
|
||||||
|
catalog: 'OLTP',
|
||||||
|
extraParams: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'sqlServer',
|
||||||
|
name: 'SQL Server',
|
||||||
|
catalog: 'OLTP',
|
||||||
|
extraParams: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'oracle',
|
||||||
|
name: 'Oracle',
|
||||||
|
catalog: 'OLTP',
|
||||||
|
extraParams: '',
|
||||||
|
charset: [
|
||||||
|
'Default',
|
||||||
|
'GBK',
|
||||||
|
'BIG5',
|
||||||
|
'ISO-8859-1',
|
||||||
|
'UTF-8',
|
||||||
|
'UTF-16',
|
||||||
|
'CP850',
|
||||||
|
'EUC_JP',
|
||||||
|
'EUC_KR'
|
||||||
|
],
|
||||||
|
targetCharset: ['Default', 'GBK', 'UTF-8']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'mongo',
|
||||||
|
name: 'Mongodb-BI',
|
||||||
|
catalog: 'OLTP',
|
||||||
|
extraParams: 'rebuildschema=true&authSource=admin'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ck',
|
||||||
|
name: 'ClickHouse',
|
||||||
|
catalog: 'OLAP',
|
||||||
|
extraParams: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'redshift',
|
||||||
|
name: 'AWS Redshift',
|
||||||
|
catalog: 'DL',
|
||||||
|
extraParams: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'API',
|
||||||
|
name: 'API',
|
||||||
|
catalog: 'OTHER',
|
||||||
|
extraParams: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Excel',
|
||||||
|
name: 'Excel',
|
||||||
|
catalog: 'LOCAL',
|
||||||
|
extraParams: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const typeList = ['OLTP', 'OLAP', 'DL', 'OTHER', 'LOCAL']
|
||||||
|
export const nameMap = {
|
||||||
|
OLTP: 'OLTP',
|
||||||
|
OLAP: 'OLAP',
|
||||||
|
DL: t('datasource.dl'),
|
||||||
|
OTHER: t('data_source.api_data'),
|
||||||
|
LOCAL: t('datasource.local_file')
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Configuration {
|
||||||
|
dataBase: string
|
||||||
|
jdbcUrl: string
|
||||||
|
urlType: string
|
||||||
|
connectionType: string
|
||||||
|
schema: string
|
||||||
|
extraParams: string
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
host: string
|
||||||
|
authMethod: string
|
||||||
|
port: string
|
||||||
|
initialPoolSize: string
|
||||||
|
minPoolSize: string
|
||||||
|
maxPoolSize: string
|
||||||
|
queryTimeout: string
|
||||||
|
useSSH: boolean
|
||||||
|
sshHost: string
|
||||||
|
sshPort: string
|
||||||
|
sshUserName: string
|
||||||
|
sshType: string
|
||||||
|
sshPassword: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiConfiguration {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
deTableName: string
|
||||||
|
method: string
|
||||||
|
copy: boolean
|
||||||
|
url: string
|
||||||
|
status: string
|
||||||
|
useJsonPath: boolean
|
||||||
|
serialNumber: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncSetting {
|
||||||
|
id: string
|
||||||
|
updateType: string
|
||||||
|
syncRate: string
|
||||||
|
simpleCronValue: number
|
||||||
|
simpleCronType: string
|
||||||
|
startTime: number
|
||||||
|
endTime: number
|
||||||
|
endLimit: string
|
||||||
|
cron: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Node {
|
||||||
|
name: string
|
||||||
|
createBy: string
|
||||||
|
creator: string
|
||||||
|
copy: boolean
|
||||||
|
createTime: string
|
||||||
|
id: number | string
|
||||||
|
size: number
|
||||||
|
description: string
|
||||||
|
type: string
|
||||||
|
nodeType: string
|
||||||
|
fileName: string
|
||||||
|
syncSetting?: SyncSetting
|
||||||
|
editType?: number
|
||||||
|
configuration?: Configuration
|
||||||
|
apiConfiguration?: ApiConfiguration[]
|
||||||
|
paramsConfiguration?: ApiConfiguration[]
|
||||||
|
weight?: number
|
||||||
|
lastSyncTime?: number | string
|
||||||
|
}
|
104
core/core-frontend/src/viewsnew/application/service/header.vue
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import {ref, onMounted,watch } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
const props = defineProps({
|
||||||
|
projectInfo: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const projectInfo:any=ref({
|
||||||
|
|
||||||
|
})
|
||||||
|
watch(() => props.projectInfo, val => {
|
||||||
|
projectInfo.value = props.projectInfo
|
||||||
|
})
|
||||||
|
onMounted(()=>{
|
||||||
|
projectInfo.value = props.projectInfo
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="project-header-box">
|
||||||
|
<div class="project-header-left">
|
||||||
|
<div class="return-box" @click="$router.go(-1)">
|
||||||
|
<img src="@/assets/newimg/u594.png" alt="">
|
||||||
|
</div>
|
||||||
|
<img src="@/assets/newimg/logosmall.png" alt="" style="margin-left: 10px;">
|
||||||
|
<div class="header-title">{{projectInfo.name }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.project-header-box{
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
background-color: rgba(37, 38, 38, 1);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid rgba(79, 80, 82, 1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 10px;
|
||||||
|
.project-header-left{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.project-header-right{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.return-box{
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.return-box:hover{
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.header-title{
|
||||||
|
font-family: 'Arial Negreta', 'Arial Normal', 'Arial';
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #F2F4F5;
|
||||||
|
padding-left: 14px;
|
||||||
|
}
|
||||||
|
.preview-button{
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
background-color: rgba(54, 55, 56, 1);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Arial Normal', 'Arial';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #F2F4F5;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.design-button{
|
||||||
|
width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
background-color: rgba(0, 137, 255, 1);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Arial Normal', 'Arial';
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #F2F4F5;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,56 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { findComponentAttr } from '@/utils/components'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
|
import ViewEditor from '@/views/chart/components/editor/index.vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
|
const { curComponent, batchOptStatus } = storeToRefs(dvMainStore)
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
canvasViewInfoMobile: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const otherEditorShow = computed(() => {
|
||||||
|
return Boolean(
|
||||||
|
curComponent.value &&
|
||||||
|
(!['UserView', 'VQuery'].includes(curComponent.value?.component) ||
|
||||||
|
(curComponent.value?.component === 'UserView' &&
|
||||||
|
curComponent.value?.innerType === 'picture-group')) &&
|
||||||
|
!batchOptStatus.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const viewEditorShow = computed(() => {
|
||||||
|
return Boolean(
|
||||||
|
curComponent.value &&
|
||||||
|
['UserView', 'VQuery'].includes(curComponent.value.component) &&
|
||||||
|
curComponent.value.innerType !== 'picture-group' &&
|
||||||
|
!batchOptStatus.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mobile_content">
|
||||||
|
<template v-if="otherEditorShow">
|
||||||
|
<component :is="findComponentAttr(curComponent)" :themes="'light'" />
|
||||||
|
</template>
|
||||||
|
<template v-if="viewEditorShow">
|
||||||
|
<view-editor
|
||||||
|
:themes="'light'"
|
||||||
|
:view="canvasViewInfoMobile[curComponent ? curComponent.id : 'default']"
|
||||||
|
></view-editor>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.mobile_content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
430
core/core-frontend/src/viewsnew/common/DeAppApply.vue
Normal file
@ -0,0 +1,430 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
:title="t('visualization.save_app')"
|
||||||
|
v-model="state.appApplyDrawer"
|
||||||
|
custom-class="de-app-drawer"
|
||||||
|
:show-close="false"
|
||||||
|
size="500px"
|
||||||
|
direction="rtl"
|
||||||
|
z-index="1000"
|
||||||
|
>
|
||||||
|
<div class="app-export">
|
||||||
|
<el-form
|
||||||
|
ref="appSaveForm"
|
||||||
|
:model="state.form"
|
||||||
|
:rules="state.rule"
|
||||||
|
class="de-form-item app-form"
|
||||||
|
size="middle"
|
||||||
|
label-width="180px"
|
||||||
|
label-position="top"
|
||||||
|
>
|
||||||
|
<div class="de-row-rules" style="margin: 0 0 16px">
|
||||||
|
<span>{{ t('visualization.base_info') }}</span>
|
||||||
|
</div>
|
||||||
|
<el-form-item :label="dvPreName + t('visualization.name')" prop="name">
|
||||||
|
<el-input
|
||||||
|
v-model="state.form.name"
|
||||||
|
autocomplete="off"
|
||||||
|
:placeholder="t('visualization.input_tips')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="dvPreName + t('visualization.position')" prop="pid">
|
||||||
|
<el-tree-select
|
||||||
|
style="width: 100%"
|
||||||
|
@keydown.stop
|
||||||
|
@keyup.stop
|
||||||
|
v-model="state.form.pid"
|
||||||
|
:data="state.dvTree"
|
||||||
|
:props="state.propsTree"
|
||||||
|
@node-click="dvTreeSelect"
|
||||||
|
:render-after-expand="false"
|
||||||
|
filterable
|
||||||
|
>
|
||||||
|
<template #default="{ data: { name } }">
|
||||||
|
<span class="custom-tree-node">
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span :title="name">{{ name }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('visualization.ds_group_name')" prop="datasetFolderName">
|
||||||
|
<el-input
|
||||||
|
v-model="state.form.datasetFolderName"
|
||||||
|
autocomplete="off"
|
||||||
|
:placeholder="t('visualization.input_tips')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('visualization.ds_group_position')" prop="datasetFolderPid">
|
||||||
|
<el-tree-select
|
||||||
|
style="width: 100%"
|
||||||
|
@keydown.stop
|
||||||
|
@keyup.stop
|
||||||
|
v-model="state.form.datasetFolderPid"
|
||||||
|
:data="state.dsTree"
|
||||||
|
:props="state.propsTree"
|
||||||
|
@node-click="dsTreeSelect"
|
||||||
|
:filter-method="dsTreeFilterMethod"
|
||||||
|
:render-after-expand="false"
|
||||||
|
filterable
|
||||||
|
>
|
||||||
|
<template #default="{ data: { name } }">
|
||||||
|
<span class="custom-tree-node">
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span :title="name">{{ name }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree-select>
|
||||||
|
</el-form-item>
|
||||||
|
<div class="de-row-rules" style="margin: 0 0 16px">
|
||||||
|
<span>{{ t('visualization.datasource_info') }}</span>
|
||||||
|
</div>
|
||||||
|
<el-row class="datasource-link">
|
||||||
|
<el-row class="head">
|
||||||
|
<el-col :span="11">{{ t('visualization.app_datasource') }}</el-col
|
||||||
|
><el-col :span="2"></el-col
|
||||||
|
><el-col :span="11">{{ t('visualization.sys_datasource') }}</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row
|
||||||
|
:key="index"
|
||||||
|
class="content"
|
||||||
|
v-for="(appDatasource, index) in state.appData.datasourceInfo"
|
||||||
|
>
|
||||||
|
<el-col :span="11">
|
||||||
|
<el-select style="width: 100%" v-model="appDatasource.name" disabled>
|
||||||
|
<el-option
|
||||||
|
:key="appDatasource.name"
|
||||||
|
:label="appDatasource.name"
|
||||||
|
:value="appDatasource.name"
|
||||||
|
>
|
||||||
|
</el-option>
|
||||||
|
</el-select> </el-col
|
||||||
|
><el-col :span="2" class="icon-center">
|
||||||
|
<Icon name="dv-link-target"
|
||||||
|
><dvLinkTarget class="svg-icon" style="width: 20px; height: 20px" /></Icon></el-col
|
||||||
|
><el-col :span="11">
|
||||||
|
<dataset-select
|
||||||
|
ref="datasetSelector"
|
||||||
|
v-model="appDatasource.systemDatasourceId"
|
||||||
|
style="flex: 1"
|
||||||
|
:state-obj="state"
|
||||||
|
themes="light"
|
||||||
|
source-type="datasource"
|
||||||
|
@add-ds-window="addDsWindow"
|
||||||
|
view-id="0"
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="apply" style="width: 100%">
|
||||||
|
<el-button v-if="isDesktop() || openType === '_self'" @click="goBack">{{
|
||||||
|
t('visualization.back')
|
||||||
|
}}</el-button>
|
||||||
|
<el-button type="primary" @click="saveApp">{{ t('visualization.save') }}</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import dvFolder from '@/assets/svg/dv-folder.svg'
|
||||||
|
import dvLinkTarget from '@/assets/svg/dv-link-target.svg'
|
||||||
|
import {
|
||||||
|
ElButton,
|
||||||
|
ElDrawer,
|
||||||
|
ElForm,
|
||||||
|
ElFormItem,
|
||||||
|
ElInput,
|
||||||
|
ElMessage,
|
||||||
|
ElTreeSelect
|
||||||
|
} from 'element-plus-secondary'
|
||||||
|
import { computed, PropType, reactive, ref, toRefs } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { queryTreeApi } from '@/api/visualization/dataVisualization'
|
||||||
|
import { BusiTreeNode, BusiTreeRequest } from '@/models/tree/TreeNode'
|
||||||
|
import { getDatasetTree } from '@/api/dataset'
|
||||||
|
import DatasetSelect from '@/views/chart/components/editor/dataset-select/DatasetSelect.vue'
|
||||||
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { deepCopy } from '@/utils/utils'
|
||||||
|
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { isDesktop } from '@/utils/ModelUtil'
|
||||||
|
import { filterFreeFolder } from '@/utils/utils'
|
||||||
|
const desktop = isDesktop()
|
||||||
|
|
||||||
|
const { wsCache } = useCache('localStorage')
|
||||||
|
const { t } = useI18n()
|
||||||
|
const emits = defineEmits(['closeDraw', 'saveAppCanvas'])
|
||||||
|
const appSaveForm = ref(null)
|
||||||
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
|
const { dvInfo, appData } = storeToRefs(dvMainStore)
|
||||||
|
const snapshotStore = snapshotStoreWithOut()
|
||||||
|
const props = defineProps({
|
||||||
|
componentData: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
canvasViewInfo: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
curCanvasType: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
themes: {
|
||||||
|
type: String as PropType<EditorTheme>,
|
||||||
|
default: 'dark'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { componentData, canvasViewInfo, curCanvasType, themes } = toRefs(props)
|
||||||
|
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
|
||||||
|
|
||||||
|
const dvPreName = computed(() =>
|
||||||
|
curCanvasType.value === 'dashboard'
|
||||||
|
? t('work_branch.dashboard')
|
||||||
|
: t('work_branch.big_data_screen')
|
||||||
|
)
|
||||||
|
const addDsWindow = () => {
|
||||||
|
// do addDsWindow
|
||||||
|
const url = '#/data/datasource?opt=create'
|
||||||
|
window.open(url, openType)
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
appApplyDrawer: false,
|
||||||
|
dvTree: [],
|
||||||
|
dsTree: [],
|
||||||
|
propsTree: {
|
||||||
|
label: 'name',
|
||||||
|
children: 'children',
|
||||||
|
isLeaf: node => !node.children?.length
|
||||||
|
},
|
||||||
|
appData: {
|
||||||
|
datasourceInfo: []
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
pid: '',
|
||||||
|
name: t('visualization.new'),
|
||||||
|
datasetFolderPid: null,
|
||||||
|
datasetFolderName: null
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
min: 2,
|
||||||
|
max: 25,
|
||||||
|
message: t('datasource.input_limit_2_25', [2, 25]),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pid: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('visualization.select_folder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
datasetFolderName: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
min: 2,
|
||||||
|
max: 25,
|
||||||
|
message: t('datasource.input_limit_2_25', [2, 25]),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
datasetFolderPid: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('visualization.select_ds_group_folder'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
window.history.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
const request = { busiFlag: curCanvasType.value, leaf: false, weight: 7 }
|
||||||
|
queryTreeApi(request).then(res => {
|
||||||
|
filterFreeFolder(res, curCanvasType.value)
|
||||||
|
const resultTree = res || []
|
||||||
|
dfs(resultTree as unknown as BusiTreeNode[])
|
||||||
|
state.dvTree = (resultTree as unknown as BusiTreeNode[]) || []
|
||||||
|
if (state.dvTree.length && state.dvTree[0].name === 'root' && state.dvTree[0].id === '0') {
|
||||||
|
state.dvTree[0].name =
|
||||||
|
curCanvasType.value === 'dataV'
|
||||||
|
? t('work_branch.big_data_screen')
|
||||||
|
: t('work_branch.dashboard')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const requestDs = { leaf: false, weight: 7 } as BusiTreeRequest
|
||||||
|
getDatasetTree(requestDs).then(res => {
|
||||||
|
filterFreeFolder(res, 'dataset')
|
||||||
|
dfs(res as unknown as BusiTreeNode[])
|
||||||
|
state.dsTree = (res as unknown as BusiTreeNode[]) || []
|
||||||
|
if (state.dsTree.length && state.dsTree[0].name === 'root' && state.dsTree[0].id === '0') {
|
||||||
|
state.dsTree[0].name = t('visualization.dataset')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const dfs = (arr: BusiTreeNode[]) => {
|
||||||
|
arr.forEach(ele => {
|
||||||
|
ele['value'] = ele.id
|
||||||
|
if (ele.children?.length) {
|
||||||
|
dfs(ele.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = params => {
|
||||||
|
state.appApplyDrawer = true
|
||||||
|
state.form = params.base
|
||||||
|
state.appData.datasourceInfo = deepCopy(appData.value?.datasourceInfo)
|
||||||
|
initData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const dvTreeSelect = element => {
|
||||||
|
state.form.pid = element.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const dsTreeSelect = element => {
|
||||||
|
state.form.datasetFolderPid = element.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emits('closeDraw')
|
||||||
|
snapshotStore.recordSnapshotCache('renderChart')
|
||||||
|
state.appApplyDrawer = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveApp = () => {
|
||||||
|
let datasourceMatchReady = true
|
||||||
|
state.appData.datasourceInfo.forEach(datasource => {
|
||||||
|
if (!datasource.systemDatasourceId) {
|
||||||
|
datasourceMatchReady = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!datasourceMatchReady) {
|
||||||
|
ElMessage.error(t('visualization.app_no_datasource_tips'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
appSaveForm.value?.validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
// 还原datasource
|
||||||
|
appData.value['datasourceInfo'] = state.appData.datasourceInfo
|
||||||
|
dvInfo.value['pid'] = state.form.pid
|
||||||
|
dvInfo.value['name'] = state.form.name
|
||||||
|
dvInfo.value['datasetFolderPid'] = state.form.datasetFolderPid
|
||||||
|
dvInfo.value['datasetFolderName'] = state.form.datasetFolderName
|
||||||
|
dvInfo.value['dataState'] = 'ready'
|
||||||
|
snapshotStore.recordSnapshotCache('renderChart')
|
||||||
|
emits('saveAppCanvas')
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
init,
|
||||||
|
close
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.app-export {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-export-bottom {
|
||||||
|
width: 100%;
|
||||||
|
height: 56px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ed-drawer__body) {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.de-row-rules {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 22px;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin: 24px 0 16px 0;
|
||||||
|
color: var(--ed-text-color-regular);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
height: 14px;
|
||||||
|
width: 2px;
|
||||||
|
background: #3370ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
span {
|
||||||
|
margin-left: 8.75px;
|
||||||
|
width: 120px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.datasource-link {
|
||||||
|
color: var(--ed-text-color-regular);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
width: 100%;
|
||||||
|
.head {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-center {
|
||||||
|
padding: 0 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.app-form {
|
||||||
|
padding-bottom: 95px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.de-app-drawer {
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
</style>
|
83
core/core-frontend/src/viewsnew/common/DeResourceArrow.vue
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import icon_left_outlined from '@/assets/svg/icon_left_outlined.svg'
|
||||||
|
import icon_right_outlined from '@/assets/svg/icon_right_outlined.svg'
|
||||||
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||||
|
const appStore = useAppStoreWithOut()
|
||||||
|
defineProps({
|
||||||
|
isInside: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emits = defineEmits(['changeSideTreeStatus'])
|
||||||
|
const handleClick = val => {
|
||||||
|
appStore.setArrowSide(val)
|
||||||
|
emits('changeSideTreeStatus', val)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
@click="handleClick(false)"
|
||||||
|
v-if="appStore.getArrowSide && !isInside"
|
||||||
|
class="arrow-side-tree arrow-side-tree-left"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_left_outlined"><icon_left_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
@click="handleClick(true)"
|
||||||
|
v-else-if="!appStore.getArrowSide && isInside"
|
||||||
|
class="arrow-side-tree arrow-side-tree-right"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_right_outlined"><icon_right_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.arrow-side-tree-left {
|
||||||
|
top: 44px;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0px 5px 10px 0px #1f23291a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-side-tree-right {
|
||||||
|
box-shadow: 0px 4px 8px 0px #0000001a;
|
||||||
|
top: 44px;
|
||||||
|
height: 24px;
|
||||||
|
width: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 2px;
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
&:hover {
|
||||||
|
padding-left: 4px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-side-tree {
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid #dee0e3;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
&:hover {
|
||||||
|
.ed-icon {
|
||||||
|
color: var(--ed-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ed-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
260
core/core-frontend/src/viewsnew/common/DeResourceCreateOpt.vue
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
class="create-dialog"
|
||||||
|
:title="t('visualization.new_from_template')"
|
||||||
|
v-model="state.dialogShow"
|
||||||
|
width="700"
|
||||||
|
:before-close="close"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<el-row class="create-main" v-loading="state.loading">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="18" style="height: 40px">
|
||||||
|
<el-radio v-model="state.inputType" label="new_outer_template"
|
||||||
|
>{{ t('visualization.import_template') }}
|
||||||
|
</el-radio>
|
||||||
|
<el-radio v-model="state.inputType" label="new_inner_template" @click="getTree"
|
||||||
|
>{{ t('visualization.copy_template') }}
|
||||||
|
</el-radio>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="state.inputType === 'new_outer_template'" :span="6" class="button-main">
|
||||||
|
<el-button class="el-icon-upload" size="small" type="primary" @click="goFile"
|
||||||
|
>{{ t('visualization.upload_template') }}
|
||||||
|
</el-button>
|
||||||
|
<input
|
||||||
|
id="input"
|
||||||
|
ref="files"
|
||||||
|
type="file"
|
||||||
|
accept=".DET2"
|
||||||
|
hidden
|
||||||
|
@change="handleFileChange"
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row style="margin-top: 5px">
|
||||||
|
<el-col :span="4" class="name-area">{{ t('visualization.name') }}</el-col>
|
||||||
|
<el-col :span="20">
|
||||||
|
<el-input v-model="state.dvCreateInfo.name" clearable size="small" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row v-if="state.inputType === 'new_inner_template'" class="preview">
|
||||||
|
<el-col :span="8" style="height: 100%; overflow-y: auto">
|
||||||
|
<de-template-preview-list
|
||||||
|
:template-list="state.templateList"
|
||||||
|
@showCurrentTemplateInfo="showCurrentTemplateInfo"
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="16" :style="classBackground" class="preview-show" />
|
||||||
|
</el-row>
|
||||||
|
<el-row
|
||||||
|
v-if="state.inputType === 'new_outer_template'"
|
||||||
|
class="preview"
|
||||||
|
:style="classBackground"
|
||||||
|
/>
|
||||||
|
<el-row class="root-class">
|
||||||
|
<el-button size="small" @click="cancel()">{{ t('commons.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" size="small" :disabled="!saveStatus" @click="save()"
|
||||||
|
>{{ t('commons.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</el-row>
|
||||||
|
</el-row>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { showTemplateList } from '@/api/template'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
|
import { imgUrlTrans } from '@/utils/imgUtils'
|
||||||
|
import { ElMessage } from 'element-plus-secondary'
|
||||||
|
import { decompression } from '@/api/visualization/dataVisualization'
|
||||||
|
import DeTemplatePreviewList from '@/views/common/DeTemplatePreviewList.vue'
|
||||||
|
const { t } = useI18n()
|
||||||
|
const emits = defineEmits(['finish'])
|
||||||
|
const files = ref(null)
|
||||||
|
const props = defineProps({
|
||||||
|
curCanvasType: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
dialogShow: false,
|
||||||
|
loading: false,
|
||||||
|
inputType: 'new_outer_template',
|
||||||
|
fieldName: 'name',
|
||||||
|
tableRadio: null,
|
||||||
|
keyWordSearch: '',
|
||||||
|
columnLabel: t('visualization.belong_to_category'),
|
||||||
|
templateList: [],
|
||||||
|
importTemplateInfo: {
|
||||||
|
snapshot: ''
|
||||||
|
},
|
||||||
|
dvCreateInfo: {
|
||||||
|
pid: -1,
|
||||||
|
name: null,
|
||||||
|
canvasStyleData: null,
|
||||||
|
componentData: null,
|
||||||
|
templateId: null,
|
||||||
|
dynamicData: null,
|
||||||
|
staticResource: null
|
||||||
|
},
|
||||||
|
templateSelected: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const saveStatus = computed(() => {
|
||||||
|
return state.dvCreateInfo.name && state.templateSelected
|
||||||
|
})
|
||||||
|
|
||||||
|
const classBackground = computed(() => {
|
||||||
|
if (state.importTemplateInfo.snapshot) {
|
||||||
|
return {
|
||||||
|
background: `url(${imgUrlTrans(state.importTemplateInfo.snapshot)}) no-repeat`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => state.inputType,
|
||||||
|
() => {
|
||||||
|
createInit()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const createInit = () => {
|
||||||
|
state.templateSelected = false
|
||||||
|
state.dvCreateInfo.name = null
|
||||||
|
state.dvCreateInfo.canvasStyleData = null
|
||||||
|
state.dvCreateInfo.componentData = null
|
||||||
|
state.importTemplateInfo.snapshot = null
|
||||||
|
state.dvCreateInfo.templateId = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const showCurrentTemplateInfo = data => {
|
||||||
|
state.dvCreateInfo.templateId = data.id
|
||||||
|
if (data.nodeType === 'folder') {
|
||||||
|
state.dvCreateInfo.name = null
|
||||||
|
state.importTemplateInfo.snapshot = null
|
||||||
|
state.templateSelected = false
|
||||||
|
} else {
|
||||||
|
state.dvCreateInfo.name = data.name
|
||||||
|
state.importTemplateInfo.snapshot = data.snapshot
|
||||||
|
state.templateSelected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTree = () => {
|
||||||
|
const request = {
|
||||||
|
level: '0',
|
||||||
|
leafDvType: props.curCanvasType,
|
||||||
|
withChildren: true
|
||||||
|
}
|
||||||
|
state.loading = true
|
||||||
|
showTemplateList(request).then(res => {
|
||||||
|
state.templateList = res.data
|
||||||
|
state.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
emits('finish')
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
if (!state.dvCreateInfo.name) {
|
||||||
|
ElMessage.warning(t('common.save_success'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.dvCreateInfo.name.length > 50) {
|
||||||
|
ElMessage.warning(t('common.char_can_not_more_50'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.dvCreateInfo.templateId && state.inputType === 'new_inner_template') {
|
||||||
|
ElMessage.warning('chart.template_can_not_empty')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
state.dvCreateInfo['newFrom'] = state.inputType
|
||||||
|
state.loading = true
|
||||||
|
decompression(state.dvCreateInfo)
|
||||||
|
.then(response => {
|
||||||
|
state.loading = false
|
||||||
|
emits('finish', response.data)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
state.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleFileChange = e => {
|
||||||
|
const file = e.target.files[0]
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = res => {
|
||||||
|
state.templateSelected = true
|
||||||
|
const result = res.target.result
|
||||||
|
state.importTemplateInfo = JSON.parse(result)
|
||||||
|
state.dvCreateInfo.name = state.importTemplateInfo['name'].name
|
||||||
|
state.dvCreateInfo.canvasStyleData = state.importTemplateInfo['canvasStyleData']
|
||||||
|
state.dvCreateInfo.componentData = state.importTemplateInfo['componentData']
|
||||||
|
state.dvCreateInfo.dynamicData = state.importTemplateInfo['dynamicData']
|
||||||
|
state.dvCreateInfo.staticResource = state.importTemplateInfo['staticResource']
|
||||||
|
}
|
||||||
|
reader.readAsText(file)
|
||||||
|
}
|
||||||
|
const goFile = () => {
|
||||||
|
files.value.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
state.dialogShow = false
|
||||||
|
}
|
||||||
|
const optInit = () => {
|
||||||
|
state.dialogShow = true
|
||||||
|
createInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
optInit
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.create-main {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-area {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: right;
|
||||||
|
}
|
||||||
|
.root-class {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: right;
|
||||||
|
margin: 15px 0px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
margin-top: 5px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
height: 310px !important;
|
||||||
|
overflow: hidden;
|
||||||
|
background-size: 100% 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-show {
|
||||||
|
border-left: 1px solid #e6e6e6;
|
||||||
|
height: 310px;
|
||||||
|
background-size: 100% 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
class="market-create-dialog"
|
||||||
|
v-model="state.dialogShow"
|
||||||
|
width="80vw"
|
||||||
|
height="90vh"
|
||||||
|
:before-close="close"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<template-market ref="templateMarketCreateRef" @close="close"></template-market>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import TemplateMarket from '@/views/template-market/index.vue'
|
||||||
|
import { nextTick, reactive, ref } from 'vue'
|
||||||
|
const templateMarketCreateRef = ref(null)
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
dialogShow: false
|
||||||
|
})
|
||||||
|
const close = () => {
|
||||||
|
state.dialogShow = false
|
||||||
|
}
|
||||||
|
const optInit = param => {
|
||||||
|
state.dialogShow = true
|
||||||
|
nextTick(() => {
|
||||||
|
templateMarketCreateRef.value.optInit(param)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
optInit
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.market-create-dialog {
|
||||||
|
border-radius: 4px !important;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
.ed-dialog__body {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
.ed-dialog__header {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
455
core/core-frontend/src/viewsnew/common/DeResourceGroupOpt.vue
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import dvFolder from '@/assets/svg/dv-folder.svg'
|
||||||
|
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||||
|
import { ref, reactive, computed, watch, toRefs, nextTick } from 'vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import nothingTree from '@/assets/img/nothing-tree.png'
|
||||||
|
import { BusiTreeNode } from '@/models/tree/TreeNode'
|
||||||
|
import {
|
||||||
|
copyResource,
|
||||||
|
dvNameCheck,
|
||||||
|
moveResource,
|
||||||
|
queryTreeApi,
|
||||||
|
ResourceOrFolder,
|
||||||
|
updateBase,
|
||||||
|
saveCanvas
|
||||||
|
} from '@/api/visualization/dataVisualization'
|
||||||
|
import { ElMessage } from 'element-plus-secondary'
|
||||||
|
import { cutTargetTree, filterFreeFolder, nameTrim } from '@/utils/utils'
|
||||||
|
const props = defineProps({
|
||||||
|
curCanvasType: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { curCanvasType } = toRefs(props)
|
||||||
|
const { wsCache } = useCache('localStorage')
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
tData: [],
|
||||||
|
nameList: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const showParentSelected = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const nodeType = ref()
|
||||||
|
const pid = ref()
|
||||||
|
const id = ref()
|
||||||
|
const cmd = ref('')
|
||||||
|
const treeRef = ref()
|
||||||
|
const filterText = ref('')
|
||||||
|
const resourceFormNameLabel = ref('')
|
||||||
|
const resourceForm = reactive({
|
||||||
|
pid: '',
|
||||||
|
pName: null,
|
||||||
|
name: '新建'
|
||||||
|
})
|
||||||
|
const sourceLabel = computed(() =>
|
||||||
|
curCanvasType.value === 'dataV' ? t('work_branch.big_data_screen') : t('work_branch.dashboard')
|
||||||
|
)
|
||||||
|
|
||||||
|
const methodMap = {
|
||||||
|
move: moveResource,
|
||||||
|
copy: copyResource,
|
||||||
|
newFolder: saveCanvas
|
||||||
|
}
|
||||||
|
const searchEmpty = ref(false)
|
||||||
|
|
||||||
|
const filterNode = (value: string, data: BusiTreeNode) => {
|
||||||
|
nextTick(() => {
|
||||||
|
searchEmpty.value = treeRef.value.isEmpty
|
||||||
|
})
|
||||||
|
if (!value) return true
|
||||||
|
return data.name.includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(filterText, val => {
|
||||||
|
treeRef.value.filter(val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const nameRepeat = value => {
|
||||||
|
if (!nameList || nameList.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return nameList.some(name => name === value)
|
||||||
|
}
|
||||||
|
const nameValidator = (_, value, callback) => {
|
||||||
|
if (nameRepeat(value)) {
|
||||||
|
callback(new Error(t('visualization.name_repeat')))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const showPid = computed(() => {
|
||||||
|
return ['newLeaf', 'copy', 'newLeafAfter'].includes(cmd.value) && showParentSelected.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const showName = computed(() => {
|
||||||
|
return !['newLeafAfter', 'move'].includes(cmd.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
let nameList = []
|
||||||
|
const resourceFormRules = ref()
|
||||||
|
|
||||||
|
const resource = ref()
|
||||||
|
const resourceDialogShow = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
let tData = []
|
||||||
|
const filterMethod = value => {
|
||||||
|
state.tData = [...tData].filter(item => item.name.includes(value))
|
||||||
|
}
|
||||||
|
const resetForm = () => {
|
||||||
|
dialogTitle.value = null
|
||||||
|
resourceFormNameLabel.value = ''
|
||||||
|
resourceForm.name = t('visualization.new')
|
||||||
|
resourceForm.pid = ''
|
||||||
|
resourceDialogShow.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const dfs = (arr: BusiTreeNode[]) => {
|
||||||
|
arr.forEach(ele => {
|
||||||
|
ele['value'] = ele.id
|
||||||
|
if (ele.children?.length) {
|
||||||
|
dfs(ele.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDialogTitle = exec => {
|
||||||
|
return {
|
||||||
|
newFolder: t('visualization.new_folder'),
|
||||||
|
newLeaf:
|
||||||
|
props.curCanvasType === 'dataV'
|
||||||
|
? t('visualization.new_screen')
|
||||||
|
: t('visualization.new_dashboard'),
|
||||||
|
move: t('visualization.move_to'),
|
||||||
|
copy: t('visualization.copy') + sourceLabel.value,
|
||||||
|
rename: t('visualization.rename'),
|
||||||
|
newLeafAfter: t('visualization.belong_folder')
|
||||||
|
}[exec]
|
||||||
|
}
|
||||||
|
const placeholder = ref('')
|
||||||
|
|
||||||
|
const optInit = (type, data: BusiTreeNode, exec, parentSelect = false) => {
|
||||||
|
showParentSelected.value = parentSelect
|
||||||
|
nodeType.value = type
|
||||||
|
const optSource = data.leaf || type === 'leaf' ? sourceLabel.value : t('visualization.folder')
|
||||||
|
const placeholderLabel =
|
||||||
|
data.leaf || type === 'leaf'
|
||||||
|
? props.curCanvasType === 'dataV'
|
||||||
|
? t('work_branch.big_data_screen')
|
||||||
|
: t('work_branch.dashboard')
|
||||||
|
: t('visualization.folder')
|
||||||
|
placeholder.value = t('visualization.input_name_tips', [placeholderLabel])
|
||||||
|
filterText.value = ''
|
||||||
|
dialogTitle.value = getDialogTitle(exec) + ('rename' === exec ? optSource : '')
|
||||||
|
resourceFormNameLabel.value = (exec === 'move' ? '' : optSource) + t('visualization.name')
|
||||||
|
const request = { busiFlag: curCanvasType.value, leaf: false, weight: 7 }
|
||||||
|
if (['newFolder'].includes(exec)) {
|
||||||
|
resourceForm.name = ''
|
||||||
|
} else if ('copy' === exec) {
|
||||||
|
resourceForm.name = data.name + '_copy'
|
||||||
|
} else {
|
||||||
|
resourceForm.name = data.name
|
||||||
|
}
|
||||||
|
queryTreeApi(request).then(res => {
|
||||||
|
filterFreeFolder(res, curCanvasType.value)
|
||||||
|
const resultTree = res || []
|
||||||
|
dfs(resultTree as unknown as BusiTreeNode[])
|
||||||
|
state.tData = (resultTree as unknown as BusiTreeNode[]) || []
|
||||||
|
if (state.tData.length && state.tData[0].name === 'root' && state.tData[0].id === '0') {
|
||||||
|
state.tData[0].name =
|
||||||
|
curCanvasType.value === 'dataV'
|
||||||
|
? t('work_branch.big_data_screen')
|
||||||
|
: t('work_branch.dashboard')
|
||||||
|
}
|
||||||
|
tData = [...state.tData]
|
||||||
|
if ('move' === exec) {
|
||||||
|
cutTargetTree(state.tData, data.id)
|
||||||
|
}
|
||||||
|
if (['newLeaf', 'newFolder'].includes(exec)) {
|
||||||
|
resourceForm.pid = data.id as string
|
||||||
|
pid.value = data.id
|
||||||
|
} else {
|
||||||
|
id.value = data.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cmd.value = exec
|
||||||
|
resourceDialogShow.value = true
|
||||||
|
resourceFormRules.value = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: placeholder.value,
|
||||||
|
trigger: 'change'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: placeholder.value,
|
||||||
|
trigger: 'blur'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
min: 1,
|
||||||
|
max: 64,
|
||||||
|
message: t('commons.char_1_64'),
|
||||||
|
trigger: 'change'
|
||||||
|
},
|
||||||
|
{ required: true, trigger: 'blur', validator: nameValidator }
|
||||||
|
],
|
||||||
|
pid: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('common.please_select'),
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
resource.value.clearValidate()
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
const editeInit = (param: BusiTreeNode) => {
|
||||||
|
pid.value = param['pid']
|
||||||
|
id.value = param.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsTree = {
|
||||||
|
label: 'name',
|
||||||
|
children: 'children',
|
||||||
|
isLeaf: node => !node.children?.length
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeClick = (data: BusiTreeNode) => {
|
||||||
|
resourceForm.pid = data.id as string
|
||||||
|
resourceForm.pName = data.name as string
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkParent = params => {
|
||||||
|
if (params.pid !== 0 && !params.pid) {
|
||||||
|
ElMessage.error(t('visualization.select_target_folder'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 如果有搜索需要校验当前pName 是否包含关键字(解决先点击再搜索后,未点击搜索结果也可以移动的问题)
|
||||||
|
if (filterText.value && !resourceForm.pName.includes(filterText.value)) {
|
||||||
|
ElMessage.error(t('visualization.select_target_folder'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 点击后不能选择自身作为父ID
|
||||||
|
if (params.pid === params.id) {
|
||||||
|
ElMessage.warning(t('visualization.select_target_tips'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveResource = () => {
|
||||||
|
resource.value.validate(async result => {
|
||||||
|
if (result) {
|
||||||
|
const params: ResourceOrFolder = {
|
||||||
|
nodeType: nodeType.value as 'folder' | 'leaf',
|
||||||
|
name: resourceForm.name,
|
||||||
|
type: curCanvasType.value
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cmd.value) {
|
||||||
|
case 'move':
|
||||||
|
params.pid = resourceForm.pid as string
|
||||||
|
params.id = id.value
|
||||||
|
break
|
||||||
|
case 'copy':
|
||||||
|
params.id = id.value
|
||||||
|
params.pid = resourceForm.pid || pid.value || '0'
|
||||||
|
break
|
||||||
|
case 'rename':
|
||||||
|
params.pid = pid.value as string
|
||||||
|
params.id = id.value
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
params.pid = resourceForm.pid || pid.value || '0'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nameTrim(params, t('components.length_1_64_characters'))
|
||||||
|
if (cmd.value === 'move' && !checkParent(params)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (['newLeaf', 'newLeafAfter', 'newFolder', 'rename', 'move', 'copy'].includes(cmd.value)) {
|
||||||
|
await dvNameCheck({ opt: cmd.value, ...params })
|
||||||
|
}
|
||||||
|
if (cmd.value === 'newLeaf') {
|
||||||
|
resourceDialogShow.value = false
|
||||||
|
emits('finish', { opt: 'newLeaf', ...params })
|
||||||
|
} else {
|
||||||
|
loading.value = true
|
||||||
|
const method = methodMap[cmd.value] ? methodMap[cmd.value] : updateBase
|
||||||
|
method(params)
|
||||||
|
.then(data => {
|
||||||
|
loading.value = false
|
||||||
|
resourceDialogShow.value = false
|
||||||
|
emits('finish')
|
||||||
|
ElMessage.success(t('visualization.save_success'))
|
||||||
|
if (cmd.value === 'copy') {
|
||||||
|
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
|
||||||
|
const baseUrl =
|
||||||
|
curCanvasType.value === 'dataV'
|
||||||
|
? '#/dvCanvas?opt=copy&dvId='
|
||||||
|
: '#/dashboard?opt=copy&resourceId='
|
||||||
|
window.open(baseUrl + data.data, openType)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
optInit,
|
||||||
|
editeInit
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['finish'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
class="create-dialog"
|
||||||
|
:title="dialogTitle"
|
||||||
|
v-model="resourceDialogShow"
|
||||||
|
:width="cmd === 'move' ? '600px' : '420px'"
|
||||||
|
:before-close="resetForm"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
v-loading="loading"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
ref="resource"
|
||||||
|
:model="resourceForm"
|
||||||
|
:rules="resourceFormRules"
|
||||||
|
>
|
||||||
|
<el-form-item v-if="showName" :label="resourceFormNameLabel" prop="name">
|
||||||
|
<el-input
|
||||||
|
@keydown.stop
|
||||||
|
@keyup.stop
|
||||||
|
:placeholder="placeholder"
|
||||||
|
v-model="resourceForm.name"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="showPid" :label="t('visualization.belong_folder')" prop="pid">
|
||||||
|
<el-tree-select
|
||||||
|
style="width: 100%"
|
||||||
|
@keydown.stop
|
||||||
|
@keyup.stop
|
||||||
|
v-model="resourceForm.pid"
|
||||||
|
:data="state.tData"
|
||||||
|
:props="propsTree"
|
||||||
|
@node-click="nodeClick"
|
||||||
|
:filter-method="filterMethod"
|
||||||
|
:render-after-expand="false"
|
||||||
|
filterable
|
||||||
|
>
|
||||||
|
<template #default="{ data: { name } }">
|
||||||
|
<span class="custom-tree-node">
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span :title="name">{{ name }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree-select>
|
||||||
|
</el-form-item>
|
||||||
|
<div v-if="cmd === 'move'">
|
||||||
|
<el-input style="margin-bottom: 12px" v-model="filterText" clearable>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_search-outline_outlined"
|
||||||
|
><icon_searchOutline_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<div class="tree-content">
|
||||||
|
<el-tree
|
||||||
|
ref="treeRef"
|
||||||
|
:filter-node-method="filterNode"
|
||||||
|
filterable
|
||||||
|
v-model="resourceForm.pid"
|
||||||
|
empty-text=""
|
||||||
|
menu
|
||||||
|
:data="state.tData"
|
||||||
|
:props="propsTree"
|
||||||
|
@node-click="nodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ data }">
|
||||||
|
<span class="custom-tree-node">
|
||||||
|
<el-icon style="font-size: 18px">
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span :title="data.name">{{ data.name }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
<div v-if="searchEmpty" class="empty-search">
|
||||||
|
<img :src="nothingTree" />
|
||||||
|
<span>{{ t('visualization.no_content') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button secondary @click="resetForm()">{{ t('visualization.cancel') }} </el-button>
|
||||||
|
<el-button type="primary" @click="saveResource()"
|
||||||
|
>{{ t('visualization.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tree-content {
|
||||||
|
width: 552px;
|
||||||
|
height: 380px;
|
||||||
|
border: 1px solid #dee0e3;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
overflow-y: auto;
|
||||||
|
.empty-search {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 57px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
img {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
color: #646a73;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
span {
|
||||||
|
margin-left: 8.75px;
|
||||||
|
width: 120px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
925
core/core-frontend/src/viewsnew/common/DeResourceTree.vue
Normal file
@ -0,0 +1,925 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import dvDashboardSpineMobile from '@/assets/svg/dv-dashboard-spine-mobile.svg'
|
||||||
|
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||||
|
import dvCopyDark from '@/assets/svg/dv-copy-dark.svg'
|
||||||
|
import dvDelete from '@/assets/svg/dv-delete.svg'
|
||||||
|
import dvMove from '@/assets/svg/dv-move.svg'
|
||||||
|
import { treeDraggbleChart } from '@/utils/treeDraggbleChart'
|
||||||
|
import { debounce } from 'lodash-es'
|
||||||
|
import dvRename from '@/assets/svg/dv-rename.svg'
|
||||||
|
import dvDashboardSpine from '@/assets/svg/dv-dashboard-spine.svg'
|
||||||
|
import dvScreenSpine from '@/assets/svg/dv-screen-spine.svg'
|
||||||
|
import dvNewFolder from '@/assets/svg/dv-new-folder.svg'
|
||||||
|
import icon_fileAdd_outlined from '@/assets/svg/icon_file-add_outlined.svg'
|
||||||
|
import dvUseTemplate from '@/assets/svg/dv-use-template.svg'
|
||||||
|
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||||
|
import dvSortAsc from '@/assets/svg/dv-sort-asc.svg'
|
||||||
|
import dvSortDesc from '@/assets/svg/dv-sort-desc.svg'
|
||||||
|
import dvFolder from '@/assets/svg/dv-folder.svg'
|
||||||
|
import icon_operationAnalysis_outlined from '@/assets/svg/icon_operation-analysis_outlined.svg'
|
||||||
|
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||||
|
import { onMounted, reactive, ref, toRefs, watch, nextTick, computed } from 'vue'
|
||||||
|
import {
|
||||||
|
copyResource,
|
||||||
|
deleteLogic,
|
||||||
|
ResourceOrFolder,
|
||||||
|
queryShareBaseApi
|
||||||
|
} from '@/api/visualization/dataVisualization'
|
||||||
|
import { ElIcon, ElMessage, ElMessageBox, ElScrollbar } from 'element-plus-secondary'
|
||||||
|
import { Icon } from '@/components/icon-custom'
|
||||||
|
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||||
|
import { HandleMore } from '@/components/handle-more'
|
||||||
|
import DeResourceGroupOpt from '@/views/common/DeResourceGroupOpt.vue'
|
||||||
|
import { useEmbedded } from '@/store/modules/embedded'
|
||||||
|
import { BusiTreeNode, BusiTreeRequest } from '@/models/tree/TreeNode'
|
||||||
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import DvHandleMore from '@/components/handle-more/src/DvHandleMore.vue'
|
||||||
|
import { interactiveStoreWithOut } from '@/store/modules/interactive'
|
||||||
|
import { useShareStoreWithOut } from '@/store/modules/share'
|
||||||
|
const shareStore = useShareStoreWithOut()
|
||||||
|
const interactiveStore = interactiveStoreWithOut()
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import DeResourceCreateOptV2 from '@/views/common/DeResourceCreateOptV2.vue'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { findParentIdByChildIdRecursive } from '@/utils/canvasUtils'
|
||||||
|
import { XpackComponent } from '@/components/plugin'
|
||||||
|
import treeSort, { treeParentWeight } from '@/utils/treeSortUtils'
|
||||||
|
import router from '@/router'
|
||||||
|
import { cancelRequestBatch } from '@/config/axios/service'
|
||||||
|
import { isFreeFolder } from '@/utils/utils'
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
|
const appStore = useAppStoreWithOut()
|
||||||
|
const embeddedStore = useEmbedded()
|
||||||
|
const { dvInfo } = storeToRefs(dvMainStore)
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
curCanvasType: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
showPosition: {
|
||||||
|
required: false,
|
||||||
|
type: String,
|
||||||
|
default: 'preview'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const defaultProps = {
|
||||||
|
children: 'children',
|
||||||
|
label: 'name'
|
||||||
|
}
|
||||||
|
const mounted = ref(false)
|
||||||
|
const rootManage = ref(false)
|
||||||
|
const anyManage = ref(false)
|
||||||
|
const { curCanvasType, showPosition } = toRefs(props)
|
||||||
|
const resourceLabel =
|
||||||
|
curCanvasType.value === 'dataV' ? t('work_branch.big_data_screen') : t('work_branch.dashboard')
|
||||||
|
const newResourceLabel =
|
||||||
|
curCanvasType.value === 'dataV' ? t('visualization.new_screen') : t('visualization.new_dashboard')
|
||||||
|
const selectedNodeKey = ref(null)
|
||||||
|
const filterText = ref(null)
|
||||||
|
const expandedArray = ref([])
|
||||||
|
const resourceListTree = ref()
|
||||||
|
const resourceGroupOpt = ref()
|
||||||
|
const resourceCreateOpt = ref()
|
||||||
|
const returnMounted = ref(false)
|
||||||
|
const state = reactive({
|
||||||
|
pWeightMap: {},
|
||||||
|
curSortType: 'time_desc',
|
||||||
|
resourceTree: [] as BusiTreeNode[],
|
||||||
|
originResourceTree: [] as BusiTreeNode[],
|
||||||
|
folderMenuList: [
|
||||||
|
{
|
||||||
|
label: t('visualization.move_to'), //'移动到'
|
||||||
|
command: 'move',
|
||||||
|
svgName: dvMove
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.rename'), //'重命名'
|
||||||
|
command: 'rename',
|
||||||
|
svgName: dvRename
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.delete'), // 删除
|
||||||
|
command: 'delete',
|
||||||
|
svgName: dvDelete,
|
||||||
|
divided: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
sortType: [
|
||||||
|
{
|
||||||
|
label: t('visualization.time_asc'), //'按时间升序'
|
||||||
|
value: 'time_asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.time_desc'), //'按时间降序'
|
||||||
|
value: 'time_desc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.name_asc'), //'按名称升序'
|
||||||
|
value: 'name_asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.name_desc'), //'按名称降序'
|
||||||
|
value: 'name_desc'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
templateCreatePid: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const dvSvgType = computed(() =>
|
||||||
|
curCanvasType.value === 'dashboard' ? dvDashboardSpine : dvScreenSpine
|
||||||
|
)
|
||||||
|
|
||||||
|
const isEmbedded = computed(() => appStore.getIsDataEaseBi || appStore.getIsIframe)
|
||||||
|
|
||||||
|
const resourceTypeList = computed(() => {
|
||||||
|
const list = [
|
||||||
|
{
|
||||||
|
label: t('work_branch.new_empty'), //'空白新建',
|
||||||
|
svgName: dvSvgType.value,
|
||||||
|
command: 'newLeaf'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('work_branch.new_using_template'),
|
||||||
|
svgName: dvUseTemplate,
|
||||||
|
command: 'newFromTemplate'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('work_branch.new_folder'), //'新建文件夹'
|
||||||
|
divided: true,
|
||||||
|
svgName: dvFolder,
|
||||||
|
command: 'newFolder'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return list
|
||||||
|
})
|
||||||
|
const { handleDrop, allowDrop, handleDragStart } = treeDraggbleChart(
|
||||||
|
state,
|
||||||
|
'resourceTree',
|
||||||
|
curCanvasType.value
|
||||||
|
)
|
||||||
|
|
||||||
|
const menuListWeight = id => {
|
||||||
|
const pWeight = state.pWeightMap[id]
|
||||||
|
return pWeight < 7 ? menuList : menuListWithCopy
|
||||||
|
}
|
||||||
|
const menuListWithCopy = [
|
||||||
|
{
|
||||||
|
label: t('visualization.copy'), //'复制',
|
||||||
|
command: 'copy',
|
||||||
|
svgName: dvCopyDark
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.move_to'), //'移动到',
|
||||||
|
command: 'move',
|
||||||
|
svgName: dvMove
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.rename'), //'重命名',
|
||||||
|
command: 'rename',
|
||||||
|
svgName: dvRename
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.delete'), //'删除',
|
||||||
|
command: 'delete',
|
||||||
|
svgName: dvDelete,
|
||||||
|
divided: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const menuList = [
|
||||||
|
{
|
||||||
|
label: t('visualization.move_to'), //'移动到',
|
||||||
|
command: 'move',
|
||||||
|
svgName: dvMove
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.rename'), //'重命名',
|
||||||
|
command: 'rename',
|
||||||
|
svgName: dvRename
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('visualization.delete'), //'删除',
|
||||||
|
command: 'delete',
|
||||||
|
svgName: dvDelete,
|
||||||
|
divided: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const infoId = wsCache.get(curCanvasType.value === 'dashboard' ? 'db-info-id' : 'dv-info-id')
|
||||||
|
const routerDvId = router.currentRoute.value.query.dvId
|
||||||
|
const dvId = embeddedStore.dvId || infoId || routerDvId
|
||||||
|
wsCache.delete(curCanvasType.value === 'dashboard' ? 'db-info-id' : 'dv-info-id')
|
||||||
|
if (dvId && showPosition.value === 'preview') {
|
||||||
|
selectedNodeKey.value = dvId
|
||||||
|
returnMounted.value = true
|
||||||
|
}
|
||||||
|
const nodeExpand = data => {
|
||||||
|
if (data.id) {
|
||||||
|
expandedArray.value.push(data.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeCollapse = data => {
|
||||||
|
if (data.id) {
|
||||||
|
expandedArray.value.splice(expandedArray.value.indexOf(data.id), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterNode = (value: string, data: BusiTreeNode) => {
|
||||||
|
if (!value) return true
|
||||||
|
return data.name?.toLocaleLowerCase().includes(value.toLocaleLowerCase())
|
||||||
|
}
|
||||||
|
//取消之前请求
|
||||||
|
const cancelPreRequest = () => {
|
||||||
|
cancelRequestBatch('/dataVisualization/findById')
|
||||||
|
cancelRequestBatch('/chartData/getData')
|
||||||
|
cancelRequestBatch('/linkage/getVisualizationAllLinkageInfo/**')
|
||||||
|
cancelRequestBatch('/linkJump/queryVisualizationJumpInfo/**')
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeClick = (data: BusiTreeNode) => {
|
||||||
|
cancelPreRequest()
|
||||||
|
selectedNodeKey.value = data.id
|
||||||
|
if (data.leaf) {
|
||||||
|
emit('nodeClick', data)
|
||||||
|
} else {
|
||||||
|
resourceListTree.value.setCurrentKey(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTree = async () => {
|
||||||
|
const request = { busiFlag: curCanvasType.value } as BusiTreeRequest
|
||||||
|
const isDashboard = curCanvasType.value == 'dashboard'
|
||||||
|
await interactiveStore.setInteractive(request)
|
||||||
|
const interactiveData = isDashboard ? interactiveStore.getPanel : interactiveStore.getScreen
|
||||||
|
const nodeData = interactiveData.treeNodes
|
||||||
|
rootManage.value = interactiveData.rootManage
|
||||||
|
anyManage.value = interactiveData.anyManage
|
||||||
|
if (
|
||||||
|
dvInfo.value &&
|
||||||
|
dvInfo.value.id &&
|
||||||
|
!JSON.stringify(nodeData).includes(dvInfo.value.id) &&
|
||||||
|
showPosition.value !== 'multiplexing'
|
||||||
|
) {
|
||||||
|
dvMainStore.resetDvInfo()
|
||||||
|
}
|
||||||
|
let curSortType = sortList[Number(wsCache.get('TreeSort-backend')) ?? 1].value
|
||||||
|
curSortType = wsCache.get(`TreeSort-${curCanvasType.value}`) ?? curSortType
|
||||||
|
if (nodeData.length && nodeData[0]['id'] === '0' && nodeData[0]['name'] === 'root') {
|
||||||
|
state.originResourceTree = nodeData[0]['children'] || []
|
||||||
|
sortTypeChange(curSortType)
|
||||||
|
afterTreeInit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.originResourceTree = nodeData
|
||||||
|
sortTypeChange(curSortType)
|
||||||
|
afterTreeInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
const flattedTree = computed<BusiTreeNode[]>(() => {
|
||||||
|
return _.filter(flatTree(state.resourceTree), node => node.leaf)
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasData = computed<boolean>(() => flattedTree.value.length > 0)
|
||||||
|
|
||||||
|
function flatTree(tree: BusiTreeNode[]) {
|
||||||
|
let result = _.cloneDeep(tree)
|
||||||
|
_.forEach(tree, node => {
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
result = _.union(result, flatTree(node.children))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const afterTreeInit = () => {
|
||||||
|
state.pWeightMap = treeParentWeight(state.originResourceTree, rootManage.value ? 9 : 0)
|
||||||
|
mounted.value = true
|
||||||
|
if (selectedNodeKey.value && returnMounted.value) {
|
||||||
|
expandedArray.value = getDefaultExpandedKeys()
|
||||||
|
returnMounted.value = false
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
resourceListTree.value.setCurrentKey(selectedNodeKey.value)
|
||||||
|
nextTick(() => {
|
||||||
|
if (selectedNodeKey.value) {
|
||||||
|
const nodeDom = document.querySelector('.is-current')
|
||||||
|
nodeDom && nodeDom.click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resourceListTree.value.filter(filterText.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyLoading = ref(false)
|
||||||
|
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
|
||||||
|
const emit = defineEmits(['nodeClick'])
|
||||||
|
|
||||||
|
const operation = (cmd: string, data: BusiTreeNode, nodeType: string) => {
|
||||||
|
if (cmd === 'delete') {
|
||||||
|
const msg = data.leaf ? '' : t('visualization.delete_tips')
|
||||||
|
const tips_label = data.leaf ? resourceLabel : t('visualization.folder')
|
||||||
|
ElMessageBox.confirm(t('visualization.delete_warn', [tips_label]), {
|
||||||
|
confirmButtonType: 'danger',
|
||||||
|
type: 'warning',
|
||||||
|
tip: msg,
|
||||||
|
autofocus: false,
|
||||||
|
showClose: false
|
||||||
|
}).then(() => {
|
||||||
|
deleteLogic(data.id, curCanvasType.value).then(() => {
|
||||||
|
ElMessage.success(t('visualization.delete_success'))
|
||||||
|
getTree()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if (cmd === 'edit') {
|
||||||
|
resourceEdit(data.id)
|
||||||
|
} else if (cmd === 'copy') {
|
||||||
|
const targetPid = findParentIdByChildIdRecursive(state.resourceTree, data.id)
|
||||||
|
const params: ResourceOrFolder = {
|
||||||
|
nodeType: nodeType as 'folder' | 'leaf',
|
||||||
|
name: data.name + '-copy',
|
||||||
|
type: curCanvasType.value,
|
||||||
|
id: data.id,
|
||||||
|
pid: targetPid || '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
copyLoading.value = true
|
||||||
|
|
||||||
|
copyResource(params)
|
||||||
|
.then(data => {
|
||||||
|
const baseUrl =
|
||||||
|
curCanvasType.value === 'dataV'
|
||||||
|
? `#/dvCanvas?opt=copy&pid=${params.pid}&dvId=${data.data}`
|
||||||
|
: `#/dashboard?opt=copy&pid=${params.pid}&resourceId=${data.data}`
|
||||||
|
if (isEmbedded.value) {
|
||||||
|
embeddedStore.clearState()
|
||||||
|
embeddedStore.setPid(params.pid as string)
|
||||||
|
embeddedStore.setOpt('copy')
|
||||||
|
if (curCanvasType.value === 'dataV') {
|
||||||
|
embeddedStore.setDvId(data.data)
|
||||||
|
} else {
|
||||||
|
embeddedStore.setResourceId(data.data)
|
||||||
|
}
|
||||||
|
useEmitt().emitter.emit(
|
||||||
|
'changeCurrentComponent',
|
||||||
|
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'DashboardEditor'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newWindow = window.open(baseUrl, openType)
|
||||||
|
initOpenHandler(newWindow)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
copyLoading.value = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resourceGroupOpt.value.optInit(nodeType, data, cmd, ['copy'].includes(cmd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addOperation = (
|
||||||
|
cmd: string,
|
||||||
|
data?: BusiTreeNode,
|
||||||
|
nodeType?: string,
|
||||||
|
parentSelect?: boolean
|
||||||
|
) => {
|
||||||
|
// 新建子节点的操作流程为先进行创建 后面选择所在目录
|
||||||
|
if (cmd === 'newLeaf') {
|
||||||
|
const baseUrl =
|
||||||
|
curCanvasType.value === 'dataV' ? '#/dvCanvas?opt=create' : '#/dashboard?opt=create'
|
||||||
|
let newWindow = null
|
||||||
|
if (isEmbedded.value) {
|
||||||
|
embeddedStore.clearState()
|
||||||
|
embeddedStore.setOpt('create')
|
||||||
|
if (data?.id) {
|
||||||
|
embeddedStore.setPid(data?.id as string)
|
||||||
|
}
|
||||||
|
useEmitt().emitter.emit(
|
||||||
|
'changeCurrentComponent',
|
||||||
|
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'DashboardEditor'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data?.id) {
|
||||||
|
newWindow = window.open(baseUrl + `&pid=${data.id}`, openType)
|
||||||
|
} else {
|
||||||
|
newWindow = window.open(baseUrl, openType)
|
||||||
|
}
|
||||||
|
initOpenHandler(newWindow)
|
||||||
|
} else if (cmd === 'newFromTemplate') {
|
||||||
|
const params = {
|
||||||
|
curPosition: 'create',
|
||||||
|
pid: data?.id,
|
||||||
|
templateType: curCanvasType.value === 'dataV' ? 'SCREEN' : 'PANEL'
|
||||||
|
}
|
||||||
|
resourceCreateOpt.value.optInit(params)
|
||||||
|
} else {
|
||||||
|
resourceGroupOpt.value.optInit(nodeType, data || {}, cmd, parentSelect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNewObject() {
|
||||||
|
return addOperation('newLeaf', null, 'leaf', true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceEdit = resourceId => {
|
||||||
|
const baseUrl = curCanvasType.value === 'dataV' ? '#/dvCanvas?dvId=' : '#/dashboard?resourceId='
|
||||||
|
if (isEmbedded.value) {
|
||||||
|
embeddedStore.clearState()
|
||||||
|
if (curCanvasType.value === 'dataV') {
|
||||||
|
embeddedStore.setDvId(resourceId)
|
||||||
|
} else {
|
||||||
|
embeddedStore.setResourceId(resourceId)
|
||||||
|
}
|
||||||
|
useEmitt().emitter.emit(
|
||||||
|
'changeCurrentComponent',
|
||||||
|
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'DashboardEditor'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newWindow = window.open(baseUrl + resourceId, openType)
|
||||||
|
initOpenHandler(newWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceOptFinish = () => {
|
||||||
|
getTree()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceCreateFinish = templateData => {
|
||||||
|
// do create
|
||||||
|
wsCache.set(`de-template-data`, JSON.stringify(templateData))
|
||||||
|
const baseUrl =
|
||||||
|
curCanvasType.value === 'dataV'
|
||||||
|
? '#/dvCanvas?opt=create&createType=template'
|
||||||
|
: '#/dashboard?opt=create&createType=template'
|
||||||
|
let newWindow = null
|
||||||
|
if (isEmbedded.value) {
|
||||||
|
embeddedStore.clearState()
|
||||||
|
embeddedStore.setOpt('create')
|
||||||
|
embeddedStore.setCreateType('template')
|
||||||
|
if (state.templateCreatePid) {
|
||||||
|
embeddedStore.setPid(state.templateCreatePid as unknown as string)
|
||||||
|
}
|
||||||
|
useEmitt().emitter.emit(
|
||||||
|
'changeCurrentComponent',
|
||||||
|
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'DashboardEditor'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.templateCreatePid) {
|
||||||
|
newWindow = window.open(baseUrl + `&pid=${state.templateCreatePid}`, openType)
|
||||||
|
} else {
|
||||||
|
newWindow = window.open(baseUrl, openType)
|
||||||
|
}
|
||||||
|
initOpenHandler(newWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getParentKeys = (tree, targetKey, parentKeys = []) => {
|
||||||
|
for (const node of tree) {
|
||||||
|
if (node.id === targetKey) {
|
||||||
|
return parentKeys
|
||||||
|
}
|
||||||
|
if (node.children) {
|
||||||
|
const newParentKeys = [...parentKeys, node.id]
|
||||||
|
const result = getParentKeys(node.children, targetKey, newParentKeys)
|
||||||
|
if (result) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultExpandedKeys = () => {
|
||||||
|
const parentKeys = getParentKeys(state.resourceTree, selectedNodeKey.value)
|
||||||
|
if (parentKeys) {
|
||||||
|
return [selectedNodeKey.value, ...parentKeys]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortList = [
|
||||||
|
{
|
||||||
|
name: t('visualization.time_asc'),
|
||||||
|
value: 'time_asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('visualization.time_desc'),
|
||||||
|
value: 'time_desc',
|
||||||
|
divided: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('visualization.name_asc'),
|
||||||
|
value: 'name_asc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('visualization.name_desc'),
|
||||||
|
value: 'name_desc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const sortTypeTip = computed(() => {
|
||||||
|
return sortList.find(ele => ele.value === state.curSortType).name
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSortTypeChange = sortType => {
|
||||||
|
state.resourceTree = treeSort(state.originResourceTree, sortType)
|
||||||
|
state.curSortType = sortType
|
||||||
|
wsCache.set('TreeSort-' + curCanvasType.value, state.curSortType)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortTypeChange = sortType => {
|
||||||
|
state.resourceTree = treeSort(state.originResourceTree, sortType)
|
||||||
|
state.curSortType = sortType
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyAllowDrop = debounce((arg1, arg2) => {
|
||||||
|
const flagArray = ['dashboard', 'dataV', 'dataset', 'datasource']
|
||||||
|
const flag = flagArray.findIndex(item => item === curCanvasType.value)
|
||||||
|
if (flag < 0 || !isFreeFolder(arg2, flag + 1)) {
|
||||||
|
return allowDrop(arg1, arg2)
|
||||||
|
}
|
||||||
|
ElMessage.warning(t('free.save_error'))
|
||||||
|
return false
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
watch(filterText, val => {
|
||||||
|
resourceListTree.value.filter(val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const openHandler = ref(null)
|
||||||
|
const initOpenHandler = newWindow => {
|
||||||
|
if (openHandler?.value) {
|
||||||
|
const pm = {
|
||||||
|
methodName: 'initOpenHandler',
|
||||||
|
args: newWindow
|
||||||
|
}
|
||||||
|
openHandler.value.invokeMethod(pm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadInit = () => {
|
||||||
|
const historyTreeSort = wsCache.get('TreeSort-' + curCanvasType.value)
|
||||||
|
if (historyTreeSort) {
|
||||||
|
state.curSortType = historyTreeSort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadShareBase = () => {
|
||||||
|
queryShareBaseApi().then(res => {
|
||||||
|
const param = {
|
||||||
|
shareDisable: res.data?.disable,
|
||||||
|
sharePeRequire: res.data?.peRequire
|
||||||
|
}
|
||||||
|
shareStore.setData(param)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
loadInit()
|
||||||
|
getTree()
|
||||||
|
loadShareBase()
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
rootManage,
|
||||||
|
hasData,
|
||||||
|
createNewObject,
|
||||||
|
mounted
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="resource-tree">
|
||||||
|
<div class="tree-header">
|
||||||
|
<div class="icon-methods" v-show="showPosition === 'preview'">
|
||||||
|
<span class="title"> {{ resourceLabel }} </span>
|
||||||
|
<div v-if="rootManage" class="flex-align-center">
|
||||||
|
<el-tooltip :content="t('work_branch.new_folder')" placement="top" effect="dark">
|
||||||
|
<el-icon
|
||||||
|
class="custom-icon btn"
|
||||||
|
style="margin-right: 20px"
|
||||||
|
@click="addOperation('newFolder', null, 'folder')"
|
||||||
|
>
|
||||||
|
<Icon name="dv-new-folder"><dvNewFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip :content="newResourceLabel" placement="top" effect="dark">
|
||||||
|
<el-dropdown popper-class="menu-outer-dv_popper" trigger="hover">
|
||||||
|
<el-icon class="custom-icon btn" @click="addOperation('newLeaf', null, 'leaf', true)">
|
||||||
|
<Icon name="icon_file-add_outlined"
|
||||||
|
><icon_fileAdd_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click="addOperation('newLeaf', null, 'leaf', true)">
|
||||||
|
<el-icon :class="`handle-icon color-${curCanvasType}`">
|
||||||
|
<Icon><component class="svg-icon" :is="dvSvgType"></component></Icon>
|
||||||
|
</el-icon>
|
||||||
|
{{ t('work_branch.new_empty') }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click="addOperation('newFromTemplate', null, 'leaf', true)">
|
||||||
|
<el-icon class="handle-icon">
|
||||||
|
<Icon name="dv-use-template"><dvUseTemplate class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
{{ t('work_branch.new_using_template') }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
:placeholder="t('commons.search')"
|
||||||
|
v-model="filterText"
|
||||||
|
clearable
|
||||||
|
class="search-bar"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Icon name="icon_search-outline_outlined"
|
||||||
|
><icon_searchOutline_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-dropdown @command="handleSortTypeChange" trigger="click">
|
||||||
|
<el-icon class="filter-icon-span">
|
||||||
|
<el-tooltip :offset="16" effect="dark" :content="sortTypeTip" placement="top">
|
||||||
|
<Icon v-if="state.curSortType.includes('asc')" name="dv-sort-asc" class="opt-icon"
|
||||||
|
><dvSortAsc class="svg-icon opt-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip :offset="16" effect="dark" :content="sortTypeTip" placement="top">
|
||||||
|
<Icon v-if="state.curSortType.includes('desc')" name="dv-sort-desc" class="opt-icon"
|
||||||
|
><dvSortDesc class="svg-icon opt-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-icon>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu style="width: 246px">
|
||||||
|
<template :key="ele.value" v-for="ele in sortList">
|
||||||
|
<el-dropdown-item
|
||||||
|
class="ed-select-dropdown__item"
|
||||||
|
:class="ele.value === state.curSortType && 'selected'"
|
||||||
|
:command="ele.value"
|
||||||
|
>
|
||||||
|
{{ ele.name }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
<li v-if="ele.divided" class="ed-dropdown-menu__item--divided"></li>
|
||||||
|
</template>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
<el-scrollbar class="custom-tree" v-loading="copyLoading">
|
||||||
|
<el-tree
|
||||||
|
menu
|
||||||
|
ref="resourceListTree"
|
||||||
|
:default-expanded-keys="expandedArray"
|
||||||
|
:data="state.resourceTree"
|
||||||
|
:props="defaultProps"
|
||||||
|
node-key="id"
|
||||||
|
highlight-current
|
||||||
|
:expand-on-click-node="true"
|
||||||
|
:filter-node-method="filterNode"
|
||||||
|
@node-expand="nodeExpand"
|
||||||
|
@node-collapse="nodeCollapse"
|
||||||
|
@node-click="nodeClick"
|
||||||
|
@node-drag-start="handleDragStart"
|
||||||
|
:allow-drop="proxyAllowDrop"
|
||||||
|
@node-drop="handleDrop"
|
||||||
|
draggable
|
||||||
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<span class="custom-tree-node">
|
||||||
|
<el-icon style="font-size: 18px" v-if="!data.leaf">
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<el-icon style="font-size: 18px" v-else-if="curCanvasType === 'dashboard'">
|
||||||
|
<Icon
|
||||||
|
><component
|
||||||
|
:is="data.extraFlag ? dvDashboardSpineMobile : dvDashboardSpine"
|
||||||
|
></component
|
||||||
|
></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<el-icon class="icon-screen-new color-dataV" style="font-size: 18px" v-else>
|
||||||
|
<Icon name="icon_operation-analysis_outlined"
|
||||||
|
><icon_operationAnalysis_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span :title="node.label" class="label-tooltip">{{ node.label }}</span>
|
||||||
|
<div class="icon-more" v-if="data.weight >= 7 && showPosition === 'preview'">
|
||||||
|
<el-icon
|
||||||
|
v-on:click.stop
|
||||||
|
v-if="data.leaf"
|
||||||
|
class="hover-icon"
|
||||||
|
@click="resourceEdit(data.id)"
|
||||||
|
>
|
||||||
|
<Icon><icon_edit_outlined class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<handle-more
|
||||||
|
@handle-command="
|
||||||
|
cmd => addOperation(cmd, data, cmd === 'newFolder' ? 'folder' : 'leaf')
|
||||||
|
"
|
||||||
|
:menu-list="resourceTypeList"
|
||||||
|
:icon-name="icon_add_outlined"
|
||||||
|
placement="bottom-start"
|
||||||
|
v-if="!data.leaf"
|
||||||
|
></handle-more>
|
||||||
|
<dv-handle-more
|
||||||
|
@handle-command="cmd => operation(cmd, data, data.leaf ? 'leaf' : 'folder')"
|
||||||
|
:node="data"
|
||||||
|
:any-manage="anyManage"
|
||||||
|
:resource-type="curCanvasType"
|
||||||
|
:menu-list="data.leaf ? menuListWeight(data.id) : state.folderMenuList"
|
||||||
|
></dv-handle-more>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
<de-resource-group-opt
|
||||||
|
:cur-canvas-type="curCanvasType"
|
||||||
|
@finish="resourceOptFinish"
|
||||||
|
ref="resourceGroupOpt"
|
||||||
|
/>
|
||||||
|
<de-resource-create-opt-v2
|
||||||
|
:cur-canvas-type="curCanvasType"
|
||||||
|
ref="resourceCreateOpt"
|
||||||
|
@finish="resourceCreateFinish"
|
||||||
|
></de-resource-create-opt-v2>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
<XpackComponent ref="openHandler" jsname="L2NvbXBvbmVudC9lbWJlZGRlZC1pZnJhbWUvT3BlbkhhbmRsZXI=" />
|
||||||
|
</template>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.filter-icon-span {
|
||||||
|
border: 1px solid #bbbfc4;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #1f2329;
|
||||||
|
padding: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.opt-icon:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: #f5f6f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #eff0f1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.resource-tree {
|
||||||
|
padding: 16px 0 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.tree-header {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-methods {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--TextPrimary, #1f2329);
|
||||||
|
padding-bottom: 16px;
|
||||||
|
.title {
|
||||||
|
margin-right: auto;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
.custom-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
&.btn {
|
||||||
|
color: var(--ed-color-primary);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.search-bar {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title-area {
|
||||||
|
margin-left: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.title-area-outer {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
width: 0px;
|
||||||
|
}
|
||||||
|
.custom-tree-node-list {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
.father .child {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.father:hover .child {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ed-input__wrapper) {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree {
|
||||||
|
height: calc(100vh - 148px);
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node {
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: content-box;
|
||||||
|
padding-right: 4px;
|
||||||
|
|
||||||
|
.label-tooltip {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 8.75px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.icon-more {
|
||||||
|
margin-left: auto;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.label-tooltip {
|
||||||
|
width: calc(100% - 78px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-more {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-screen-new {
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.menu-outer-dv_popper {
|
||||||
|
width: 140px;
|
||||||
|
margin-top: -2px !important;
|
||||||
|
|
||||||
|
.ed-icon {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-type-normal {
|
||||||
|
i {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-type-checked {
|
||||||
|
color: var(--ed-color-primary);
|
||||||
|
i {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
113
core/core-frontend/src/viewsnew/common/DeTemplatePreviewList.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<el-col>
|
||||||
|
<el-row style="display: inherit; margin-top: 5px">
|
||||||
|
<el-row>
|
||||||
|
<el-input
|
||||||
|
v-model="state.templateFilterText"
|
||||||
|
:placeholder="t('visualization.filter_keywords')"
|
||||||
|
size="small"
|
||||||
|
clearable
|
||||||
|
prefix-icon="el-icon-search"
|
||||||
|
/>
|
||||||
|
</el-row>
|
||||||
|
<el-row style="display: inherit; margin-top: 5px">
|
||||||
|
<el-tree
|
||||||
|
ref="templateTree"
|
||||||
|
:default-expanded-keys="state.defaultExpandedKeys"
|
||||||
|
:data="templateList"
|
||||||
|
node-key="id"
|
||||||
|
:expand-on-click-node="true"
|
||||||
|
:filter-node-method="filterNode"
|
||||||
|
:highlight-current="true"
|
||||||
|
@node-click="nodeClick"
|
||||||
|
>
|
||||||
|
<template #default="{ data }">
|
||||||
|
<span class="custom-tree-node">
|
||||||
|
<span class="custom-label">
|
||||||
|
<el-icon style="font-size: 18px" v-if="data.nodeType === 'folder'">
|
||||||
|
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<el-icon style="font-size: 18px" v-else-if="data.dvType === 'dashboard'">
|
||||||
|
<Icon name="dv-dashboard-spine"><dvDashboardSpine class="svg-icon" /></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<el-icon class="icon-screen-new" style="font-size: 18px" v-else>
|
||||||
|
<Icon name="icon_operation-analysis_outlined"
|
||||||
|
><icon_operationAnalysis_outlined class="svg-icon"
|
||||||
|
/></Icon>
|
||||||
|
</el-icon>
|
||||||
|
<span :title="data.name" class="custom-name">{{ data.name }}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</el-row>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import dvFolder from '@/assets/svg/dv-folder.svg'
|
||||||
|
import dvDashboardSpine from '@/assets/svg/dv-dashboard-spine.svg'
|
||||||
|
import icon_operationAnalysis_outlined from '@/assets/svg/icon_operation-analysis_outlined.svg'
|
||||||
|
import { findOne } from '@/api/template'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
const { t } = useI18n()
|
||||||
|
const emits = defineEmits(['showCurrentTemplateInfo'])
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
curCanvasType: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
templateList: {
|
||||||
|
type: Array,
|
||||||
|
default: function () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
templateFilterText: '',
|
||||||
|
defaultExpandedKeys: [],
|
||||||
|
currentTemplateShowList: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const filterNode = (value, data) => {
|
||||||
|
if (!value) return true
|
||||||
|
return data.label.indexOf(value) !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeClick = data => {
|
||||||
|
if (data.nodeType === 'template') {
|
||||||
|
findOne(data.id).then(res => {
|
||||||
|
emits('showCurrentTemplateInfo', res.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.custom-label {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-name {
|
||||||
|
margin-left: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tree-node {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 14px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
67
core/core-frontend/src/viewsnew/common/DvDetailInfo.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-title">
|
||||||
|
{{
|
||||||
|
`${
|
||||||
|
dvInfo.type === 'dashboard'
|
||||||
|
? t('work_branch.dashboard')
|
||||||
|
: t('work_branch.big_data_screen')
|
||||||
|
}ID`
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="info-content">{{ dvInfo.id }}</div>
|
||||||
|
<div v-if="dvInfo.creatorName" class="info-title">{{ t('visualization.create_by') }}</div>
|
||||||
|
<div v-if="dvInfo.creatorName" class="info-content">{{ dvInfo.creatorName }}</div>
|
||||||
|
<div class="info-title">{{ t('visualization.create_time') }}</div>
|
||||||
|
<div class="info-content">{{ timestampFormatDate(dvInfo.createTime) }}</div>
|
||||||
|
<div v-if="dvInfo.updateName" class="info-title">{{ t('visualization.update_by') }}</div>
|
||||||
|
<div v-if="dvInfo.updateName" class="info-content">{{ dvInfo.updateName }}</div>
|
||||||
|
<div class="info-title">{{ t('visualization.update_time') }}</div>
|
||||||
|
<div v-if="dvInfo.updateTime" class="info-content">
|
||||||
|
{{ timestampFormatDate(dvInfo.updateTime) }}
|
||||||
|
</div>
|
||||||
|
<div v-if="!dvInfo.updateTime" class="info-content">N/A</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
|
const { dvInfo } = storeToRefs(dvMainStore)
|
||||||
|
|
||||||
|
const timestampFormatDate = value => {
|
||||||
|
if (!value) {
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
return new Date(value).toLocaleString()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.info-card {
|
||||||
|
font-family: var(--de-custom_font, 'PingFang');
|
||||||
|
font-style: normal;
|
||||||
|
padding-left: 4px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
|
||||||
|
.info-title {
|
||||||
|
color: #646a73;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.info-content {
|
||||||
|
color: #1f2329;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
159
core/core-frontend/src/viewsnew/common/MultiplexingCanvas.vue
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
direction="btt"
|
||||||
|
size="90%"
|
||||||
|
v-model="dialogShow"
|
||||||
|
trigger="click"
|
||||||
|
:title="t('visualization.multiplexing')"
|
||||||
|
custom-class="custom-drawer"
|
||||||
|
>
|
||||||
|
<dashboard-preview-show
|
||||||
|
v-if="dialogShow && curDvType === 'dashboard'"
|
||||||
|
ref="multiplexingPreviewShowRef"
|
||||||
|
class="multiplexing-area"
|
||||||
|
no-close
|
||||||
|
show-position="multiplexing"
|
||||||
|
></dashboard-preview-show>
|
||||||
|
<preview-show
|
||||||
|
v-if="dialogShow && curDvType === 'dataV'"
|
||||||
|
ref="multiplexingPreviewShowRef"
|
||||||
|
class="multiplexing-area"
|
||||||
|
no-close
|
||||||
|
show-position="multiplexing"
|
||||||
|
></preview-show>
|
||||||
|
<template #footer>
|
||||||
|
<el-row class="multiplexing-footer">
|
||||||
|
<el-col class="adapt-count">
|
||||||
|
<span>{{ t('visualization.multi_selected', [selectComponentCount]) }} </span>
|
||||||
|
</el-col>
|
||||||
|
<el-col class="adapt-select">
|
||||||
|
<span class="adapt-text">{{ t('visualization.component_style') }} : </span>
|
||||||
|
<el-select
|
||||||
|
style="width: 120px"
|
||||||
|
v-model="multiplexingStyleAdapt"
|
||||||
|
placeholder="Select"
|
||||||
|
placement="top-start"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in state.copyOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-button class="close-button" @click="dialogShow = false">{{
|
||||||
|
t('visualization.close')
|
||||||
|
}}</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!selectComponentCount"
|
||||||
|
class="confirm-button"
|
||||||
|
@click="saveMultiplexing"
|
||||||
|
>{{ t('visualization.multiplexing') }}</el-button
|
||||||
|
>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, reactive, ref, nextTick } from 'vue'
|
||||||
|
import DashboardPreviewShow from '@/views/dashboard/DashboardPreviewShow.vue'
|
||||||
|
import { copyStoreWithOut } from '@/store/modules/data-visualization/copy'
|
||||||
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
||||||
|
import PreviewShow from '@/views/data-visualization/PreviewShow.vue'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
const dvMainStore = dvMainStoreWithOut()
|
||||||
|
const snapshotStore = snapshotStoreWithOut()
|
||||||
|
const dialogShow = ref(false)
|
||||||
|
const copyStore = copyStoreWithOut()
|
||||||
|
const multiplexingPreviewShowRef = ref(null)
|
||||||
|
const { multiplexingStyleAdapt, curMultiplexingComponents } = storeToRefs(dvMainStore)
|
||||||
|
const curDvType = ref('dashboard')
|
||||||
|
const { t } = useI18n()
|
||||||
|
const selectComponentCount = computed(() => Object.keys(curMultiplexingComponents.value).length)
|
||||||
|
const state = reactive({
|
||||||
|
copyOptions: [
|
||||||
|
{ label: t('visualization.adapt_new_subject'), value: true },
|
||||||
|
{ label: t('visualization.keep_subject'), value: false }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const dialogInit = (dvType = 'dashboard') => {
|
||||||
|
curDvType.value = dvType
|
||||||
|
dialogShow.value = true
|
||||||
|
dvMainStore.initCurMultiplexingComponents()
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveMultiplexing = () => {
|
||||||
|
dialogShow.value = false
|
||||||
|
const previewStateInfo = multiplexingPreviewShowRef.value.getPreviewStateInfo()
|
||||||
|
const canvasViewInfoPreview = previewStateInfo.canvasViewInfoPreview
|
||||||
|
nextTick(() => {
|
||||||
|
copyStore.copyMultiplexingComponents(canvasViewInfoPreview)
|
||||||
|
snapshotStore.recordSnapshotCache('saveMultiplexing')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
dialogInit
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 120px;
|
||||||
|
}
|
||||||
|
.confirm-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
.multiplexing-area {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.multiplexing-footer {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adapt-count {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
left: 20px;
|
||||||
|
color: #646a73;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adapt-select {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 220px;
|
||||||
|
}
|
||||||
|
.adapt-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #1f2329;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.custom-drawer {
|
||||||
|
.ed-drawer__footer {
|
||||||
|
height: 64px !important;
|
||||||
|
padding: 0px !important;
|
||||||
|
box-shadow: 0 -1px 0px #d7d7d7 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ed-drawer__body {
|
||||||
|
padding: 0 0 64px 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -23,6 +23,8 @@ public class DatasetGroupInfoDTO extends DatasetNodeDTO {
|
|||||||
|
|
||||||
private String sql;
|
private String sql;
|
||||||
|
|
||||||
|
private String appId;
|
||||||
|
|
||||||
private Long total;
|
private Long total;
|
||||||
|
|
||||||
private String creator;
|
private String creator;
|
||||||
|