Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
7bd5c034e5
|
@ -1,4 +1,5 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
index.html
|
index.html
|
||||||
.vscode
|
.vscode
|
||||||
|
docker
|
|
@ -0,0 +1,6 @@
|
||||||
|
FROM nginx
|
||||||
|
ADD nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
ADD docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
COPY dist /usr/share/nginx/html
|
||||||
|
CMD ["sh","/docker-entrypoint.sh"]
|
||||||
|
#ADD oauth2 /usr/share/nginx/html/oauth2
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:1.0.0 .
|
||||||
|
docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:1.0.0
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
API_BASE_PATH=$API_BASE_PATH;
|
||||||
|
NAMESERVERS=$(cat /etc/resolv.conf | grep "nameserver" | awk '{print $2}' | tr '\n' ' ')
|
||||||
|
if [ -z "$API_BASE_PATH" ]; then
|
||||||
|
API_BASE_PATH="http://jetlinks:8844/";
|
||||||
|
fi
|
||||||
|
|
||||||
|
apiUrl="proxy_pass $API_BASE_PATH\$1;"
|
||||||
|
resolver="resolver $NAMESERVERS ipv6=off;"
|
||||||
|
|
||||||
|
sed -i '11c '"$resolver"'' /etc/nginx/conf.d/default.conf
|
||||||
|
sed -i '20c '"$apiUrl"'' /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
nginx -g "daemon off;"
|
|
@ -0,0 +1,38 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
# gzip config
|
||||||
|
gzip on;
|
||||||
|
gzip_min_length 1k;
|
||||||
|
gzip_comp_level 9;
|
||||||
|
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_disable "MSIE [1-6]\.";
|
||||||
|
resolver $NAMESERVERS ipv6=off;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
location / {
|
||||||
|
index index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~/api/ {
|
||||||
|
if ($request_uri ~* ^/api/(.*)$) {
|
||||||
|
proxy_pass http://host.docker.internal:8840/$1;
|
||||||
|
}
|
||||||
|
#proxy_pass http://host.docker.internal:8840/;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_connect_timeout 1;
|
||||||
|
proxy_buffering off;
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
proxy_cache off;
|
||||||
|
proxy_send_timeout 30m;
|
||||||
|
proxy_read_timeout 30m;
|
||||||
|
client_max_body_size 500m;
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,14 +31,15 @@
|
||||||
"unplugin-vue-components": "^0.22.12",
|
"unplugin-vue-components": "^0.22.12",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
"vue3-markdown-it": "^1.0.10"
|
"vue3-markdown-it": "^1.0.10",
|
||||||
|
"vue3-ts-jsoneditor": "^2.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.4.1",
|
"@commitlint/cli": "^17.4.1",
|
||||||
"@commitlint/config-conventional": "^17.4.0",
|
"@commitlint/config-conventional": "^17.4.0",
|
||||||
"@types/lodash-es": "^4.17.6",
|
"@types/lodash-es": "^4.17.6",
|
||||||
"@types/moment": "^2.13.0",
|
"@types/moment": "^2.13.0",
|
||||||
"@types/node": "^18.11.17",
|
"@types/node": "^18.14.0",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"@vuemap/unplugin-resolver": "^1.0.4",
|
"@vuemap/unplugin-resolver": "^1.0.4",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
|
@ -1572,9 +1573,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "18.11.18",
|
"version": "18.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz",
|
||||||
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
|
"integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/normalize-package-data": {
|
"node_modules/@types/normalize-package-data": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
|
@ -8883,6 +8884,11 @@
|
||||||
"builtins": "^1.0.3"
|
"builtins": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vanilla-jsoneditor": {
|
||||||
|
"version": "0.7.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vanilla-jsoneditor/-/vanilla-jsoneditor-0.7.11.tgz",
|
||||||
|
"integrity": "sha512-DFm6Fg6gzP4FlOaydDCRWpqEsmI5umpb9fOV3hKbbNDO1ZHTUpuMwi1SmMyoEsSgdDGGzRLFNCrW0URQ9Skx4w=="
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
|
||||||
|
@ -9146,6 +9152,27 @@
|
||||||
"markdown-it-toc-done-right": "^4.2.0"
|
"markdown-it-toc-done-right": "^4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue3-ts-jsoneditor": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue3-ts-jsoneditor/-/vue3-ts-jsoneditor-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-u8dWSG21dK0+iwbY6yhpcyRstQEKbO53+4Ma4Qhf9ED/wbGURjLvX/GTMbVsU7xB4gJKEs1a01AxBIgIab+4uA==",
|
||||||
|
"funding": [
|
||||||
|
"https://uahelp.monobank.ua/",
|
||||||
|
"https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi",
|
||||||
|
"https://www.comebackalive.in.ua/",
|
||||||
|
"https://armysos.com.ua/donate/",
|
||||||
|
"http://wings-phoenix.org.ua/en/about-fund/",
|
||||||
|
"https://novaposhta.ua/eng/",
|
||||||
|
"https://voices.org.ua/en/donat/",
|
||||||
|
"https://www.unicef.org.uk/donate/donate-now-to-protect-children-in-ukraine/",
|
||||||
|
"https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=FYFZPVQN8J7YC&source=url",
|
||||||
|
"https://novaukraine.org/"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"vanilla-jsoneditor": "^0.7.9",
|
||||||
|
"vue": "^3.2.37"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/warning": {
|
"node_modules/warning": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
|
@ -10594,9 +10621,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "18.11.18",
|
"version": "18.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz",
|
||||||
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
|
"integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A=="
|
||||||
},
|
},
|
||||||
"@types/normalize-package-data": {
|
"@types/normalize-package-data": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
|
@ -16470,6 +16497,11 @@
|
||||||
"builtins": "^1.0.3"
|
"builtins": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vanilla-jsoneditor": {
|
||||||
|
"version": "0.7.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vanilla-jsoneditor/-/vanilla-jsoneditor-0.7.11.tgz",
|
||||||
|
"integrity": "sha512-DFm6Fg6gzP4FlOaydDCRWpqEsmI5umpb9fOV3hKbbNDO1ZHTUpuMwi1SmMyoEsSgdDGGzRLFNCrW0URQ9Skx4w=="
|
||||||
|
},
|
||||||
"vite": {
|
"vite": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.3.tgz",
|
||||||
|
@ -16657,6 +16689,15 @@
|
||||||
"markdown-it-toc-done-right": "^4.2.0"
|
"markdown-it-toc-done-right": "^4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue3-ts-jsoneditor": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue3-ts-jsoneditor/-/vue3-ts-jsoneditor-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-u8dWSG21dK0+iwbY6yhpcyRstQEKbO53+4Ma4Qhf9ED/wbGURjLvX/GTMbVsU7xB4gJKEs1a01AxBIgIab+4uA==",
|
||||||
|
"requires": {
|
||||||
|
"vanilla-jsoneditor": "^0.7.9",
|
||||||
|
"vue": "^3.2.37"
|
||||||
|
}
|
||||||
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
"name": "jetlinks-vue",
|
"name": "jetlinks-vue",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --mode develop",
|
"dev": "vite --mode develop",
|
||||||
"build": "vite build --mode production",
|
"build": "node --max_old_space_size=1024000 ./node_modules/vite/bin/vite.js build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"eslint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
"eslint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
||||||
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
|
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
|
||||||
|
@ -22,6 +21,7 @@
|
||||||
"driver.js": "^0.9.8",
|
"driver.js": "^0.9.8",
|
||||||
"echarts": "^5.4.1",
|
"echarts": "^5.4.1",
|
||||||
"event-source-polyfill": "^1.0.31",
|
"event-source-polyfill": "^1.0.31",
|
||||||
|
"global": "^4.4.0",
|
||||||
"jetlinks-store": "^0.0.3",
|
"jetlinks-store": "^0.0.3",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
|
@ -31,12 +31,15 @@
|
||||||
"mavon-editor": "^2.10.4",
|
"mavon-editor": "^2.10.4",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"monaco-editor": "^0.24.0",
|
"monaco-editor": "^0.24.0",
|
||||||
|
"nrm": "^1.2.5",
|
||||||
"pinia": "^2.0.28",
|
"pinia": "^2.0.28",
|
||||||
"unplugin-auto-import": "^0.12.1",
|
"unplugin-auto-import": "^0.12.1",
|
||||||
"unplugin-vue-components": "^0.22.12",
|
"unplugin-vue-components": "^0.22.12",
|
||||||
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
"vue3-markdown-it": "^1.0.10"
|
"vue3-markdown-it": "^1.0.10",
|
||||||
|
"vue3-ts-jsoneditor": "^2.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.4.1",
|
"@commitlint/cli": "^17.4.1",
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import server from '@/utils/request';
|
||||||
|
import { BASE_API_PATH } from '@/utils/variable';
|
||||||
|
|
||||||
|
export const FIRMWARE_UPLOAD = `${BASE_API_PATH}/file/upload`;
|
||||||
|
|
||||||
|
export const save = (data: object) => server.post(`/firmware`, data);
|
||||||
|
|
||||||
|
export const update = (data: object) => server.patch(`/firmware`, data);
|
||||||
|
|
||||||
|
export const remove = (id: string) => server.remove(`/firmware/${id}`);
|
||||||
|
|
||||||
|
export const query = (data: object) => server.post(`/firmware/_query/`, data);
|
||||||
|
|
||||||
|
export const querySystemApi = (data?: object) =>
|
||||||
|
server.post(`/system/config/scopes`, data);
|
||||||
|
|
||||||
|
export const task = (data: Record<string, unknown>) =>
|
||||||
|
server.post(`/firmware/upgrade/task/detail/_query`, data);
|
||||||
|
|
||||||
|
export const taskById = (id: string) =>
|
||||||
|
server.get(`/firmware/upgrade/task/${id}`);
|
||||||
|
|
||||||
|
export const saveTask = (data: Record<string, unknown>) =>
|
||||||
|
server.post(`/firmware/upgrade/task`, data);
|
||||||
|
|
||||||
|
export const deleteTask = (id: string) =>
|
||||||
|
server.remove(`/firmware/upgrade/task/${id}`);
|
||||||
|
|
||||||
|
export const history = (data: Record<string, unknown>) =>
|
||||||
|
server.post(`/firmware/upgrade/history/_query`, data);
|
||||||
|
|
||||||
|
export const historyCount = (data: Record<string, unknown>) =>
|
||||||
|
server.post(`/firmware/upgrade/history/_count`, data);
|
||||||
|
|
||||||
|
export const startTask = (id: string, data: string[]) =>
|
||||||
|
server.post(`/firmware/upgrade/task/${id}/_start`, data);
|
||||||
|
|
||||||
|
export const stopTask = (id: string) =>
|
||||||
|
server.post(`/firmware/upgrade/task/${id}/_stop`);
|
||||||
|
|
||||||
|
export const startOneTask = (data: string[]) =>
|
||||||
|
server.post(`/firmware/upgrade/task/_start`, data);
|
||||||
|
|
||||||
|
// export const queryProduct = (data?: any) =>
|
||||||
|
// server.post(`/device-product/_query/no-paging`, data);
|
||||||
|
export const queryProduct = (data?: any) =>
|
||||||
|
server.post(`/device-product/detail/_query/no-paging`, data);
|
||||||
|
|
||||||
|
export const queryDevice = () =>
|
||||||
|
server.get(`/device/instance/_query/no-paging?paging=false`);
|
||||||
|
|
||||||
|
export const validateVersion = (productId: string, versionOrder: number) =>
|
||||||
|
server.get(`/firmware/${productId}/${versionOrder}/exists`);
|
|
@ -83,22 +83,22 @@ export const batchDeleteDevice = (data: string[]) => server.put(`/device-instanc
|
||||||
* @param type 文件类型
|
* @param type 文件类型
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deviceTemplateDownload = (productId: string, type: string) => `${BASE_API_PATH}/device-instance/${productId}/template.${type}`
|
export const deviceTemplateDownload = (productId: string, type: string) => `${BASE_API_PATH}/device-instance/${productId}/template.${type}`
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备导入
|
* 设备导入
|
||||||
* @param productId 产品id
|
* @param productId 产品id
|
||||||
* @param type 文件类型
|
* @param type 文件类型
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
|
export const deviceImport = (productId: string, fileUrl: string, autoDeploy: boolean) => `${BASE_API_PATH}/device-instance/${productId}/import?fileUrl=${fileUrl}&autoDeploy=${autoDeploy}&:X_Access_Token=${LocalStore.get(TOKEN_KEY)}`
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备导出
|
* 设备导出
|
||||||
* @param productId 产品id
|
* @param productId 产品id
|
||||||
* @param type 文件类型
|
* @param type 文件类型
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? '/' + productId : ''}/export.${type}`
|
export const deviceExport = (productId: string, type: string) => `${BASE_API_PATH}/device-instance${!!productId ? '/' + productId : ''}/export.${type}`
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,7 +143,7 @@ export const _disconnect = (id: string) => server.post(`/device-instance/${id}/d
|
||||||
*/
|
*/
|
||||||
export const queryUserListNoPaging = () => server.post(`/user/_query/no-paging`, {
|
export const queryUserListNoPaging = () => server.post(`/user/_query/no-paging`, {
|
||||||
paging: false,
|
paging: false,
|
||||||
sorts: [{name: 'name', order: "asc"}]
|
sorts: [{ name: 'name', order: "asc" }]
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -347,4 +347,59 @@ export const settingProperties = (deviceId: string, data: any) => server.put(`/d
|
||||||
* @param data
|
* @param data
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const execute = (id: string, action: string, data: any) => server.post(`/device/invoked/${id}/function/${action}`, data)
|
export const execute = (id: string, action: string, data: any) => server.post(`/device/invoked/${id}/function/${action}`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询通道列表不分页
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const queryChannelNoPaging = (data: any) => server.post(`data-collect/channel/_query/no-paging`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询采集器列表不分页
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const queryCollectorNoPaging = (data: any) => server.post(`/data-collect/collector/_query/no-paging`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询点位列表不分页
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const queryPointNoPaging = (data: any) => server.post(`/data-collect/point/_query/no-paging`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询映射列表
|
||||||
|
* @param thingType
|
||||||
|
* @param thingId
|
||||||
|
* @param params
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const queryMapping = (thingType: string, thingId: any, params?: any) => server.get(`/things/collector/${thingType}/${thingId}/_query`, params)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除映射
|
||||||
|
* @param thingType
|
||||||
|
* @param thingId
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const removeMapping = (thingType: string, thingId: any, data?: any) => server.post(`/things/collector/${thingType}/${thingId}/_delete`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射树
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const treeMapping = (data?: any) => server.post(`/data-collect/channel/_all/tree`, data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存映射
|
||||||
|
* @param thingId
|
||||||
|
* @param provider
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const saveMapping = (thingId: any, provider: string, data?: any) => server.patch(`/things/collector/device/${thingId}/${provider}`, data)
|
|
@ -3,4 +3,24 @@ import server from '@/utils/request';
|
||||||
// 获取tree数据-第一层
|
// 获取tree数据-第一层
|
||||||
export const getTreeOne_api = () => server.get(`/v3/api-docs/swagger-config`);
|
export const getTreeOne_api = () => server.get(`/v3/api-docs/swagger-config`);
|
||||||
// 获取tree数据-第二层
|
// 获取tree数据-第二层
|
||||||
export const getTreeTwo_api = (name:string) => server.get(`/v3/api-docs/${name}`);
|
export const getTreeTwo_api = (name: string) => server.get(`/v3/api-docs/${name}`);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已授权的接口ID
|
||||||
|
* @param id 第三方平台的ID
|
||||||
|
*/
|
||||||
|
export const getApiGranted_api = (id: string) => server.get(`/application/${id}/granted`);
|
||||||
|
/**
|
||||||
|
* 获取可授权的接口ID
|
||||||
|
*/
|
||||||
|
export const apiOperations_api = () => server.get(`/application/operations`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增可授权的接口ID
|
||||||
|
*/
|
||||||
|
export const addOperations_api = (data:object) => server.patch(`/application/operations/_batch`,data);
|
||||||
|
/**
|
||||||
|
* 删除可授权的接口ID
|
||||||
|
*/
|
||||||
|
export const delOperations_api = (data:object) => server.remove(`/application/operations/_batch`,{},{data});
|
|
@ -49,6 +49,9 @@ const iconKeys = [
|
||||||
'PartitionOutlined',
|
'PartitionOutlined',
|
||||||
'ShareAltOutlined',
|
'ShareAltOutlined',
|
||||||
'playCircleOutlined',
|
'playCircleOutlined',
|
||||||
|
'RightOutlined',
|
||||||
|
'FileTextOutlined',
|
||||||
|
'UploadOutlined'
|
||||||
]
|
]
|
||||||
|
|
||||||
const Icon = (props: {type: string}) => {
|
const Icon = (props: {type: string}) => {
|
||||||
|
|
|
@ -6,29 +6,29 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import * as monaco from 'monaco-editor';
|
import * as monaco from 'monaco-editor';
|
||||||
|
|
||||||
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
// import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||||
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
// import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
||||||
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
|
// import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
|
||||||
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
|
// import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
|
||||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
// import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
||||||
|
//
|
||||||
self.MonacoEnvironment = {
|
// self.MonacoEnvironment = {
|
||||||
getWorker(_, label) {
|
// getWorker(_, label) {
|
||||||
if (label === 'json') {
|
// if (label === 'json') {
|
||||||
return new jsonWorker();
|
// return new jsonWorker();
|
||||||
}
|
// }
|
||||||
if (label === 'css') {
|
// if (label === 'css') {
|
||||||
return new cssWorker();
|
// return new cssWorker();
|
||||||
}
|
// }
|
||||||
if (label === 'html') {
|
// if (label === 'html') {
|
||||||
return new htmlWorker();
|
// return new htmlWorker();
|
||||||
}
|
// }
|
||||||
if (['typescript', 'javascript'].includes(label)) {
|
// if (['typescript', 'javascript'].includes(label)) {
|
||||||
return new tsWorker();
|
// return new tsWorker();
|
||||||
}
|
// }
|
||||||
return new editorWorker();
|
// return new editorWorker();
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: [String, Number],
|
modelValue: [String, Number],
|
||||||
|
|
|
@ -110,12 +110,19 @@ import { PropType } from 'vue'
|
||||||
import type { SearchItemData, SearchProps, Terms } from './types'
|
import type { SearchItemData, SearchProps, Terms } from './types'
|
||||||
import { cloneDeep, get, isArray, isFunction } from 'lodash-es'
|
import { cloneDeep, get, isArray, isFunction } from 'lodash-es'
|
||||||
import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm'
|
import { filterTreeSelectNode, filterSelectNode } from '@/utils/comm'
|
||||||
|
import { useUrlSearchParams } from '@vueuse/core'
|
||||||
|
|
||||||
type ItemType = SearchProps['type']
|
type ItemType = SearchProps['type']
|
||||||
|
|
||||||
interface Emit {
|
interface Emit {
|
||||||
(e: 'change', data: SearchItemData): void
|
(e: 'change', data: SearchItemData): void
|
||||||
}
|
}
|
||||||
|
type UrlParam = {
|
||||||
|
q: string | null
|
||||||
|
target: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlParams = useUrlSearchParams<UrlParam>('hash')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
columns: {
|
columns: {
|
||||||
|
@ -134,6 +141,10 @@ const props = defineProps({
|
||||||
termsItem: {
|
termsItem: {
|
||||||
type: Object as PropType<Terms>,
|
type: Object as PropType<Terms>,
|
||||||
default: {}
|
default: {}
|
||||||
|
},
|
||||||
|
reset: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -278,28 +289,35 @@ const valueChange = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleQuery = (_params: UrlParam) => {
|
||||||
|
if (_params.q) {
|
||||||
|
const path = props.index < 4 ? [0, 'terms', props.index - 1] : [1, 'terms', props.index - 4]
|
||||||
|
const itemData: SearchItemData = get(props.termsItem.terms, path)
|
||||||
|
if (itemData) {
|
||||||
|
termsModel.type = itemData.type
|
||||||
|
termsModel.column = itemData.column
|
||||||
|
termsModel.termType = itemData.termType
|
||||||
|
termsModel.value = itemData.value
|
||||||
|
const item = columnOptionMap.get(itemData.column)
|
||||||
|
getComponent(item.type) // 处理Item的组件类型
|
||||||
|
|
||||||
|
// 处理options 以及 request
|
||||||
|
if ('options' in item) {
|
||||||
|
handleItemOptions(item.options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleItem()
|
handleItem()
|
||||||
|
|
||||||
watch( props.termsItem, (newValue) => {
|
nextTick(() => {
|
||||||
|
handleQuery(urlParams)
|
||||||
|
})
|
||||||
|
|
||||||
const path = props.index < 4 ? [0, 'terms', props.index - 1] : [1, 'terms', props.index - 4]
|
watch(() => props.reset, () => {
|
||||||
const itemData: SearchItemData = get(newValue.terms, path)
|
handleItem()
|
||||||
if (itemData) {
|
})
|
||||||
termsModel.type = itemData.type
|
|
||||||
termsModel.column = itemData.column
|
|
||||||
termsModel.termType = itemData.termType
|
|
||||||
termsModel.value = itemData.value
|
|
||||||
const item = columnOptionMap.get(itemData.column)
|
|
||||||
getComponent(item.type) // 处理Item的组件类型
|
|
||||||
|
|
||||||
// 处理options 以及 request
|
|
||||||
if ('options' in item) {
|
|
||||||
handleItemOptions(item.options)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
handleItem()
|
|
||||||
}
|
|
||||||
}, { immediate: true, deep: true })
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
<div v-if='props.type === "advanced"' :class='["JSearch-content senior", expand ? "senior-expand" : "", screenSize ? "big" : "small"]'>
|
<div v-if='props.type === "advanced"' :class='["JSearch-content senior", expand ? "senior-expand" : "", screenSize ? "big" : "small"]'>
|
||||||
<div :class='["JSearch-items", expand ? "items-expand" : "", layout]'>
|
<div :class='["JSearch-items", expand ? "items-expand" : "", layout]'>
|
||||||
<div class='left'>
|
<div class='left'>
|
||||||
<SearchItem :expand='expand' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms'/>
|
<SearchItem :expand='expand' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms' :reset='resetNumber'/>
|
||||||
<SearchItem v-if='expand' :expand='expand' :index='2' :columns='searchItems' @change='(v) => itemValueChange(v, 2)' :termsItem='terms'/>
|
<SearchItem v-if='expand' :expand='expand' :index='2' :columns='searchItems' @change='(v) => itemValueChange(v, 2)' :termsItem='terms' :reset='resetNumber'/>
|
||||||
<SearchItem v-if='expand' :expand='expand' :index='3' :columns='searchItems' @change='(v) => itemValueChange(v, 3)' :termsItem='terms'/>
|
<SearchItem v-if='expand' :expand='expand' :index='3' :columns='searchItems' @change='(v) => itemValueChange(v, 3)' :termsItem='terms' :reset='resetNumber'/>
|
||||||
</div>
|
</div>
|
||||||
<div class='center' v-if='expand'>
|
<div class='center' v-if='expand'>
|
||||||
<a-select
|
<a-select
|
||||||
|
@ -16,9 +16,9 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class='right' v-if='expand'>
|
<div class='right' v-if='expand'>
|
||||||
<SearchItem :expand='expand' :index='4' :columns='searchItems' @change='(v) => itemValueChange(v, 4)' :termsItem='terms'/>
|
<SearchItem :expand='expand' :index='4' :columns='searchItems' @change='(v) => itemValueChange(v, 4)' :termsItem='terms' :reset='resetNumber'/>
|
||||||
<SearchItem :expand='expand' :index='5' :columns='searchItems' @change='(v) => itemValueChange(v, 5)' :termsItem='terms'/>
|
<SearchItem :expand='expand' :index='5' :columns='searchItems' @change='(v) => itemValueChange(v, 5)' :termsItem='terms' :reset='resetNumber'/>
|
||||||
<SearchItem :expand='expand' :index='6' :columns='searchItems' @change='(v) => itemValueChange(v, 6)' :termsItem='terms'/>
|
<SearchItem :expand='expand' :index='6' :columns='searchItems' @change='(v) => itemValueChange(v, 6)' :termsItem='terms' :reset='resetNumber'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class='["JSearch-footer", expand ? "expand" : ""]'>
|
<div :class='["JSearch-footer", expand ? "expand" : ""]'>
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
<div v-else class='JSearch-content simple big'>
|
<div v-else class='JSearch-content simple big'>
|
||||||
<div class='JSearch-items'>
|
<div class='JSearch-items'>
|
||||||
<div class='left'>
|
<div class='left'>
|
||||||
<SearchItem :expand='false' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms'/>
|
<SearchItem :expand='false' :index='1' :columns='searchItems' @change='(v) => itemValueChange(v, 1)' :termsItem='terms' :reset='resetNumber'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='JSearch-footer'>
|
<div class='JSearch-footer'>
|
||||||
|
@ -114,8 +114,10 @@ const historyList = ref([])
|
||||||
const layout = ref('horizontal')
|
const layout = ref('horizontal')
|
||||||
// 当前组件宽度 true 大于1000
|
// 当前组件宽度 true 大于1000
|
||||||
const screenSize = ref(true)
|
const screenSize = ref(true)
|
||||||
|
const resetNumber = ref(1)
|
||||||
|
|
||||||
const searchItems = ref<SearchProps[]>([])
|
const searchItems = ref<SearchProps[]>([])
|
||||||
|
|
||||||
// 当前查询条件
|
// 当前查询条件
|
||||||
const terms = reactive<Terms>({ terms: [] })
|
const terms = reactive<Terms>({ terms: [] })
|
||||||
|
|
||||||
|
@ -134,7 +136,8 @@ const searchParams = reactive({
|
||||||
const handleItems = () => {
|
const handleItems = () => {
|
||||||
searchItems.value = []
|
searchItems.value = []
|
||||||
columnOptionMap.clear()
|
columnOptionMap.clear()
|
||||||
props.columns!.forEach((item, index) => {
|
const cloneColumns = cloneDeep(props.columns)
|
||||||
|
cloneColumns!.forEach((item, index) => {
|
||||||
if (item.search && Object.keys(item.search).length) {
|
if (item.search && Object.keys(item.search).length) {
|
||||||
columnOptionMap.set(item.dataIndex, item.search)
|
columnOptionMap.set(item.dataIndex, item.search)
|
||||||
searchItems.value.push({
|
searchItems.value.push({
|
||||||
|
@ -231,6 +234,8 @@ const reset = () => {
|
||||||
urlParams.q = null
|
urlParams.q = null
|
||||||
urlParams.target = null
|
urlParams.target = null
|
||||||
}
|
}
|
||||||
|
resetNumber.value += 1
|
||||||
|
emit('search', terms)
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(width, (value) => {
|
watch(width, (value) => {
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<a-table rowKey="id" :rowSelection="rowSelection" :columns="[..._columns]" :dataSource="_dataSource" :pagination="false">
|
<a-table rowKey="operationId" :rowSelection="rowSelection" :columns="[..._columns]" :dataSource="_dataSource" :pagination="false">
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<!-- <template v-if="column.key === 'action'">
|
<!-- <template v-if="column.key === 'action'">
|
||||||
<a-space>
|
<a-space>
|
||||||
|
|
|
@ -10,4 +10,5 @@ declare module '*.gif';
|
||||||
declare module '*.bmp';
|
declare module '*.bmp';
|
||||||
declare module '*.js';
|
declare module '*.js';
|
||||||
declare module '*.ts';
|
declare module '*.ts';
|
||||||
declare module 'js-cookie';
|
declare module 'js-cookie';
|
||||||
|
declare module 'jetlinks-ui-components';
|
|
@ -5,10 +5,13 @@ import components from './components'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import './style.less'
|
import './style.less'
|
||||||
import 'ant-design-vue/es/notification/style/css';
|
import 'ant-design-vue/es/notification/style/css';
|
||||||
|
// import jConmonents from 'jetlinks-ui-components'
|
||||||
|
// import 'jetlinks-ui-components/lib/style'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(store)
|
app.use(store)
|
||||||
.use(router)
|
.use(router)
|
||||||
.use(components)
|
.use(components)
|
||||||
|
// .use(jConmonents)
|
||||||
.mount('#app')
|
.mount('#app')
|
||||||
|
|
|
@ -8,59 +8,51 @@ import { useMenuStore } from 'store/menu'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes: menus
|
routes: menus,
|
||||||
|
scrollBehavior(to, form, savedPosition) {
|
||||||
|
return savedPosition || { top: 0 }
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const filterPath = [
|
|
||||||
'/form',
|
|
||||||
'/search'
|
|
||||||
]
|
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
// TODO 切换路由取消请求
|
// TODO 切换路由取消请求
|
||||||
const isFilterPath = filterPath.includes(to.path)
|
const token = getToken()
|
||||||
if (isFilterPath) {
|
if (token) {
|
||||||
next()
|
if (to.path === LoginPath) {
|
||||||
} else {
|
next({ path: '/' })
|
||||||
const token = getToken()
|
|
||||||
if (token) {
|
|
||||||
if (to.path === LoginPath) {
|
|
||||||
next({ path: '/' })
|
|
||||||
} else {
|
|
||||||
const userInfo = useUserInfo()
|
|
||||||
const system = useSystem()
|
|
||||||
const menu = useMenuStore()
|
|
||||||
|
|
||||||
if (!menu.siderMenus.length) {
|
|
||||||
userInfo.getUserInfo().then(() => {
|
|
||||||
system.getSystemVersion().then((menuData: any[]) => {
|
|
||||||
menuData.forEach(r => {
|
|
||||||
router.addRoute('base', r)
|
|
||||||
})
|
|
||||||
router.addRoute('base',{
|
|
||||||
path: '/:pathMatch(.*)',
|
|
||||||
name: 'error',
|
|
||||||
component: () => NotFindPage
|
|
||||||
})
|
|
||||||
|
|
||||||
next({ ...to, replace: true })
|
|
||||||
})
|
|
||||||
}).catch(() => {
|
|
||||||
cleanToken()
|
|
||||||
next({ path: LoginPath })
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (to.path === LoginPath) {
|
const userInfo = useUserInfo()
|
||||||
next()
|
const system = useSystem()
|
||||||
|
const menu = useMenuStore()
|
||||||
|
if (!menu.siderMenus.length) {
|
||||||
|
userInfo.getUserInfo().then(() => {
|
||||||
|
system.getSystemVersion().then((menuData: any[]) => {
|
||||||
|
menuData.forEach(r => {
|
||||||
|
router.addRoute('base', r)
|
||||||
|
})
|
||||||
|
router.addRoute('base',{
|
||||||
|
path: '/:pathMatch(.*)',
|
||||||
|
name: 'error',
|
||||||
|
component: () => NotFindPage
|
||||||
|
})
|
||||||
|
|
||||||
|
next({ ...to, replace: true })
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
cleanToken()
|
||||||
|
next({ path: LoginPath })
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
next({ path: LoginPath })
|
next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (to.path === LoginPath) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
next({ path: LoginPath })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,10 @@ export default [
|
||||||
path: '/search',
|
path: '/search',
|
||||||
component: () => import('@/views/demo/Search.vue')
|
component: () => import('@/views/demo/Search.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/system/Api',
|
||||||
|
component: () => import('@/views/system/Platforms/index.vue')
|
||||||
|
},
|
||||||
|
|
||||||
// end: 测试用, 可删除
|
// end: 测试用, 可删除
|
||||||
|
|
||||||
|
|
|
@ -1,60 +1,117 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from 'pinia'
|
||||||
import { queryOwnThree } from '@/api/system/menu'
|
import { queryOwnThree } from '@/api/system/menu'
|
||||||
import { filterAsnycRouter } from '@/utils/menu'
|
import { filterAsnycRouter, MenuItem } from '@/utils/menu'
|
||||||
import { cloneDeep } from 'lodash-es'
|
import { isArray } from 'lodash-es'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
|
const defaultOwnParams = [
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'owner',
|
||||||
|
termType: 'eq',
|
||||||
|
value: 'iot'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
column: 'owner',
|
||||||
|
termType: 'isnull',
|
||||||
|
value: '1',
|
||||||
|
type: 'or'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
type MenuStateType = {
|
||||||
|
menus: {
|
||||||
|
[key: string]: {
|
||||||
|
buttons?: string[]
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
siderMenus: MenuItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const useMenuStore = defineStore({
|
export const useMenuStore = defineStore({
|
||||||
id: 'menu',
|
id: 'menu',
|
||||||
state: () => ({
|
state: (): MenuStateType => ({
|
||||||
menus: {},
|
menus: {},
|
||||||
menuData: [],
|
siderMenus: []
|
||||||
siderMenus: [],
|
|
||||||
menusKey: []
|
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
hasPermission(state) {
|
hasPermission(state) {
|
||||||
return (menuCode: string | string[]) => {
|
return (code: string | string[]) => {
|
||||||
if (!menuCode) {
|
if (!code) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (!!Object.keys(state.menus).length) {
|
if (!!Object.keys(state.menus).length) {
|
||||||
if (typeof menuCode === 'string') {
|
let codes: string[] = []
|
||||||
return !!this.menus[menuCode]
|
|
||||||
|
if (typeof code === 'string') {
|
||||||
|
codes.push(code)
|
||||||
|
} else {
|
||||||
|
codes = code
|
||||||
}
|
}
|
||||||
return menuCode.some(code => !!this.menus[code])
|
|
||||||
|
return codes.some(_c => {
|
||||||
|
const menu_code = _c.split(':')
|
||||||
|
if (menu_code.length > 1) {
|
||||||
|
return !!this.menus[menu_code[0]]?.buttons?.includes(menu_code[1])
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
hasMenu(code: string) {
|
||||||
|
return this.menus[code]?.path
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 路由跳转
|
||||||
|
* @param name 菜单code
|
||||||
|
* @param params 路由参数
|
||||||
|
* @param query 路由参数
|
||||||
|
*/
|
||||||
|
jumpPage(name: string, params?: Record<string, any>, query?: Record<string, any>) {
|
||||||
|
const path = this.hasMenu(name)
|
||||||
|
if (path) {
|
||||||
|
router.push({
|
||||||
|
name, params, query
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.warn(`没有找到对应的页面: ${name}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
queryMenuTree(isCommunity = false): Promise<any[]> {
|
queryMenuTree(isCommunity = false): Promise<any[]> {
|
||||||
return new Promise(async (res) => {
|
return new Promise(async (res) => {
|
||||||
//过滤非集成的菜单
|
//过滤非集成的菜单
|
||||||
const params = [
|
const resp = await queryOwnThree({ paging: false, terms: defaultOwnParams })
|
||||||
{
|
|
||||||
terms: [
|
|
||||||
{
|
|
||||||
terms: [
|
|
||||||
{
|
|
||||||
column: 'owner',
|
|
||||||
termType: 'eq',
|
|
||||||
value: 'iot',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
column: 'owner',
|
|
||||||
termType: 'isnull',
|
|
||||||
value: '1',
|
|
||||||
type: 'or',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const resp = await queryOwnThree({ paging: false, terms: params })
|
|
||||||
if (resp.success) {
|
if (resp.success) {
|
||||||
const { menusData, silderMenus } = filterAsnycRouter(resp.result)
|
const { menusData, silderMenus } = filterAsnycRouter(resp.result)
|
||||||
|
this.menus = {}
|
||||||
|
const handleMenuItem = (menu: any) => {
|
||||||
|
if (isArray(menu)) {
|
||||||
|
menu.forEach(menuItem => {
|
||||||
|
this.menus[menuItem.name] = {
|
||||||
|
path: menuItem.path,
|
||||||
|
buttons: menuItem.meta.buttons
|
||||||
|
}
|
||||||
|
if (menuItem.children && menuItem.children.length) {
|
||||||
|
handleMenuItem(menuItem.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMenuItem(menusData)
|
||||||
menusData.push({
|
menusData.push({
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: menusData[0]?.path,
|
redirect: menusData[0]?.path,
|
||||||
|
@ -63,7 +120,7 @@ export const useMenuStore = defineStore({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.siderMenus = silderMenus
|
this.siderMenus = silderMenus
|
||||||
console.log('silderMenus', silderMenus)
|
console.log('menusData', menusData)
|
||||||
res(menusData)
|
res(menusData)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep } from 'lodash-es'
|
||||||
import NotFindPage from '@/views/404.vue'
|
|
||||||
import { BlankLayoutPage, BasicLayoutPage } from 'components/Layout'
|
import { BlankLayoutPage, BasicLayoutPage } from 'components/Layout'
|
||||||
const pagesComponent = import.meta.glob('../views/**/*.vue', { eager: true });
|
const pagesComponent = import.meta.glob('../views/**/*.vue');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限信息
|
* 权限信息
|
||||||
|
@ -152,9 +151,12 @@ const resolveComponent = (name: any) => {
|
||||||
const importPage = pagesComponent[`../views/${name}/index.vue`];
|
const importPage = pagesComponent[`../views/${name}/index.vue`];
|
||||||
if (!importPage) {
|
if (!importPage) {
|
||||||
console.warn(`Unknown page ${name}. Is it located under Pages with a .vue extension?`)
|
console.warn(`Unknown page ${name}. Is it located under Pages with a .vue extension?`)
|
||||||
|
return undefined
|
||||||
|
} else {
|
||||||
|
const res = () => importPage()
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
return !!importPage ? importPage.default : undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const findChildrenRoute = (code: string, url: string, routes: any[] = []): MenuItem[] => {
|
const findChildrenRoute = (code: string, url: string, routes: any[] = []): MenuItem[] => {
|
||||||
|
@ -176,7 +178,7 @@ const findDetailRouteItem = (code: string, url: string): Partial<MenuItem> | nul
|
||||||
const detailComponent = resolveComponent(`${code}/Detail`)
|
const detailComponent = resolveComponent(`${code}/Detail`)
|
||||||
if (detailComponent) {
|
if (detailComponent) {
|
||||||
return {
|
return {
|
||||||
url: `${url}/Detail/:id`,
|
url: `${url}/detail/:id`,
|
||||||
code: `${code}/Detail`,
|
code: `${code}/Detail`,
|
||||||
component: detailComponent,
|
component: detailComponent,
|
||||||
name: '详情信息',
|
name: '详情信息',
|
||||||
|
@ -205,11 +207,12 @@ export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level =
|
||||||
_asyncRouterMap.forEach((route: any) => {
|
_asyncRouterMap.forEach((route: any) => {
|
||||||
const _route: any = {
|
const _route: any = {
|
||||||
path: `${route.url}`,
|
path: `${route.url}`,
|
||||||
|
name: route.code,
|
||||||
meta: {
|
meta: {
|
||||||
icon: route.icon,
|
icon: route.icon,
|
||||||
title: route.name,
|
title: route.name,
|
||||||
hideInMenu: route.isShow === false,
|
hideInMenu: route.isShow === false,
|
||||||
buttons: route.buttons || []
|
buttons: route.buttons?.map((b: any) => b.id) || []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,12 +227,13 @@ export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level =
|
||||||
_route.children = _menusData
|
_route.children = _menusData
|
||||||
silder.children = _silderMenus
|
silder.children = _silderMenus
|
||||||
const showChildren = _route.children.some((r: any) => !r.meta.hideInMenu)
|
const showChildren = _route.children.some((r: any) => !r.meta.hideInMenu)
|
||||||
if (showChildren) {
|
|
||||||
_route.component = () => level === 1 ? BasicLayoutPage : BlankLayoutPage
|
if (showChildren && _route.children.length) {
|
||||||
|
_route.component = level === 1 ? BasicLayoutPage : BlankLayoutPage
|
||||||
_route.redirect = route.children[0].url
|
_route.redirect = route.children[0].url
|
||||||
} else {
|
} else {
|
||||||
const myComponent = resolveComponent(route.code)
|
const myComponent = resolveComponent(route.code)
|
||||||
_route.component = myComponent ? myComponent : BlankLayoutPage;
|
// _route.component = myComponent ? myComponent : BlankLayoutPage;
|
||||||
if (!!myComponent) {
|
if (!!myComponent) {
|
||||||
_route.component = myComponent;
|
_route.component = myComponent;
|
||||||
_route.children.map((r: any) => menusData.push(r))
|
_route.children.map((r: any) => menusData.push(r))
|
||||||
|
@ -239,7 +243,7 @@ export function filterAsnycRouter(asyncRouterMap: any, parentCode = '', level =
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_route.component = route.component || resolveComponent(route.code) || BlankLayoutPage;
|
_route.component = route.component || resolveComponent(route.code) || BlankLayoutPage
|
||||||
}
|
}
|
||||||
menusData.push(_route)
|
menusData.push(_route)
|
||||||
silderMenus.push(silder)
|
silderMenus.push(silder)
|
||||||
|
|
|
@ -151,12 +151,15 @@ import {
|
||||||
import type { ActionsType } from '@/components/Table/index.vue';
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
import { useMenuStore } from 'store/menu'
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const instanceRef = ref<Record<string, any>>({});
|
const instanceRef = ref<Record<string, any>>({});
|
||||||
const params = ref<Record<string, any>>({});
|
const params = ref<Record<string, any>>({});
|
||||||
const current = ref<Record<string, any>>({});
|
const current = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
const menuStory = useMenuStore()
|
||||||
|
|
||||||
const statusMap = new Map();
|
const statusMap = new Map();
|
||||||
statusMap.set('enabled', 'success');
|
statusMap.set('enabled', 'success');
|
||||||
statusMap.set('disabled', 'error');
|
statusMap.set('disabled', 'error');
|
||||||
|
@ -209,19 +212,14 @@ const columns = [
|
||||||
* 新增
|
* 新增
|
||||||
*/
|
*/
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
router.push('/iot/northbound/AliCloud/detail/:id');
|
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: ':id'})
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查看
|
* 查看
|
||||||
*/
|
*/
|
||||||
const handleView = (id: string) => {
|
const handleView = (id: string) => {
|
||||||
router.push({
|
menuStory.jumpPage('Northbound/AliCloud/Detail', { id }, { type: 'view'})
|
||||||
path: '/iot/northbound/AliCloud/detail/' + id,
|
|
||||||
query: {
|
|
||||||
type: 'view'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActions = (
|
const getActions = (
|
||||||
|
@ -249,12 +247,7 @@ const getActions = (
|
||||||
},
|
},
|
||||||
icon: 'EditOutlined',
|
icon: 'EditOutlined',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
router.push({
|
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: data.id }, { type: 'edit'})
|
||||||
path: '/iot/northbound/AliCloud/detail/' + data.id,
|
|
||||||
query: {
|
|
||||||
type: 'edit'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
|
@ -147,11 +147,13 @@ import { query, _undeploy, _deploy, _delete, queryProductList, queryTypes } from
|
||||||
import type { ActionsType } from '@/components/Table/index.vue';
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
import { useMenuStore } from 'store/menu'
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const instanceRef = ref<Record<string, any>>({});
|
const instanceRef = ref<Record<string, any>>({});
|
||||||
const params = ref<Record<string, any>>({});
|
const params = ref<Record<string, any>>({});
|
||||||
const current = ref<Record<string, any>>({});
|
const current = ref<Record<string, any>>({});
|
||||||
|
const menuStory = useMenuStore()
|
||||||
|
|
||||||
const statusMap = new Map();
|
const statusMap = new Map();
|
||||||
statusMap.set('enabled', 'success');
|
statusMap.set('enabled', 'success');
|
||||||
|
@ -236,20 +238,14 @@ const columns = [
|
||||||
* 新增
|
* 新增
|
||||||
*/
|
*/
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
router.push('/iot/northbound/DuerOS/detail/:id');
|
menuStory.jumpPage('Northbound/DuerOS/Detail', { id: ':id'})
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查看
|
* 查看
|
||||||
*/
|
*/
|
||||||
const handleView = (id: string) => {
|
const handleView = (id: string) => {
|
||||||
// router.push('/iot/northbound/DuerOS/detail/' + id);
|
menuStory.jumpPage('Northbound/DuerOS/Detail', { id }, { type: 'view' })
|
||||||
router.push({
|
|
||||||
path: '/iot/northbound/DuerOS/detail/' + id,
|
|
||||||
query: {
|
|
||||||
type: 'view',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActions = (
|
const getActions = (
|
||||||
|
@ -277,12 +273,7 @@ const getActions = (
|
||||||
},
|
},
|
||||||
icon: 'EditOutlined',
|
icon: 'EditOutlined',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
router.push({
|
menuStory.jumpPage('Northbound/DuerOS/Detail', { id: data.id }, { type: 'edit' })
|
||||||
path: '/iot/northbound/DuerOS/detail/' + data.id,
|
|
||||||
query: {
|
|
||||||
type: 'edit',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
|
@ -0,0 +1,96 @@
|
||||||
|
<template>
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<a-input
|
||||||
|
placeholder="请上传文件"
|
||||||
|
v-model:value="fileValue"
|
||||||
|
style="width: calc(100% - 110px)"
|
||||||
|
:disabled="true"
|
||||||
|
/>
|
||||||
|
<a-upload
|
||||||
|
name="file"
|
||||||
|
:multiple="true"
|
||||||
|
:action="FIRMWARE_UPLOAD"
|
||||||
|
:headers="{
|
||||||
|
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
|
||||||
|
}"
|
||||||
|
@change="handleChange"
|
||||||
|
:showUploadList="false"
|
||||||
|
class="upload-box"
|
||||||
|
>
|
||||||
|
<a-button type="primary">
|
||||||
|
<div>
|
||||||
|
<AIcon type="UploadOutlined" /><span class="upload-text"
|
||||||
|
>上传文件</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</a-button>
|
||||||
|
</a-upload>
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="FileUpload">
|
||||||
|
import { LocalStore } from '@/utils/comm';
|
||||||
|
import { TOKEN_KEY } from '@/utils/variable';
|
||||||
|
import { FIRMWARE_UPLOAD, querySystemApi } from '@/api/device/firmware';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import type { UploadChangeParam, UploadProps } from 'ant-design-vue';
|
||||||
|
import { notification as Notification } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'update:extraValue', 'change']);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: () => '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileValue = ref(props.modelValue);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const handleChange = async (info: UploadChangeParam) => {
|
||||||
|
loading.value = true;
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
loading.value = false;
|
||||||
|
const result = info.file.response?.result;
|
||||||
|
const api = await querySystemApi(['paths']);
|
||||||
|
const path = api.result[0]?.properties
|
||||||
|
? api.result[0]?.properties['base-path']
|
||||||
|
: '';
|
||||||
|
const f = `${path}/file/${result.id}?accessKey=${result.others.accessKey}`;
|
||||||
|
message.success('上传成功!');
|
||||||
|
fileValue.value = f;
|
||||||
|
emit('update:modelValue', f);
|
||||||
|
emit('update:extraValue', result);
|
||||||
|
} else {
|
||||||
|
if (info.file.error) {
|
||||||
|
Notification.error({
|
||||||
|
// key: '403',
|
||||||
|
message: '系统提示',
|
||||||
|
description: '系统未知错误,请反馈给管理员',
|
||||||
|
});
|
||||||
|
loading.value = false;
|
||||||
|
} else if (info.file.response) {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(value) => {
|
||||||
|
fileValue.value = value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.upload-box {
|
||||||
|
:deep(.ant-btn) {
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
.upload-text {
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,388 @@
|
||||||
|
<template lang="">
|
||||||
|
<a-modal
|
||||||
|
:title="data.id ? '编辑' : '新增'"
|
||||||
|
ok-text="确认"
|
||||||
|
cancel-text="取消"
|
||||||
|
:visible="true"
|
||||||
|
width="700px"
|
||||||
|
:confirm-loading="loading"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@ok="handleOk"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
class="form"
|
||||||
|
layout="vertical"
|
||||||
|
:model="formData"
|
||||||
|
name="basic"
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<a-row :gutter="[24, 0]">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="名称" v-bind="validateInfos.name">
|
||||||
|
<a-input
|
||||||
|
placeholder="请输入名称"
|
||||||
|
v-model:value="formData.name"
|
||||||
|
/></a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24"
|
||||||
|
><a-form-item
|
||||||
|
label="所属产品"
|
||||||
|
v-bind="validateInfos.productId"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.productId"
|
||||||
|
:options="productOptions"
|
||||||
|
placeholder="请选择所属产品"
|
||||||
|
allowClear
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
/> </a-form-item
|
||||||
|
></a-col>
|
||||||
|
<a-col :span="12"
|
||||||
|
><a-form-item label="版本号" v-bind="validateInfos.version">
|
||||||
|
<a-input
|
||||||
|
placeholder="请输入版本号"
|
||||||
|
v-model:value="formData.version" /></a-form-item
|
||||||
|
></a-col>
|
||||||
|
<a-col :span="12"
|
||||||
|
><a-form-item
|
||||||
|
label="版本序号"
|
||||||
|
v-bind="validateInfos.versionOrder"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
placeholder="请输入版本序号"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1"
|
||||||
|
:max="99999"
|
||||||
|
v-model:value="
|
||||||
|
formData.versionOrder
|
||||||
|
" /></a-form-item
|
||||||
|
></a-col>
|
||||||
|
<a-col :span="12"
|
||||||
|
><a-form-item
|
||||||
|
label="签名方式"
|
||||||
|
v-bind="validateInfos.signMethod"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="formData.signMethod"
|
||||||
|
:options="[
|
||||||
|
{ label: 'MD5', value: 'md5' },
|
||||||
|
{ label: 'SHA256', value: 'sha256' },
|
||||||
|
]"
|
||||||
|
placeholder="请选择签名方式"
|
||||||
|
allowClear
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
@change="changeSignMethod"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12"
|
||||||
|
><a-form-item label="签名" v-bind="validateInfos.sign">
|
||||||
|
<a-input
|
||||||
|
placeholder="请输入签名"
|
||||||
|
v-model:value="formData.sign" /></a-form-item
|
||||||
|
></a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item label="固件上传" v-bind="validateInfos.url">
|
||||||
|
<FileUpload
|
||||||
|
v-model:modelValue="formData.url"
|
||||||
|
v-model:extraValue="extraValue"
|
||||||
|
/> </a-form-item
|
||||||
|
></a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item
|
||||||
|
label="其他配置"
|
||||||
|
v-bind="validateInfos.properties"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
:class="
|
||||||
|
dynamicValidateForm.properties.length !== 0 &&
|
||||||
|
'formRef'
|
||||||
|
"
|
||||||
|
ref="formRef"
|
||||||
|
name="dynamic_form_nest_item"
|
||||||
|
:model="dynamicValidateForm"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="formRef-content"
|
||||||
|
v-for="(
|
||||||
|
propertie, index
|
||||||
|
) in dynamicValidateForm.properties"
|
||||||
|
:key="propertie.keyid"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="index === 0 && 'Key'"
|
||||||
|
class="formRef-form-item"
|
||||||
|
:name="['properties', index, 'id']"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请输入KEY',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="propertie.id"
|
||||||
|
placeholder="请输入KEY"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="index === 0 && 'Value'"
|
||||||
|
class="formRef-form-item"
|
||||||
|
:name="['properties', index, 'value']"
|
||||||
|
:rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请输入VALUE',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="propertie.value"
|
||||||
|
placeholder="请输入VALUE"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="index === 0 && '操作'"
|
||||||
|
class="formRef-form-item"
|
||||||
|
style="width: 10%"
|
||||||
|
>
|
||||||
|
<a-popconfirm
|
||||||
|
title="确认删除吗?"
|
||||||
|
ok-text="确认"
|
||||||
|
cancel-text="取消"
|
||||||
|
@confirm="removeUser(propertie)"
|
||||||
|
>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-form-item>
|
||||||
|
</div>
|
||||||
|
<a-form-item class="formRef-form-item-add">
|
||||||
|
<a-button type="dashed" block @click="addUser">
|
||||||
|
<AIcon type="PlusOutlined" />
|
||||||
|
添加
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-form-item></a-col
|
||||||
|
>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-form-item
|
||||||
|
label="说明"
|
||||||
|
v-bind="validateInfos.description"
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
placeholder="请输入说明"
|
||||||
|
v-model:value="formData.description"
|
||||||
|
:maxlength="200"
|
||||||
|
:rows="3"
|
||||||
|
showCount
|
||||||
|
/> </a-form-item
|
||||||
|
></a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { message, Form } from 'ant-design-vue';
|
||||||
|
import { getImage } from '@/utils/comm';
|
||||||
|
import type { UploadChangeParam } from 'ant-design-vue';
|
||||||
|
import FileUpload from './FileUpload.vue';
|
||||||
|
import { save, update, queryProduct } from '@/api/device/firmware';
|
||||||
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
|
import type { Properties } from '../type';
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const dynamicValidateForm = reactive<{ properties: Properties[] }>({
|
||||||
|
properties: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const removeUser = (item: Properties) => {
|
||||||
|
let index = dynamicValidateForm.properties.indexOf(item);
|
||||||
|
if (index !== -1) {
|
||||||
|
dynamicValidateForm.properties.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const addUser = () => {
|
||||||
|
dynamicValidateForm.properties.push({
|
||||||
|
id: '',
|
||||||
|
value: '',
|
||||||
|
keyid: Date.now(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const useForm = Form.useForm;
|
||||||
|
const productOptions = ref([]);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
|
const id = props.data.id;
|
||||||
|
|
||||||
|
const formData = ref({
|
||||||
|
name: '',
|
||||||
|
productId: undefined,
|
||||||
|
version: '',
|
||||||
|
versionOrder: '',
|
||||||
|
signMethod: undefined,
|
||||||
|
sign: '',
|
||||||
|
url: '',
|
||||||
|
properties: [],
|
||||||
|
description: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const extraValue = ref({});
|
||||||
|
|
||||||
|
const validatorSign = async (_: Record<string, any>, value: string) => {
|
||||||
|
const { signMethod, url } = formData.value;
|
||||||
|
if (value && !!signMethod && !!url && !extraValue.value) {
|
||||||
|
return extraValue.value[signMethod] !== value
|
||||||
|
? Promise.reject('签名不一致,请检查文件是否上传正确')
|
||||||
|
: Promise.resolve();
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { resetFields, validate, validateInfos } = useForm(
|
||||||
|
formData,
|
||||||
|
reactive({
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入名称' },
|
||||||
|
{ max: 64, message: '最多可输入64个字符' },
|
||||||
|
],
|
||||||
|
productId: [{ required: true, message: '请选择所属产品' }],
|
||||||
|
version: [
|
||||||
|
{ required: true, message: '请输入版本号' },
|
||||||
|
{ max: 64, message: '最多可输入64个字符', trigger: 'change' },
|
||||||
|
],
|
||||||
|
versionOrder: [{ required: true, message: '请输入版本号' }],
|
||||||
|
signMethod: [{ required: true, message: '请选择签名方式' }],
|
||||||
|
sign: [
|
||||||
|
{ required: true, message: '请输入签名' },
|
||||||
|
{ validator: validatorSign },
|
||||||
|
],
|
||||||
|
url: [{ required: true, message: '请上传文件' }],
|
||||||
|
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
const { properties } = await formRef.value?.validate();
|
||||||
|
|
||||||
|
validate()
|
||||||
|
.then(async (res) => {
|
||||||
|
const product = productOptions.value.find(
|
||||||
|
(item) => item.value === res.productId,
|
||||||
|
);
|
||||||
|
const productName = product.label || props.data?.url;
|
||||||
|
const size = extraValue.value.length || props.data?.size;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
...toRaw(formData.value),
|
||||||
|
properties: !!properties ? properties : [],
|
||||||
|
productName,
|
||||||
|
size,
|
||||||
|
};
|
||||||
|
loading.value = true;
|
||||||
|
const response = !id
|
||||||
|
? await save(params)
|
||||||
|
: await update({ ...props.data, ...params });
|
||||||
|
if (response.status === 200) {
|
||||||
|
message.success('操作成功');
|
||||||
|
emit('change', true);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
onSubmit();
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('change', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeSignMethod = () => {
|
||||||
|
formData.value.sign = '';
|
||||||
|
formData.value.url = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
queryProduct({
|
||||||
|
paging: false,
|
||||||
|
terms: [{ column: 'state', value: 1 }],
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
}).then((resp) => {
|
||||||
|
productOptions.value = resp.result.map((item) => ({
|
||||||
|
value: item.id,
|
||||||
|
label: item.name,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(value) => {
|
||||||
|
if (value.id) {
|
||||||
|
formData.value = value;
|
||||||
|
dynamicValidateForm.properties = value.properties;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => extraValue.value,
|
||||||
|
() => validate('sign'),
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.form {
|
||||||
|
.form-radio-button {
|
||||||
|
width: 148px;
|
||||||
|
height: 80px;
|
||||||
|
padding: 0;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.form-url-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.form-submit {
|
||||||
|
background-color: @primary-color !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.formRef {
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
.formRef-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.formRef-content {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
.formRef-form-item {
|
||||||
|
width: 47%;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.formRef-form-item-add {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,268 @@
|
||||||
|
<template>
|
||||||
|
<page-container>
|
||||||
|
<div>
|
||||||
|
<Search :columns="columns" target="search" @search="handleSearch" />
|
||||||
|
<JTable
|
||||||
|
ref="tableRef"
|
||||||
|
model="TABLE"
|
||||||
|
:columns="columns"
|
||||||
|
:request="query"
|
||||||
|
:defaultParams="{
|
||||||
|
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||||
|
}"
|
||||||
|
:params="params"
|
||||||
|
>
|
||||||
|
<template #headerTitle>
|
||||||
|
<a-button type="primary" @click="handlAdd"
|
||||||
|
><plus-outlined />新增</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<template #productId="slotProps">
|
||||||
|
<span>{{ slotProps.productName }}</span>
|
||||||
|
</template>
|
||||||
|
<template #createTime="slotProps">
|
||||||
|
<span>{{
|
||||||
|
moment(slotProps.createTime).format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
<template #action="slotProps">
|
||||||
|
<a-space :size="16">
|
||||||
|
<a-tooltip
|
||||||
|
v-for="i in getActions(slotProps)"
|
||||||
|
:key="i.key"
|
||||||
|
v-bind="i.tooltip"
|
||||||
|
>
|
||||||
|
<a-popconfirm
|
||||||
|
v-if="i.popConfirm"
|
||||||
|
v-bind="i.popConfirm"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
:disabled="i.disabled"
|
||||||
|
style="padding: 0"
|
||||||
|
type="link"
|
||||||
|
><AIcon :type="i.icon"
|
||||||
|
/></a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
<a-button
|
||||||
|
style="padding: 0"
|
||||||
|
type="link"
|
||||||
|
v-else
|
||||||
|
@click="i.onClick && i.onClick(slotProps)"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
:disabled="i.disabled"
|
||||||
|
style="padding: 0"
|
||||||
|
type="link"
|
||||||
|
><AIcon :type="i.icon"
|
||||||
|
/></a-button>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</JTable>
|
||||||
|
</div>
|
||||||
|
<Save v-if="visible" :data="current" @change="saveChange" />
|
||||||
|
</page-container>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup name="CertificatePage">
|
||||||
|
import type { ActionsType } from '@/components/Table/index.vue';
|
||||||
|
// import { save, query, remove } from '@/api/link/certificate';
|
||||||
|
import { query, queryProduct, remove } from '@/api/device/firmware';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import moment from 'moment';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import Save from './Save/index.vue';
|
||||||
|
|
||||||
|
const tableRef = ref<Record<string, any>>({});
|
||||||
|
const router = useRouter();
|
||||||
|
const params = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
const productOptions = ref([]);
|
||||||
|
const visible = ref(false);
|
||||||
|
const current = ref({});
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '固件名称',
|
||||||
|
key: 'name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
fixed: 'left',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '固件版本',
|
||||||
|
dataIndex: 'version',
|
||||||
|
key: 'version',
|
||||||
|
ellipsis: true,
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属产品',
|
||||||
|
dataIndex: 'productId',
|
||||||
|
key: 'productId',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 200,
|
||||||
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: productOptions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '签名方式',
|
||||||
|
dataIndex: 'signMethod',
|
||||||
|
key: 'signMethod',
|
||||||
|
scopedSlots: true,
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'MD5',
|
||||||
|
value: 'md5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SHA256',
|
||||||
|
value: 'sha256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'createTime',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
search: {
|
||||||
|
type: 'time',
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '说明',
|
||||||
|
dataIndex: 'description',
|
||||||
|
key: 'description',
|
||||||
|
ellipsis: true,
|
||||||
|
search: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 200,
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'FileTextOutlined',
|
||||||
|
text: '升级任务',
|
||||||
|
tooltip: {
|
||||||
|
title: '升级任务',
|
||||||
|
},
|
||||||
|
icon: 'FileTextOutlined',
|
||||||
|
onClick: async () => {
|
||||||
|
handlUpdate(data.id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'edit',
|
||||||
|
text: '编辑',
|
||||||
|
tooltip: {
|
||||||
|
title: '编辑',
|
||||||
|
},
|
||||||
|
icon: 'EditOutlined',
|
||||||
|
onClick: async () => {
|
||||||
|
handlEdit(data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
text: '删除',
|
||||||
|
popConfirm: {
|
||||||
|
title: '确认删除?',
|
||||||
|
okText: ' 确定',
|
||||||
|
cancelText: '取消',
|
||||||
|
onConfirm: async () => {
|
||||||
|
handlDelete(data.id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: 'DeleteOutlined',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlUpdate = (id: string) => {
|
||||||
|
// router.push({
|
||||||
|
// path: `/iot/link/certificate/detail/${id}`,
|
||||||
|
// query: { view: true },
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlAdd = () => {
|
||||||
|
current.value = {};
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
const handlEdit = (data: object) => {
|
||||||
|
current.value = _.cloneDeep(data);
|
||||||
|
visible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveChange = (value: object) => {
|
||||||
|
visible.value = false;
|
||||||
|
current.value = {};
|
||||||
|
if (value) {
|
||||||
|
message.success('操作成功');
|
||||||
|
tableRef.value.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlDelete = async (id: string) => {
|
||||||
|
const res = await remove(id);
|
||||||
|
if (res.success) {
|
||||||
|
message.success('操作成功');
|
||||||
|
tableRef.value.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
queryProduct({
|
||||||
|
paging: false,
|
||||||
|
sorts: [{ name: 'name', order: 'desc' }],
|
||||||
|
}).then((resp) => {
|
||||||
|
const list = resp.result.filter((it) => {
|
||||||
|
return _.map(it?.features || [], 'id').includes('supportFirmware');
|
||||||
|
});
|
||||||
|
productOptions.value = list.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
const handleSearch = (e: any) => {
|
||||||
|
params.value = e;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
|
@ -0,0 +1,23 @@
|
||||||
|
export type FormDataType = {
|
||||||
|
description: string;
|
||||||
|
name: string;
|
||||||
|
productId: string | undefined;
|
||||||
|
version: undefined;
|
||||||
|
versionOrder: undefined;
|
||||||
|
signMethod: string | undefined;
|
||||||
|
sign: string;
|
||||||
|
url: string;
|
||||||
|
size: number;
|
||||||
|
properties: Array<Properties>;
|
||||||
|
id?: string;
|
||||||
|
format?: string;
|
||||||
|
mode?: object;
|
||||||
|
creatorId?: string;
|
||||||
|
createTime?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Properties {
|
||||||
|
id: string;
|
||||||
|
value: any;
|
||||||
|
keyid: number;
|
||||||
|
}
|
|
@ -1,20 +1,47 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dialog-item" :key="data.key" :class="{'dialog-active' : !data?.upstream}">
|
<div
|
||||||
|
class="dialog-item"
|
||||||
|
:key="data.key"
|
||||||
|
:class="{ 'dialog-active': !data?.upstream }"
|
||||||
|
>
|
||||||
<div class="dialog-card">
|
<div class="dialog-card">
|
||||||
<div class="dialog-list" v-for="item in data.list" :key="item.key">
|
<div class="dialog-list" v-for="item in data.list" :key="item.key">
|
||||||
<div class="dialog-icon">
|
<div class="dialog-icon" @click="getDetail(item)">
|
||||||
<AIcon :type="visible.includes(item.key) ? 'DownOutlined' : 'RightOutlined'" />
|
<AIcon
|
||||||
|
v-if="visible.includes(item.key)"
|
||||||
|
type="DownOutlined"
|
||||||
|
/>
|
||||||
|
<AIcon v-else type="RightOutlined" />
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-box">
|
<div class="dialog-box">
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
<div class="dialog-title">
|
<div class="dialog-title">
|
||||||
<a-badge :color="statusColor.get(item.error ? 'error' : 'success')" style="margin-right: 5px" />
|
<a-badge
|
||||||
{{operationMap.get(item.operation) || item?.operation}}
|
:color="
|
||||||
|
statusColor.get(
|
||||||
|
item.error ? 'error' : 'success',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
style="margin-right: 5px"
|
||||||
|
/>
|
||||||
|
{{
|
||||||
|
operationMap.get(item.operation) ||
|
||||||
|
item?.operation
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="dialog-time">
|
||||||
|
{{
|
||||||
|
moment(item.endTime).format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss',
|
||||||
|
)
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-item">{{moment(item.endTime).format('YYYY-MM-DD HH:mm:ss')}}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-editor" v-if="visible.includes(item.key)">
|
<div
|
||||||
<a-textarea :bordered="false" :value="item?.detail" />
|
class="dialog-editor"
|
||||||
|
v-if="visible.includes(item.key)"
|
||||||
|
>
|
||||||
|
<a-textarea autoSize :bordered="false" :value="item?.detail" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,7 +51,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const operationMap = new Map();
|
const operationMap = new Map();
|
||||||
import moment from 'moment'
|
import moment from 'moment';
|
||||||
operationMap.set('connection', '连接');
|
operationMap.set('connection', '连接');
|
||||||
operationMap.set('auth', '权限验证');
|
operationMap.set('auth', '权限验证');
|
||||||
operationMap.set('decode', '解码');
|
operationMap.set('decode', '解码');
|
||||||
|
@ -41,102 +68,113 @@ statusColor.set('success', '#24B276');
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const visible = ref<string[]>([]);
|
||||||
|
const getDetail = (item: any) => {
|
||||||
|
const index = visible.value.indexOf(item.key);
|
||||||
|
if (index === -1) {
|
||||||
|
visible.value.push(item.key);
|
||||||
|
} else {
|
||||||
|
visible.value.splice(index, 1);
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
const visible = ref<string[]>([])
|
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
console.log(props.data)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import 'ant-design-vue/es/style/themes/default.less';
|
@import 'ant-design-vue/es/style/themes/default.less';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--dialog-primary-color: @primary-color;
|
--dialog-primary-color: @primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-item {
|
.dialog-item {
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: 12px;
|
|
||||||
|
|
||||||
.dialog-card {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
justify-content: flex-start;
|
||||||
width: 60%;
|
width: 100%;
|
||||||
padding: 24px;
|
padding-bottom: 12px;
|
||||||
background-color: #fff;
|
|
||||||
|
|
||||||
.dialog-list {
|
.dialog-card {
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.dialog-icon {
|
|
||||||
margin-right: 10px;
|
|
||||||
color: rgba(0, 0, 0, 0.75);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-box {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 60%;
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
.dialog-header {
|
.dialog-list {
|
||||||
.dialog-title {
|
display: flex;
|
||||||
color: rgba(0, 0, 0, 0.75);
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-time {
|
.dialog-icon {
|
||||||
color: rgba(0, 0, 0, 0.65);
|
margin-right: 10px;
|
||||||
font-size: 12px;
|
color: rgba(0, 0, 0, 0.75);
|
||||||
}
|
font-weight: 500;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
.dialog-title {
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-time {
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-editor {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
|
||||||
|
textarea::-webkit-scrollbar {
|
||||||
|
width: 5px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-editor {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 10px;
|
|
||||||
color: rgba(0, 0, 0, 0.75);
|
|
||||||
|
|
||||||
textarea::-webkit-scrollbar {
|
|
||||||
width: 5px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-active {
|
.dialog-active {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
.dialog-card {
|
.dialog-card {
|
||||||
background-color: @primary-color;
|
background-color: @primary-color;
|
||||||
|
|
||||||
.dialog-list {
|
.dialog-list {
|
||||||
.dialog-icon {
|
.dialog-icon {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-box {
|
.dialog-box {
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
.dialog-title,
|
.dialog-title,
|
||||||
.dialog-time {
|
.dialog-time {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-editor {
|
||||||
|
textarea {
|
||||||
|
color: #fff !important;
|
||||||
|
background-color: @primary-color !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-editor {
|
|
||||||
textarea {
|
|
||||||
color: #fff !important;
|
|
||||||
background-color: @primary-color !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -67,7 +67,7 @@
|
||||||
message: '请输入值',
|
message: '请输入值',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<a-input v-model:value="propertyValue" />
|
<a-input v-model:value="modelRef.propertyValue" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="6" v-if="modelRef.type === 'INVOKE_FUNCTION'">
|
<a-col :span="6" v-if="modelRef.type === 'INVOKE_FUNCTION'">
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="16">
|
<a-col :span="16">
|
||||||
<a-row :gutter="24" style="margin-bottom: 20px;">
|
<a-row :gutter="24" style="margin-bottom: 20px">
|
||||||
<a-col :span="12" v-for="item in messageArr" :key="item">
|
<a-col :span="12" v-for="item in messageArr" :key="item">
|
||||||
<div :style="messageStyleMap.get(item.status)" class="message-status">
|
<div
|
||||||
<a-badge :status="messageStatusMap.get(item.status)" style="margin-right: 5px;" />
|
:style="messageStyleMap.get(item.status)"
|
||||||
<span>{{item.text}}</span>
|
class="message-status"
|
||||||
|
>
|
||||||
|
<a-badge
|
||||||
|
:status="messageStatusMap.get(item.status)"
|
||||||
|
style="margin-right: 5px"
|
||||||
|
/>
|
||||||
|
<span>{{ item.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -26,7 +32,11 @@
|
||||||
<TitleComponent data="日志" />
|
<TitleComponent data="日志" />
|
||||||
<div :style="{ marginTop: '10px' }">
|
<div :style="{ marginTop: '10px' }">
|
||||||
<template v-if="logList.length">
|
<template v-if="logList.length">
|
||||||
<Log v-for="item in logList" :data="item" :key="item.key" />
|
<Log
|
||||||
|
v-for="item in logList"
|
||||||
|
:data="item"
|
||||||
|
:key="item.key"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<a-empty v-else />
|
<a-empty v-else />
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,58 +46,146 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { MessageType } from './util'
|
import type { MessageType } from './util';
|
||||||
import { messageStatusMap, messageStyleMap } from './util'
|
import { messageStatusMap, messageStyleMap } from './util';
|
||||||
import Dialog from './Dialog/index.vue'
|
import Dialog from './Dialog/index.vue';
|
||||||
import Function from './Function/index.vue'
|
import Function from './Function/index.vue';
|
||||||
import Log from './Log/index.vue'
|
import Log from './Log/index.vue';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { useInstanceStore } from '@/store/instance';
|
||||||
|
import { getWebSocket } from '@/utils/websocket';
|
||||||
|
import { randomString } from '@/utils/utils';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const message = reactive<MessageType>({
|
const message = reactive<MessageType>({
|
||||||
up: {
|
up: {
|
||||||
text: '上行消息诊断中',
|
text: '上行消息诊断中',
|
||||||
status: 'loading',
|
status: 'loading',
|
||||||
},
|
},
|
||||||
down: {
|
down: {
|
||||||
text: '下行消息诊断中',
|
text: '下行消息诊断中',
|
||||||
status: 'loading',
|
status: 'loading',
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const dialogList = ref<Record<string, any>>([])
|
const instanceStore = useInstanceStore();
|
||||||
const logList = ref<Record<string, any>>([])
|
|
||||||
|
const allDialogList = ref<Record<string, any>[]>([]);
|
||||||
|
const dialogList = ref<Record<string, any>[]>([]);
|
||||||
|
const logList = ref<Record<string, any>[]>([]);
|
||||||
|
|
||||||
|
const diagnoseRef = ref();
|
||||||
|
|
||||||
const messageArr = computed(() => {
|
const messageArr = computed(() => {
|
||||||
const arr = Object.keys(message) || []
|
const arr = Object.keys(message) || [];
|
||||||
return arr.map(i => { return {...message[i], key: i}})
|
return arr.map((i) => {
|
||||||
})
|
return { ...message[i], key: i };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const subscribeLog = () => {
|
const subscribeLog = () => {
|
||||||
|
const id = `device-debug-${instanceStore.current?.id}`;
|
||||||
|
const topic = `/debug/device/${instanceStore.current?.id}/trace`;
|
||||||
|
diagnoseRef.value = getWebSocket(id, topic, {})
|
||||||
|
?.pipe(map((res: any) => res.payload))
|
||||||
|
.subscribe((payload) => {
|
||||||
|
if (payload.type === 'log') {
|
||||||
|
logList.value.push({
|
||||||
|
key: randomString(),
|
||||||
|
...payload,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const data = { key: randomString(), ...payload };
|
||||||
|
allDialogList.value.push(data);
|
||||||
|
const flag = allDialogList.value
|
||||||
|
.filter(
|
||||||
|
(i: any) =>
|
||||||
|
i.traceId === data.traceId &&
|
||||||
|
(data.downstream === i.downstream ||
|
||||||
|
data.upstream === i.upstream),
|
||||||
|
)
|
||||||
|
.every((item: any) => {
|
||||||
|
return !item.error;
|
||||||
|
});
|
||||||
|
if (!data.upstream) {
|
||||||
|
message.down = {
|
||||||
|
text: !flag ? '下行消息通信异常' : '下行消息通信正常',
|
||||||
|
status: !flag ? 'error' : 'success',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
message.up = {
|
||||||
|
text: !flag ? '上行消息通信异常' : '上行消息通信正常',
|
||||||
|
status: !flag ? 'error' : 'success',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const list: any[] = _.cloneDeep(dialogList.value);
|
||||||
|
const t = list.find(
|
||||||
|
(item) =>
|
||||||
|
item.traceId === data.traceId &&
|
||||||
|
data.downstream === item.downstream &&
|
||||||
|
data.upstream === item.upstream,
|
||||||
|
);
|
||||||
|
if (t) {
|
||||||
|
const arr = list.map((item) => {
|
||||||
|
if (item.traceId === data.traceId) {
|
||||||
|
item.list.push(data);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
dialogList.value = _.cloneDeep(arr);
|
||||||
|
} else {
|
||||||
|
list.push({
|
||||||
|
key: randomString(),
|
||||||
|
traceId: data.traceId,
|
||||||
|
downstream: data.downstream,
|
||||||
|
upstream: data.upstream,
|
||||||
|
list: [data],
|
||||||
|
});
|
||||||
|
dialogList.value = _.cloneDeep(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const chatBox = document.getElementById('dialog');
|
||||||
|
if (chatBox) {
|
||||||
|
chatBox.scrollTop = chatBox.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
}
|
const topState: any = inject('topState') || '';
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (topState && topState?.value === 'success') {
|
||||||
|
subscribeLog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (diagnoseRef.value) {
|
||||||
|
diagnoseRef.value.unsubscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.message-status {
|
.message-status {
|
||||||
padding: 8px 24px;
|
padding: 8px 24px;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: #f2f5f7;
|
background-color: #f2f5f7;
|
||||||
}
|
}
|
||||||
.right-log {
|
.right-log {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
border-left: 1px solid rgba(0, 0, 0, .09);
|
border-left: 1px solid rgba(0, 0, 0, 0.09);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
|
@ -34,8 +34,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Message v-if="activeKey === 'message'" />
|
<Message v-show="activeKey === 'message'" />
|
||||||
<Status v-else :providerType="providerType" @countChange="countChange" @percentChange="percentChange" @stateChange="stateChange" />
|
<Status v-show="activeKey !== 'message'" :providerType="providerType" @countChange="countChange" @percentChange="percentChange" @stateChange="stateChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
@ -70,6 +70,7 @@ const percent = ref<number>(0)
|
||||||
const activeKey = ref<'status' | 'message'>('status')
|
const activeKey = ref<'status' | 'message'>('status')
|
||||||
const providerType = ref()
|
const providerType = ref()
|
||||||
|
|
||||||
|
provide('topState', topState)
|
||||||
|
|
||||||
const onTabChange = (key: 'status' | 'message') => {
|
const onTabChange = (key: 'status' | 'message') => {
|
||||||
if(topState.value === 'success'){
|
if(topState.value === 'success'){
|
||||||
|
|
|
@ -0,0 +1,289 @@
|
||||||
|
<template>
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<a-card>
|
||||||
|
<template #extra>
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="visible = true">批量映射</a-button>
|
||||||
|
<a-button type="primary" @click="onSave">保存</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<a-form ref="formRef" :model="modelRef">
|
||||||
|
<a-table :dataSource="modelRef.dataSource" :columns="columns">
|
||||||
|
<template #headerCell="{ column }">
|
||||||
|
<template v-if="column.key === 'collectorId'">
|
||||||
|
采集器
|
||||||
|
<a-tooltip title="边缘网关代理的真实物理设备">
|
||||||
|
<AIcon type="QuestionCircleOutlined" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record, index }">
|
||||||
|
<template v-if="column.dataIndex === 'channelId'">
|
||||||
|
<a-form-item
|
||||||
|
:name="['dataSource', index, 'channelId']"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
style="width: 100%"
|
||||||
|
v-model:value="record[column.dataIndex]"
|
||||||
|
placeholder="请选择"
|
||||||
|
allowClear
|
||||||
|
:filter-option="filterOption"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in channelList"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
>{{ item.label }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'collectorId'">
|
||||||
|
<a-form-item
|
||||||
|
:name="['dataSource', index, 'collectorId']"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: !!record.channelId,
|
||||||
|
message: '请选择采集器',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<MSelect
|
||||||
|
v-model="record[column.dataIndex]"
|
||||||
|
:id="record.channelId"
|
||||||
|
type="COLLECTOR"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'pointId'">
|
||||||
|
<a-form-item
|
||||||
|
:name="['dataSource', index, 'pointId']"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: !!record.channelId,
|
||||||
|
message: '请选择点位',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<MSelect
|
||||||
|
v-model="record[column.dataIndex]"
|
||||||
|
:id="record.collectorId"
|
||||||
|
type="POINT"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'id'">
|
||||||
|
<a-badge
|
||||||
|
v-if="record[column.dataIndex]"
|
||||||
|
status="success"
|
||||||
|
text="已绑定"
|
||||||
|
/>
|
||||||
|
<a-badge v-else status="error" text="未绑定" />
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-tooltip title="解绑">
|
||||||
|
<a-popconfirm
|
||||||
|
title="确认解绑"
|
||||||
|
@confirm="unbind(record.id)"
|
||||||
|
>
|
||||||
|
<a-button type="link" :disabled="!record.id"
|
||||||
|
><AIcon type="icon-jiebang"
|
||||||
|
/></a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
<!-- <PatchMapping
|
||||||
|
:deviceId="instanceStore.current.id"
|
||||||
|
v-if="visible"
|
||||||
|
@close="visible = false"
|
||||||
|
@save="onPatchBind"
|
||||||
|
:type="provider"
|
||||||
|
:metaData="modelRef.dataSource"
|
||||||
|
/> -->
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useInstanceStore } from '@/store/instance';
|
||||||
|
import {
|
||||||
|
queryMapping,
|
||||||
|
saveMapping,
|
||||||
|
removeMapping,
|
||||||
|
queryChannelNoPaging,
|
||||||
|
} from '@/api/device/instance';
|
||||||
|
import MSelect from '../components/MSelect.vue';
|
||||||
|
// import PatchMapping from '../components/PatchMapping.vue';
|
||||||
|
import { message } from 'ant-design-vue/es';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'metadataName',
|
||||||
|
key: 'metadataName',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '通道',
|
||||||
|
dataIndex: 'channelId',
|
||||||
|
key: 'channelId',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '采集器',
|
||||||
|
dataIndex: 'collectorId',
|
||||||
|
key: 'collectorId',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '点位',
|
||||||
|
key: 'pointId',
|
||||||
|
dataIndex: 'pointId',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'id',
|
||||||
|
dataIndex: 'id',
|
||||||
|
width: '10%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: '10%',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
provider: {
|
||||||
|
type: String,
|
||||||
|
default: 'MODBUS_TCP',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const instanceStore = useInstanceStore();
|
||||||
|
const metadata = JSON.parse(instanceStore.current?.metadata || '{}');
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const channelList = ref([]);
|
||||||
|
|
||||||
|
const modelRef = reactive({
|
||||||
|
dataSource: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
|
||||||
|
const getChannel = async () => {
|
||||||
|
const resp: any = await queryChannelNoPaging({
|
||||||
|
paging: false,
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'provider',
|
||||||
|
value: props.provider,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (resp.status === 200) {
|
||||||
|
channelList.value = resp.result?.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
provider: item.provider,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
getChannel();
|
||||||
|
const _metadata = metadata.properties.map((item: any) => ({
|
||||||
|
metadataId: item.id,
|
||||||
|
metadataName: `${item.name}(${item.id})`,
|
||||||
|
metadataType: 'property',
|
||||||
|
name: item.name,
|
||||||
|
}));
|
||||||
|
if (_metadata && _metadata.length) {
|
||||||
|
const resp: any = await queryMapping(
|
||||||
|
'device',
|
||||||
|
instanceStore.current.id,
|
||||||
|
);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
const array = resp.result.reduce((x: any, y: any) => {
|
||||||
|
const metadataId = _metadata.find(
|
||||||
|
(item: any) => item.metadataId === y.metadataId,
|
||||||
|
);
|
||||||
|
if (metadataId) {
|
||||||
|
Object.assign(metadataId, y);
|
||||||
|
} else {
|
||||||
|
x.push(y);
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}, _metadata);
|
||||||
|
modelRef.dataSource = array;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const unbind = async (id: string) => {
|
||||||
|
if (id) {
|
||||||
|
const resp = await removeMapping('device', instanceStore.current.id, [
|
||||||
|
id,
|
||||||
|
]);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPatchBind = () => {
|
||||||
|
visible.value = false;
|
||||||
|
handleSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
handleSearch();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSave = () => {
|
||||||
|
formRef.value
|
||||||
|
.validate()
|
||||||
|
.then(async () => {
|
||||||
|
const arr = toRaw(modelRef).dataSource.filter(
|
||||||
|
(i: any) => i.channelId,
|
||||||
|
);
|
||||||
|
if (arr && arr.length !== 0) {
|
||||||
|
const resp = await saveMapping(
|
||||||
|
instanceStore.current.id,
|
||||||
|
props.provider,
|
||||||
|
arr,
|
||||||
|
);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log('error', err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.ant-form-item) {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<EditTable provider="MODBUS_TCP" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import EditTable from '../components/EditTable/index.vue'
|
||||||
|
</script>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<EditTable provider="OPC_UA" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import EditTable from '../components/EditTable/index.vue'
|
||||||
|
</script>
|
|
@ -24,7 +24,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<ValueRender :data="data" :value="_props.data" />
|
<ValueRender :data="data" :value="_props.data" type="card" />
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<div style="color: rgba(0, 0, 0, .65); font-size: 12px">更新时间</div>
|
<div style="color: rgba(0, 0, 0, .65); font-size: 12px">更新时间</div>
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:maskClosable="false"
|
||||||
|
width="600px"
|
||||||
|
:visible="true"
|
||||||
|
title="详情"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
@ok="handleCancel"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<template v-if="['.jpg', '.png'].includes(type)">
|
||||||
|
<a-image :src="value?.formatValue" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="['.flv', '.m3u8', '.mp4'].includes(type)">
|
||||||
|
<!-- TODO 视频组件缺失 -->
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<!-- <json-viewer
|
||||||
|
:value="{
|
||||||
|
'id': '123'
|
||||||
|
}"
|
||||||
|
copyable
|
||||||
|
boxed
|
||||||
|
sort
|
||||||
|
></json-viewer> -->
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
// import JsonViewer from 'vue3-json-viewer';
|
||||||
|
|
||||||
|
const _data = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [Object, String],
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const _emit = defineEmits(['close']);
|
||||||
|
const handleCancel = () => {
|
||||||
|
_emit('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
// watchEffect(() => {
|
||||||
|
// console.log(_data.value?.formatValue)
|
||||||
|
// })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
</style>
|
|
@ -1,11 +1,58 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{{value?.value || '--'}}
|
<div v-if="value?.formatValue !== 0 && !value?.formatValue" :class="valueClass">--</div>
|
||||||
|
<div v-else-if="data?.valueType?.type === 'file'">
|
||||||
|
<template v-if="data?.valueType?.fileType === 'base64'">
|
||||||
|
<div :class="valueClass" v-if="!!getType(value?.formatValue)">
|
||||||
|
<img :src="imgMap.get(_type)" @error="onError" />
|
||||||
|
</div>
|
||||||
|
<div v-else :class="valueClass">
|
||||||
|
<img :src="imgMap.get('other')" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else-if="data?.valueType?.fileType === 'Binary(二进制)'" :class="valueClass">
|
||||||
|
<img :src="imgMap.get('other')" />
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<template v-if="imgList.some((item) => value?.formatValue.includes(item))">
|
||||||
|
<div :class="valueClass" @click="getDetail('img')">
|
||||||
|
<img :src="value?.formatValue" @error="imgError" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="videoList.some((item) => value?.formatValue.includes(item))">
|
||||||
|
<div :class="valueClass" @click="getDetail('video')">
|
||||||
|
<img :src="imgMap.get('video')" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="fileList.some((item) => value?.formatValue.includes(item))">
|
||||||
|
<div :class="valueClass">
|
||||||
|
<img :src="imgMap.get(fileList.find((item) => value?.formatValue.includes(item)).slice(1))" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div :class="valueClass">
|
||||||
|
<img :src="imgMap.get('other')" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="data?.valueType?.type === 'object'" @click="getDetail('obj')" :class="valueClass">
|
||||||
|
<img :src="imgMap.get('obj')" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="data?.valueType?.type === 'geoPoint' || data?.valueType?.type === 'array'" :class="valueClass">
|
||||||
|
{{JSON.stringify(value?.formatValue)}}
|
||||||
|
</div>
|
||||||
|
<div v-else :class="valueClass">
|
||||||
|
{{String(value?.formatValue)}}
|
||||||
|
</div>
|
||||||
|
<ValueDetail v-if="visible" :type="_types" :value="value" @close="visible = false" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getImage } from "@/utils/comm";
|
import { getImage } from "@/utils/comm";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
import ValueDetail from './ValueDetail.vue'
|
||||||
|
|
||||||
const _data = defineProps({
|
const _data = defineProps({
|
||||||
data: {
|
data: {
|
||||||
|
@ -22,6 +69,10 @@ const _data = defineProps({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const valueClass = computed(() => {
|
||||||
|
return _data.type === 'card' ? 'cardValue' : 'otherValue'
|
||||||
|
})
|
||||||
|
|
||||||
const imgMap = new Map<any, any>();
|
const imgMap = new Map<any, any>();
|
||||||
imgMap.set('txt', getImage('/running/txt.png'));
|
imgMap.set('txt', getImage('/running/txt.png'));
|
||||||
imgMap.set('doc', getImage('/running/doc.png'));
|
imgMap.set('doc', getImage('/running/doc.png'));
|
||||||
|
@ -41,6 +92,64 @@ const imgList = ['.jpg', '.png', '.swf', '.tiff'];
|
||||||
const videoList = ['.m3u8', '.flv', '.mp4', '.rmvb', '.mvb'];
|
const videoList = ['.m3u8', '.flv', '.mp4', '.rmvb', '.mvb'];
|
||||||
const fileList = ['.txt', '.doc', '.xls', '.pdf', '.ppt', '.docx', '.xlsx', '.pptx'];
|
const fileList = ['.txt', '.doc', '.xls', '.pdf', '.ppt', '.docx', '.xlsx', '.pptx'];
|
||||||
|
|
||||||
|
const isHttps = document.location.protocol === 'https:';
|
||||||
|
|
||||||
|
const _types = ref<string>('')
|
||||||
|
const visible = ref<boolean>(false)
|
||||||
|
const temp = ref<boolean>(false)
|
||||||
|
|
||||||
|
const getType = (url: string) => {
|
||||||
|
let t: string = '';
|
||||||
|
[...imgList, ...videoList, ...fileList].map((item) => {
|
||||||
|
const str = item.slice(1, item.length);
|
||||||
|
if (url && String(url).indexOf(str) !== -1) {
|
||||||
|
if (imgList.includes(item)) {
|
||||||
|
t = 'img';
|
||||||
|
} else if (videoList.includes(item)) {
|
||||||
|
t = 'video';
|
||||||
|
} else {
|
||||||
|
t = str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return t;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = (e: any) => {
|
||||||
|
e.target.src = imgMap.get('other')
|
||||||
|
}
|
||||||
|
|
||||||
|
const imgError = (e: any) => {
|
||||||
|
e.target.src = imgMap.get('error')
|
||||||
|
temp.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDetail = (_type: string) => {
|
||||||
|
const value = _data.value
|
||||||
|
let flag: string = ''
|
||||||
|
if(_type === 'img'){
|
||||||
|
if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
|
||||||
|
message.error('域名为https时,不支持访问http地址');
|
||||||
|
} else if (temp.value) {
|
||||||
|
message.error('该图片无法访问');
|
||||||
|
} else {
|
||||||
|
flag = ['.jpg', '.png'].find((item) => value?.formatValue.includes(item)) || '--';
|
||||||
|
}
|
||||||
|
} else if(_type === 'video'){
|
||||||
|
if (isHttps && value?.formatValue.indexOf('http:') !== -1) {
|
||||||
|
message.error('域名为https时,不支持访问http地址');
|
||||||
|
} else if (['.rmvb', '.mvb'].some((item) => value?.formatValue.includes(item))) {
|
||||||
|
message.error('当前仅支持播放.mp4,.flv,.m3u8格式的视频');
|
||||||
|
} else {
|
||||||
|
flag = ['.m3u8', '.flv', '.mp4'].find((item) => value?.formatValue.includes(item)) || '--';
|
||||||
|
}
|
||||||
|
}else if(_type === 'obj'){
|
||||||
|
flag = 'obj'
|
||||||
|
}
|
||||||
|
_types.value = flag
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #value="slotProps">
|
<template #value="slotProps">
|
||||||
<ValueRender
|
<ValueRender
|
||||||
|
type="table"
|
||||||
:data="slotProps"
|
:data="slotProps"
|
||||||
:value="propertyValue[slotProps?.id]"
|
:value="propertyValue[slotProps?.id]"
|
||||||
/>
|
/>
|
||||||
|
@ -332,6 +333,10 @@ watch(
|
||||||
const onSearch = () => {
|
const onSearch = () => {
|
||||||
query(0, 8, value.value);
|
query(0, 8, value.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
subRef.value && subRef.value?.unsubscribe()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
@ -109,6 +109,7 @@ const tabChange = (key: string) => {
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.property-box {
|
.property-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
.property-box-left {
|
.property-box-left {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
width="900px"
|
||||||
|
title="批量映射"
|
||||||
|
visible
|
||||||
|
@ok="handleClick"
|
||||||
|
@cancel="handleClose"
|
||||||
|
>
|
||||||
|
<div class="map-tree">
|
||||||
|
<div class="map-tree-top">
|
||||||
|
采集器的点位名称与属性名称一致时将自动映射绑定;有多个采集器点位名称与属性名称一致时以第1个采集器的点位数据进行绑定
|
||||||
|
</div>
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<div class="map-tree-content">
|
||||||
|
<a-card class="map-tree-content-card" title="源数据">
|
||||||
|
<a-tree
|
||||||
|
checkable
|
||||||
|
:height="300"
|
||||||
|
:tree-data="dataSource"
|
||||||
|
:checkedKeys="checkedKeys"
|
||||||
|
@check="onCheck"
|
||||||
|
/>
|
||||||
|
</a-card>
|
||||||
|
<div style="width: 100px">
|
||||||
|
<a-button
|
||||||
|
:disabled="rightList.length >= leftList.length"
|
||||||
|
@click="onRight"
|
||||||
|
>加入右侧</a-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<a-card class="map-tree-content-card" title="采集器">
|
||||||
|
<a-list
|
||||||
|
size="small"
|
||||||
|
:data-source="rightList"
|
||||||
|
class="map-tree-content-card-list"
|
||||||
|
>
|
||||||
|
<template #renderItem="{ item }">
|
||||||
|
<a-list-item>
|
||||||
|
{{ item.title }}
|
||||||
|
<template #actions>
|
||||||
|
<a-popconfirm
|
||||||
|
title="确定删除?"
|
||||||
|
@confirm="_delete(item.key)"
|
||||||
|
>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</a-popconfirm>
|
||||||
|
</template>
|
||||||
|
</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { treeMapping, saveMapping } from '@/api/device/instance';
|
||||||
|
import { message } from 'ant-design-vue/es';
|
||||||
|
const _props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'MODBUS_TCP',
|
||||||
|
},
|
||||||
|
metaData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
deviceId: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const _emits = defineEmits(['close', 'save']);
|
||||||
|
|
||||||
|
const checkedKeys = ref<string[]>([]);
|
||||||
|
|
||||||
|
const leftList = ref<any[]>([]);
|
||||||
|
const rightList = ref<any[]>([]);
|
||||||
|
|
||||||
|
const dataSource = ref<any[]>([]);
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
|
||||||
|
const handleData = (data: any[], type: string) => {
|
||||||
|
data.forEach((item) => {
|
||||||
|
item.key = item.id;
|
||||||
|
item.title = item.name;
|
||||||
|
item.checkable = type === 'collectors';
|
||||||
|
if (
|
||||||
|
item.collectors &&
|
||||||
|
Array.isArray(item.collectors) &&
|
||||||
|
item.collectors.length
|
||||||
|
) {
|
||||||
|
item.children = handleData(item.collectors, 'collectors');
|
||||||
|
}
|
||||||
|
if (item.points && Array.isArray(item.points) && item.points.length) {
|
||||||
|
item.children = handleData(item.points, 'points');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data as any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const resp = await treeMapping({
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'provider',
|
||||||
|
value: _props.type,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
loading.value = false;
|
||||||
|
if (resp.status === 200) {
|
||||||
|
dataSource.value = handleData(resp.result as any[], 'channel');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCheck = (keys: string[], e: any) => {
|
||||||
|
checkedKeys.value = [...keys];
|
||||||
|
leftList.value = e?.checkedNodes || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRight = () => {
|
||||||
|
rightList.value = leftList.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _delete = (_key: string) => {
|
||||||
|
const _index = rightList.value.findIndex((i) => i.key === _key);
|
||||||
|
rightList.value.splice(_index, 1);
|
||||||
|
checkedKeys.value = rightList.value.map((i) => i.key);
|
||||||
|
leftList.value = rightList.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
if (!rightList.value.length) {
|
||||||
|
message.warning('请选择采集器');
|
||||||
|
} else {
|
||||||
|
const params: any[] = [];
|
||||||
|
rightList.value.map((item: any) => {
|
||||||
|
const array = (item.children || []).map((element: any) => ({
|
||||||
|
channelId: item.parentId,
|
||||||
|
collectorId: element.collectorId,
|
||||||
|
pointId: element.id,
|
||||||
|
metadataType: 'property',
|
||||||
|
metadataId: (_props.metaData as any[]).find((i: any) => i.name === element.name)
|
||||||
|
?.metadataId,
|
||||||
|
provider: _props.type
|
||||||
|
}));
|
||||||
|
params.push(...array);
|
||||||
|
});
|
||||||
|
const filterParms = params.filter((item) => !!item.metadataId);
|
||||||
|
if (filterParms && filterParms.length !== 0) {
|
||||||
|
const res = await saveMapping(_props.deviceId, _props.type, filterParms);
|
||||||
|
if (res.status === 200) {
|
||||||
|
message.success('操作成功');
|
||||||
|
_emits('save');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error('暂无对应属性的映射');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleClose = () => {
|
||||||
|
_emits('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (_props.type) {
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.map-tree-content {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.map-tree-content-card {
|
||||||
|
width: 350px;
|
||||||
|
height: 400px;
|
||||||
|
|
||||||
|
.map-tree-content-card-list {
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,289 @@
|
||||||
|
<template>
|
||||||
|
<a-spin :spinning="loading">
|
||||||
|
<a-card>
|
||||||
|
<template #extra>
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="visible = true">批量映射</a-button>
|
||||||
|
<a-button type="primary" @click="onSave">保存</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<a-form ref="formRef" :model="modelRef">
|
||||||
|
<a-table :dataSource="modelRef.dataSource" :columns="columns">
|
||||||
|
<template #headerCell="{ column }">
|
||||||
|
<template v-if="column.key === 'collectorId'">
|
||||||
|
采集器
|
||||||
|
<a-tooltip title="数据采集中配置的真实物理设备">
|
||||||
|
<AIcon type="QuestionCircleOutlined" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #bodyCell="{ column, record, index }">
|
||||||
|
<template v-if="column.dataIndex === 'channelId'">
|
||||||
|
<a-form-item
|
||||||
|
:name="['dataSource', index, 'channelId']"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
style="width: 100%"
|
||||||
|
v-model:value="record[column.dataIndex]"
|
||||||
|
placeholder="请选择"
|
||||||
|
allowClear
|
||||||
|
:filter-option="filterOption"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in channelList"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
>{{ item.label }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'collectorId'">
|
||||||
|
<a-form-item
|
||||||
|
:name="['dataSource', index, 'collectorId']"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: !!record.channelId,
|
||||||
|
message: '请选择采集器',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<MSelect
|
||||||
|
v-model="record[column.dataIndex]"
|
||||||
|
:id="record.channelId"
|
||||||
|
type="COLLECTOR"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'pointId'">
|
||||||
|
<a-form-item
|
||||||
|
:name="['dataSource', index, 'pointId']"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: !!record.channelId,
|
||||||
|
message: '请选择点位',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<MSelect
|
||||||
|
v-model="record[column.dataIndex]"
|
||||||
|
:id="record.collectorId"
|
||||||
|
type="POINT"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'id'">
|
||||||
|
<a-badge
|
||||||
|
v-if="record[column.dataIndex]"
|
||||||
|
status="success"
|
||||||
|
text="已绑定"
|
||||||
|
/>
|
||||||
|
<a-badge v-else status="error" text="未绑定" />
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-tooltip title="解绑">
|
||||||
|
<a-popconfirm
|
||||||
|
title="确认解绑"
|
||||||
|
@confirm="unbind(record.id)"
|
||||||
|
>
|
||||||
|
<a-button type="link" :disabled="!record.id"
|
||||||
|
><AIcon type="icon-jiebang"
|
||||||
|
/></a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
<PatchMapping
|
||||||
|
:deviceId="instanceStore.current.id"
|
||||||
|
v-if="visible"
|
||||||
|
@close="visible = false"
|
||||||
|
@save="onPatchBind"
|
||||||
|
:type="provider"
|
||||||
|
:metaData="modelRef.dataSource"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useInstanceStore } from '@/store/instance';
|
||||||
|
import {
|
||||||
|
queryMapping,
|
||||||
|
saveMapping,
|
||||||
|
removeMapping,
|
||||||
|
queryChannelNoPaging,
|
||||||
|
} from '@/api/device/instance';
|
||||||
|
import MSelect from '../MSelect.vue';
|
||||||
|
import PatchMapping from './PatchMapping.vue';
|
||||||
|
import { message } from 'ant-design-vue/es';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'metadataName',
|
||||||
|
key: 'metadataName',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '通道',
|
||||||
|
dataIndex: 'channelId',
|
||||||
|
key: 'channelId',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '采集器',
|
||||||
|
dataIndex: 'collectorId',
|
||||||
|
key: 'collectorId',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '点位',
|
||||||
|
key: 'pointId',
|
||||||
|
dataIndex: 'pointId',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'id',
|
||||||
|
dataIndex: 'id',
|
||||||
|
width: '10%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: '10%',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
provider: {
|
||||||
|
type: String,
|
||||||
|
default: 'MODBUS_TCP',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const instanceStore = useInstanceStore();
|
||||||
|
const metadata = JSON.parse(instanceStore.current?.metadata || '{}');
|
||||||
|
const loading = ref<boolean>(false);
|
||||||
|
const channelList = ref([]);
|
||||||
|
|
||||||
|
const modelRef = reactive({
|
||||||
|
dataSource: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
|
||||||
|
const getChannel = async () => {
|
||||||
|
const resp: any = await queryChannelNoPaging({
|
||||||
|
paging: false,
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'provider',
|
||||||
|
value: props.provider,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (resp.status === 200) {
|
||||||
|
channelList.value = resp.result?.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id,
|
||||||
|
provider: item.provider,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
getChannel();
|
||||||
|
const _metadata = metadata.properties.map((item: any) => ({
|
||||||
|
metadataId: item.id,
|
||||||
|
metadataName: `${item.name}(${item.id})`,
|
||||||
|
metadataType: 'property',
|
||||||
|
name: item.name,
|
||||||
|
}));
|
||||||
|
if (_metadata && _metadata.length) {
|
||||||
|
const resp: any = await queryMapping(
|
||||||
|
'device',
|
||||||
|
instanceStore.current.id,
|
||||||
|
);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
const array = resp.result.reduce((x: any, y: any) => {
|
||||||
|
const metadataId = _metadata.find(
|
||||||
|
(item: any) => item.metadataId === y.metadataId,
|
||||||
|
);
|
||||||
|
if (metadataId) {
|
||||||
|
Object.assign(metadataId, y);
|
||||||
|
} else {
|
||||||
|
x.push(y);
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}, _metadata);
|
||||||
|
modelRef.dataSource = array;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const unbind = async (id: string) => {
|
||||||
|
if (id) {
|
||||||
|
const resp = await removeMapping('device', instanceStore.current.id, [
|
||||||
|
id,
|
||||||
|
]);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPatchBind = () => {
|
||||||
|
visible.value = false;
|
||||||
|
handleSearch();
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
handleSearch();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSave = () => {
|
||||||
|
formRef.value
|
||||||
|
.validate()
|
||||||
|
.then(async () => {
|
||||||
|
const arr = toRaw(modelRef).dataSource.filter(
|
||||||
|
(i: any) => i.channelId,
|
||||||
|
);
|
||||||
|
if (arr && arr.length !== 0) {
|
||||||
|
const resp = await saveMapping(
|
||||||
|
instanceStore.current.id,
|
||||||
|
props.provider,
|
||||||
|
arr,
|
||||||
|
);
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log('error', err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
:deep(.ant-form-item) {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<a-select allowClear v-model:value="_value" @change="onChange" placeholder="请选择" style="width: 100%">
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in list"
|
||||||
|
:key="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:filter-option="filterOption"
|
||||||
|
>{{ item.name }}</a-select-option
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
queryCollectorNoPaging,
|
||||||
|
queryPointNoPaging,
|
||||||
|
} from '@/api/device/instance';
|
||||||
|
|
||||||
|
const _props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'POINT',
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Emits = {
|
||||||
|
(e: 'update:modelValue', data: string | undefined): void;
|
||||||
|
};
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const list = ref<any[]>([]);
|
||||||
|
const _value = ref<string | undefined>(undefined);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
_value.value = _props.modelValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onChange = (_val: string) => {
|
||||||
|
emit('update:modelValue', _val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCollector = async (_val: string) => {
|
||||||
|
if (!_val) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
const resp = await queryCollectorNoPaging({
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'channelId',
|
||||||
|
value: _val,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (resp.status === 200) {
|
||||||
|
list.value = resp.result as any[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPoint = async (_val: string) => {
|
||||||
|
if (!_val) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
const resp = await queryPointNoPaging({
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
terms: [
|
||||||
|
{
|
||||||
|
column: 'collectorId',
|
||||||
|
value: _val,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (resp.status === 200) {
|
||||||
|
list.value = resp.result as any[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (_props.id) {
|
||||||
|
if (_props.type === 'POINT') {
|
||||||
|
getPoint(_props.id);
|
||||||
|
} else {
|
||||||
|
getCollector(_props.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list.value = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
</style>
|
|
@ -1,146 +1,261 @@
|
||||||
<template>
|
<template>
|
||||||
<page-container :tabList="list" @back="onBack" :tabActiveKey="instanceStore.active" @tabChange="onTabChange">
|
<page-container
|
||||||
|
:tabList="list"
|
||||||
|
@back="onBack"
|
||||||
|
:tabActiveKey="instanceStore.active"
|
||||||
|
@tabChange="onTabChange"
|
||||||
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div>
|
<div>
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center">
|
||||||
<div>{{instanceStore.current.name}}</div>
|
<div>{{ instanceStore.current.name }}</div>
|
||||||
<a-divider type="vertical" />
|
<a-divider type="vertical" />
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-badge :text="instanceStore.current.state?.text" :status="statusMap.get(instanceStore.current.state?.value)" />
|
<a-badge
|
||||||
<a-popconfirm title="确认启用设备" @confirm="handleAction" v-if="instanceStore.current.state?.value === 'notActive'">
|
:text="instanceStore.current.state?.text"
|
||||||
|
:status="
|
||||||
|
statusMap.get(
|
||||||
|
instanceStore.current.state?.value,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<a-popconfirm
|
||||||
|
title="确认启用设备"
|
||||||
|
@confirm="handleAction"
|
||||||
|
v-if="
|
||||||
|
instanceStore.current.state?.value ===
|
||||||
|
'notActive'
|
||||||
|
"
|
||||||
|
>
|
||||||
<a-button type="link">启用设备</a-button>
|
<a-button type="link">启用设备</a-button>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
<a-popconfirm title="确认断开连接" @confirm="handleDisconnect" v-if="instanceStore.current.state?.value === 'online'">
|
<a-popconfirm
|
||||||
|
title="确认断开连接"
|
||||||
|
@confirm="handleDisconnect"
|
||||||
|
v-if="
|
||||||
|
instanceStore.current.state?.value === 'online'
|
||||||
|
"
|
||||||
|
>
|
||||||
<a-button type="link">断开连接</a-button>
|
<a-button type="link">断开连接</a-button>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
<a-tooltip v-if="instanceStore.current?.accessProvider === 'child-device' &&
|
<a-tooltip
|
||||||
instanceStore.current?.state?.value === 'offline'" :title="instanceStore.current?.features?.find((item) => item.id === 'selfManageState')
|
v-if="
|
||||||
? '该设备的在线状态与父设备(网关设备)保持一致'
|
instanceStore.current?.accessProvider ===
|
||||||
: '该设备在线状态由设备自身运行状态决定,不继承父设备(网关设备)的在线状态'">
|
'child-device' &&
|
||||||
<AIcon type="QuestionCircleOutlined" style="font-size: 14px" />
|
instanceStore.current?.state?.value ===
|
||||||
|
'offline'
|
||||||
|
"
|
||||||
|
:title="
|
||||||
|
instanceStore.current?.features?.find(
|
||||||
|
(item) => item.id === 'selfManageState',
|
||||||
|
)
|
||||||
|
? '该设备的在线状态与父设备(网关设备)保持一致'
|
||||||
|
: '该设备在线状态由设备自身运行状态决定,不继承父设备(网关设备)的在线状态'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="font-size: 14px"
|
||||||
|
/>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-top: 10px">
|
<div style="padding-top: 10px">
|
||||||
<a-descriptions size="small" :column="4">
|
<a-descriptions size="small" :column="4">
|
||||||
<a-descriptions-item label="ID">{{ instanceStore.current.id }}</a-descriptions-item>
|
<a-descriptions-item label="ID">{{
|
||||||
|
instanceStore.current.id
|
||||||
|
}}</a-descriptions-item>
|
||||||
<a-descriptions-item label="所属产品">
|
<a-descriptions-item label="所属产品">
|
||||||
<a-button style="margin-top: -5px; padding: 0" type="link" @click="jumpProduct">{{ instanceStore.current.productName }}</a-button>
|
<a-button
|
||||||
|
style="margin-top: -5px; padding: 0"
|
||||||
|
type="link"
|
||||||
|
@click="jumpProduct"
|
||||||
|
>{{
|
||||||
|
instanceStore.current.productName
|
||||||
|
}}</a-button
|
||||||
|
>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<img @click="handleRefresh" :src="getImage('/device/button.png')" style="margin-right: 20px; cursor: pointer;" />
|
<img
|
||||||
|
@click="handleRefresh"
|
||||||
|
:src="getImage('/device/button.png')"
|
||||||
|
style="margin-right: 20px; cursor: pointer"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<component :is="tabs[instanceStore.tabActiveKey]" v-bind="{ type: 'device' }" @onJump="onTabChange" />
|
<component
|
||||||
|
:is="tabs[instanceStore.tabActiveKey]"
|
||||||
|
v-bind="{ type: 'device' }"
|
||||||
|
@onJump="onTabChange"
|
||||||
|
/>
|
||||||
</page-container>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useInstanceStore } from '@/store/instance';
|
import { useInstanceStore } from '@/store/instance';
|
||||||
import Info from './Info/index.vue';
|
import Info from './Info/index.vue';
|
||||||
import Running from './Running/index.vue'
|
import Running from './Running/index.vue';
|
||||||
import Metadata from '../../components/Metadata/index.vue';
|
import Metadata from '../../components/Metadata/index.vue';
|
||||||
import ChildDevice from './ChildDevice/index.vue';
|
import ChildDevice from './ChildDevice/index.vue';
|
||||||
import Diagnose from './Diagnose/index.vue'
|
import Diagnose from './Diagnose/index.vue';
|
||||||
import Function from './Function/index.vue'
|
import Function from './Function/index.vue';
|
||||||
import { _deploy, _disconnect } from '@/api/device/instance'
|
import Modbus from './Modbus/index.vue';
|
||||||
|
import OPCUA from './OPCUA/index.vue';
|
||||||
|
import EdgeMap from './EdgeMap/index.vue';
|
||||||
|
import { _deploy, _disconnect } from '@/api/device/instance';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
|
import { getWebSocket } from '@/utils/websocket';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const instanceStore = useInstanceStore()
|
const instanceStore = useInstanceStore();
|
||||||
|
|
||||||
const statusMap = new Map();
|
const statusMap = new Map();
|
||||||
statusMap.set('online', 'success');
|
statusMap.set('online', 'success');
|
||||||
statusMap.set('offline', 'error');
|
statusMap.set('offline', 'error');
|
||||||
statusMap.set('notActive', 'warning');
|
statusMap.set('notActive', 'warning');
|
||||||
|
|
||||||
const list = [
|
const statusRef = ref();
|
||||||
|
|
||||||
|
const list = ref([
|
||||||
{
|
{
|
||||||
key: 'Info',
|
key: 'Info',
|
||||||
tab: '实例信息'
|
tab: '实例信息',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Running',
|
key: 'Running',
|
||||||
tab: '运行状态'
|
tab: '运行状态',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Metadata',
|
key: 'Metadata',
|
||||||
tab: '物模型'
|
tab: '物模型',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'Function',
|
key: 'Function',
|
||||||
tab: '设备功能'
|
tab: '设备功能',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'ChildDevice',
|
key: 'ChildDevice',
|
||||||
tab: '子设备'
|
tab: '子设备',
|
||||||
},
|
},
|
||||||
{
|
]);
|
||||||
key: 'Diagnose',
|
|
||||||
tab: '设备诊断'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const tabs = {
|
const tabs = {
|
||||||
Info,
|
Info,
|
||||||
Metadata,
|
Metadata,
|
||||||
Running,
|
Running,
|
||||||
ChildDevice,
|
ChildDevice,
|
||||||
Diagnose,
|
Diagnose,
|
||||||
Function
|
Function,
|
||||||
}
|
Modbus,
|
||||||
|
OPCUA,
|
||||||
|
EdgeMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatus = (id: string) => {
|
||||||
|
statusRef.value = getWebSocket(
|
||||||
|
`instance-editor-info-status-${id}`,
|
||||||
|
`/dashboard/device/status/change/realTime`,
|
||||||
|
{
|
||||||
|
deviceId: id,
|
||||||
|
},
|
||||||
|
).subscribe(() => {
|
||||||
|
instanceStore.refresh(id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.params.id,
|
() => route.params.id,
|
||||||
(newId) => {
|
(newId) => {
|
||||||
if(newId){
|
if (newId) {
|
||||||
instanceStore.tabActiveKey = 'Info'
|
instanceStore.tabActiveKey = 'Info';
|
||||||
instanceStore.refresh(newId as string)
|
instanceStore.refresh(newId as string);
|
||||||
|
|
||||||
|
getStatus(String(newId));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{immediate: true, deep: true}
|
{ immediate: true, deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const onBack = () => {
|
const onBack = () => {};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTabChange = (e: string) => {
|
const onTabChange = (e: string) => {
|
||||||
instanceStore.tabActiveKey = e
|
instanceStore.tabActiveKey = e;
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleAction = async () => {
|
const handleAction = async () => {
|
||||||
if(instanceStore.current.id){
|
if (instanceStore.current.id) {
|
||||||
const resp = await _deploy(instanceStore.current.id)
|
const resp = await _deploy(instanceStore.current.id);
|
||||||
if(resp.status === 200){
|
if (resp.status === 200) {
|
||||||
message.success('操作成功!')
|
message.success('操作成功!');
|
||||||
instanceStore.refresh(instanceStore.current.id)
|
instanceStore.refresh(instanceStore.current.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleDisconnect = async () => {
|
const handleDisconnect = async () => {
|
||||||
if(instanceStore.current.id){
|
if (instanceStore.current.id) {
|
||||||
const resp = await _disconnect(instanceStore.current.id)
|
const resp = await _disconnect(instanceStore.current.id);
|
||||||
if(resp.status === 200){
|
if (resp.status === 200) {
|
||||||
message.success('操作成功!')
|
message.success('操作成功!');
|
||||||
instanceStore.refresh(instanceStore.current.id)
|
instanceStore.refresh(instanceStore.current.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
if(instanceStore.current.id){
|
if (instanceStore.current.id) {
|
||||||
await instanceStore.refresh(instanceStore.current.id)
|
await instanceStore.refresh(instanceStore.current.id);
|
||||||
message.success('操作成功')
|
message.success('操作成功');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const jumpProduct = () => {
|
const jumpProduct = () => {
|
||||||
message.warn('暂未开发')
|
message.warn('暂未开发');
|
||||||
}
|
};
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
const keys = list.value.map((i) => i.key);
|
||||||
|
if (instanceStore.current.protocol && !(['modbus-tcp', 'opc-ua'].includes(instanceStore.current.protocol)) && !keys.includes('Diagnose')) {
|
||||||
|
list.value.push({
|
||||||
|
key: 'Diagnose',
|
||||||
|
tab: '设备诊断',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
instanceStore.current.protocol === 'modbus-tcp' &&
|
||||||
|
!keys.includes('Modbus')
|
||||||
|
) {
|
||||||
|
list.value.push({
|
||||||
|
key: 'Modbus',
|
||||||
|
tab: 'Modbus TCP',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
instanceStore.current.protocol === 'opc-ua' &&
|
||||||
|
!keys.includes('OPCUA')
|
||||||
|
) {
|
||||||
|
list.value.push({
|
||||||
|
key: 'OPCUA',
|
||||||
|
tab: 'OPC UA',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
instanceStore.current.accessProvider === 'edge-child-device' &&
|
||||||
|
instanceStore.current.parentId &&
|
||||||
|
!keys.includes('EdgeMap')
|
||||||
|
) {
|
||||||
|
list.value.push({
|
||||||
|
key: 'EdgeMap',
|
||||||
|
tab: '边缘端映射',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
statusRef.value && statusRef.value.unsubscribe();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
|
@ -153,6 +153,7 @@ import { getImage } from '@/utils/comm';
|
||||||
import { list, remove } from '@/api/link/protocol';
|
import { list, remove } from '@/api/link/protocol';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import Save from './Save/index.vue';
|
import Save from './Save/index.vue';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const tableRef = ref<Record<string, any>>({});
|
const tableRef = ref<Record<string, any>>({});
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -261,7 +262,7 @@ const handlAdd = () => {
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
};
|
};
|
||||||
const handlEdit = (data: object) => {
|
const handlEdit = (data: object) => {
|
||||||
current.value = data;
|
current.value = _.cloneDeep(data);
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<page-container>
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="6">
|
<a-col :span="6">
|
||||||
<TopCard
|
<TopCard
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
/>
|
/>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -200,10 +200,7 @@ const getPlayCount = async (params: any) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.page-container {
|
.dash-board-bottom {
|
||||||
padding: 24px;
|
margin-top: 24px;
|
||||||
.dash-board-bottom {
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<!-- 通知模板详情 -->
|
<!-- 通知模板详情 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<page-container>
|
||||||
<a-card>
|
<a-card>
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
|
@ -241,7 +241,7 @@
|
||||||
:channel="formData.channel"
|
:channel="formData.channel"
|
||||||
@close="getProductList"
|
@close="getProductList"
|
||||||
/>
|
/>
|
||||||
</div>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -353,7 +353,8 @@ const saveProductVis = ref(false);
|
||||||
const getDetail = async () => {
|
const getDetail = async () => {
|
||||||
const res = await DeviceApi.detail(route.query.id as string);
|
const res = await DeviceApi.detail(route.query.id as string);
|
||||||
// console.log('res: ', res);
|
// console.log('res: ', res);
|
||||||
formData.value = res.result;
|
// formData.value = res.result;
|
||||||
|
Object.assign(formData.value, res.result);
|
||||||
formData.value.channel = res.result.provider;
|
formData.value.channel = res.result.provider;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -366,8 +367,7 @@ onMounted(() => {
|
||||||
*/
|
*/
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const form = useForm(formData.value, formRules.value);
|
validate()
|
||||||
form.validate()
|
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
btnLoading.value = true;
|
btnLoading.value = true;
|
||||||
let res;
|
let res;
|
||||||
|
@ -392,8 +392,4 @@ const handleSubmit = () => {
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import './index.less';
|
@import './index.less';
|
||||||
.page-container {
|
|
||||||
background: #f0f2f5;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<page-container>
|
||||||
<Search
|
<Search
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
target="notice-config"
|
target="notice-config"
|
||||||
|
@ -126,7 +126,7 @@
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
</JTable>
|
</JTable>
|
||||||
</div>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -137,7 +137,9 @@ import { getImage } from '@/utils/comm';
|
||||||
import { PROVIDER_OPTIONS } from '@/views/media/Device/const';
|
import { PROVIDER_OPTIONS } from '@/views/media/Device/const';
|
||||||
import { providerType } from './const';
|
import { providerType } from './const';
|
||||||
|
|
||||||
const router = useRouter();
|
import { useMenuStore } from 'store/menu';
|
||||||
|
|
||||||
|
const menuStory = useMenuStore();
|
||||||
|
|
||||||
const listRef = ref<Record<string, any>>({});
|
const listRef = ref<Record<string, any>>({});
|
||||||
const params = ref<Record<string, any>>({});
|
const params = ref<Record<string, any>>({});
|
||||||
|
@ -240,7 +242,9 @@ const handleSearch = (e: any) => {
|
||||||
* 新增
|
* 新增
|
||||||
*/
|
*/
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
router.push(`/media/device/Save`);
|
menuStory.jumpPage('media/Device/Save', {
|
||||||
|
id: ':id',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActions = (
|
const getActions = (
|
||||||
|
@ -257,7 +261,9 @@ const getActions = (
|
||||||
},
|
},
|
||||||
icon: 'EditOutlined',
|
icon: 'EditOutlined',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
router.push(`/media/device/Save?id=${data.id}`);
|
menuStory.jumpPage('media/Device/Save', {
|
||||||
|
id: data.id,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -268,9 +274,13 @@ const getActions = (
|
||||||
},
|
},
|
||||||
icon: 'PartitionOutlined',
|
icon: 'PartitionOutlined',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
router.push(
|
// router.push(
|
||||||
`/media/device/Channel?id=${data.id}&type=${data.provider}`,
|
// `/media/device/Channel?id=${data.id}&type=${data.provider}`,
|
||||||
);
|
// );
|
||||||
|
menuStory.jumpPage('media/Device/Channel', {
|
||||||
|
id: data.id,
|
||||||
|
type: data.provider,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -320,9 +330,3 @@ const getActions = (
|
||||||
return actions;
|
return actions;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
|
||||||
.page-container {
|
|
||||||
background: #f0f2f5;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -27,6 +27,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import homeApi from '@/api/media/home';
|
import homeApi from '@/api/media/home';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
|
import { useMenuStore } from 'store/menu';
|
||||||
|
|
||||||
|
const menuStory = useMenuStore();
|
||||||
|
|
||||||
const channelCount = ref(0);
|
const channelCount = ref(0);
|
||||||
const deviceCount = ref(0);
|
const deviceCount = ref(0);
|
||||||
|
@ -44,9 +47,10 @@ const getData = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const jumpPage = () => {
|
const jumpPage = () => {
|
||||||
router.push('/media/dashboard');
|
menuStory.jumpPage('media/DashBoard', {
|
||||||
|
id: ':id',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<page-container>
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="14">
|
<a-col :span="14">
|
||||||
<BootCard
|
<BootCard
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
/>
|
/>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -96,9 +96,3 @@ const deviceStepDetails: recommendList[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.page-container {
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<!-- 通知配置详情 -->
|
<!-- 通知配置详情 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<page-container>
|
||||||
<a-card>
|
<a-card>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="10">
|
<a-col :span="10">
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="formData.type"
|
v-model:value="formData.type"
|
||||||
placeholder="请选择通知方式"
|
placeholder="请选择通知方式"
|
||||||
|
:disabled="!!formData.id"
|
||||||
>
|
>
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="(item, index) in NOTICE_METHOD"
|
v-for="(item, index) in NOTICE_METHOD"
|
||||||
|
@ -272,7 +273,7 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</div>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -426,8 +427,8 @@ const clearValid = () => {
|
||||||
const getDetail = async () => {
|
const getDetail = async () => {
|
||||||
if (route.params.id === ':id') return;
|
if (route.params.id === ':id') return;
|
||||||
const res = await configApi.detail(route.params.id as string);
|
const res = await configApi.detail(route.params.id as string);
|
||||||
// console.log('res: ', res);
|
// formData.value = res.result;
|
||||||
formData.value = res.result;
|
Object.assign(formData.value, res.result);
|
||||||
// console.log('formData.value: ', formData.value);
|
// console.log('formData.value: ', formData.value);
|
||||||
};
|
};
|
||||||
getDetail();
|
getDetail();
|
||||||
|
@ -461,10 +462,3 @@ const handleSubmit = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.page-container {
|
|
||||||
background: #f0f2f5;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ const columns = [
|
||||||
search: {
|
search: {
|
||||||
type: 'date',
|
type: 'date',
|
||||||
handleValue: (v: any) => {
|
handleValue: (v: any) => {
|
||||||
return '123';
|
return v;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -110,7 +110,7 @@ const columns = [
|
||||||
{ label: '失败', value: 'error' },
|
{ label: '失败', value: 'error' },
|
||||||
],
|
],
|
||||||
handleValue: (v: any) => {
|
handleValue: (v: any) => {
|
||||||
return '123';
|
return v;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -139,7 +139,16 @@ const handleSearch = (e: any) => {
|
||||||
const handleError = (e: any) => {
|
const handleError = (e: any) => {
|
||||||
Modal.info({
|
Modal.info({
|
||||||
title: '错误信息',
|
title: '错误信息',
|
||||||
content: JSON.stringify(e),
|
content: h(
|
||||||
|
'p',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
maxHeight: '300px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JSON.stringify(e),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
|
@ -148,7 +157,16 @@ const handleError = (e: any) => {
|
||||||
const handleDetail = (e: any) => {
|
const handleDetail = (e: any) => {
|
||||||
Modal.info({
|
Modal.info({
|
||||||
title: '详情信息',
|
title: '详情信息',
|
||||||
content: JSON.stringify(e),
|
content: h(
|
||||||
|
'p',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
maxHeight: '300px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JSON.stringify(e),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<page-container>
|
||||||
<Search
|
<Search
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
target="notice-config"
|
target="notice-config"
|
||||||
|
@ -166,7 +166,7 @@
|
||||||
<Debug v-model:visible="debugVis" :data="currentConfig" />
|
<Debug v-model:visible="debugVis" :data="currentConfig" />
|
||||||
<Log v-model:visible="logVis" :data="currentConfig" />
|
<Log v-model:visible="logVis" :data="currentConfig" />
|
||||||
<SyncUser v-model:visible="syncVis" :data="currentConfig" />
|
<SyncUser v-model:visible="syncVis" :data="currentConfig" />
|
||||||
</div>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -180,14 +180,15 @@ import SyncUser from './SyncUser/index.vue';
|
||||||
import Debug from './Debug/index.vue';
|
import Debug from './Debug/index.vue';
|
||||||
import Log from './Log/index.vue';
|
import Log from './Log/index.vue';
|
||||||
import { downloadObject } from '@/utils/utils';
|
import { downloadObject } from '@/utils/utils';
|
||||||
|
import { useMenuStore } from 'store/menu';
|
||||||
|
|
||||||
|
const menuStory = useMenuStore();
|
||||||
|
|
||||||
let providerList: any = [];
|
let providerList: any = [];
|
||||||
Object.keys(MSG_TYPE).forEach((key) => {
|
Object.keys(MSG_TYPE).forEach((key) => {
|
||||||
providerList = [...providerList, ...MSG_TYPE[key]];
|
providerList = [...providerList, ...MSG_TYPE[key]];
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const configRef = ref<Record<string, any>>({});
|
const configRef = ref<Record<string, any>>({});
|
||||||
const params = ref<Record<string, any>>({});
|
const params = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
@ -209,7 +210,7 @@ const columns = [
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: NOTICE_METHOD,
|
options: NOTICE_METHOD,
|
||||||
handleValue: (v: any) => {
|
handleValue: (v: any) => {
|
||||||
return '123';
|
return v;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -222,7 +223,7 @@ const columns = [
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: providerList,
|
options: providerList,
|
||||||
handleValue: (v: any) => {
|
handleValue: (v: any) => {
|
||||||
return '123';
|
return v;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -270,7 +271,7 @@ const getMethodTxt = (type: string) => {
|
||||||
* 新增
|
* 新增
|
||||||
*/
|
*/
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
router.push(`/iot/notice/Config/detail/:id`);
|
menuStory.jumpPage('notice/Config/Detail', { id: ':id' });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -330,7 +331,9 @@ const getActions = (
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
// visible.value = true;
|
// visible.value = true;
|
||||||
// current.value = data;
|
// current.value = data;
|
||||||
router.push(`/iot/notice/Config/detail/${data.id}`);
|
menuStory.jumpPage('notice/Config/Detail', {
|
||||||
|
id: data.id,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -426,9 +429,3 @@ const getActions = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
|
||||||
.page-container {
|
|
||||||
background: #f0f2f5;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
placeholder="请选择收信部门"
|
placeholder="请选择收信部门"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:allowClear="true"
|
:allowClear="true"
|
||||||
|
v-model:value="_value"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -17,12 +18,19 @@ type Emits = {
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
toParty: { type: String, default: '' },
|
||||||
type: { type: String, default: '' },
|
type: { type: String, default: '' },
|
||||||
configId: { type: String, default: '' },
|
configId: { type: String, default: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const _value = computed({
|
||||||
|
get: () => props.toParty,
|
||||||
|
set: (val: string) => emit('update:toParty', val),
|
||||||
|
});
|
||||||
|
|
||||||
const options = ref([]);
|
const options = ref([]);
|
||||||
const queryData = async () => {
|
const queryData = async () => {
|
||||||
|
if (!props.configId) return;
|
||||||
const { result } = await templateApi.getDept(props.type, props.configId);
|
const { result } = await templateApi.getDept(props.type, props.configId);
|
||||||
options.value = result.map((item: any) => ({
|
options.value = result.map((item: any) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
placeholder="请选择标签推送,多个标签用,号分隔"
|
placeholder="请选择标签推送,多个标签用,号分隔"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:allowClear="true"
|
:allowClear="true"
|
||||||
|
v-model:value="_value"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -17,12 +18,19 @@ type Emits = {
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
toTag: { type: String, default: '' },
|
||||||
type: { type: String, default: '' },
|
type: { type: String, default: '' },
|
||||||
configId: { type: String, default: '' },
|
configId: { type: String, default: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const _value = computed({
|
||||||
|
get: () => props.toTag,
|
||||||
|
set: (val: string) => emit('update:toTag', val),
|
||||||
|
});
|
||||||
|
|
||||||
const options = ref([]);
|
const options = ref([]);
|
||||||
const queryData = async () => {
|
const queryData = async () => {
|
||||||
|
if (!props.configId) return;
|
||||||
const { result } = await templateApi.getTags(props.configId);
|
const { result } = await templateApi.getTags(props.configId);
|
||||||
options.value = result.map((item: any) => ({
|
options.value = result.map((item: any) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
placeholder="请选择收信人"
|
placeholder="请选择收信人"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
:allowClear="true"
|
:allowClear="true"
|
||||||
|
v-model:value="_value"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -17,12 +18,19 @@ type Emits = {
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
toUser: { type: String, default: '' },
|
||||||
type: { type: String, default: '' },
|
type: { type: String, default: '' },
|
||||||
configId: { type: String, default: '' },
|
configId: { type: String, default: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const _value = computed({
|
||||||
|
get: () => props.toUser,
|
||||||
|
set: (val: string) => emit('update:toUser', val),
|
||||||
|
});
|
||||||
|
|
||||||
const options = ref([]);
|
const options = ref([]);
|
||||||
const queryData = async () => {
|
const queryData = async () => {
|
||||||
|
if (!props.configId) return;
|
||||||
const { result } = await templateApi.getUser(props.type, props.configId);
|
const { result } = await templateApi.getUser(props.type, props.configId);
|
||||||
options.value = result.map((item: any) => ({
|
options.value = result.map((item: any) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<!-- 通知模板详情 -->
|
<!-- 通知模板详情 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<page-container>
|
||||||
<a-card>
|
<a-card>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="10">
|
<a-col :span="10">
|
||||||
|
@ -12,6 +12,8 @@
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="formData.type"
|
v-model:value="formData.type"
|
||||||
placeholder="请选择通知方式"
|
placeholder="请选择通知方式"
|
||||||
|
:disabled="!!formData.id"
|
||||||
|
@change="handleTypeChange"
|
||||||
>
|
>
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="(item, index) in NOTICE_METHOD"
|
v-for="(item, index) in NOTICE_METHOD"
|
||||||
|
@ -39,14 +41,26 @@
|
||||||
<RadioCard
|
<RadioCard
|
||||||
:options="msgType"
|
:options="msgType"
|
||||||
v-model="formData.provider"
|
v-model="formData.provider"
|
||||||
@change="getConfigList"
|
@change="handleProviderChange"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="绑定配置"
|
|
||||||
v-bind="validateInfos.configId"
|
v-bind="validateInfos.configId"
|
||||||
v-if="formData.type !== 'email'"
|
v-if="formData.type !== 'email'"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
绑定配置
|
||||||
|
<a-tooltip
|
||||||
|
title="使用固定的通知配置来发送此通知模版"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="formData.configId"
|
v-model:value="formData.configId"
|
||||||
placeholder="请选择绑定配置"
|
placeholder="请选择绑定配置"
|
||||||
|
@ -67,9 +81,19 @@
|
||||||
v-if="formData.provider === 'dingTalkMessage'"
|
v-if="formData.provider === 'dingTalkMessage'"
|
||||||
>
|
>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="AgentId"
|
|
||||||
v-bind="validateInfos['template.agentId']"
|
v-bind="validateInfos['template.agentId']"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
AgentID
|
||||||
|
<a-tooltip title="应用唯一标识">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.template.agentId
|
formData.template.agentId
|
||||||
|
@ -77,6 +101,45 @@
|
||||||
placeholder="请输入AppSecret"
|
placeholder="请输入AppSecret"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-row :gutter="10">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item label="收信部门">
|
||||||
|
<ToOrg
|
||||||
|
v-model:toParty="
|
||||||
|
formData.template.toParty
|
||||||
|
"
|
||||||
|
:type="formData.type"
|
||||||
|
:config-id="formData.configId"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
收信人
|
||||||
|
<a-tooltip
|
||||||
|
title="如果不填写该字段,将在使用此模板发送通知时进行指定"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="
|
||||||
|
margin-left: 2px;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<ToUser
|
||||||
|
v-model:toUser="
|
||||||
|
formData.template.toUser
|
||||||
|
"
|
||||||
|
:type="formData.type"
|
||||||
|
:config-id="formData.configId"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
v-if="
|
v-if="
|
||||||
|
@ -165,10 +228,13 @@
|
||||||
}"
|
}"
|
||||||
:showUploadList="false"
|
:showUploadList="false"
|
||||||
@change="
|
@change="
|
||||||
(e) => handleChange(e)
|
(e) =>
|
||||||
|
handleLinkChange(e)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<AIcon type="UploadOutlined" />
|
<AIcon
|
||||||
|
type="UploadOutlined"
|
||||||
|
/>
|
||||||
</a-upload>
|
</a-upload>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
|
@ -188,9 +254,19 @@
|
||||||
<!-- 微信 -->
|
<!-- 微信 -->
|
||||||
<template v-if="formData.type === 'weixin'">
|
<template v-if="formData.type === 'weixin'">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="AgentId"
|
|
||||||
v-bind="validateInfos['template.agentId']"
|
v-bind="validateInfos['template.agentId']"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
AgentId
|
||||||
|
<a-tooltip title="应用唯一标识">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="formData.template.agentId"
|
v-model:value="formData.template.agentId"
|
||||||
placeholder="请输入agentId"
|
placeholder="请输入agentId"
|
||||||
|
@ -198,9 +274,22 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="收信人">
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
收信人
|
||||||
|
<a-tooltip
|
||||||
|
title="如果不填写该字段,将在使用此模版发送通知时进行指定。"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<ToUser
|
<ToUser
|
||||||
v-model:to-user="
|
v-model:toUser="
|
||||||
formData.template.toUser
|
formData.template.toUser
|
||||||
"
|
"
|
||||||
:type="formData.type"
|
:type="formData.type"
|
||||||
|
@ -211,7 +300,7 @@
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="收信部门">
|
<a-form-item label="收信部门">
|
||||||
<ToOrg
|
<ToOrg
|
||||||
v-model:to-user="
|
v-model:toParty="
|
||||||
formData.template.toParty
|
formData.template.toParty
|
||||||
"
|
"
|
||||||
:type="formData.type"
|
:type="formData.type"
|
||||||
|
@ -220,9 +309,22 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-form-item label="标签推送">
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
标签推送
|
||||||
|
<a-tooltip
|
||||||
|
title="本企业微信的标签ID列表,最多支持100个,如果不填写该字段,将在使用此模版发送通知时进行指定"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<ToTag
|
<ToTag
|
||||||
v-model:to-user="formData.template.toTag"
|
v-model:toTag="formData.template.toTag"
|
||||||
:type="formData.type"
|
:type="formData.type"
|
||||||
:config-id="formData.configId"
|
:config-id="formData.configId"
|
||||||
/>
|
/>
|
||||||
|
@ -231,15 +333,38 @@
|
||||||
<!-- 邮件 -->
|
<!-- 邮件 -->
|
||||||
<template v-if="formData.type === 'email'">
|
<template v-if="formData.type === 'email'">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="标题"
|
|
||||||
v-bind="validateInfos['template.subject']"
|
v-bind="validateInfos['template.subject']"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
标题
|
||||||
|
<a-tooltip title="邮件标题">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="formData.template.subject"
|
v-model:value="formData.template.subject"
|
||||||
placeholder="请输入标题"
|
placeholder="请输入标题"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="收件人">
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
收件人
|
||||||
|
<a-tooltip
|
||||||
|
title="多个收件人用换行分隔 最大支持1000个号码"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-select
|
<a-select
|
||||||
mode="tags"
|
mode="tags"
|
||||||
:options="[]"
|
:options="[]"
|
||||||
|
@ -247,7 +372,20 @@
|
||||||
placeholder="请选择收件人"
|
placeholder="请选择收件人"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="附件信息">
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
附件信息
|
||||||
|
<a-tooltip
|
||||||
|
title="附件只输入文件名称将在发送邮件时进行文件上传"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<Attachments
|
<Attachments
|
||||||
v-model:attachments="
|
v-model:attachments="
|
||||||
formData.template.attachments
|
formData.template.attachments
|
||||||
|
@ -258,9 +396,21 @@
|
||||||
<!-- 语音 -->
|
<!-- 语音 -->
|
||||||
<template v-if="formData.type === 'voice'">
|
<template v-if="formData.type === 'voice'">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="类型"
|
|
||||||
v-bind="validateInfos['template.templateType']"
|
v-bind="validateInfos['template.templateType']"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
类型
|
||||||
|
<a-tooltip
|
||||||
|
title="语音验证码类型可配置变量,并且只支持数字和英文字母"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.template.templateType
|
formData.template.templateType
|
||||||
|
@ -279,13 +429,25 @@
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="模板ID"
|
|
||||||
v-bind="
|
v-bind="
|
||||||
validateInfos[
|
validateInfos[
|
||||||
'template.templateCode'
|
'template.templateCode'
|
||||||
]
|
]
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
模板ID
|
||||||
|
<a-tooltip
|
||||||
|
title="阿里云内部分配的唯一ID标识"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.template.templateCode
|
formData.template.templateCode
|
||||||
|
@ -295,7 +457,20 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="被叫号码">
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
被叫号码
|
||||||
|
<a-tooltip
|
||||||
|
title="仅支持中国大陆号码"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.template.calledNumber
|
formData.template.calledNumber
|
||||||
|
@ -305,7 +480,20 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-form-item label="被叫显号">
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
被叫显号
|
||||||
|
<a-tooltip
|
||||||
|
title="必须是已购买的号码,用于呼叫号码显示"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.template.calledShowNumbers
|
formData.template.calledShowNumbers
|
||||||
|
@ -313,16 +501,39 @@
|
||||||
placeholder="请输入被叫显号"
|
placeholder="请输入被叫显号"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="播放次数">
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
播放次数
|
||||||
|
<a-tooltip title="语音文件的播放次数">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="formData.template.playTimes"
|
v-model:value="formData.template.playTimes"
|
||||||
placeholder="请输入播放次数"
|
placeholder="请输入播放次数"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="模版内容"
|
|
||||||
v-if="formData.template.templateType === 'tts'"
|
v-if="formData.template.templateType === 'tts'"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
模版内容
|
||||||
|
<a-tooltip
|
||||||
|
title="语音验证码内容输入框,用于渲染验语音证码变量。"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:value="formData.template.message"
|
v-model:value="formData.template.message"
|
||||||
show-count
|
show-count
|
||||||
|
@ -336,14 +547,27 @@
|
||||||
<a-row :gutter="10">
|
<a-row :gutter="10">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="模板"
|
|
||||||
v-bind="validateInfos['template.code']"
|
v-bind="validateInfos['template.code']"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
模板
|
||||||
|
<a-tooltip
|
||||||
|
title="阿里云短信平台自定义的模版名称"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.template.code
|
formData.template.code
|
||||||
"
|
"
|
||||||
placeholder="请选择模板"
|
placeholder="请选择模板"
|
||||||
|
@change="handleTemplateChange"
|
||||||
>
|
>
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="(
|
v-for="(
|
||||||
|
@ -358,7 +582,20 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="收信人">
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
收信人
|
||||||
|
<a-tooltip
|
||||||
|
title="仅支持中国大陆号码"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="
|
v-model:value="
|
||||||
formData.template.phoneNumber
|
formData.template.phoneNumber
|
||||||
|
@ -369,9 +606,21 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="签名"
|
|
||||||
v-bind="validateInfos['template.signName']"
|
v-bind="validateInfos['template.signName']"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
签名
|
||||||
|
<a-tooltip
|
||||||
|
title="用于短信内容签名信息显示"
|
||||||
|
>
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="formData.template.signName"
|
v-model:value="formData.template.signName"
|
||||||
placeholder="请选择签名"
|
placeholder="请选择签名"
|
||||||
|
@ -416,17 +665,28 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
label="模版内容"
|
|
||||||
v-if="
|
v-if="
|
||||||
formData.type !== 'sms' &&
|
|
||||||
formData.type !== 'webhook' &&
|
formData.type !== 'webhook' &&
|
||||||
formData.type !== 'voice'
|
formData.type !== 'voice'
|
||||||
"
|
"
|
||||||
|
v-bind="validateInfos['template.message']"
|
||||||
>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>
|
||||||
|
模版内容
|
||||||
|
<a-tooltip title="发送的内容,支持录入变量">
|
||||||
|
<AIcon
|
||||||
|
type="QuestionCircleOutlined"
|
||||||
|
style="margin-left: 2px"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:value="formData.template.message"
|
v-model:value="formData.template.message"
|
||||||
:maxlength="200"
|
:maxlength="200"
|
||||||
:rows="5"
|
:rows="5"
|
||||||
|
:disabled="formData.type === 'sms'"
|
||||||
placeholder="变量格式:${name};
|
placeholder="变量格式:${name};
|
||||||
示例:尊敬的${name},${time}有设备触发告警,请注意处理"
|
示例:尊敬的${name},${time}有设备触发告警,请注意处理"
|
||||||
/>
|
/>
|
||||||
|
@ -470,7 +730,7 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</div>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -533,32 +793,62 @@ const formData = ref<TemplateFormData>({
|
||||||
configId: '',
|
configId: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置公用字段值
|
||||||
|
*/
|
||||||
|
const resetPublicFiles = () => {
|
||||||
|
formData.value.template.message = '';
|
||||||
|
formData.value.configId = undefined;
|
||||||
|
|
||||||
|
if (
|
||||||
|
formData.value.type === 'dingTalk' ||
|
||||||
|
formData.value.type === 'weixin'
|
||||||
|
) {
|
||||||
|
formData.value.template.toTag = undefined;
|
||||||
|
formData.value.template.toUser = undefined;
|
||||||
|
formData.value.template.agentId = undefined;
|
||||||
|
}
|
||||||
|
if (formData.value.type === 'weixin')
|
||||||
|
formData.value.template.toParty = undefined;
|
||||||
|
if (formData.value.type === 'email')
|
||||||
|
formData.value.template.toParty = undefined;
|
||||||
|
// formData.value.description = '';
|
||||||
|
};
|
||||||
|
|
||||||
// 根据通知方式展示对应的字段
|
// 根据通知方式展示对应的字段
|
||||||
watch(
|
watch(
|
||||||
() => formData.value.type,
|
() => formData.value.type,
|
||||||
(val) => {
|
(val) => {
|
||||||
// formData.value.template = TEMPLATE_FIELD_MAP[val];
|
|
||||||
msgType.value = MSG_TYPE[val];
|
msgType.value = MSG_TYPE[val];
|
||||||
|
formData.value.provider =
|
||||||
formData.value.provider = msgType.value[0].value;
|
route.params.id !== ':id'
|
||||||
|
? formData.value.provider
|
||||||
|
: msgType.value[0].value;
|
||||||
|
// formData.value.provider = formData.value.provider || msgType.value[0].value;
|
||||||
// console.log('formData.value.template: ', formData.value.template);
|
// console.log('formData.value.template: ', formData.value.template);
|
||||||
|
|
||||||
formData.value.template =
|
// formData.value.template =
|
||||||
TEMPLATE_FIELD_MAP[val][formData.value.provider];
|
// TEMPLATE_FIELD_MAP[val][formData.value.provider];
|
||||||
|
|
||||||
getConfigList();
|
if (val !== 'email') getConfigList();
|
||||||
clearValid();
|
// clearValid();
|
||||||
|
// console.log('formData.value: ', formData.value);
|
||||||
|
|
||||||
|
if (val === 'sms') {
|
||||||
|
getTemplateList();
|
||||||
|
getSignsList();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
// watch(
|
||||||
() => formData.value.provider,
|
// () => formData.value.provider,
|
||||||
(val) => {
|
// (val) => {
|
||||||
formData.value.template = TEMPLATE_FIELD_MAP[formData.value.type][val];
|
// formData.value.template = TEMPLATE_FIELD_MAP[formData.value.type][val];
|
||||||
|
|
||||||
clearValid();
|
// clearValid();
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
// 验证规则
|
// 验证规则
|
||||||
const formRules = ref({
|
const formRules = ref({
|
||||||
|
@ -586,6 +876,7 @@ const formRules = ref({
|
||||||
'template.signName': [{ required: true, message: '请输入签名' }],
|
'template.signName': [{ required: true, message: '请输入签名' }],
|
||||||
// webhook
|
// webhook
|
||||||
description: [{ max: 200, message: '最多可输入200个字符' }],
|
description: [{ max: 200, message: '最多可输入200个字符' }],
|
||||||
|
'template.message': [{ required: true, message: '请输入' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
const { resetFields, validate, validateInfos, clearValidate } = useForm(
|
||||||
|
@ -620,23 +911,25 @@ watch(
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearValid = () => {
|
// const clearValid = () => {
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
formData.value.variableDefinitions = [];
|
// formData.value.variableDefinitions = [];
|
||||||
clearValidate();
|
// clearValidate();
|
||||||
}, 200);
|
// }, 200);
|
||||||
};
|
// };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取详情
|
* 获取详情
|
||||||
*/
|
*/
|
||||||
const getDetail = async () => {
|
const getDetail = async () => {
|
||||||
const res = await templateApi.detail(route.params.id as string);
|
if (route.params.id !== ':id') {
|
||||||
// console.log('res: ', res);
|
const res = await templateApi.detail(route.params.id as string);
|
||||||
formData.value = res.result;
|
// formData.value = res.result;
|
||||||
// console.log('formData.value: ', formData.value);
|
Object.assign(formData.value, res.result);
|
||||||
|
// console.log('formData.value: ', formData.value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// getDetail();
|
getDetail();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取绑定配置
|
* 获取绑定配置
|
||||||
|
@ -650,12 +943,33 @@ const getConfigList = async () => {
|
||||||
const { result } = await templateApi.getConfig({ terms });
|
const { result } = await templateApi.getConfig({ terms });
|
||||||
configList.value = result;
|
configList.value = result;
|
||||||
};
|
};
|
||||||
getConfigList();
|
|
||||||
|
/**
|
||||||
|
* 通知方式改变
|
||||||
|
*/
|
||||||
|
const handleTypeChange = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
formData.value.template =
|
||||||
|
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||||
|
resetPublicFiles();
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知类型改变
|
||||||
|
*/
|
||||||
|
const handleProviderChange = () => {
|
||||||
|
formData.value.template =
|
||||||
|
TEMPLATE_FIELD_MAP[formData.value.type][formData.value.provider];
|
||||||
|
console.log('formData.value.template: ', formData.value.template);
|
||||||
|
getConfigList();
|
||||||
|
resetPublicFiles();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* link消息类型 图片链接
|
* link消息类型 图片链接
|
||||||
*/
|
*/
|
||||||
const handleChange = (info: UploadChangeParam) => {
|
const handleLinkChange = (info: UploadChangeParam) => {
|
||||||
if (info.file.status === 'done') {
|
if (info.file.status === 'done') {
|
||||||
formData.value.template.link.picUrl = info.file.response?.result;
|
formData.value.template.link.picUrl = info.file.response?.result;
|
||||||
}
|
}
|
||||||
|
@ -674,22 +988,29 @@ const handleConfigChange = () => {
|
||||||
*/
|
*/
|
||||||
const templateList = ref();
|
const templateList = ref();
|
||||||
const getTemplateList = async () => {
|
const getTemplateList = async () => {
|
||||||
const { result } = await templateApi.getAliTemplate(
|
const id = formData.value.configId || undefined;
|
||||||
formData.value.configId,
|
const { result } = await templateApi.getAliTemplate(id);
|
||||||
);
|
|
||||||
templateList.value = result;
|
templateList.value = result;
|
||||||
};
|
};
|
||||||
getTemplateList();
|
|
||||||
|
/**
|
||||||
|
* 短信模板改变
|
||||||
|
*/
|
||||||
|
const handleTemplateChange = () => {
|
||||||
|
formData.value.template.message = templateList.value.find(
|
||||||
|
(f: any) => formData.value.template.code === f.templateCode,
|
||||||
|
)?.templateContent;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取签名
|
* 获取签名
|
||||||
*/
|
*/
|
||||||
const signsList = ref();
|
const signsList = ref();
|
||||||
const getSignsList = async () => {
|
const getSignsList = async () => {
|
||||||
const { result } = await templateApi.getSigns(formData.value.configId);
|
const id = formData.value.configId || undefined;
|
||||||
|
const { result } = await templateApi.getSigns(id);
|
||||||
signsList.value = result;
|
signsList.value = result;
|
||||||
};
|
};
|
||||||
getSignsList();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单提交
|
* 表单提交
|
||||||
|
@ -697,8 +1018,10 @@ getSignsList();
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (formData.value.type === 'email') delete formData.value.configId;
|
if (formData.value.type === 'email') delete formData.value.configId;
|
||||||
if (formData.value.template.messageType === 'markdown') delete formData.value.template.link
|
if (formData.value.template.messageType === 'markdown')
|
||||||
if (formData.value.template.messageType === 'link') delete formData.value.template.markdown
|
delete formData.value.template.link;
|
||||||
|
if (formData.value.template.messageType === 'link')
|
||||||
|
delete formData.value.template.markdown;
|
||||||
// console.log('formData.value: ', formData.value);
|
// console.log('formData.value: ', formData.value);
|
||||||
validate()
|
validate()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
@ -735,10 +1058,3 @@ const handleSubmit = () => {
|
||||||
// );
|
// );
|
||||||
// test
|
// test
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.page-container {
|
|
||||||
background: #f0f2f5;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ const columns = [
|
||||||
search: {
|
search: {
|
||||||
type: 'date',
|
type: 'date',
|
||||||
handleValue: (v: any) => {
|
handleValue: (v: any) => {
|
||||||
return '123';
|
return v;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -110,7 +110,7 @@ const columns = [
|
||||||
{ label: '失败', value: 'error' },
|
{ label: '失败', value: 'error' },
|
||||||
],
|
],
|
||||||
handleValue: (v: any) => {
|
handleValue: (v: any) => {
|
||||||
return '123';
|
return v;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -139,7 +139,16 @@ const handleSearch = (e: any) => {
|
||||||
const handleError = (e: any) => {
|
const handleError = (e: any) => {
|
||||||
Modal.info({
|
Modal.info({
|
||||||
title: '错误信息',
|
title: '错误信息',
|
||||||
content: JSON.stringify(e),
|
content: h(
|
||||||
|
'p',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
maxHeight: '300px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JSON.stringify(e),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
|
@ -148,7 +157,16 @@ const handleError = (e: any) => {
|
||||||
const handleDetail = (e: any) => {
|
const handleDetail = (e: any) => {
|
||||||
Modal.info({
|
Modal.info({
|
||||||
title: '详情信息',
|
title: '详情信息',
|
||||||
content: JSON.stringify(e),
|
content: h(
|
||||||
|
'p',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
maxHeight: '300px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JSON.stringify(e),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<page-container>
|
||||||
<Search
|
<Search
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
target="notice-config"
|
target="notice-config"
|
||||||
|
@ -152,7 +152,7 @@
|
||||||
|
|
||||||
<Debug v-model:visible="debugVis" :data="currentConfig" />
|
<Debug v-model:visible="debugVis" :data="currentConfig" />
|
||||||
<Log v-model:visible="logVis" :data="currentConfig" />
|
<Log v-model:visible="logVis" :data="currentConfig" />
|
||||||
</div>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -167,14 +167,15 @@ import { NOTICE_METHOD, MSG_TYPE } from '@/views/notice/const';
|
||||||
import Debug from './Debug/index.vue';
|
import Debug from './Debug/index.vue';
|
||||||
import Log from './Log/index.vue';
|
import Log from './Log/index.vue';
|
||||||
import { downloadObject } from '@/utils/utils';
|
import { downloadObject } from '@/utils/utils';
|
||||||
|
import { useMenuStore } from 'store/menu';
|
||||||
|
|
||||||
|
const menuStory = useMenuStore();
|
||||||
|
|
||||||
let providerList: any = [];
|
let providerList: any = [];
|
||||||
Object.keys(MSG_TYPE).forEach((key) => {
|
Object.keys(MSG_TYPE).forEach((key) => {
|
||||||
providerList = [...providerList, ...MSG_TYPE[key]];
|
providerList = [...providerList, ...MSG_TYPE[key]];
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const configRef = ref<Record<string, any>>({});
|
const configRef = ref<Record<string, any>>({});
|
||||||
const params = ref<Record<string, any>>({});
|
const params = ref<Record<string, any>>({});
|
||||||
|
|
||||||
|
@ -196,7 +197,7 @@ const columns = [
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: NOTICE_METHOD,
|
options: NOTICE_METHOD,
|
||||||
handleValue: (v: any) => {
|
handleValue: (v: any) => {
|
||||||
return '123';
|
return v;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -209,7 +210,7 @@ const columns = [
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: providerList,
|
options: providerList,
|
||||||
handleValue: (v: any) => {
|
handleValue: (v: any) => {
|
||||||
return '123';
|
return v;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -257,7 +258,9 @@ const getMethodTxt = (type: string) => {
|
||||||
* 新增
|
* 新增
|
||||||
*/
|
*/
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
router.push(`/iot/notice/Template/detail/:id`);
|
menuStory.jumpPage('notice/Template/Detail', {
|
||||||
|
id: ':id',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -324,7 +327,9 @@ const getActions = (
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
// visible.value = true;
|
// visible.value = true;
|
||||||
// current.value = data;
|
// current.value = data;
|
||||||
router.push(`/iot/notice/Template/detail/${data.id}`);
|
menuStory.jumpPage('notice/Template/Detail', {
|
||||||
|
id: data.id,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -383,9 +388,3 @@ const getActions = (
|
||||||
return actions;
|
return actions;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
|
||||||
.page-container {
|
|
||||||
background: #f0f2f5;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -184,11 +184,11 @@ export const TEMPLATE_FIELD_MAP = {
|
||||||
},
|
},
|
||||||
voice: {
|
voice: {
|
||||||
aliyun: {
|
aliyun: {
|
||||||
templateType: '',
|
templateType: 'tts',
|
||||||
templateCode: '',
|
templateCode: '',
|
||||||
ttsCode: '',
|
ttsCode: '',
|
||||||
message: '',
|
message: '',
|
||||||
playTimes: undefined,
|
playTimes: 1,
|
||||||
calledShowNumbers: '',
|
calledShowNumbers: '',
|
||||||
calledNumber: '',
|
calledNumber: '',
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'index'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<page-container>
|
||||||
|
<search
|
||||||
|
:columns='columns'
|
||||||
|
/>
|
||||||
|
<j-table
|
||||||
|
:columns='columns'
|
||||||
|
/>
|
||||||
|
</page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts'>
|
||||||
|
|
||||||
|
import type { SceneItem } from './typings'
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
dataIndex: 'name',
|
||||||
|
fixed: 'left',
|
||||||
|
ellipsis: true,
|
||||||
|
width: 300,
|
||||||
|
title: '名称',
|
||||||
|
search: {
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'triggerType',
|
||||||
|
title: '触发方式',
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: '手动触发', value: 'manual'},
|
||||||
|
{ label: '定时触发', value: 'timer'},
|
||||||
|
{ label: '设备触发', value: 'device'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'description',
|
||||||
|
title: '说明',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'state',
|
||||||
|
title: '状态',
|
||||||
|
search: {
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: '正常', value: 'started'},
|
||||||
|
{ label: '禁用', value: 'disable'},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,307 @@
|
||||||
|
type State = {
|
||||||
|
value: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Action = {
|
||||||
|
executor: string;
|
||||||
|
configuration: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Trigger = {
|
||||||
|
type: string;
|
||||||
|
device: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum ParallelEnum {
|
||||||
|
'parallel' = 'parallel',
|
||||||
|
'serial' = 'serial',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ParallelType = keyof typeof ParallelEnum;
|
||||||
|
|
||||||
|
export enum Source {
|
||||||
|
'manual' = 'manual',
|
||||||
|
'metric' = 'metric',
|
||||||
|
'fixed' = 'fixed',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ActionDeviceSelector {
|
||||||
|
'all' = 'all',
|
||||||
|
'fixed' = 'fixed',
|
||||||
|
'tag' = 'tag',
|
||||||
|
'relation' = 'relation',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ActionDeviceSource {
|
||||||
|
'fixed' = 'fixed',
|
||||||
|
'upper' = 'upper',
|
||||||
|
'relation' = 'relation',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OperatorType {
|
||||||
|
'online' = 'online',
|
||||||
|
'offline' = 'offline',
|
||||||
|
'reportEvent' = 'reportEvent',
|
||||||
|
'reportProperty' = 'reportProperty',
|
||||||
|
'readProperty' = 'readProperty',
|
||||||
|
'writeProperty' = 'writeProperty',
|
||||||
|
'invokeFunction' = 'invokeFunction',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TimerTrigger {
|
||||||
|
'week' = 'week',
|
||||||
|
'month' = 'month',
|
||||||
|
'cron' = 'cron',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TimeUnit {
|
||||||
|
'seconds' = 'seconds',
|
||||||
|
'minutes' = 'minutes',
|
||||||
|
'hours' = 'hours',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Executor {
|
||||||
|
'notify' = 'notify',
|
||||||
|
'delay' = 'delay',
|
||||||
|
'device' = 'device',
|
||||||
|
'alarm' = 'alarm',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DeviceMessageType {
|
||||||
|
'INVOKE_FUNCTION' = 'INVOKE_FUNCTION',
|
||||||
|
'READ_PROPERTY' = 'READ_PROPERTY',
|
||||||
|
'WRITE_PROPERTY' = 'WRITE_PROPERTY',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ActionAlarmMode {
|
||||||
|
'trigger' = 'trigger',
|
||||||
|
'relieve' = 'relieve',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OperationTimerPeriod {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
every: string[];
|
||||||
|
unit: keyof typeof TimeUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OperationTimer {
|
||||||
|
trigger: keyof typeof TimerTrigger;
|
||||||
|
mod: string;
|
||||||
|
cron?: string;
|
||||||
|
when?: string[];
|
||||||
|
period?: OperationTimerPeriod;
|
||||||
|
once?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TriggerDeviceOptions {
|
||||||
|
operator: keyof typeof OperatorType;
|
||||||
|
/** 触发类型为readProperty,writeProperty,invokeFunction时不能为空 */
|
||||||
|
timer?: OperationTimer;
|
||||||
|
/** 触发类型为reportEvent时不能为空 */
|
||||||
|
eventId?: string;
|
||||||
|
/** 触发类型为readProperty时不能为空 */
|
||||||
|
readProperties?: string[];
|
||||||
|
/** 触发类型为writeProperty时不能为空 */
|
||||||
|
writeProperties?: Record<string, any>;
|
||||||
|
/** 触发类型为invokeFunction时不能为空 */
|
||||||
|
functionId?: string;
|
||||||
|
/** 触发类型为invokeFunction时不能为空 */
|
||||||
|
functionParameters?: Record<string, any>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备触发配置
|
||||||
|
*/
|
||||||
|
export interface TriggerDevice {
|
||||||
|
productId: string;
|
||||||
|
selector: string;
|
||||||
|
selectorValues?: Record<string, any>[];
|
||||||
|
operation?: TriggerDeviceOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShakeLimitType {
|
||||||
|
enabled: boolean;
|
||||||
|
groupType?: string; // 执行动作没有该参数
|
||||||
|
time: number;
|
||||||
|
threshold: number;
|
||||||
|
alarmFirst: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BranchesType {
|
||||||
|
enabled: boolean;
|
||||||
|
groupType?: string; // 执行动作没有该参数
|
||||||
|
time: number;
|
||||||
|
threshold: number;
|
||||||
|
alarmFirst: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SceneItem {
|
||||||
|
parallel: boolean;
|
||||||
|
state: State;
|
||||||
|
actions: Action[];
|
||||||
|
trigger: Trigger;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
branches: BranchesType[];
|
||||||
|
options: any;
|
||||||
|
triggerType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TriggerType = {
|
||||||
|
type: string;
|
||||||
|
/**
|
||||||
|
* 防抖配置
|
||||||
|
*/
|
||||||
|
shakeLimit?: ShakeLimitType;
|
||||||
|
/**
|
||||||
|
* 拓展信息
|
||||||
|
*/
|
||||||
|
options?: Record<string, any>;
|
||||||
|
/**
|
||||||
|
* 设备触发配置
|
||||||
|
*/
|
||||||
|
device?: TriggerDevice;
|
||||||
|
/**
|
||||||
|
* 定时触发配置
|
||||||
|
*/
|
||||||
|
timer?: OperationTimer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface TermsVale {
|
||||||
|
source: keyof typeof Source;
|
||||||
|
/** 手动输入值,source为 manual 时不能为空 */
|
||||||
|
value?: Record<string, any> | any[];
|
||||||
|
/** 指标值,source为 metric 时不能为空 */
|
||||||
|
metric?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TermsType = {
|
||||||
|
column?: string;
|
||||||
|
value?: TermsVale;
|
||||||
|
type?: string;
|
||||||
|
termType?: string;
|
||||||
|
options?: any[];
|
||||||
|
terms?: TermsType[];
|
||||||
|
key?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PlatformRelation = {
|
||||||
|
objectType: string;
|
||||||
|
objectId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Relationship = {
|
||||||
|
objectType: string;
|
||||||
|
objectSource: {
|
||||||
|
source: string;
|
||||||
|
upperKey: string;
|
||||||
|
};
|
||||||
|
related: {
|
||||||
|
objectType: string;
|
||||||
|
relation: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface NotifyVariablesType {
|
||||||
|
source: string;
|
||||||
|
value?: Record<string, any>;
|
||||||
|
upperKey?: string;
|
||||||
|
relation?: PlatformRelation | Relationship;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotifyProps {
|
||||||
|
notifyType: string;
|
||||||
|
notifierId: string;
|
||||||
|
templateId: string;
|
||||||
|
variables: Record<string, NotifyVariablesType>;
|
||||||
|
options?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectorValuesType =
|
||||||
|
| { value: string; name: string }
|
||||||
|
| { value: { column: string; value: any }[]; name: string }
|
||||||
|
| { value: { objectType: string; relation: any }[] };
|
||||||
|
|
||||||
|
export type ActionDeviceMessageType = {
|
||||||
|
deviceId: string;
|
||||||
|
messageType: keyof typeof DeviceMessageType;
|
||||||
|
/** 功能调用时使用 */
|
||||||
|
functionId?: string;
|
||||||
|
/** 功能调用时使用 */
|
||||||
|
inputs?: Record<string, any>[];
|
||||||
|
/** 读取属性时使用, 读取属性时为String数组,设置属性时为 Object */
|
||||||
|
properties?: string[] | Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ActionsDeviceProps {
|
||||||
|
selector: keyof typeof ActionDeviceSelector;
|
||||||
|
source: keyof typeof ActionDeviceSource;
|
||||||
|
productId?: string;
|
||||||
|
message?: ActionDeviceMessageType;
|
||||||
|
selectorValues?: SelectorValuesType[];
|
||||||
|
/** 来源为upper时不能为空 */
|
||||||
|
upperKey?: string;
|
||||||
|
/** 来源为relation时不能为空 */
|
||||||
|
relation?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BranchesThen {
|
||||||
|
parallel: boolean;
|
||||||
|
actions: ActionsType[];
|
||||||
|
key?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActionBranchesProps {
|
||||||
|
when: TermsType[];
|
||||||
|
shakeLimit: ShakeLimitType;
|
||||||
|
then: BranchesThen[];
|
||||||
|
key?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActionsType {
|
||||||
|
executor: keyof typeof Executor;
|
||||||
|
/** 执行器类型为notify时不能为空 */
|
||||||
|
notify?: NotifyProps;
|
||||||
|
/** 执行器类型为delay时不能为空 */
|
||||||
|
delay?: {
|
||||||
|
time?: number;
|
||||||
|
unit?: keyof typeof TimeUnit;
|
||||||
|
};
|
||||||
|
device?: ActionsDeviceProps;
|
||||||
|
alarm?: {
|
||||||
|
mode: keyof typeof ActionAlarmMode;
|
||||||
|
};
|
||||||
|
terms?: TermsType[];
|
||||||
|
/** map中的key,用于删除 */
|
||||||
|
key?: string;
|
||||||
|
options?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormModelType {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
/**
|
||||||
|
* 触发方式
|
||||||
|
*/
|
||||||
|
trigger?: TriggerType;
|
||||||
|
/**
|
||||||
|
* 触发条件,结构与通用查询条件相同。条件数据来自接口:根据触发器解析出支持的条件列
|
||||||
|
*/
|
||||||
|
terms?: TermsType[];
|
||||||
|
/**
|
||||||
|
* 执行动作
|
||||||
|
*/
|
||||||
|
actions?: ActionsType[];
|
||||||
|
/**
|
||||||
|
* 动作分支
|
||||||
|
*/
|
||||||
|
branches?: ActionBranchesProps[];
|
||||||
|
/**
|
||||||
|
* 拓展信息,用于前端存储一些渲染数据
|
||||||
|
*/
|
||||||
|
options?: Record<string, any>;
|
||||||
|
description?: string;
|
||||||
|
}
|
|
@ -1,51 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="api-test-container">
|
|
||||||
<div class="top">
|
|
||||||
<h5>{{ selectApi.summary }}</h5>
|
|
||||||
<div class="input">
|
|
||||||
<InputCard :value="selectApi.method" />
|
|
||||||
<a-input :value="selectApi?.url" disabled />
|
|
||||||
<span class="send">发送</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { apiDetailsType } from '../typing';
|
|
||||||
import InputCard from './InputCard.vue';
|
|
||||||
import { PropType } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
selectApi: {
|
|
||||||
type: Object as PropType<apiDetailsType>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { selectApi } = toRefs(props);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.api-test-container {
|
|
||||||
.top {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.send {
|
|
||||||
width: 65px;
|
|
||||||
padding: 4px 15px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #1890ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="choose-api-container">
|
|
||||||
<JTable
|
|
||||||
:columns="columns"
|
|
||||||
:dataSource="props.tableData"
|
|
||||||
:rowSelection="rowSelection"
|
|
||||||
noPagination
|
|
||||||
model="TABLE"
|
|
||||||
>
|
|
||||||
<template #url="slotProps">
|
|
||||||
<span
|
|
||||||
style="color: #1d39c4; cursor: pointer"
|
|
||||||
@click="jump(slotProps.row)"
|
|
||||||
>{{ slotProps.row.url }}</span
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</JTable>
|
|
||||||
|
|
||||||
<a-button type="primary">保存</a-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { TableProps } from 'ant-design-vue';
|
|
||||||
|
|
||||||
const emits = defineEmits(['update:clickApi'])
|
|
||||||
const props = defineProps({
|
|
||||||
tableData: Array,
|
|
||||||
clickApi: Object
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: 'API',
|
|
||||||
dataIndex: 'url',
|
|
||||||
key: 'url',
|
|
||||||
scopedSlots: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '说明',
|
|
||||||
dataIndex: 'summary',
|
|
||||||
key: 'summary',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const rowSelection: TableProps['rowSelection'] = {
|
|
||||||
onChange: (selectedRowKeys, selectedRows) => {
|
|
||||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const jump = (row:object) => {
|
|
||||||
emits('update:clickApi',row)
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.choose-api-container {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
:deep(.jtable-body-header) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,84 +0,0 @@
|
||||||
<template>
|
|
||||||
<a-card class="api-page-container">
|
|
||||||
api
|
|
||||||
<a-row :gutter="24">
|
|
||||||
<a-col :span="5">
|
|
||||||
<LeftTree @select="treeSelect" />
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="19">
|
|
||||||
<ChooseApi
|
|
||||||
v-show="!selectedApi.url"
|
|
||||||
v-model:click-api="selectedApi"
|
|
||||||
:table-data="tableData"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="api-details"
|
|
||||||
v-show="selectedApi.url && tableData.length > 0"
|
|
||||||
>
|
|
||||||
<a-button @click="selectedApi = initSelectedApi" style="margin-bottom: 24px;"
|
|
||||||
>返回</a-button
|
|
||||||
>
|
|
||||||
<a-tabs v-model:activeKey="activeKey" type="card">
|
|
||||||
<a-tab-pane key="does" tab="文档">
|
|
||||||
<ApiDoes :select-api="selectedApi" :schemas="schemas" />
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="test" tab="调试">
|
|
||||||
<ApiTest :select-api="selectedApi" />
|
|
||||||
</a-tab-pane>
|
|
||||||
</a-tabs>
|
|
||||||
</div>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts" name="apiPage">
|
|
||||||
import type { treeNodeTpye, apiObjType, apiDetailsType } from './typing';
|
|
||||||
import LeftTree from './components/LeftTree.vue';
|
|
||||||
import ChooseApi from './components/ChooseApi.vue';
|
|
||||||
import ApiDoes from './components/ApiDoes.vue';
|
|
||||||
import ApiTest from './components/ApiTest.vue';
|
|
||||||
|
|
||||||
const tableData = ref([]);
|
|
||||||
const treeSelect = (node: treeNodeTpye, nodeSchemas:object = {}) => {
|
|
||||||
schemas.value = nodeSchemas
|
|
||||||
if (!node.apiList) return;
|
|
||||||
const apiList: apiObjType[] = node.apiList as apiObjType[];
|
|
||||||
const table: any = [];
|
|
||||||
// 将对象形式的数据转换为表格需要的形式
|
|
||||||
apiList?.forEach((apiItem) => {
|
|
||||||
const { method, url } = apiItem;
|
|
||||||
for (const key in method) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(method, key)) {
|
|
||||||
table.push({
|
|
||||||
...method[key],
|
|
||||||
url,
|
|
||||||
method: key,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tableData.value = table;
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeKey = ref('does');
|
|
||||||
const schemas = ref({});
|
|
||||||
const initSelectedApi:apiDetailsType = {
|
|
||||||
url: '',
|
|
||||||
method: '',
|
|
||||||
summary: '',
|
|
||||||
parameters: [],
|
|
||||||
responses: {},
|
|
||||||
requestBody: {}
|
|
||||||
};
|
|
||||||
const selectedApi = ref<apiDetailsType>(initSelectedApi);
|
|
||||||
|
|
||||||
watch(tableData, () => (selectedApi.value = initSelectedApi));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.api-page-container {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -28,10 +28,10 @@
|
||||||
model="TABLE"
|
model="TABLE"
|
||||||
>
|
>
|
||||||
<template #required="slotProps">
|
<template #required="slotProps">
|
||||||
<span>{{ Boolean(slotProps.row.required) + '' }}</span>
|
<span>{{ Boolean(slotProps.required) + '' }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #type="slotProps">
|
<template #type="slotProps">
|
||||||
<span>{{ slotProps.row.schema.type }}</span>
|
<span>{{ slotProps.schema.type }}</span>
|
||||||
</template>
|
</template>
|
||||||
</JTable>
|
</JTable>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,15 +68,23 @@
|
||||||
>
|
>
|
||||||
</JTable>
|
</JTable>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<MonacoEditor
|
||||||
|
v-model:modelValue="codeText"
|
||||||
|
style="height: 300px; width: 100%"
|
||||||
|
theme="vs"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||||
import type { apiDetailsType } from '../typing';
|
import type { apiDetailsType } from '../typing';
|
||||||
import InputCard from './InputCard.vue';
|
import InputCard from './InputCard.vue';
|
||||||
import { PropType } from 'vue';
|
import { PropType } from 'vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:paramsTable'])
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
selectApi: {
|
selectApi: {
|
||||||
type: Object as PropType<apiDetailsType>,
|
type: Object as PropType<apiDetailsType>,
|
||||||
|
@ -92,6 +100,7 @@ const { selectApi } = toRefs(props);
|
||||||
type tableCardType = {
|
type tableCardType = {
|
||||||
columns: object[];
|
columns: object[];
|
||||||
tableData: object[];
|
tableData: object[];
|
||||||
|
codeText?: any;
|
||||||
activeKey?: any;
|
activeKey?: any;
|
||||||
getData?: any;
|
getData?: any;
|
||||||
};
|
};
|
||||||
|
@ -190,11 +199,12 @@ const respParamsCard = reactive<tableCardType>({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tableData: [],
|
tableData: [],
|
||||||
|
codeText: '',
|
||||||
getData: (code: string) => {
|
getData: (code: string) => {
|
||||||
type schemaObjType = {
|
type schemaObjType = {
|
||||||
paramsName: string;
|
paramsName: string;
|
||||||
paramsType: string;
|
paramsType: string;
|
||||||
desc: string;
|
desc?: string;
|
||||||
children?: schemaObjType[];
|
children?: schemaObjType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -202,13 +212,21 @@ const respParamsCard = reactive<tableCardType>({
|
||||||
(item: any) => item.code === code,
|
(item: any) => item.code === code,
|
||||||
)?.schema;
|
)?.schema;
|
||||||
const schemas = toRaw(props.schemas);
|
const schemas = toRaw(props.schemas);
|
||||||
|
const basicType = ['string', 'integer', 'boolean'];
|
||||||
|
|
||||||
|
const tableData = findData(schemaName);
|
||||||
|
const codeText = getCodeText(tableData, 3);
|
||||||
|
|
||||||
|
emit('update:paramsTable', tableData)
|
||||||
|
respParamsCard.tableData = tableData;
|
||||||
|
respParamsCard.codeText = JSON.stringify(codeText);
|
||||||
|
|
||||||
function findData(schemaName: string) {
|
function findData(schemaName: string) {
|
||||||
if (!schemaName || !schemas[schemaName]) {
|
if (!schemaName || !schemas[schemaName]) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const result: schemaObjType[] = [];
|
const result: schemaObjType[] = [];
|
||||||
const schema = schemas[schemaName];
|
const schema = schemas[schemaName];
|
||||||
const basicType = ['string', 'integer', 'boolean'];
|
|
||||||
Object.entries(schema.properties).forEach((item: [string, any]) => {
|
Object.entries(schema.properties).forEach((item: [string, any]) => {
|
||||||
const paramsType =
|
const paramsType =
|
||||||
item[1].type ||
|
item[1].type ||
|
||||||
|
@ -224,16 +242,62 @@ const respParamsCard = reactive<tableCardType>({
|
||||||
schemaObj.children = findData(paramsType);
|
schemaObj.children = findData(paramsType);
|
||||||
result.push(schemaObj);
|
result.push(schemaObj);
|
||||||
});
|
});
|
||||||
console.log(result);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getCodeText(arr: schemaObjType[], level: number): object {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
respParamsCard.tableData = findData(schemaName);
|
arr.forEach((item) => {
|
||||||
// console.log(respParamsCard.tableData);
|
switch (item.paramsType) {
|
||||||
|
case 'string':
|
||||||
|
result[item.paramsName] = '';
|
||||||
|
break;
|
||||||
|
case 'integer':
|
||||||
|
result[item.paramsName] = 0;
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
result[item.paramsName] = true;
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
result[item.paramsName] = [];
|
||||||
|
break;
|
||||||
|
case 'object':
|
||||||
|
result[item.paramsName] = {};
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
const properties = schemas[item.paramsType]
|
||||||
|
.properties as object;
|
||||||
|
const newArr = Object.entries(properties).map(
|
||||||
|
(item: [string, any]) => ({
|
||||||
|
paramsName: item[0],
|
||||||
|
paramsType: level
|
||||||
|
? (item[1].$ref &&
|
||||||
|
item[1].$ref.split('/').pop()) ||
|
||||||
|
(item[1].items &&
|
||||||
|
item[1].items.$ref
|
||||||
|
.split('/')
|
||||||
|
.pop()) ||
|
||||||
|
item[1].type ||
|
||||||
|
''
|
||||||
|
: item[1].type,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
result[item.paramsName] = getCodeText(
|
||||||
|
newArr,
|
||||||
|
level - 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { codeText } = toRefs(requestCard);
|
||||||
|
|
||||||
const getContent = (data: any) => {
|
const getContent = (data: any) => {
|
||||||
if (data && data.content) {
|
if (data && data.content) {
|
||||||
return Object.keys(data.content || {})[0];
|
return Object.keys(data.content || {})[0];
|
||||||
|
|
|
@ -1,28 +1,80 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="api-test-container">
|
<div class="api-test-container">
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<h5>{{ selectApi.summary }}</h5>
|
<h5>{{ props.selectApi.summary }}</h5>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<InputCard :value="selectApi.method" />
|
<InputCard :value="props.selectApi.method" />
|
||||||
<a-input :value="selectApi?.url" disabled />
|
<a-input :value="props.selectApi?.url" disabled />
|
||||||
<span class="send">发送</span>
|
<span class="send">发送</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="api-card">
|
||||||
|
<h5>请求参数</h5>
|
||||||
|
<div class="content">
|
||||||
|
<!-- <VueJsoneditor
|
||||||
|
height="400"
|
||||||
|
mode="tree"
|
||||||
|
v-model:text="requestBody.paramsText"
|
||||||
|
/> -->
|
||||||
|
<MonacoEditor
|
||||||
|
v-model:modelValue="requestBody.paramsText"
|
||||||
|
style="height: 300px; width: 100%"
|
||||||
|
theme="vs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="api-card">
|
||||||
|
<h5>响应参数</h5>
|
||||||
|
<div class="content">
|
||||||
|
<VueJsoneditor
|
||||||
|
height="400"
|
||||||
|
mode="tree"
|
||||||
|
v-model:text="responsesContent"
|
||||||
|
/>
|
||||||
|
<!-- <MonacoEditor
|
||||||
|
v-model:modelValue="responsesContent"
|
||||||
|
style="height: 300px; width: 100%"
|
||||||
|
theme="vs"
|
||||||
|
/> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { apiDetailsType } from '../typing';
|
import VueJsoneditor from 'vue3-ts-jsoneditor';
|
||||||
|
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||||
|
import type { apiDetailsType } from '../typing';
|
||||||
import InputCard from './InputCard.vue';
|
import InputCard from './InputCard.vue';
|
||||||
import { PropType } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps<{
|
||||||
selectApi: {
|
selectApi: apiDetailsType;
|
||||||
type: Object as PropType<apiDetailsType>,
|
paramsTable: any[];
|
||||||
required: true,
|
}>();
|
||||||
},
|
|
||||||
|
const requestBody = reactive({
|
||||||
|
paramsTable: [] as requestObj[],
|
||||||
|
paramsText: '',
|
||||||
});
|
});
|
||||||
const { selectApi } = toRefs(props);
|
|
||||||
|
const responsesContent = ref('{"a":123}');
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.paramsTable,
|
||||||
|
(n) => {
|
||||||
|
const table = n?.map((item: any) => ({
|
||||||
|
paramsName: item.paramsName,
|
||||||
|
value: '',
|
||||||
|
}));
|
||||||
|
requestBody.paramsTable = table;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
type requestObj = {
|
||||||
|
paramsName: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -47,5 +99,36 @@ const { selectApi } = toRefs(props);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.api-card {
|
||||||
|
margin-top: 24px;
|
||||||
|
h5 {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 4px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #1d39c4;
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding-left: 10px;
|
||||||
|
|
||||||
|
:deep(.jtable-body) {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.jtable-body-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
<template #url="slotProps">
|
<template #url="slotProps">
|
||||||
<span
|
<span
|
||||||
style="color: #1d39c4; cursor: pointer"
|
style="color: #1d39c4; cursor: pointer"
|
||||||
@click="jump(slotProps.row)"
|
@click="jump(slotProps)"
|
||||||
>{{ slotProps.row.url }}</span
|
>{{ slotProps.url}}</span
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</JTable>
|
</JTable>
|
||||||
|
@ -48,8 +48,9 @@ const rowSelection: TableProps['rowSelection'] = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const jump = (row:object) => {
|
const jump = (row:any) => {
|
||||||
emits('update:clickApi',row)
|
emits('update:clickApi',row)
|
||||||
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ const getTreeData = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const clickSelectItem: TreeProps['onSelect'] = (key, node: any) => {
|
const clickSelectItem: TreeProps['onSelect'] = (key, node: any) => {
|
||||||
|
if(!node.node.parent) return
|
||||||
emits('select', node.node.dataRef, node.node?.parent.node.schemas);
|
emits('select', node.node.dataRef, node.node?.parent.node.schemas);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<a-card class="api-page-container">
|
<a-card class="api-page-container">
|
||||||
apply/api
|
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="5">
|
<a-col :span="5">
|
||||||
<LeftTree @select="treeSelect" />
|
<LeftTree @select="treeSelect" />
|
||||||
|
@ -14,17 +13,23 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="api-details"
|
class="api-details"
|
||||||
v-show="selectedApi.url && tableData.length > 0"
|
v-if="selectedApi.url && tableData.length > 0"
|
||||||
>
|
>
|
||||||
<a-button @click="selectedApi = initSelectedApi" style="margin-bottom: 24px;"
|
<a-button
|
||||||
|
@click="selectedApi = initSelectedApi"
|
||||||
|
style="margin-bottom: 24px"
|
||||||
>返回</a-button
|
>返回</a-button
|
||||||
>
|
>
|
||||||
<a-tabs v-model:activeKey="activeKey" type="card">
|
<a-tabs v-model:activeKey="activeKey" type="card">
|
||||||
<a-tab-pane key="does" tab="文档">
|
<a-tab-pane key="does" tab="文档">
|
||||||
<ApiDoes :select-api="selectedApi" :schemas="schemas" />
|
<ApiDoes
|
||||||
|
:select-api="selectedApi"
|
||||||
|
:schemas="schemas"
|
||||||
|
v-model:params-table="paramsTable"
|
||||||
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="test" tab="调试">
|
<a-tab-pane key="test" tab="调试">
|
||||||
<ApiTest :select-api="selectedApi" />
|
<ApiTest :select-api="selectedApi" :params-table="paramsTable" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,8 +46,8 @@ import ApiDoes from './components/ApiDoes.vue';
|
||||||
import ApiTest from './components/ApiTest.vue';
|
import ApiTest from './components/ApiTest.vue';
|
||||||
|
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
const treeSelect = (node: treeNodeTpye, nodeSchemas:object = {}) => {
|
const treeSelect = (node: treeNodeTpye, nodeSchemas: object = {}) => {
|
||||||
schemas.value = nodeSchemas
|
schemas.value = nodeSchemas;
|
||||||
if (!node.apiList) return;
|
if (!node.apiList) return;
|
||||||
const apiList: apiObjType[] = node.apiList as apiObjType[];
|
const apiList: apiObjType[] = node.apiList as apiObjType[];
|
||||||
const table: any = [];
|
const table: any = [];
|
||||||
|
@ -62,23 +67,29 @@ const treeSelect = (node: treeNodeTpye, nodeSchemas:object = {}) => {
|
||||||
tableData.value = table;
|
tableData.value = table;
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeKey = ref('does');
|
const activeKey = ref<'does' | 'test'>('does');
|
||||||
const schemas = ref({});
|
const schemas = ref({});
|
||||||
const initSelectedApi:apiDetailsType = {
|
const paramsTable = ref([])
|
||||||
|
const initSelectedApi: apiDetailsType = {
|
||||||
url: '',
|
url: '',
|
||||||
method: '',
|
method: '',
|
||||||
summary: '',
|
summary: '',
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {},
|
responses: {},
|
||||||
requestBody: {}
|
requestBody: {},
|
||||||
};
|
};
|
||||||
const selectedApi = ref<apiDetailsType>(initSelectedApi);
|
const selectedApi = ref<apiDetailsType>(initSelectedApi);
|
||||||
|
|
||||||
watch(tableData, () => (selectedApi.value = initSelectedApi));
|
watch(tableData, () => {
|
||||||
|
activeKey.value = 'does';
|
||||||
|
selectedApi.value = initSelectedApi;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.api-page-container {
|
.api-page-container {
|
||||||
|
padding: 24px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
应用管理
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -28,10 +28,10 @@
|
||||||
model="TABLE"
|
model="TABLE"
|
||||||
>
|
>
|
||||||
<template #required="slotProps">
|
<template #required="slotProps">
|
||||||
<span>{{ Boolean(slotProps.row.required) + '' }}</span>
|
<span>{{ Boolean(slotProps.required) + '' }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #type="slotProps">
|
<template #type="slotProps">
|
||||||
<span>{{ slotProps.row.schema.type }}</span>
|
<span>{{ slotProps.schema.type }}</span>
|
||||||
</template>
|
</template>
|
||||||
</JTable>
|
</JTable>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,11 +68,18 @@
|
||||||
>
|
>
|
||||||
</JTable>
|
</JTable>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<MonacoEditor
|
||||||
|
v-model:modelValue="codeText"
|
||||||
|
style="height: 300px; width: 100%"
|
||||||
|
theme="vs"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||||
import type { apiDetailsType } from '../typing';
|
import type { apiDetailsType } from '../typing';
|
||||||
import InputCard from './InputCard.vue';
|
import InputCard from './InputCard.vue';
|
||||||
import { PropType } from 'vue';
|
import { PropType } from 'vue';
|
||||||
|
@ -92,6 +99,7 @@ const { selectApi } = toRefs(props);
|
||||||
type tableCardType = {
|
type tableCardType = {
|
||||||
columns: object[];
|
columns: object[];
|
||||||
tableData: object[];
|
tableData: object[];
|
||||||
|
codeText?: any;
|
||||||
activeKey?: any;
|
activeKey?: any;
|
||||||
getData?: any;
|
getData?: any;
|
||||||
};
|
};
|
||||||
|
@ -190,11 +198,12 @@ const respParamsCard = reactive<tableCardType>({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tableData: [],
|
tableData: [],
|
||||||
|
codeText: '',
|
||||||
getData: (code: string) => {
|
getData: (code: string) => {
|
||||||
type schemaObjType = {
|
type schemaObjType = {
|
||||||
paramsName: string;
|
paramsName: string;
|
||||||
paramsType: string;
|
paramsType: string;
|
||||||
desc: string;
|
desc?: string;
|
||||||
children?: schemaObjType[];
|
children?: schemaObjType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -202,13 +211,20 @@ const respParamsCard = reactive<tableCardType>({
|
||||||
(item: any) => item.code === code,
|
(item: any) => item.code === code,
|
||||||
)?.schema;
|
)?.schema;
|
||||||
const schemas = toRaw(props.schemas);
|
const schemas = toRaw(props.schemas);
|
||||||
|
const basicType = ['string', 'integer', 'boolean'];
|
||||||
|
|
||||||
|
const tableData = findData(schemaName);
|
||||||
|
const codeText = getCodeText(tableData, 3);
|
||||||
|
|
||||||
|
respParamsCard.tableData = tableData;
|
||||||
|
respParamsCard.codeText = JSON.stringify(codeText);
|
||||||
|
|
||||||
function findData(schemaName: string) {
|
function findData(schemaName: string) {
|
||||||
if (!schemaName || !schemas[schemaName]) {
|
if (!schemaName || !schemas[schemaName]) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const result: schemaObjType[] = [];
|
const result: schemaObjType[] = [];
|
||||||
const schema = schemas[schemaName];
|
const schema = schemas[schemaName];
|
||||||
const basicType = ['string', 'integer', 'boolean'];
|
|
||||||
Object.entries(schema.properties).forEach((item: [string, any]) => {
|
Object.entries(schema.properties).forEach((item: [string, any]) => {
|
||||||
const paramsType =
|
const paramsType =
|
||||||
item[1].type ||
|
item[1].type ||
|
||||||
|
@ -224,16 +240,62 @@ const respParamsCard = reactive<tableCardType>({
|
||||||
schemaObj.children = findData(paramsType);
|
schemaObj.children = findData(paramsType);
|
||||||
result.push(schemaObj);
|
result.push(schemaObj);
|
||||||
});
|
});
|
||||||
console.log(result);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function getCodeText(arr: schemaObjType[], level: number): object {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
respParamsCard.tableData = findData(schemaName);
|
arr.forEach((item) => {
|
||||||
// console.log(respParamsCard.tableData);
|
switch (item.paramsType) {
|
||||||
|
case 'string':
|
||||||
|
result[item.paramsName] = '';
|
||||||
|
break;
|
||||||
|
case 'integer':
|
||||||
|
result[item.paramsName] = 0;
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
result[item.paramsName] = true;
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
result[item.paramsName] = [];
|
||||||
|
break;
|
||||||
|
case 'object':
|
||||||
|
result[item.paramsName] = {};
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
const properties = schemas[item.paramsType]
|
||||||
|
.properties as object;
|
||||||
|
const newArr = Object.entries(properties).map(
|
||||||
|
(item: [string, any]) => ({
|
||||||
|
paramsName: item[0],
|
||||||
|
paramsType: level
|
||||||
|
? (item[1].$ref &&
|
||||||
|
item[1].$ref.split('/').pop()) ||
|
||||||
|
(item[1].items &&
|
||||||
|
item[1].items.$ref
|
||||||
|
.split('/')
|
||||||
|
.pop()) ||
|
||||||
|
item[1].type ||
|
||||||
|
''
|
||||||
|
: item[1].type,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
result[item.paramsName] = getCodeText(
|
||||||
|
newArr,
|
||||||
|
level - 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { codeText } = toRefs(requestCard);
|
||||||
|
|
||||||
const getContent = (data: any) => {
|
const getContent = (data: any) => {
|
||||||
if (data && data.content) {
|
if (data && data.content) {
|
||||||
return Object.keys(data.content || {})[0];
|
return Object.keys(data.content || {})[0];
|
|
@ -0,0 +1,299 @@
|
||||||
|
<template>
|
||||||
|
<div class="api-test-container">
|
||||||
|
<div class="top">
|
||||||
|
<h5>{{ props.selectApi.summary }}</h5>
|
||||||
|
<div class="input">
|
||||||
|
<InputCard :value="props.selectApi.method" />
|
||||||
|
<a-input :value="props.selectApi?.url" disabled />
|
||||||
|
<span class="send" @click="send">发送</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="api-card">
|
||||||
|
<h5>请求参数</h5>
|
||||||
|
<div class="content">
|
||||||
|
<!-- <VueJsoneditor
|
||||||
|
height="400"
|
||||||
|
mode="tree"
|
||||||
|
v-model:text="requestBody.paramsText"
|
||||||
|
/> -->
|
||||||
|
<div class="table" v-if="paramsTable.length">
|
||||||
|
<a-form :model="requestBody.params" ref="formRef" >
|
||||||
|
<a-table
|
||||||
|
:columns="requestBody.tableColumns"
|
||||||
|
:dataSource="paramsTable"
|
||||||
|
:pagination="false"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record, index }">
|
||||||
|
<template v-if="column.key === 'name'">
|
||||||
|
<a-form-item
|
||||||
|
:name="[
|
||||||
|
'paramsTable',
|
||||||
|
index +
|
||||||
|
(requestBody.pageNum - 1) *
|
||||||
|
requestBody.pageSize,
|
||||||
|
'name',
|
||||||
|
]"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '该字段是必填字段',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="record.name"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'value'">
|
||||||
|
<a-form-item
|
||||||
|
:name="[
|
||||||
|
'paramsTable',
|
||||||
|
index +
|
||||||
|
(requestBody.pageNum - 1) *
|
||||||
|
requestBody.pageSize,
|
||||||
|
'value',
|
||||||
|
]"
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '该字段是必填字段',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="record.value"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'action'">
|
||||||
|
<PermissionButton
|
||||||
|
type="link"
|
||||||
|
:uhasPermission="`{permission}:delete`"
|
||||||
|
:popConfirm="{
|
||||||
|
title: `确定删除`,
|
||||||
|
onConfirm: () =>
|
||||||
|
requestBody.clickDel(index),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<AIcon type="DeleteOutlined" />
|
||||||
|
</PermissionButton>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<a-pagination
|
||||||
|
:pageSize="requestBody.pageSize"
|
||||||
|
v-model:current="requestBody.pageNum"
|
||||||
|
:total="requestBody.params.paramsTable.length"
|
||||||
|
hideOnSinglePage
|
||||||
|
style="text-align: center"
|
||||||
|
/>
|
||||||
|
<a-button
|
||||||
|
@click="requestBody.addRow"
|
||||||
|
style="width: 100%; text-align: center"
|
||||||
|
>
|
||||||
|
<AIcon type="PlusOutlined" />新增
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<MonacoEditor
|
||||||
|
v-model:modelValue="requestBody.paramsText"
|
||||||
|
style="height: 300px; width: 100%"
|
||||||
|
theme="vs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="api-card">
|
||||||
|
<h5>响应参数</h5>
|
||||||
|
<div class="content">
|
||||||
|
<VueJsoneditor
|
||||||
|
height="400"
|
||||||
|
mode="tree"
|
||||||
|
v-model:text="responsesContent"
|
||||||
|
:disabled="true"
|
||||||
|
/>
|
||||||
|
<!-- <MonacoEditor
|
||||||
|
v-model:modelValue="responsesContent"
|
||||||
|
style="height: 300px; width: 100%"
|
||||||
|
theme="vs"
|
||||||
|
/> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import VueJsoneditor from 'vue3-ts-jsoneditor';
|
||||||
|
import MonacoEditor from '@/components/MonacoEditor/index.vue';
|
||||||
|
import type { apiDetailsType } from '../typing';
|
||||||
|
import InputCard from './InputCard.vue';
|
||||||
|
import { cloneDeep, toLower } from 'lodash';
|
||||||
|
import { FormInstance } from 'ant-design-vue';
|
||||||
|
import server from '@/utils/request'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
selectApi: apiDetailsType;
|
||||||
|
}>();
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const requestBody = reactive({
|
||||||
|
tableColumns: [
|
||||||
|
{
|
||||||
|
title: '参数名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '参数值',
|
||||||
|
dataIndex: 'value',
|
||||||
|
key: 'value',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'action',
|
||||||
|
key: 'action',
|
||||||
|
width: '80px',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageSize: 10,
|
||||||
|
pageNum: 1,
|
||||||
|
params: {
|
||||||
|
paramsTable: cloneDeep(props.selectApi.parameters || []) as requestObj[],
|
||||||
|
},
|
||||||
|
|
||||||
|
paramsText: '',
|
||||||
|
|
||||||
|
addRow: () => {
|
||||||
|
if (paramsTable.value.length === 10)
|
||||||
|
requestBody.pageNum = requestBody.pageNum + 1;
|
||||||
|
requestBody.params.paramsTable.push({
|
||||||
|
name: '',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clickDel: (index: number) => {
|
||||||
|
if (paramsTable.value.length === 1 && requestBody.pageNum > 1)
|
||||||
|
requestBody.pageNum = requestBody.pageNum - 1;
|
||||||
|
requestBody.params.paramsTable.splice(index, 1);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const paramsTable = computed(() => {
|
||||||
|
const startIndex = (requestBody.pageNum - 1) * requestBody.pageSize;
|
||||||
|
const endIndex = requestBody.pageNum * requestBody.pageSize;
|
||||||
|
return requestBody.params.paramsTable.slice(startIndex, endIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
const responsesContent = ref('{"a":123}');
|
||||||
|
|
||||||
|
const send = () => {
|
||||||
|
formRef.value &&
|
||||||
|
formRef.value.validate().then(() => {
|
||||||
|
const methodName = toLower(props.selectApi.method)
|
||||||
|
const methodObj = {
|
||||||
|
get: 'get',
|
||||||
|
post: 'post',
|
||||||
|
patch: 'patch',
|
||||||
|
put: 'put',
|
||||||
|
delete: 'remove'
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = props.selectApi?.url;
|
||||||
|
const urlParams = {}
|
||||||
|
requestBody.params.paramsTable.forEach(item=>{
|
||||||
|
if(methodName === 'get')
|
||||||
|
urlParams[item.name] = item.value
|
||||||
|
if(url.includes(`{${item.name}}`))
|
||||||
|
url = url.replace(`{${item.name}}`, item.value)
|
||||||
|
})
|
||||||
|
const params = {
|
||||||
|
...JSON.parse(requestBody.paramsText || '{}'),
|
||||||
|
...urlParams
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
server[methodObj[methodName]](url,params).then((resp:any)=>{
|
||||||
|
responsesContent.value = JSON.stringify(resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
type requestObj = {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.api-test-container {
|
||||||
|
.top {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.send {
|
||||||
|
width: 65px;
|
||||||
|
padding: 4px 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1890ff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.api-card {
|
||||||
|
margin-top: 24px;
|
||||||
|
h5 {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 4px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #1d39c4;
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding-left: 10px;
|
||||||
|
|
||||||
|
:deep(.jtable-body) {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.jtable-body-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
:deep(.ant-table-cell) {
|
||||||
|
padding: 0 8px;
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
<div class="choose-api-container">
|
||||||
|
<JTable
|
||||||
|
:columns="columns"
|
||||||
|
:dataSource="props.tableData"
|
||||||
|
:rowSelection="rowSelection"
|
||||||
|
noPagination
|
||||||
|
model="TABLE"
|
||||||
|
>
|
||||||
|
<template #url="slotProps">
|
||||||
|
<span
|
||||||
|
style="color: #1d39c4; cursor: pointer"
|
||||||
|
@click="jump(slotProps)"
|
||||||
|
>{{ slotProps.url }}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</JTable>
|
||||||
|
|
||||||
|
<a-button type="primary" @click="save">保存</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { addOperations_api, delOperations_api } from '@/api/system/apiPage';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { modeType } from '../typing';
|
||||||
|
const emits = defineEmits(['update:clickApi', 'update:selectedRowKeys']);
|
||||||
|
const props = defineProps<{
|
||||||
|
tableData: any[];
|
||||||
|
clickApi: any;
|
||||||
|
selectedRowKeys: string[];
|
||||||
|
sourceKeys: string[];
|
||||||
|
mode: modeType;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'API',
|
||||||
|
dataIndex: 'url',
|
||||||
|
key: 'url',
|
||||||
|
scopedSlots: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '说明',
|
||||||
|
dataIndex: 'summary',
|
||||||
|
key: 'summary',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const rowSelection = {
|
||||||
|
onSelect: (record: any) => {
|
||||||
|
let newKeys = [...props.selectedRowKeys];
|
||||||
|
|
||||||
|
if (props.selectedRowKeys.includes(record.id)) {
|
||||||
|
newKeys = newKeys.filter((id) => id !== record.id);
|
||||||
|
} else newKeys.push(record.id);
|
||||||
|
|
||||||
|
emits('update:selectedRowKeys', newKeys);
|
||||||
|
},
|
||||||
|
selectedRowKeys: ref<string[]>([]),
|
||||||
|
};
|
||||||
|
const save = () => {
|
||||||
|
const keys = props.selectedRowKeys;
|
||||||
|
|
||||||
|
const removeKeys = props.sourceKeys.filter((key) => !keys.includes(key));
|
||||||
|
const addKeys = keys.filter((key) => !props.sourceKeys.includes(key));
|
||||||
|
|
||||||
|
if (props.mode === 'api') {
|
||||||
|
// 此时是api配置
|
||||||
|
removeKeys.length &&
|
||||||
|
delOperations_api(removeKeys)
|
||||||
|
.finally(() => addOperations_api(addKeys))
|
||||||
|
.then(() => message.success('操作成功'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const jump = (row: any) => {
|
||||||
|
emits('update:clickApi', row);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.selectedRowKeys,
|
||||||
|
(n) => {
|
||||||
|
rowSelection.selectedRowKeys.value = n;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.choose-api-container {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
:deep(.jtable-body-header) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
:deep(.ant-alert-info) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -15,9 +15,12 @@
|
||||||
import { TreeProps } from 'ant-design-vue';
|
import { TreeProps } from 'ant-design-vue';
|
||||||
|
|
||||||
import { getTreeOne_api, getTreeTwo_api } from '@/api/system/apiPage';
|
import { getTreeOne_api, getTreeTwo_api } from '@/api/system/apiPage';
|
||||||
import { treeNodeTpye } from '../typing';
|
import type { modeType, treeNodeTpye } from '../typing';
|
||||||
|
|
||||||
const emits = defineEmits(['select']);
|
const emits = defineEmits(['select']);
|
||||||
|
const props = defineProps<{
|
||||||
|
mode:modeType
|
||||||
|
}>()
|
||||||
|
|
||||||
const treeData = ref<TreeProps['treeData']>([]);
|
const treeData = ref<TreeProps['treeData']>([]);
|
||||||
|
|
||||||
|
@ -39,6 +42,7 @@ const getTreeData = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const clickSelectItem: TreeProps['onSelect'] = (key, node: any) => {
|
const clickSelectItem: TreeProps['onSelect'] = (key, node: any) => {
|
||||||
|
if(!node.node.parent) return
|
||||||
emits('select', node.node.dataRef, node.node?.parent.node.schemas);
|
emits('select', node.node.dataRef, node.node?.parent.node.schemas);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,25 +2,32 @@
|
||||||
<a-card class="api-page-container">
|
<a-card class="api-page-container">
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="5">
|
<a-col :span="5">
|
||||||
<LeftTree @select="treeSelect" />
|
<LeftTree @select="treeSelect" :mode="props.mode" />
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="19">
|
<a-col :span="19">
|
||||||
<ChooseApi
|
<ChooseApi
|
||||||
v-show="!selectedApi.url"
|
v-show="!selectedApi.url"
|
||||||
v-model:click-api="selectedApi"
|
v-model:click-api="selectedApi"
|
||||||
:table-data="tableData"
|
:table-data="tableData"
|
||||||
|
v-model:selectedRowKeys="selectedKeys"
|
||||||
|
:source-keys="selectSourceKeys" :mode="props.mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="api-details"
|
class="api-details"
|
||||||
v-show="selectedApi.url && tableData.length > 0"
|
v-if="selectedApi.url && tableData.length > 0"
|
||||||
>
|
>
|
||||||
<a-button @click="selectedApi = initSelectedApi" style="margin-bottom: 24px;"
|
<a-button
|
||||||
|
@click="selectedApi = initSelectedApi"
|
||||||
|
style="margin-bottom: 24px"
|
||||||
>返回</a-button
|
>返回</a-button
|
||||||
>
|
>
|
||||||
<a-tabs v-model:activeKey="activeKey" type="card">
|
<a-tabs v-model:activeKey="activeKey" type="card">
|
||||||
<a-tab-pane key="does" tab="文档">
|
<a-tab-pane key="does" tab="文档">
|
||||||
<ApiDoes :select-api="selectedApi" :schemas="schemas" />
|
<ApiDoes
|
||||||
|
:select-api="selectedApi"
|
||||||
|
:schemas="schemas"
|
||||||
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="test" tab="调试">
|
<a-tab-pane key="test" tab="调试">
|
||||||
<ApiTest :select-api="selectedApi" />
|
<ApiTest :select-api="selectedApi" />
|
||||||
|
@ -33,27 +40,40 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="apiPage">
|
<script setup lang="ts" name="apiPage">
|
||||||
import type { treeNodeTpye, apiObjType, apiDetailsType } from './typing';
|
|
||||||
|
import { getApiGranted_api, apiOperations_api } from '@/api/system/apiPage';
|
||||||
|
import type {
|
||||||
|
treeNodeTpye,
|
||||||
|
apiObjType,
|
||||||
|
apiDetailsType,
|
||||||
|
modeType,
|
||||||
|
} from './typing';
|
||||||
import LeftTree from './components/LeftTree.vue';
|
import LeftTree from './components/LeftTree.vue';
|
||||||
import ChooseApi from './components/ChooseApi.vue';
|
import ChooseApi from './components/ChooseApi.vue';
|
||||||
import ApiDoes from './components/ApiDoes.vue';
|
import ApiDoes from './components/ApiDoes.vue';
|
||||||
import ApiTest from './components/ApiTest.vue';
|
import ApiTest from './components/ApiTest.vue';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const props = defineProps<{
|
||||||
|
mode: modeType;
|
||||||
|
}>();
|
||||||
|
|
||||||
const tableData = ref([]);
|
const tableData = ref([]);
|
||||||
const treeSelect = (node: treeNodeTpye, nodeSchemas:object = {}) => {
|
const treeSelect = (node: treeNodeTpye, nodeSchemas: object = {}) => {
|
||||||
schemas.value = nodeSchemas
|
schemas.value = nodeSchemas;
|
||||||
if (!node.apiList) return;
|
if (!node.apiList) return;
|
||||||
const apiList: apiObjType[] = node.apiList as apiObjType[];
|
const apiList: apiObjType[] = node.apiList as apiObjType[];
|
||||||
const table: any = [];
|
const table: any = [];
|
||||||
// 将对象形式的数据转换为表格需要的形式
|
// 将对象形式的数据转换为表格需要的形式
|
||||||
apiList?.forEach((apiItem) => {
|
apiList?.forEach((apiItem) => {
|
||||||
const { method, url } = apiItem;
|
const { method, url } = apiItem as any;
|
||||||
for (const key in method) {
|
for (const key in method) {
|
||||||
if (Object.prototype.hasOwnProperty.call(method, key)) {
|
if (Object.prototype.hasOwnProperty.call(method, key)) {
|
||||||
table.push({
|
table.push({
|
||||||
...method[key],
|
...method[key],
|
||||||
url,
|
url,
|
||||||
method: key,
|
method: key,
|
||||||
|
id: method[key].operationId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,23 +81,44 @@ const treeSelect = (node: treeNodeTpye, nodeSchemas:object = {}) => {
|
||||||
tableData.value = table;
|
tableData.value = table;
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeKey = ref('does');
|
const activeKey = ref<'does' | 'test'>('does');
|
||||||
const schemas = ref({});
|
const schemas = ref({});
|
||||||
const initSelectedApi:apiDetailsType = {
|
const initSelectedApi: apiDetailsType = {
|
||||||
url: '',
|
url: '',
|
||||||
method: '',
|
method: '',
|
||||||
summary: '',
|
summary: '',
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {},
|
responses: {},
|
||||||
requestBody: {}
|
requestBody: {},
|
||||||
};
|
};
|
||||||
const selectedApi = ref<apiDetailsType>(initSelectedApi);
|
const selectedApi = ref<apiDetailsType>(initSelectedApi);
|
||||||
|
|
||||||
watch(tableData, () => (selectedApi.value = initSelectedApi));
|
const canSelectKeys = ref<string[]>([]); // 左侧可展示的项
|
||||||
|
const selectedKeys = ref<string[]>([]); // 右侧默认勾选的项
|
||||||
|
let selectSourceKeys = ref<string[]>([])
|
||||||
|
init();
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
const code = route.query.code;
|
||||||
|
if (props.mode === 'appManger') {
|
||||||
|
} else if (props.mode === 'home') {
|
||||||
|
} else if (props.mode === 'api') {
|
||||||
|
apiOperations_api().then(resp=>{
|
||||||
|
selectedKeys.value = resp.result as string[]
|
||||||
|
selectSourceKeys.value = [...resp.result as string[]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
watch(tableData, () => {
|
||||||
|
activeKey.value = 'does';
|
||||||
|
selectedApi.value = initSelectedApi;
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.api-page-container {
|
.api-page-container {
|
||||||
|
padding: 24px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -22,4 +22,6 @@ export type apiDetailsType = {
|
||||||
parameters: any[];
|
parameters: any[];
|
||||||
requestBody?: any;
|
requestBody?: any;
|
||||||
responses:object;
|
responses:object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type modeType = 'api'| 'appManger' | 'home'
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Api mode="api" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="Platforms">
|
||||||
|
import Api from './Api/index.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -1,316 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="api-does-container">
|
|
||||||
<div class="top">
|
|
||||||
<h5>{{ selectApi.summary }}</h5>
|
|
||||||
<div class="input">
|
|
||||||
<InputCard :value="selectApi.method" />
|
|
||||||
<a-input :value="selectApi?.url" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<span class="label">请求数据类型</span>
|
|
||||||
<span>{{
|
|
||||||
getContent(selectApi.requestBody) ||
|
|
||||||
'application/x-www-form-urlencoded'
|
|
||||||
}}</span>
|
|
||||||
<span class="label">响应数据类型</span>
|
|
||||||
<span>{{ `["/"]` }}</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="api-card">
|
|
||||||
<h5>请求参数</h5>
|
|
||||||
<div class="content">
|
|
||||||
<JTable
|
|
||||||
:columns="requestCard.columns"
|
|
||||||
:dataSource="requestCard.tableData"
|
|
||||||
noPagination
|
|
||||||
model="TABLE"
|
|
||||||
>
|
|
||||||
<template #required="slotProps">
|
|
||||||
<span>{{ Boolean(slotProps.row.required) + '' }}</span>
|
|
||||||
</template>
|
|
||||||
<template #type="slotProps">
|
|
||||||
<span>{{ slotProps.row.schema.type }}</span>
|
|
||||||
</template>
|
|
||||||
</JTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="api-card">
|
|
||||||
<h5>响应状态</h5>
|
|
||||||
<div class="content">
|
|
||||||
<JTable
|
|
||||||
:columns="responseStatusCard.columns"
|
|
||||||
:dataSource="responseStatusCard.tableData"
|
|
||||||
noPagination
|
|
||||||
model="TABLE"
|
|
||||||
>
|
|
||||||
</JTable>
|
|
||||||
|
|
||||||
<a-tabs v-model:activeKey="responseStatusCard.activeKey">
|
|
||||||
<a-tab-pane
|
|
||||||
:key="key"
|
|
||||||
:tab="key"
|
|
||||||
v-for="key in tabs"
|
|
||||||
></a-tab-pane>
|
|
||||||
</a-tabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="api-card">
|
|
||||||
<h5>响应参数</h5>
|
|
||||||
<div class="content">
|
|
||||||
<JTable
|
|
||||||
:columns="respParamsCard.columns"
|
|
||||||
:dataSource="respParamsCard.tableData"
|
|
||||||
noPagination
|
|
||||||
model="TABLE"
|
|
||||||
>
|
|
||||||
</JTable>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { apiDetailsType } from '../typing';
|
|
||||||
import InputCard from './InputCard.vue';
|
|
||||||
import { PropType } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
selectApi: {
|
|
||||||
type: Object as PropType<apiDetailsType>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
schemas: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { selectApi } = toRefs(props);
|
|
||||||
|
|
||||||
type tableCardType = {
|
|
||||||
columns: object[];
|
|
||||||
tableData: object[];
|
|
||||||
activeKey?: any;
|
|
||||||
getData?: any;
|
|
||||||
};
|
|
||||||
const requestCard = reactive<tableCardType>({
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
title: '参数名',
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '参数说明',
|
|
||||||
dataIndex: 'description',
|
|
||||||
key: 'description',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '请求类型',
|
|
||||||
dataIndex: 'in',
|
|
||||||
key: 'in',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '是否必须',
|
|
||||||
dataIndex: 'required',
|
|
||||||
key: 'required',
|
|
||||||
scopedSlots: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '参数类型',
|
|
||||||
dataIndex: 'type',
|
|
||||||
key: 'type',
|
|
||||||
scopedSlots: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tableData: [],
|
|
||||||
getData: () => {
|
|
||||||
requestCard.tableData = props.selectApi.parameters;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const responseStatusCard = reactive<tableCardType>({
|
|
||||||
activeKey: '',
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
title: '状态码',
|
|
||||||
dataIndex: 'code',
|
|
||||||
key: 'code',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '说明',
|
|
||||||
dataIndex: 'desc',
|
|
||||||
key: 'desc',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'schema',
|
|
||||||
dataIndex: 'schema',
|
|
||||||
key: 'schema',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tableData: [],
|
|
||||||
getData: () => {
|
|
||||||
if (!Object.keys(props.selectApi.responses).length)
|
|
||||||
return (responseStatusCard.tableData = []);
|
|
||||||
|
|
||||||
const tableData = <any>[];
|
|
||||||
Object.entries(props.selectApi.responses || {}).forEach((item: any) => {
|
|
||||||
const desc = item[1].description;
|
|
||||||
const schema = item[1].content['*/*'].schema.$ref?.split('/') || '';
|
|
||||||
|
|
||||||
tableData.push({
|
|
||||||
code: item[0],
|
|
||||||
desc,
|
|
||||||
schema: schema && schema.pop(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
responseStatusCard.activeKey = tableData[0]?.code;
|
|
||||||
responseStatusCard.tableData = tableData;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const tabs = computed(() =>
|
|
||||||
responseStatusCard.tableData
|
|
||||||
.map((item: any) => item.code + '')
|
|
||||||
.filter((code: string) => code !== '400'),
|
|
||||||
);
|
|
||||||
const respParamsCard = reactive<tableCardType>({
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
title: '参数名称',
|
|
||||||
dataIndex: 'paramsName',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '参数说明',
|
|
||||||
dataIndex: 'desc',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '类型',
|
|
||||||
dataIndex: 'paramsType',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
tableData: [],
|
|
||||||
getData: (code: string) => {
|
|
||||||
type schemaObjType = {
|
|
||||||
paramsName: string;
|
|
||||||
paramsType: string;
|
|
||||||
desc: string;
|
|
||||||
children?: schemaObjType[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const schemaName = responseStatusCard.tableData.find(
|
|
||||||
(item: any) => item.code === code,
|
|
||||||
)?.schema;
|
|
||||||
const schemas = toRaw(props.schemas);
|
|
||||||
function findData(schemaName: string) {
|
|
||||||
if (!schemaName || !schemas[schemaName]) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const result: schemaObjType[] = [];
|
|
||||||
const schema = schemas[schemaName];
|
|
||||||
const basicType = ['string', 'integer', 'boolean'];
|
|
||||||
Object.entries(schema.properties).forEach((item: [string, any]) => {
|
|
||||||
const paramsType =
|
|
||||||
item[1].type ||
|
|
||||||
(item[1].$ref && item[1].$ref.split('/').pop()) ||
|
|
||||||
(item[1].items && item[1].items.$ref.split('/').pop()) ||
|
|
||||||
'';
|
|
||||||
const schemaObj: schemaObjType = {
|
|
||||||
paramsName: item[0],
|
|
||||||
paramsType,
|
|
||||||
desc: item[1].description || '',
|
|
||||||
};
|
|
||||||
if (!basicType.includes(paramsType))
|
|
||||||
schemaObj.children = findData(paramsType);
|
|
||||||
result.push(schemaObj);
|
|
||||||
});
|
|
||||||
console.log(result);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
respParamsCard.tableData = findData(schemaName);
|
|
||||||
// console.log(respParamsCard.tableData);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const getContent = (data: any) => {
|
|
||||||
if (data && data.content) {
|
|
||||||
return Object.keys(data.content || {})[0];
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
onMounted(() => {
|
|
||||||
requestCard.getData();
|
|
||||||
responseStatusCard.getData();
|
|
||||||
});
|
|
||||||
watch(
|
|
||||||
() => props.selectApi,
|
|
||||||
() => {
|
|
||||||
requestCard.getData();
|
|
||||||
responseStatusCard.getData();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch([() => responseStatusCard.activeKey, () => props.selectApi], (n) => {
|
|
||||||
n[0] && respParamsCard.getData(n[0]);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.api-does-container {
|
|
||||||
.top {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
display: flex;
|
|
||||||
margin: 24px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.api-card {
|
|
||||||
margin-top: 24px;
|
|
||||||
h5 {
|
|
||||||
position: relative;
|
|
||||||
padding-left: 10px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 4px;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #1d39c4;
|
|
||||||
border-radius: 0 3px 3px 0;
|
|
||||||
content: ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
padding-left: 10px;
|
|
||||||
|
|
||||||
:deep(.jtable-body) {
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.jtable-body-header {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,51 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="api-test-container">
|
|
||||||
<div class="top">
|
|
||||||
<h5>{{ selectApi.summary }}</h5>
|
|
||||||
<div class="input">
|
|
||||||
<InputCard :value="selectApi.method" />
|
|
||||||
<a-input :value="selectApi?.url" disabled />
|
|
||||||
<span class="send">发送</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { apiDetailsType } from '../typing';
|
|
||||||
import InputCard from './InputCard.vue';
|
|
||||||
import { PropType } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
selectApi: {
|
|
||||||
type: Object as PropType<apiDetailsType>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { selectApi } = toRefs(props);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.api-test-container {
|
|
||||||
.top {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.send {
|
|
||||||
width: 65px;
|
|
||||||
padding: 4px 15px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #1890ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,65 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="choose-api-container">
|
|
||||||
<JTable
|
|
||||||
:columns="columns"
|
|
||||||
:dataSource="props.tableData"
|
|
||||||
:rowSelection="rowSelection"
|
|
||||||
noPagination
|
|
||||||
model="TABLE"
|
|
||||||
>
|
|
||||||
<template #url="slotProps">
|
|
||||||
<span
|
|
||||||
style="color: #1d39c4; cursor: pointer"
|
|
||||||
@click="jump(slotProps.row)"
|
|
||||||
>{{ slotProps.row.url }}</span
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</JTable>
|
|
||||||
|
|
||||||
<a-button type="primary">保存</a-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { TableProps } from 'ant-design-vue';
|
|
||||||
|
|
||||||
const emits = defineEmits(['update:clickApi'])
|
|
||||||
const props = defineProps({
|
|
||||||
tableData: Array,
|
|
||||||
clickApi: Object
|
|
||||||
});
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: 'API',
|
|
||||||
dataIndex: 'url',
|
|
||||||
key: 'url',
|
|
||||||
scopedSlots: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '说明',
|
|
||||||
dataIndex: 'summary',
|
|
||||||
key: 'summary',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const rowSelection: TableProps['rowSelection'] = {
|
|
||||||
onChange: (selectedRowKeys, selectedRows) => {
|
|
||||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const jump = (row:object) => {
|
|
||||||
emits('update:clickApi',row)
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.choose-api-container {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
:deep(.jtable-body-header) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<template>
|
|
||||||
<span class="input-card-container" :class="props.value">
|
|
||||||
{{ props.value?.toLocaleUpperCase() }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
const props = defineProps({
|
|
||||||
value: String,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.input-card-container {
|
|
||||||
padding: 4px 15px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
&.get {
|
|
||||||
background-color: #1890ff;
|
|
||||||
}
|
|
||||||
&.put {
|
|
||||||
background-color: #fa8c16;
|
|
||||||
}
|
|
||||||
&.post {
|
|
||||||
background-color: #52c41a;
|
|
||||||
}
|
|
||||||
&.delete {
|
|
||||||
background-color: #f5222d;
|
|
||||||
}
|
|
||||||
&.patch {
|
|
||||||
background-color: #a0d911;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,96 +0,0 @@
|
||||||
<template>
|
|
||||||
<a-tree
|
|
||||||
:tree-data="treeData"
|
|
||||||
@select="clickSelectItem"
|
|
||||||
showLine
|
|
||||||
class="left-tree-container"
|
|
||||||
>
|
|
||||||
<template #title="{ name }">
|
|
||||||
{{ name }}
|
|
||||||
</template>
|
|
||||||
</a-tree>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { TreeProps } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { getTreeOne_api, getTreeTwo_api } from '@/api/system/apiPage';
|
|
||||||
import { treeNodeTpye } from '../typing';
|
|
||||||
|
|
||||||
const emits = defineEmits(['select']);
|
|
||||||
|
|
||||||
const treeData = ref<TreeProps['treeData']>([]);
|
|
||||||
|
|
||||||
const getTreeData = () => {
|
|
||||||
let tree: treeNodeTpye[] = [];
|
|
||||||
getTreeOne_api().then((resp: any) => {
|
|
||||||
tree = resp.urls.map((item: any) => ({
|
|
||||||
...item,
|
|
||||||
key: item.url,
|
|
||||||
}));
|
|
||||||
const allPromise = tree.map((item) => getTreeTwo_api(item.name));
|
|
||||||
Promise.all(allPromise).then((values) => {
|
|
||||||
values.forEach((item: any, i) => {
|
|
||||||
tree[i].children = combData(item?.paths);
|
|
||||||
tree[i].schemas = item.components.schemas
|
|
||||||
});
|
|
||||||
treeData.value = tree;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const clickSelectItem: TreeProps['onSelect'] = (key, node: any) => {
|
|
||||||
emits('select', node.node.dataRef, node.node?.parent.node.schemas);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getTreeData();
|
|
||||||
});
|
|
||||||
|
|
||||||
const combData = (dataSource: object) => {
|
|
||||||
const apiList: treeNodeTpye[] = [];
|
|
||||||
const keys = Object.keys(dataSource);
|
|
||||||
|
|
||||||
keys.forEach((key) => {
|
|
||||||
const method = Object.keys(dataSource[key] || {})[0];
|
|
||||||
const name = dataSource[key][method].tags[0];
|
|
||||||
let apiObj: treeNodeTpye | undefined = apiList.find(
|
|
||||||
(item) => item.name === name,
|
|
||||||
);
|
|
||||||
if (apiObj) {
|
|
||||||
apiObj.apiList?.push({
|
|
||||||
url: key,
|
|
||||||
method: dataSource[key],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
apiObj = {
|
|
||||||
name,
|
|
||||||
key: name,
|
|
||||||
apiList: [
|
|
||||||
{
|
|
||||||
url: key,
|
|
||||||
method: dataSource[key],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
apiList.push(apiObj);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return apiList;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less">
|
|
||||||
.left-tree-container {
|
|
||||||
border-right: 1px solid #e9e9e9;
|
|
||||||
height: calc(100vh - 150px);
|
|
||||||
overflow-y: auto;
|
|
||||||
.ant-tree-list {
|
|
||||||
.ant-tree-list-holder-inner {
|
|
||||||
.ant-tree-switcher-noop {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,25 +0,0 @@
|
||||||
export type treeNodeTpye = {
|
|
||||||
name: string;
|
|
||||||
key: string;
|
|
||||||
schemas?:object;
|
|
||||||
link?: string;
|
|
||||||
apiList?: object[];
|
|
||||||
children?: treeNodeTpye[];
|
|
||||||
|
|
||||||
};
|
|
||||||
export type methodType = {
|
|
||||||
[key: string]: object
|
|
||||||
}
|
|
||||||
export type apiObjType = {
|
|
||||||
url: string,
|
|
||||||
method: methodType
|
|
||||||
}
|
|
||||||
|
|
||||||
export type apiDetailsType = {
|
|
||||||
url: string;
|
|
||||||
method: string;
|
|
||||||
summary: string;
|
|
||||||
parameters: any[];
|
|
||||||
requestBody?: any;
|
|
||||||
responses:object;
|
|
||||||
}
|
|
|
@ -10,6 +10,7 @@ import {VueAmapResolver} from '@vuemap/unplugin-resolver'
|
||||||
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
|
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
|
||||||
import { createStyleImportPlugin, AndDesignVueResolve } from 'vite-plugin-style-import'
|
import { createStyleImportPlugin, AndDesignVueResolve } from 'vite-plugin-style-import'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import monacoEditorPlugin from 'vite-plugin-monaco-editor';
|
||||||
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
|
@ -47,7 +48,9 @@ export default defineConfig(({ mode}) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
||||||
vue(),
|
vue(),
|
||||||
|
monacoEditorPlugin({}),
|
||||||
vueJsx(),
|
vueJsx(),
|
||||||
Components({
|
Components({
|
||||||
resolvers: [AntDesignVueResolver({ importStyle: 'less' }), VueAmapResolver()],
|
resolvers: [AntDesignVueResolver({ importStyle: 'less' }), VueAmapResolver()],
|
||||||
|
|
Loading…
Reference in New Issue