前端修改
@ -218,6 +218,9 @@ public class DatasetGroupManage {
 | 
			
		||||
        if (ObjectUtils.isNotEmpty(request.getLeaf())) {
 | 
			
		||||
            queryWrapper.eq("node_type", request.getLeaf() ? "dataset" : "folder");
 | 
			
		||||
        }
 | 
			
		||||
        if (ObjectUtils.isNotEmpty(request.getAppId())) {
 | 
			
		||||
            queryWrapper.eq("app_id", request.getAppId());
 | 
			
		||||
        }
 | 
			
		||||
        String info = CommunityUtils.getInfo();
 | 
			
		||||
        if (StringUtils.isNotBlank(info)) {
 | 
			
		||||
            queryWrapper.notExists(String.format(info, "core_dataset_group.id"));
 | 
			
		||||
 | 
			
		||||
@ -2,13 +2,13 @@ export default {
 | 
			
		||||
  server: {
 | 
			
		||||
    proxy: {
 | 
			
		||||
      '/api/f': {
 | 
			
		||||
        target: 'http://192.168.1.16:8100',
 | 
			
		||||
        target: 'http://192.168.1.38:8100',
 | 
			
		||||
        changeOrigin: true,
 | 
			
		||||
        rewrite: path => path.replace(/^\/api\/f/, '')
 | 
			
		||||
      },
 | 
			
		||||
      // 使用 proxy 实例
 | 
			
		||||
      '/api': {
 | 
			
		||||
        target: 'http://192.168.1.16:8100',
 | 
			
		||||
        target: 'http://192.168.1.38:8100',
 | 
			
		||||
        changeOrigin: true,
 | 
			
		||||
        rewrite: path => path.replace(/^\/api/, '')
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "0.0.0",
 | 
			
		||||
  "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",
 | 
			
		||||
    "ts:check": "vue-tsc --noEmit",
 | 
			
		||||
    "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",
 | 
			
		||||
    "ace-builds": "^1.15.3",
 | 
			
		||||
    "axios": "^1.3.3",
 | 
			
		||||
    "codemirror": "^6.0.1",
 | 
			
		||||
    "cross-env": "^7.0.3",
 | 
			
		||||
    "crypto-js": "^4.1.1",
 | 
			
		||||
    "dayjs": "^1.11.9",
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ export interface DatasetOrFolder {
 | 
			
		||||
  action?: string
 | 
			
		||||
  id?: number | string
 | 
			
		||||
  pid?: number | string
 | 
			
		||||
  appId?: number | string
 | 
			
		||||
  nodeType: 'folder' | 'dataset'
 | 
			
		||||
  union?: Array<{}>
 | 
			
		||||
  allFields?: Array<{}>
 | 
			
		||||
@ -151,8 +152,11 @@ export const perDelete = async (id): Promise<boolean> => {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getDatasourceList = async (weight?: number): Promise<IResponse> => {
 | 
			
		||||
  const data = { busiFlag: 'datasource' }
 | 
			
		||||
export const getDatasourceList = async (weight: number,appId:any): Promise<IResponse> => {
 | 
			
		||||
  const data = { 
 | 
			
		||||
    busiFlag: 'datasource',
 | 
			
		||||
    appId:appId
 | 
			
		||||
   }
 | 
			
		||||
  if (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">
 | 
			
		||||
import dvFilter from '@/assets/svg/dv-filter.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 dvTab from '@/assets/svg/dv-tab.svg'
 | 
			
		||||
import dvText from '@/assets/svg/dv-text.svg'
 | 
			
		||||
import dvView from '@/assets/svg/dv-view.svg'
 | 
			
		||||
import dvText from '@/assets/newimg/camvassvg/dv-text.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_copy_filled from '@/assets/svg/icon_copy_filled.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_redo_outlined from '@/assets/svg/icon_redo_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 { ElMessage, ElMessageBox } from 'element-plus-secondary'
 | 
			
		||||
import eventBus from '@/utils/eventBus'
 | 
			
		||||
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">
 | 
			
		||||
            {{ dvInfo.name }}
 | 
			
		||||
          </span>
 | 
			
		||||
          <div class="opt-area">
 | 
			
		||||
          <!-- <div class="opt-area">
 | 
			
		||||
            <el-tooltip effect="ndark" :content="$t('visualization.undo')" placement="bottom">
 | 
			
		||||
              <el-icon
 | 
			
		||||
                class="toolbar-hover-icon"
 | 
			
		||||
@ -348,11 +362,22 @@ const fullScreenPreview = () => {
 | 
			
		||||
                <Icon name="icon_redo_outlined"><icon_redo_outlined class="svg-icon" /></Icon>
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </div>
 | 
			
		||||
          </div> -->
 | 
			
		||||
        </div>
 | 
			
		||||
        <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
 | 
			
		||||
            show-split-line
 | 
			
		||||
            
 | 
			
		||||
            is-label
 | 
			
		||||
            :base-width="410"
 | 
			
		||||
            :icon-name="dvView"
 | 
			
		||||
@ -360,9 +385,35 @@ const fullScreenPreview = () => {
 | 
			
		||||
          >
 | 
			
		||||
            <user-view-group></user-view-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
 | 
			
		||||
            :base-width="115"
 | 
			
		||||
            :show-split-line="true"
 | 
			
		||||
            is-label
 | 
			
		||||
            :icon-name="dvFilter"
 | 
			
		||||
            :title="t('visualization.query_component')"
 | 
			
		||||
@ -377,15 +428,7 @@ const fullScreenPreview = () => {
 | 
			
		||||
          >
 | 
			
		||||
            <text-group></text-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">
 | 
			
		||||
            <tabs-group :dv-model="dvModel"></tabs-group>
 | 
			
		||||
          </component-group>
 | 
			
		||||
@ -406,12 +449,33 @@ const fullScreenPreview = () => {
 | 
			
		||||
          >
 | 
			
		||||
            <common-group></common-group>
 | 
			
		||||
          </component-group>
 | 
			
		||||
          
 | 
			
		||||
          <component-button-label
 | 
			
		||||
            :icon-name="icon_copy_filled"
 | 
			
		||||
            :title="t('visualization.multiplexing')"
 | 
			
		||||
            is-label
 | 
			
		||||
            @customClick="multiplexingCanvasOpen"
 | 
			
		||||
          ></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>
 | 
			
		||||
      </template>
 | 
			
		||||
      <div class="right-area">
 | 
			
		||||
@ -508,7 +572,7 @@ const fullScreenPreview = () => {
 | 
			
		||||
  height: @top-bar-height;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  overflow-x: auto;
 | 
			
		||||
  background: #1a1a1a;
 | 
			
		||||
  background: rgb(37,38,38);
 | 
			
		||||
  color: #ffffff;
 | 
			
		||||
  box-shadow: 0px 2px 4px 0px rgba(31, 35, 41, 0.12);
 | 
			
		||||
  display: flex;
 | 
			
		||||
@ -526,13 +590,18 @@ const fullScreenPreview = () => {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    .name-area {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      line-height: 24px;
 | 
			
		||||
      height: 24px;
 | 
			
		||||
      line-height: 46px;
 | 
			
		||||
      height: 46px;
 | 
			
		||||
      font-size: 16px;
 | 
			
		||||
      width: 300px;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      color: @dv-canvas-main-font-color;
 | 
			
		||||
      font-weight: 700;
 | 
			
		||||
      font-style: normal;
 | 
			
		||||
      font-size: 16px;
 | 
			
		||||
      color: #F2F4F5;
 | 
			
		||||
        
 | 
			
		||||
      input {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        left: 0;
 | 
			
		||||
@ -603,4 +672,35 @@ const fullScreenPreview = () => {
 | 
			
		||||
  margin-right: 20px;
 | 
			
		||||
  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>
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ withDefaults(
 | 
			
		||||
    inTable?: boolean
 | 
			
		||||
  }>(),
 | 
			
		||||
  {
 | 
			
		||||
    placement: 'bottom-end',
 | 
			
		||||
    placement: 'right-start',
 | 
			
		||||
    iconSize: '16px',
 | 
			
		||||
    inTable: false
 | 
			
		||||
  }
 | 
			
		||||
@ -53,9 +53,9 @@ const emit = defineEmits(['handleCommand'])
 | 
			
		||||
          :key="ele.label"
 | 
			
		||||
          :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>
 | 
			
		||||
          </el-icon>
 | 
			
		||||
          </el-icon> -->
 | 
			
		||||
          {{ ele.label }}
 | 
			
		||||
        </el-dropdown-item>
 | 
			
		||||
      </el-dropdown-menu>
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,9 @@ const emits = defineEmits(['customClick'])
 | 
			
		||||
    <el-row class="group_icon" :title="tips" @click="emits('customClick')">
 | 
			
		||||
      <el-col :span="24" class="group_inner" :class="{ 'inner-active': active }">
 | 
			
		||||
        <Icon><component class="svg-icon toolbar-icon" :is="iconName"></component></Icon>
 | 
			
		||||
        <span>{{ title }}</span>
 | 
			
		||||
        
 | 
			
		||||
         <!-- <img src="@/assets/newimg/avatar.png" alt="" srcset=""> -->
 | 
			
		||||
        <span >{{ title }}</span>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
    <el-divider class="group-right-border" v-if="showSplitLine" direction="vertical" />
 | 
			
		||||
@ -34,18 +36,18 @@ const emits = defineEmits(['customClick'])
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.group_inner {
 | 
			
		||||
  padding: 8px;
 | 
			
		||||
  padding: 0px 10px 4px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  color: #a6a6a6;
 | 
			
		||||
  color: #ffffff;
 | 
			
		||||
 | 
			
		||||
  span {
 | 
			
		||||
    float: left;
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    margin-top: 8px;
 | 
			
		||||
    margin-top: 5px;
 | 
			
		||||
    line-height: 12px;
 | 
			
		||||
  }
 | 
			
		||||
  &:hover {
 | 
			
		||||
 | 
			
		||||
@ -3308,7 +3308,8 @@ defineExpose({
 | 
			
		||||
    }
 | 
			
		||||
    .query-condition-list {
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      background: #f5f6f7;
 | 
			
		||||
      // background: #f5f6f7;
 | 
			
		||||
      background: rgb(38,38,38);
 | 
			
		||||
      border-right: 1px solid #dee0e3;
 | 
			
		||||
      width: 208px;
 | 
			
		||||
      overflow-y: auto;
 | 
			
		||||
@ -3377,7 +3378,7 @@ defineExpose({
 | 
			
		||||
        position: sticky;
 | 
			
		||||
        top: 0;
 | 
			
		||||
        justify-content: space-between;
 | 
			
		||||
        background: #fff;
 | 
			
		||||
        background: transparent;
 | 
			
		||||
        z-index: 5;
 | 
			
		||||
        .ed-radio {
 | 
			
		||||
          height: 20px;
 | 
			
		||||
@ -3497,7 +3498,7 @@ defineExpose({
 | 
			
		||||
        position: sticky;
 | 
			
		||||
        top: 0;
 | 
			
		||||
        justify-content: space-between;
 | 
			
		||||
        background: #fff;
 | 
			
		||||
        background: transparent;
 | 
			
		||||
        z-index: 5;
 | 
			
		||||
        .ed-checkbox {
 | 
			
		||||
          height: 20px;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
export interface BusiTreeNode {
 | 
			
		||||
  id: string | number
 | 
			
		||||
  pid: string | number
 | 
			
		||||
  appId: string | number
 | 
			
		||||
  name: string
 | 
			
		||||
  leaf?: boolean
 | 
			
		||||
  weight: number
 | 
			
		||||
@ -10,6 +11,7 @@ export interface BusiTreeNode {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface BusiTreeRequest {
 | 
			
		||||
  appId?: string
 | 
			
		||||
  busiFlag?: string
 | 
			
		||||
  leaf?: boolean
 | 
			
		||||
  weight?: number
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ const { loadStart, loadDone } = usePageLoading()
 | 
			
		||||
 | 
			
		||||
const whiteList = ['/login', '/de-link', '/chart-view', '/admin-login', '/401'] // 不重定向白名单
 | 
			
		||||
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) => {
 | 
			
		||||
  start()
 | 
			
		||||
  loadStart()
 | 
			
		||||
@ -138,6 +138,10 @@ router.beforeEach(async (to, from, next) => {
 | 
			
		||||
        next({ path: '/dataset-embedded-form', query: to.query })
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      if (to.path.includes('/datasetForm')) {
 | 
			
		||||
        next({ path: '/datasetEmbeddedForm', query: to.query })
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      permissionStore.setCurrentPath(to.path)
 | 
			
		||||
      next()
 | 
			
		||||
    } else if (
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,12 @@
 | 
			
		||||
import { isExternal } from '@/utils/validate'
 | 
			
		||||
import { cloneDeep } from 'lodash'
 | 
			
		||||
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')
 | 
			
		||||
export const Layout = () => import('@/layout/index.vue')
 | 
			
		||||
 | 
			
		||||
const xpackComName = 'components/plugin'
 | 
			
		||||
export const LayoutTransition = () => import('@/layout/components/LayoutTransition.vue')
 | 
			
		||||
// 后端控制路由生成
 | 
			
		||||
@ -33,13 +37,13 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
 | 
			
		||||
      meta: route.meta,
 | 
			
		||||
      props: route.props as Recordable
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (route.component) {
 | 
			
		||||
      let comModule = null
 | 
			
		||||
      if (route.component === xpackComName) {
 | 
			
		||||
        comModule = XpackComponent
 | 
			
		||||
      } else if (!route.component.startsWith('Layout')) {
 | 
			
		||||
        comModule = modules[`../views/${route.component}/index.vue`]
 | 
			
		||||
        // comModule = modules[`../viewsnew/${route.component}/index.vue`]
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (route.component === 'Layout') {
 | 
			
		||||
 | 
			
		||||
@ -177,6 +177,27 @@ export const routes: AppRouteRecordRaw[] = [
 | 
			
		||||
    hidden: true,
 | 
			
		||||
    meta: {},
 | 
			
		||||
    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 { store } from '@/store'
 | 
			
		||||
import { ref} from 'vue'
 | 
			
		||||
 | 
			
		||||
import { queryTreeApi, queryBusiTreeApi } from '@/api/visualization/dataVisualization'
 | 
			
		||||
import { getDatasetTree } from '@/api/dataset'
 | 
			
		||||
import { listDatasources } from '@/api/datasource'
 | 
			
		||||
@ -7,6 +9,8 @@ import type { BusiTreeRequest, BusiTreeNode } from '@/models/tree/TreeNode'
 | 
			
		||||
import { pathValid } from '@/store/modules/permission'
 | 
			
		||||
import { useCache } from '@/hooks/web/useCache'
 | 
			
		||||
import { useAppStoreWithOut } from '@/store/modules/app'
 | 
			
		||||
import { useRoute } from 'vue-router'
 | 
			
		||||
 | 
			
		||||
const appStore = useAppStoreWithOut()
 | 
			
		||||
const { wsCache } = useCache()
 | 
			
		||||
export interface InnerInteractive {
 | 
			
		||||
@ -58,6 +62,7 @@ export const interactiveStore = defineStore('interactive', {
 | 
			
		||||
          menuAuth: false
 | 
			
		||||
        }
 | 
			
		||||
        this.data[flag] = tempData
 | 
			
		||||
        
 | 
			
		||||
        if (flag === 0) {
 | 
			
		||||
          wsCache.set('panel-weight', {})
 | 
			
		||||
        }
 | 
			
		||||
@ -68,7 +73,13 @@ export const interactiveStore = defineStore('interactive', {
 | 
			
		||||
      }
 | 
			
		||||
      let res = resParam
 | 
			
		||||
      if (!resParam) {
 | 
			
		||||
        const route = useRoute()
 | 
			
		||||
        const appId:any = ref('')
 | 
			
		||||
        if (route.query.id) {
 | 
			
		||||
          appId.value = route.query.id
 | 
			
		||||
        }
 | 
			
		||||
        const method = apiMap[flag]
 | 
			
		||||
        param.appId = appId.value
 | 
			
		||||
        res = await method(param)
 | 
			
		||||
      }
 | 
			
		||||
      this.data[flag] = convertInteractive(res)
 | 
			
		||||
 | 
			
		||||
@ -46,9 +46,38 @@ export const usePermissionStore = defineStore('permission', {
 | 
			
		||||
    },
 | 
			
		||||
    generateRoutes(routers?: AppCustomRouteRecordRaw[] | string[]): Promise<unknown> {
 | 
			
		||||
      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[] = []
 | 
			
		||||
        routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[]) || []
 | 
			
		||||
 | 
			
		||||
        this.addRouters = routerMap.concat([
 | 
			
		||||
          {
 | 
			
		||||
            path: '/:catchAll(.*)',
 | 
			
		||||
 | 
			
		||||
@ -194,3 +194,32 @@
 | 
			
		||||
.vjs-custom-skin > .video-js .vjs-control-bar .vjs-fullscreen-control {
 | 
			
		||||
  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;
 | 
			
		||||
  font-size: 16px !important;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  color: #646a73 !important;
 | 
			
		||||
  color: #ffffff !important;
 | 
			
		||||
 | 
			
		||||
  &[aria-expanded='true'] {
 | 
			
		||||
    background: rgba(31, 35, 41, 0.1);
 | 
			
		||||
@ -671,3 +671,28 @@ strong {
 | 
			
		||||
.ed-main{
 | 
			
		||||
  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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
: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;
 | 
			
		||||
  line-height: 24px;
 | 
			
		||||
  height: 24px;
 | 
			
		||||
  font-size: 14px !important;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  input {
 | 
			
		||||
 | 
			
		||||
@ -263,6 +263,7 @@ const getNodeField = ({ datasourceId, tableName }) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getDatasource = () => {
 | 
			
		||||
  debugger  
 | 
			
		||||
  getDatasourceList().then(res => {
 | 
			
		||||
    const _list = (res as unknown as DataSource[]) || []
 | 
			
		||||
    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 { iconFieldMap } from '@/components/icon-group/field-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 {
 | 
			
		||||
  dataTransfer: DataTransfer
 | 
			
		||||
}
 | 
			
		||||
@ -81,7 +86,6 @@ const { wsCache } = useCache()
 | 
			
		||||
const appStore = useAppStoreWithOut()
 | 
			
		||||
const embeddedStore = useEmbedded()
 | 
			
		||||
const { t } = useI18n()
 | 
			
		||||
const route = useRoute()
 | 
			
		||||
const { push } = useRouter()
 | 
			
		||||
const quotaTableHeight = ref(238)
 | 
			
		||||
const creatDsFolder = ref()
 | 
			
		||||
@ -1332,7 +1336,7 @@ const getSqlResultHeight = () => {
 | 
			
		||||
  sqlResultHeight.value = (document.querySelector('.sql-result') as HTMLElement).offsetHeight
 | 
			
		||||
}
 | 
			
		||||
const getDatasource = (weight?: number) => {
 | 
			
		||||
  getDatasourceList(weight).then(res => {
 | 
			
		||||
  getDatasourceList(weight,appId.value).then(res => {
 | 
			
		||||
    const _list = (res as unknown as DataSource[]) || []
 | 
			
		||||
    if (_list && _list.length > 0 && _list[0].id === '0') {
 | 
			
		||||
      state.dataSourceList = dfsChild(_list[0].children)
 | 
			
		||||
 | 
			
		||||
@ -66,7 +66,7 @@ const handleChange = (val: boolean) => {
 | 
			
		||||
          </el-icon>
 | 
			
		||||
          <div class="info">
 | 
			
		||||
            <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>
 | 
			
		||||
          <el-button class="create" secondary :disabled="disabled" @click="createDataset">
 | 
			
		||||
            {{ t('data_source.go_to_create') }}
 | 
			
		||||
 | 
			
		||||
@ -46,22 +46,22 @@ function updateClick(row:any){
 | 
			
		||||
  paramsObj.value = row
 | 
			
		||||
  isAppPopUp.value = true
 | 
			
		||||
}
 | 
			
		||||
function routerClick(item){
 | 
			
		||||
function routerClick(item,path){
 | 
			
		||||
  router.push({
 | 
			
		||||
    path: '/module',
 | 
			
		||||
    path: path,
 | 
			
		||||
    query: {
 | 
			
		||||
      id: item.id
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
function delClick(id){
 | 
			
		||||
  ElMessageBox.confirm('您确定删除该项目模块及内容吗?', {
 | 
			
		||||
function delClick(row){
 | 
			
		||||
  ElMessageBox.confirm('您确定删除'+ row.name+'项目及所有模块吗?', {
 | 
			
		||||
      confirmButtonType: 'primary',
 | 
			
		||||
      type: 'warning',
 | 
			
		||||
      confirmButtonText: '确定',
 | 
			
		||||
      cancelButtonText: '取消',
 | 
			
		||||
    }).then(() => {
 | 
			
		||||
      applicationDel(id).then(res => {
 | 
			
		||||
      applicationDel(row.id).then(res => {
 | 
			
		||||
        if(res.data.code == '0'){
 | 
			
		||||
          getDatasetList()
 | 
			
		||||
          ElMessage.success('删除成功')
 | 
			
		||||
@ -107,16 +107,17 @@ function delClick(id){
 | 
			
		||||
          <div class="mask_box_img">
 | 
			
		||||
            <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/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/permission.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/del.png" alt="" title="删除" @click="delClick(item.id)">
 | 
			
		||||
            <img src="@/assets/newimg/icon/del.png" alt="" title="删除" @click="delClick(item)">
 | 
			
		||||
          </div>
 | 
			
		||||
          <div style="display: flex;justify-content: center;">
 | 
			
		||||
            <div class="yulan">预览</div>
 | 
			
		||||
            <div class="mokuaipeizhi" @click="routerClick(item)">模块配置</div>
 | 
			
		||||
            <div class="mokuaipeizhi" @click="routerClick(item,'/module')">模块配置</div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@ -284,3 +285,9 @@ function delClick(id){
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
<style>
 | 
			
		||||
.ed-message-box__headerbtn{
 | 
			
		||||
  top: -15px;
 | 
			
		||||
  right: -10px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@ -430,8 +430,8 @@ function delTreeClic(){ // 删除
 | 
			
		||||
  border-color: rgba(51, 51, 51, 0);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  box-shadow:0 0 0 1px rgba(67, 67, 67, 1) ;
 | 
			
		||||
    }
 | 
			
		||||
    :deep(.ed-input__inner){
 | 
			
		||||
}
 | 
			
		||||
:deep(.ed-input__inner){
 | 
			
		||||
  background-color: rgba(37, 38, 38, 1);
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  border-width: 1px;
 | 
			
		||||
@ -439,17 +439,17 @@ function delTreeClic(){ // 删除
 | 
			
		||||
  border-color: rgba(51, 51, 51, 0);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
    }
 | 
			
		||||
    :deep(.ed-input__wrapper:hover){
 | 
			
		||||
}
 | 
			
		||||
:deep(.ed-input__wrapper:hover){
 | 
			
		||||
  box-shadow:0 0 0 1px rgba(80, 80, 80, 1) ;
 | 
			
		||||
    }
 | 
			
		||||
    :deep(.ed-input.is-disabled .ed-input__wrapper){
 | 
			
		||||
}
 | 
			
		||||
:deep(.ed-input.is-disabled .ed-input__wrapper){
 | 
			
		||||
  background-color: rgba(37, 38, 38, 0);
 | 
			
		||||
  box-shadow:0 0 0 1px rgba(80, 80, 80, 1) ;
 | 
			
		||||
    }
 | 
			
		||||
    :deep(.ed-input.is-disabled .ed-input__inner){
 | 
			
		||||
}
 | 
			
		||||
:deep(.ed-input.is-disabled .ed-input__inner){
 | 
			
		||||
  background-color: rgba(37, 38, 38, 0);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
@ -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 appId;
 | 
			
		||||
 | 
			
		||||
    private Long total;
 | 
			
		||||
 | 
			
		||||
    private String creator;
 | 
			
		||||
 | 
			
		||||