前端修改
@ -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;
|
||||
|