push
This commit is contained in:
8
nervui-resource-manage/.env
Normal file
8
nervui-resource-manage/.env
Normal file
@@ -0,0 +1,8 @@
|
||||
# port
|
||||
VITE_PORT = 4200
|
||||
|
||||
# spa-title
|
||||
VITE_GLOB_APP_TITLE = nervui-resource-manage
|
||||
|
||||
# spa shortname
|
||||
VITE_GLOB_APP_SHORT_NAME = nervui-resource-manage
|
||||
22
nervui-resource-manage/.env.development
Normal file
22
nervui-resource-manage/.env.development
Normal file
@@ -0,0 +1,22 @@
|
||||
# Whether to open mock
|
||||
VITE_USE_MOCK = true
|
||||
|
||||
# public path
|
||||
VITE_PUBLIC_PATH = /nervui-resource-manage/
|
||||
|
||||
# Cross-domain proxy, you can configure multiple
|
||||
# Please note that no line breaks
|
||||
VITE_PROXY = {"/api": { "target": "http://portal.cloud.dev2.dingcloud.com:30080", "changeOrigin": true }}
|
||||
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]
|
||||
|
||||
# Delete console
|
||||
VITE_DROP_CONSOLE = false
|
||||
|
||||
# Basic interface address SPA
|
||||
VITE_GLOB_API_URL=/basic-api
|
||||
|
||||
# File upload address, optional
|
||||
VITE_GLOB_UPLOAD_URL=/upload
|
||||
|
||||
# Interface prefix
|
||||
VITE_GLOB_API_URL_PREFIX=
|
||||
35
nervui-resource-manage/.env.production
Normal file
35
nervui-resource-manage/.env.production
Normal file
@@ -0,0 +1,35 @@
|
||||
# Whether to open mock
|
||||
VITE_USE_MOCK = true
|
||||
|
||||
# public path
|
||||
VITE_PUBLIC_PATH = /nervui-resource-manage/
|
||||
|
||||
# Delete console
|
||||
VITE_DROP_CONSOLE = true
|
||||
|
||||
# Whether to enable gzip or brotli compression
|
||||
# Optional: gzip | brotli | none
|
||||
# If you need multiple forms, you can use `,` to separate
|
||||
VITE_BUILD_COMPRESS = 'none'
|
||||
|
||||
# Whether to delete origin files when using compress, default false
|
||||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
|
||||
|
||||
# Basic interface address SPA
|
||||
VITE_GLOB_API_URL=/basic-api
|
||||
|
||||
# File upload address, optional
|
||||
# It can be forwarded by nginx or write the actual address directly
|
||||
VITE_GLOB_UPLOAD_URL=/upload
|
||||
|
||||
# Interface prefix
|
||||
VITE_GLOB_API_URL_PREFIX=
|
||||
|
||||
# Whether to enable image compression
|
||||
VITE_USE_IMAGEMIN= true
|
||||
|
||||
# use pwa
|
||||
VITE_USE_PWA = false
|
||||
|
||||
# Is it compatible with older browsers
|
||||
VITE_LEGACY = false
|
||||
1
nervui-resource-manage/.version
Normal file
1
nervui-resource-manage/.version
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
74
nervui-resource-manage/build.sh
Normal file
74
nervui-resource-manage/build.sh
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
SOURCE="$0"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
if [ -z $WORKSPACE ];then
|
||||
echo "WORKSPACE not exists"
|
||||
else
|
||||
set DIR=$WORKSPACE
|
||||
fi
|
||||
|
||||
echo "current dir"
|
||||
echo "$DIR"
|
||||
|
||||
cd "$DIR"
|
||||
projectname=$(basename `pwd`)
|
||||
|
||||
npm run rm-build
|
||||
|
||||
if [ -d "$DIR/dist" ];then
|
||||
|
||||
cd "$DIR/dist"
|
||||
|
||||
# copy module.json
|
||||
cp ../module.json ./
|
||||
|
||||
# package
|
||||
VERSION=$(cat ../.version)
|
||||
lastdir=../release/
|
||||
if [ -d ${lastdir} ];then
|
||||
echo "删除旧release文件夹"
|
||||
rm -rf ${lastdir}
|
||||
else
|
||||
echo "文件夹不存在!"
|
||||
fi
|
||||
mkdir -p ${lastdir}
|
||||
|
||||
dir=../release/nerv/$projectname/$VERSION
|
||||
mkdir -p ${dir}
|
||||
tar -zcvf "${dir}/$projectname-$VERSION.tgz" ./*
|
||||
|
||||
templatedir=../release/resources/templates/nerv/$projectname/$VERSION/$projectname
|
||||
mkdir -p ${templatedir}
|
||||
cp -r ../resources/templates/* ${templatedir}
|
||||
|
||||
cd ../
|
||||
|
||||
releasefile=nerv-$projectname-$VERSION.tgz
|
||||
if [ -f ${releasefile} ];then
|
||||
echo "删除旧包!"
|
||||
rm -rf ${releasefile}
|
||||
fi
|
||||
|
||||
tar -zcvf ${releasefile} ./release/* release.yaml
|
||||
|
||||
mkdir -p ./release/nervui
|
||||
cp -r ./release/nerv/* ./release/nervui
|
||||
|
||||
if [ -f ${releasefile} ];then
|
||||
echo "编译成功!"
|
||||
mv ${releasefile} ./release
|
||||
else
|
||||
echo "编译失败!!!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
else
|
||||
echo "编译失败!!!"
|
||||
exit 1
|
||||
fi
|
||||
13
nervui-resource-manage/index.html
Normal file
13
nervui-resource-manage/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link href="/favicon.ico" rel="icon" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>资源管理</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./src/main.ts" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
22
nervui-resource-manage/module.json
Normal file
22
nervui-resource-manage/module.json
Normal file
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"catalog": "计算",
|
||||
"icon": "",
|
||||
"name": "resourceManage",
|
||||
"label": "资源管理",
|
||||
"menus": [
|
||||
{
|
||||
"name": "resource",
|
||||
"url": "/rm/resourceManage",
|
||||
"label": "资源管理",
|
||||
"operation": {
|
||||
"resource": "resource",
|
||||
"method": "list"
|
||||
},
|
||||
"submenus": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
11
nervui-resource-manage/offline-release.json
Normal file
11
nervui-resource-manage/offline-release.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"release": [
|
||||
{
|
||||
"src": "nervui-resource-manage/release",
|
||||
"dest": "/upload/pkg",
|
||||
"include": [
|
||||
"nervui-resource-manage-(\\d+).(\\d+).(\\d+)(|-\\w+).tar.gz"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
31
nervui-resource-manage/offline.sh
Normal file
31
nervui-resource-manage/offline.sh
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SOURCE="$0"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
||||
|
||||
cd "$DIR" || exit
|
||||
|
||||
VERSION=$(cat "$(pwd)"/.version)
|
||||
sed "s|\${VERSION}|${VERSION}|g" "$(pwd)"/offline.tpl.yaml > "$(pwd)"/offline.yaml || exit 1
|
||||
|
||||
|
||||
releaseDir="./release/nervui/nerv/nervui-resource-manage-offline"
|
||||
rm -rf $releaseDir
|
||||
version="${VERSION}-$(cat "$(pwd)"/offline.version)"
|
||||
|
||||
set -x
|
||||
docker run --rm -i \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v "$(pwd)"/offline.yaml:/app/build.yaml \
|
||||
-v "$(pwd)/$releaseDir":/app/output \
|
||||
-e name=nervui-resource-manage \
|
||||
-e version="$version" \
|
||||
--pull=always \
|
||||
registry.nervhub.nervstack.io/nerv3/deploy:latest
|
||||
|
||||
rm -rf $releaseDir/output
|
||||
5
nervui-resource-manage/offline.tpl.yaml
Normal file
5
nervui-resource-manage/offline.tpl.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
images:
|
||||
helm:
|
||||
- name: nervui-resource-manage
|
||||
version: "${VERSION}"
|
||||
repository: "https://registry.nervhub.nervstack.io/chartrepo/nerv3-ui"
|
||||
1
nervui-resource-manage/offline.version
Normal file
1
nervui-resource-manage/offline.version
Normal file
@@ -0,0 +1 @@
|
||||
alpha1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
BIN
nervui-resource-manage/public/asset/image/login/logo-paas.png
Normal file
BIN
nervui-resource-manage/public/asset/image/login/logo-paas.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
BIN
nervui-resource-manage/public/favicon.ico
Normal file
BIN
nervui-resource-manage/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
12
nervui-resource-manage/release.yaml
Normal file
12
nervui-resource-manage/release.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# 上传release目录信息到nerv-file仓库
|
||||
release:
|
||||
- {src: release, dest: /upload/pkg, include: [".*(.tgz)$"]}
|
||||
- {src: release/resources/templates, dest: /upload/templates}
|
||||
register:
|
||||
name: nervui-resource-manage
|
||||
version: 1.0.0
|
||||
components:
|
||||
- type: nervui-resource-manage
|
||||
resources:
|
||||
- {type: template, relativePath: /nervui-resource-manage/deploy.json}
|
||||
|
||||
69
nervui-resource-manage/src/App.vue
Normal file
69
nervui-resource-manage/src/App.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<Skeleton
|
||||
active
|
||||
:loading="loading">
|
||||
<ns-application/>
|
||||
</Skeleton>
|
||||
</template>
|
||||
|
||||
<script lang="ts" type="module">
|
||||
import {defineComponent, provide, ref, onUnmounted, onBeforeUnmount} from 'vue';
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||||
import {useRoute} from 'vue-router';
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
components: {Skeleton},
|
||||
setup() {
|
||||
let loading = ref(false);
|
||||
const route = useRoute();
|
||||
let showLayout = ref(true);
|
||||
let event = ref(null);
|
||||
let type = ref('');
|
||||
provide('showLayout', showLayout);
|
||||
provide('event', event);
|
||||
let eventData = ref({});
|
||||
const onEventData = () => { //动态改变值
|
||||
return event.value
|
||||
};
|
||||
provide('onEventData', onEventData);
|
||||
const onHandle = function(e){
|
||||
loading.value = false;
|
||||
event.value = e;
|
||||
const data = e?.data;
|
||||
if (data && data.type ) {
|
||||
eventData.value = data;
|
||||
type.value = data.type;
|
||||
if(data.type === 'style') {
|
||||
showLayout.value = data?.showLayout;
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener('message', onHandle );
|
||||
|
||||
onBeforeUnmount(()=>{
|
||||
window.removeEventListener('message', onHandle)
|
||||
})
|
||||
return {
|
||||
locale: zhCN,
|
||||
route,
|
||||
showLayout, event,
|
||||
type,
|
||||
eventData,
|
||||
loading
|
||||
};
|
||||
},
|
||||
unmounted(){
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
50
nervui-resource-manage/src/api/index.ts
Normal file
50
nervui-resource-manage/src/api/index.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/***
|
||||
*配置接口 格式 module:Array<resource>
|
||||
*/
|
||||
export const apiModule = {
|
||||
pension: [
|
||||
'User',
|
||||
'CurrentUser',
|
||||
'Gaffer',
|
||||
// 'Organizational',
|
||||
// 'Device',
|
||||
// 'Region',
|
||||
// 'Owner',
|
||||
// 'AllList',
|
||||
// 'VisitorRecord',
|
||||
// 'Room',
|
||||
// 'Device',
|
||||
// 'CommunityVisitor',
|
||||
// 'AccessControlGroup',
|
||||
// 'AccessControlLeft',
|
||||
// 'AccessControlRight',
|
||||
// 'AccessControlRightIdentity',
|
||||
// 'Visitor',
|
||||
// 'DeviceListByDeviceCategory',
|
||||
// 'Perimeter',
|
||||
// 'AccessControlRightVisitorIdentity',
|
||||
// 'BedChoose',
|
||||
// 'Gateway',
|
||||
// 'FileUpload',
|
||||
// 'User',
|
||||
// 'CurrentUser',
|
||||
// 'Organizational',
|
||||
// 'Device',
|
||||
// 'Region',
|
||||
// 'Owner',
|
||||
// 'AllList',
|
||||
// 'VisitorRecord',
|
||||
// 'Room',
|
||||
// 'Device',
|
||||
// 'CommunityVisitor',
|
||||
// 'AccessControlGroup',
|
||||
// 'AccessControlLeft',
|
||||
// 'AccessControlRight',
|
||||
// 'AccessControlRightIdentity',
|
||||
// 'Visitor',
|
||||
// 'DeviceListByDeviceCategory',
|
||||
// 'BedChoose',
|
||||
// 'Perimeter',
|
||||
// 'OrganizationConfig',
|
||||
],
|
||||
};
|
||||
15
nervui-resource-manage/src/api/user.ts
Normal file
15
nervui-resource-manage/src/api/user.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {http} from '/nerv-lib/paas';
|
||||
|
||||
enum Api {
|
||||
USER_LOGIN = '/api/passport/objs/login', //用户登录
|
||||
USER_INFO = '/api/webui/webui/objs/PassportUserInfo', //获取用户信息
|
||||
}
|
||||
export const userLogin = (data: RoomListModel) => http.post(Api.USER_LOGIN, data);
|
||||
export const userInfo = () => http.get(Api.USER_INFO);
|
||||
/**
|
||||
* @description 用户登录
|
||||
* @property `[fatherRegionUuid]` 父级区域唯一标识
|
||||
*/
|
||||
interface RoomListModel {
|
||||
data: string;
|
||||
}
|
||||
105
nervui-resource-manage/src/component/markdown.vue
Normal file
105
nervui-resource-manage/src/component/markdown.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<MdEditor v-bind="getBindValue" v-model="text" @change="change" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from 'vue';
|
||||
import MdEditor from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
export default defineComponent({
|
||||
name: 'NsMarkDown',
|
||||
components: { MdEditor },
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: () => {
|
||||
return '';
|
||||
},
|
||||
},
|
||||
toolbars: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [
|
||||
'bold',
|
||||
'underline',
|
||||
'italic',
|
||||
'-',
|
||||
'title',
|
||||
'strikeThrough',
|
||||
'sub',
|
||||
'sup',
|
||||
'quote',
|
||||
'unorderedList',
|
||||
'orderedList',
|
||||
'-',
|
||||
'codeRow',
|
||||
'code',
|
||||
'link',
|
||||
// 'image',
|
||||
'table',
|
||||
'mermaid',
|
||||
'katex',
|
||||
'-',
|
||||
'revoke',
|
||||
'next',
|
||||
'save',
|
||||
'=',
|
||||
'pageFullscreen',
|
||||
'fullscreen',
|
||||
'preview',
|
||||
'htmlPreview',
|
||||
'catalog',
|
||||
// 'github',
|
||||
];
|
||||
},
|
||||
},
|
||||
// readonly: {
|
||||
// type: Boolean,
|
||||
// default: () => false,
|
||||
// },
|
||||
},
|
||||
emits: ['change'],
|
||||
setup(props, { attrs, emit }) {
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
// model: model.value || 'test',
|
||||
}));
|
||||
|
||||
const text = ref(props.value || '');
|
||||
|
||||
const change = (val) => {
|
||||
console.log(val);
|
||||
text.value = val;
|
||||
};
|
||||
|
||||
// watchEffect(() => {
|
||||
// dataSource.value = props.initData;
|
||||
// });
|
||||
|
||||
return {
|
||||
getBindValue,
|
||||
text,
|
||||
change,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
text: {
|
||||
handler(val) {
|
||||
this.$emit('change', val);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#md-editor-v3 {
|
||||
width: 100% !important;
|
||||
}
|
||||
:deep(.ant-form-item-explain.ant-form-item-explain-error) {
|
||||
display: flex;
|
||||
min-width: 130px !important;
|
||||
width: 140px !important;
|
||||
}
|
||||
</style>
|
||||
7
nervui-resource-manage/src/config/app.config.ts
Normal file
7
nervui-resource-manage/src/config/app.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const appConfig = {
|
||||
projectType: 'web',
|
||||
baseApi: '/api',
|
||||
baseHeader: '/home',
|
||||
baseRouter: '/home/index',
|
||||
timeout: 15 * 1000
|
||||
};
|
||||
3
nervui-resource-manage/src/config/index.ts
Normal file
3
nervui-resource-manage/src/config/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import {appConfig} from '/@/config/app.config';
|
||||
|
||||
export {appConfig};
|
||||
18
nervui-resource-manage/src/main.ts
Normal file
18
nervui-resource-manage/src/main.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/** @format */
|
||||
|
||||
import {createApp} from 'vue';
|
||||
import App from '/@/App.vue';
|
||||
import {paasInit} from '/nerv-lib/paas';
|
||||
import {apiModule} from '/@/api';
|
||||
import {appConfig} from '/@/config';
|
||||
import '/@/theme/theme.scss';
|
||||
|
||||
const app = createApp(App);
|
||||
app.config.isCustomElement = (tag) => tag === 'plastic-button';
|
||||
paasInit({
|
||||
app,
|
||||
apiModule,
|
||||
appConfig,
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
@@ -0,0 +1,35 @@
|
||||
echo "=====================================================create====================================================="
|
||||
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function create() {
|
||||
|
||||
if [ -d "$nervui_app_home" ];then
|
||||
echo "$nervui_app_home exists!"
|
||||
else
|
||||
echo "start mkdir $nervui_app_home"
|
||||
mkdir -p "$nervui_app_home"
|
||||
fi
|
||||
|
||||
pkg_file_name=${pkg_url##*/}
|
||||
pkg_file_path="$nervui_app_home$pkg_file_name"
|
||||
|
||||
|
||||
echo "start download $pkg_url"
|
||||
curl -L -o $pkg_file_path $pkg_url
|
||||
|
||||
|
||||
echo "start install $pkg_file_path"
|
||||
tar -xf $pkg_file_path -C $nervui_app_home
|
||||
|
||||
}
|
||||
|
||||
if [ "$pkg_url" == "" ]; then
|
||||
echo {\"error\":\"pkg_url is empty\"}
|
||||
exit 1
|
||||
elif [ "$nervui_app_home" == "" ]; then
|
||||
echo {\"error\":\"nervui_app_home is empty\"}
|
||||
exit 1
|
||||
else
|
||||
create
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
echo "=====================================================delete====================================================="
|
||||
@@ -0,0 +1 @@
|
||||
echo "=====================================================setup====================================================="
|
||||
@@ -0,0 +1 @@
|
||||
echo "=====================================================start====================================================="
|
||||
@@ -0,0 +1 @@
|
||||
echo "=====================================================stop====================================================="
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "/nervui/nervui-resource-manage",
|
||||
"operations": [
|
||||
{
|
||||
"name": "Create",
|
||||
"type": "shell",
|
||||
"implementor": "create.sh"
|
||||
},
|
||||
{
|
||||
"name": "Delete",
|
||||
"type": "shell",
|
||||
"implementor": "delete.sh"
|
||||
},
|
||||
{
|
||||
"name": "Setup",
|
||||
"type": "shell",
|
||||
"implementor": "setup.sh"
|
||||
},
|
||||
{
|
||||
"name": "Start",
|
||||
"type": "shell",
|
||||
"implementor": "start.sh"
|
||||
},
|
||||
{
|
||||
"name": "Stop",
|
||||
"type": "shell",
|
||||
"implementor": "stop.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
64
nervui-resource-manage/src/resources/templates/deploy.json
Normal file
64
nervui-resource-manage/src/resources/templates/deploy.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "/nervui/nervui-resource-manage",
|
||||
"version": 1,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "server_ip",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "应用安装IP地址",
|
||||
"inputType": "ipSelectType"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "软件版本",
|
||||
"inputType": "versionSelectType"
|
||||
},
|
||||
{
|
||||
"name": "install_dir",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"defaultValue": "/data",
|
||||
"inputType": "textInputType",
|
||||
"description": "安装目录"
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"name": "nervui-resource-manage",
|
||||
"type": "/nerv/nerv-orchestrator/cluster/Nervui",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "file_repository",
|
||||
"value": "${nerv_file_repository}"
|
||||
},
|
||||
{
|
||||
"name": "install_dir",
|
||||
"value": "${install_dir}"
|
||||
},
|
||||
{
|
||||
"name": "pkg_url",
|
||||
"value": "/api/pkg/nerv/nervui-resource-manage/${version}/nervui-resource-manage-${version}.tgz"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"type": "contained",
|
||||
"target": "host"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "host",
|
||||
"type": "/nerv/nerv-orchestrator/compute/Host",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "address",
|
||||
"value": "${server_ip}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
85
nervui-resource-manage/src/router/index.ts
Normal file
85
nervui-resource-manage/src/router/index.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
const SideNav = () => import('/nerv-lib/paas/view/service/side-nav.vue');
|
||||
const Base = () => import('/nerv-lib/paas/view/system/layout/content.vue');
|
||||
const ListTable = () => import('/nerv-lib/paas/view/service/list-table.vue');
|
||||
const Detail = () => import('/nerv-lib/paas/view/service/detail.vue');
|
||||
const treeAndTableList = () => import('/view/tree-and-table-list.vue');
|
||||
const resourceTypeList = () => import('/view/resource-type-list.vue');
|
||||
const resourceTypeDetailForm = () => import('/view/resource-type-detail.vue');
|
||||
const mockAddForm = () => import('/view/mock-add.vue');
|
||||
|
||||
export const APP = 'nerv-rm-server'
|
||||
|
||||
|
||||
const rmRoute = {
|
||||
path: '/rm',
|
||||
name: 'resourceManage',
|
||||
meta: {
|
||||
sideMenus: {
|
||||
title: '资源管理',
|
||||
name: 'resourceManage',
|
||||
root: true,
|
||||
menus: [
|
||||
{
|
||||
name: 'resource',
|
||||
label: '资源管理',
|
||||
url: 'resourceManage',
|
||||
module: '',
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [
|
||||
/****** 资源管理 start *********** */
|
||||
{
|
||||
path: 'resourceManage',
|
||||
name: 'resourceModule',
|
||||
redirect: '/rm/resourceManage/list',
|
||||
component: Base,
|
||||
children: [
|
||||
// 资源管理-列表
|
||||
{
|
||||
path: 'list',
|
||||
name: 'resource',
|
||||
component: treeAndTableList,
|
||||
props: {
|
||||
enableTableSession: true,
|
||||
refreshTime: 10,
|
||||
}
|
||||
},
|
||||
// 资源管理-添加
|
||||
{
|
||||
name: 'resourceAdd',
|
||||
path: 'add',
|
||||
component: resourceTypeList,
|
||||
props: {
|
||||
title: '选择资源类型',
|
||||
enableTableSession: true,
|
||||
api: `/api/${APP}/objs/ManageResourceTypes`,
|
||||
formConfig: {
|
||||
schemas: [],
|
||||
showAction: false,
|
||||
},
|
||||
rowSelection: null,
|
||||
resultField: 'data',
|
||||
columns: [],
|
||||
columnActions: {},
|
||||
headerActions: [],
|
||||
rowKey: 'ID',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'resourceDetail',
|
||||
path: ':id/detail',
|
||||
component: resourceTypeDetailForm,
|
||||
},
|
||||
{
|
||||
name: 'resourceMock',
|
||||
path: 'mock',
|
||||
component: mockAddForm,
|
||||
},
|
||||
]
|
||||
}
|
||||
/****** 资源管理 end *********** */
|
||||
]
|
||||
};
|
||||
export default rmRoute;
|
||||
9
nervui-resource-manage/src/router/redirect.ts
Normal file
9
nervui-resource-manage/src/router/redirect.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
const RootRoute = {
|
||||
path: '/root',
|
||||
name: 'root',
|
||||
redirect: '/rm/resourceManage',
|
||||
meta: {
|
||||
title: 'Root',
|
||||
},
|
||||
};
|
||||
export default RootRoute;
|
||||
3
nervui-resource-manage/src/theme/global.scss
Normal file
3
nervui-resource-manage/src/theme/global.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.application .contentMenu .content {
|
||||
background-color: #fff;
|
||||
}
|
||||
2
nervui-resource-manage/src/theme/theme.scss
Normal file
2
nervui-resource-manage/src/theme/theme.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
@import "variable";
|
||||
@import "global";
|
||||
0
nervui-resource-manage/src/theme/variable.less
Normal file
0
nervui-resource-manage/src/theme/variable.less
Normal file
0
nervui-resource-manage/src/theme/variable.scss
Normal file
0
nervui-resource-manage/src/theme/variable.scss
Normal file
48
nervui-resource-manage/tsconfig.json
Normal file
48
nervui-resource-manage/tsconfig.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": "./",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "preserve",
|
||||
"lib": ["esnext", "dom"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"strictFunctionTypes": false,
|
||||
"target": "esnext",
|
||||
"types": ["vite/client"],
|
||||
"typeRoots": [
|
||||
"../node_modules/@types",
|
||||
"../node_modules/@vue",
|
||||
"../type"
|
||||
],
|
||||
"paths": {
|
||||
"/@/*": [
|
||||
"src/*"
|
||||
],
|
||||
"/nerv-lib/*": [
|
||||
"../lib/*"
|
||||
],
|
||||
"/nerv-base/*": [
|
||||
"../lib/paas/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"type/**/*",
|
||||
"mock/**/*",
|
||||
"vite.config.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.js"
|
||||
]
|
||||
}
|
||||
739
nervui-resource-manage/view/custom-list-table.vue
Normal file
739
nervui-resource-manage/view/custom-list-table.vue
Normal file
@@ -0,0 +1,739 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="ns-table">
|
||||
<a-spin :spinning="tableState.loading">
|
||||
<!-- {{ formConfig }} -->
|
||||
<div class="ns-table-search" v-if="!isEmpty(formConfig)">
|
||||
<ns-form
|
||||
ref="formElRef"
|
||||
class="ns-table-form"
|
||||
:showAction="true"
|
||||
v-bind="formConfig"
|
||||
formLayout="flex"
|
||||
:expand="expand"
|
||||
:showExpand="showExpand"
|
||||
:model="formModel"
|
||||
@finish="formFinish" />
|
||||
</div>
|
||||
<a-row type="flex" class="ns-table-main">
|
||||
<a-col :flex="getTreeWidth" v-if="!isEmpty(treeConfig)">
|
||||
<ns-tree v-if="getTreeData.length" v-bind="getTreeBindValue" @select="treeSelect" />
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<ns-table-header
|
||||
v-if="!isEmpty(headerActions) || tableTitle"
|
||||
:headerActions="headerActions"
|
||||
:searchData="formModel"
|
||||
:tableTitle="tableTitle"
|
||||
:data="tableState.selectedRows">
|
||||
<template #header="data">
|
||||
<slot name="header" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</ns-table-header>
|
||||
<ns-basic-table ref="tableElRef" v-bind="getTableBindValues" :dataSource="tableData">
|
||||
<template #emptyText>
|
||||
<template v-if="tableState.loadError">
|
||||
<div class="ns-table-content">
|
||||
<div class="fetch-error">
|
||||
<p>{{ tableState.loadErrorMessage }}</p>
|
||||
<ns-button type="primary" ghost @click="reload">重新加载</ns-button></div
|
||||
></div
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="tableState.loading"
|
||||
><div class="ns-table-content"></div
|
||||
></template>
|
||||
<template v-else>
|
||||
<div class="ns-table-content"> <a-empty /> </div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
<template v-if="item === 'bodyCell'">
|
||||
<template v-if="data?.column?.textEllipsis">
|
||||
<span class="tool-tips" :style="{ width: `${data.column.width}px` }">
|
||||
<ns-tooltip
|
||||
placement="top"
|
||||
v-if="
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex]
|
||||
">
|
||||
<template #title>
|
||||
<span>{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</template>
|
||||
<span class="text-ellipsis">{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</ns-tooltip>
|
||||
<span class="text-ellipsis" v-else> - </span>
|
||||
</span>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template v-if="data.column.dataIndex === 'tableAction'">
|
||||
<ns-table-action
|
||||
:data="data.record"
|
||||
:searchData="formModel"
|
||||
:columnActions="getColumnActions" />
|
||||
</template>
|
||||
<template v-if="data.column.edit">
|
||||
<ns-table-cell
|
||||
:value="data.text"
|
||||
:record="data.record"
|
||||
:column="data.column"
|
||||
:index="data.index" />
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="item === 'footer'">
|
||||
<ns-table-footer :footerActions="footerActions" :data="ediRowData" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template #bodyCell="data" v-if="!Object.keys($slots).includes('bodyCell')">
|
||||
<template v-if="data.column.textEllipsis">
|
||||
<span class="tool-tips" :style="{ width: `${data.column.width}px` }">
|
||||
<ns-tooltip
|
||||
placement="top"
|
||||
v-if="
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex]
|
||||
">
|
||||
<template #title>
|
||||
<span>{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</template>
|
||||
<span class="text-ellipsis">{{
|
||||
data.column.customRender
|
||||
? data.column.customRender(data)
|
||||
: data.record[data.column.dataIndex] || '-'
|
||||
}}</span>
|
||||
</ns-tooltip>
|
||||
<span class="text-ellipsis" v-else> - </span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="data.column.dataIndex === 'tableAction'">
|
||||
<ns-table-action
|
||||
:data="data.record"
|
||||
:searchData="formModel"
|
||||
:columnActions="getColumnActions" />
|
||||
</template>
|
||||
<template v-if="data.column.edit">
|
||||
<ns-table-cell
|
||||
:value="data.text"
|
||||
:record="data.record"
|
||||
:column="data.column"
|
||||
:index="data.index" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template
|
||||
#footer
|
||||
v-if="!Object.keys($slots).includes('footer') && !isEmpty(footerActions)">
|
||||
<ns-table-footer :footerActions="footerActions" :data="ediRowData" />
|
||||
</template>
|
||||
</ns-basic-table>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, provide, reactive, ref, unref, watch } from 'vue';
|
||||
import { RequestParams } from '/nerv-lib/component/table/table';
|
||||
import {
|
||||
cloneDeep,
|
||||
debounce,
|
||||
get,
|
||||
isArray,
|
||||
isEmpty,
|
||||
isEqual,
|
||||
isFunction,
|
||||
isObject,
|
||||
isString,
|
||||
isUndefined,
|
||||
} from 'lodash-es';
|
||||
import { useParams } from '/nerv-lib/use/use-params';
|
||||
import { transformColumns } from '/nerv-lib/component/table/table-columns';
|
||||
import NsBasicTable from '/nerv-lib/component/table/basic-table.vue';
|
||||
import { tableProps } from '/nerv-lib/component/table/props';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { useApi } from '/nerv-lib/use/use-api';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useTableEdit } from '/nerv-lib/component/table/use-table-edit';
|
||||
import { Form } from 'ant-design-vue';
|
||||
import { stringUtil } from '/nerv-lib/util/string-util';
|
||||
import { useTableRefresh } from '/nerv-lib/component/table/use-table-refresh';
|
||||
import { tableConfig } from '/nerv-base/config/table.config';
|
||||
import { useTableSession } from '/nerv-lib/component/table/use-table-session';
|
||||
import { useTableColumn } from '/nerv-lib/component/table/use-table-column';
|
||||
import NsTableAction from '/nerv-lib/component/table/table-action.vue';
|
||||
import NsTableHeader from '/nerv-lib/component/table/table-header.vue';
|
||||
import NsTableFooter from '/nerv-lib/component/table/table-footer.vue';
|
||||
import NsTableCell from '/nerv-lib/component/table/edit/table-cell.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NsCustomListTable',
|
||||
components: {
|
||||
NsBasicTable,
|
||||
NsTableAction,
|
||||
NsTableHeader,
|
||||
NsTableFooter,
|
||||
NsTableCell,
|
||||
},
|
||||
props: tableProps,
|
||||
|
||||
emits: ['cellChange', 'update:value', 'dataSourceChange', 'update:dataSource'],
|
||||
setup(props, { attrs, emit }) {
|
||||
const tableElRef = ref(null);
|
||||
const formElRef = ref(null);
|
||||
const dataRef = ref([]);
|
||||
const treeParamsRef = ref({});
|
||||
const formParamsRef = ref({});
|
||||
const orderRef = ref({});
|
||||
const formModel = reactive<Recordable>({});
|
||||
const tableData = ref<Recordable[]>([]);
|
||||
const tableState = reactive({
|
||||
selectedRowKeys: [],
|
||||
selectedRows: [],
|
||||
loading: false,
|
||||
loadError: false,
|
||||
loadErrorMessage: '',
|
||||
});
|
||||
const route = useRoute();
|
||||
const { getColumnActionWidth } = useTableColumn({
|
||||
columnActions: Object.assign({}, tableConfig.columnActions, props.columnActions),
|
||||
});
|
||||
|
||||
const defaultPageRef = ref(1 - props.pageFieldOffset);
|
||||
|
||||
const { setTableSession } = useTableSession(formModel, formParamsRef, defaultPageRef);
|
||||
|
||||
const { delayFetch } = useTableRefresh({ props, reload });
|
||||
|
||||
watch(
|
||||
[() => props.value, () => props.dataSource],
|
||||
() => {
|
||||
tableData.value = props.value || props.dataSource || [];
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const formItemContext = Form.useInjectFormItemContext();
|
||||
|
||||
const getColumnActions = computed(() => {
|
||||
const { actions } = props.columnActions as any;
|
||||
const _tableConfig = cloneDeep(tableConfig);
|
||||
if (actions) {
|
||||
_tableConfig.columnActions.width = getColumnActionWidth(actions);
|
||||
}
|
||||
return Object.assign(_tableConfig.columnActions, props.columnActions);
|
||||
});
|
||||
|
||||
const getColumns = computed(() => {
|
||||
const columns = transformColumns(cloneDeep(props.columns || []));
|
||||
const { title, width, dataIndex, fixed } = getColumnActions.value;
|
||||
if (props.columnActions) {
|
||||
columns.push({
|
||||
title,
|
||||
width,
|
||||
dataIndex,
|
||||
fixed,
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => tableData.value,
|
||||
(val) => {
|
||||
// console.log(val, tableData.value);
|
||||
|
||||
if (isEqual(val, tableData.value)) return;
|
||||
|
||||
const data = cloneDeep(tableData.value);
|
||||
if (props.editable) {
|
||||
Object.keys(data).forEach((key) => {
|
||||
delete data[key][props.rowKey];
|
||||
});
|
||||
}
|
||||
// emit('update:value', data);
|
||||
emit('dataSourceChange', data);
|
||||
formItemContext.onFieldChange();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
const tableEdit = useTableEdit({
|
||||
dataSource: tableData,
|
||||
columns: getColumns,
|
||||
rowKey: props.rowKey,
|
||||
editable: ref(props.editable),
|
||||
});
|
||||
provide('tableEdit', tableEdit);
|
||||
|
||||
const { getParams } = useParams();
|
||||
|
||||
const rowSelection = computed(() => {
|
||||
const { rowSelection } = props;
|
||||
if (rowSelection === false || rowSelection === null) {
|
||||
return null;
|
||||
}
|
||||
return Object.assign(
|
||||
{
|
||||
fixed: true,
|
||||
columnWidth: 48,
|
||||
preserveSelectedRowKeys: true, // 跨页选中默认不清除选中key
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: (selectedRowKeys: never[], selectedRows: never[]) => {
|
||||
tableState.selectedRowKeys = selectedRowKeys;
|
||||
tableState.selectedRows = selectedRows;
|
||||
},
|
||||
},
|
||||
rowSelection,
|
||||
);
|
||||
});
|
||||
|
||||
const customizeRenderEmpty = computed(() => {
|
||||
return () => '暂无数据';
|
||||
});
|
||||
|
||||
const formFinish = debounce((data: object) => {
|
||||
formParamsRef.value = data;
|
||||
fetch({
|
||||
page: 1,
|
||||
});
|
||||
}, 300);
|
||||
|
||||
function setLoading(loading: boolean) {
|
||||
tableState.loading = loading;
|
||||
}
|
||||
|
||||
const tableChangeEvent = (pagination: Props, filters: [], sorter: any) => {
|
||||
// console.log('params', pagination, filters, sorter);
|
||||
if (sorter?.field) {
|
||||
if (sorter.order) {
|
||||
orderRef.value = {
|
||||
[props.paramsOrderField]: stringUtil.toLine(
|
||||
`${sorter.field} ${sorter.order.replace('end', '')}`,
|
||||
),
|
||||
};
|
||||
} else {
|
||||
orderRef.value = { [props.paramsOrderField]: '' }; //覆盖默认params
|
||||
}
|
||||
fetch({
|
||||
page: pagination?.current || getPagination.value?.current || 1,
|
||||
pageSize: pagination?.pageSize,
|
||||
});
|
||||
} else if (pagination?.current) {
|
||||
fetch({
|
||||
page: pagination?.current,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// pagination
|
||||
const getPagination: Recordable | Boolean = computed(() => {
|
||||
const { pagination } = props;
|
||||
if (pagination) {
|
||||
const current = get(dataRef.value, props.pageField);
|
||||
return {
|
||||
showQuickJumper: true,
|
||||
showLessItems: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total: number, range: Array<number>) =>
|
||||
`显示第${range[0]}到${range[1]}条记录 ,共 ${total} 条记录`,
|
||||
...(pagination as Props),
|
||||
total: get(dataRef.value, props.totalField),
|
||||
current: (current >= 0 ? current : 0) + props.pageFieldOffset, // 后端0 开始
|
||||
pageSize: get(dataRef.value, props.sizeField),
|
||||
};
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const getTableBindValues = computed(() => {
|
||||
const { params, dynamicParams } = props;
|
||||
return {
|
||||
...attrs,
|
||||
...props,
|
||||
rowSelection: rowSelection.value,
|
||||
params: dynamicParams
|
||||
? getParams({ ...route.params, ...route.query }, dynamicParams, params)
|
||||
: params || {},
|
||||
columns: getColumns.value,
|
||||
pagination: getPagination.value,
|
||||
onChange: tableChangeEvent,
|
||||
};
|
||||
});
|
||||
|
||||
watch(
|
||||
() => getTableBindValues.value.api,
|
||||
() => {
|
||||
fetch();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 请求函数
|
||||
* @param requestParams 主要是传入页面,部分变量闭包处理
|
||||
* @param clearDelay 是否需要清除刷新时间(页面操作之后,自动刷新重新计算)
|
||||
*/
|
||||
|
||||
function fetch(requestParams: RequestParams = {}, clearDelay = true) {
|
||||
clearDelay && delayFetch();
|
||||
const { api, pagination } = props;
|
||||
const { page, pageSize } = requestParams;
|
||||
if (api) {
|
||||
let pageParams: Recordable = {};
|
||||
|
||||
if (pagination !== false) {
|
||||
pageParams = {
|
||||
[props.paramsPageField]: page ? page - props.pageFieldOffset : defaultPageRef.value, // 后端0 开始
|
||||
[props.paramsPageSizeField]:
|
||||
pageSize || getPagination.value?.pageSize || props.defaultPageSize,
|
||||
};
|
||||
} else {
|
||||
pageParams = {
|
||||
[props.paramsPageField]: defaultPageRef.value, // 后端0 开始
|
||||
[props.paramsPageSizeField]:
|
||||
pageSize || getPagination.value?.pageSize || props.defaultPageSize,
|
||||
};
|
||||
}
|
||||
const httpParams = {
|
||||
...getTableBindValues.value.params,
|
||||
...pageParams,
|
||||
...formParamsRef.value,
|
||||
...treeParamsRef.value,
|
||||
...orderRef.value,
|
||||
};
|
||||
if (!checkrequiredParams(httpParams)) {
|
||||
console.log('check fail');
|
||||
return;
|
||||
}
|
||||
|
||||
setTableSession(pageParams[props.paramsPageField]);
|
||||
|
||||
clearDelay && setLoading(true);
|
||||
|
||||
const requestConfig: AxiosRequestConfig = { method: 'get' };
|
||||
const { httpRequest } = useApi();
|
||||
httpRequest({
|
||||
api,
|
||||
params: httpParams,
|
||||
pathParams: { ...route.params, ...route.query },
|
||||
requestConfig,
|
||||
})
|
||||
.then((res: any) => {
|
||||
console.log(res)
|
||||
res = {
|
||||
"page": 1, // 页码
|
||||
"pageSize": 20, // 条目数
|
||||
"pageCount": 3,
|
||||
"totalCount": 3, // 满足搜索条件的总数
|
||||
"data": [
|
||||
{
|
||||
"alias": "111", // 资源实例别名
|
||||
"RM_UUID": "xxxx", // 资源实例的RM_UUID
|
||||
"typeAlias": "xxxx", // 资源类型别名
|
||||
"typeCode": "xxxx", // 资源类型代码
|
||||
"projectName": "xxx", // 所属项目名称
|
||||
"projectID": 1, // 所属项目id
|
||||
"status": "running", // 状态
|
||||
"actions": [
|
||||
{
|
||||
"name": "edit",
|
||||
"label": "编辑",
|
||||
"mode": "page",
|
||||
"pageAddr": "前端提供",
|
||||
"actionApi": "",
|
||||
"authRequired": false
|
||||
},
|
||||
{
|
||||
"name": "remove",
|
||||
"label": "卸载",
|
||||
"mode": "api",
|
||||
"pageAddr": "",
|
||||
"actionApi": "/api/objs/RA/Volume/Detach",
|
||||
"authRequired": false
|
||||
},
|
||||
{
|
||||
"name": "extend",
|
||||
"label": "磁盘扩容",
|
||||
"mode": "page",
|
||||
"pageAddr": "前端提供",
|
||||
"actionApi": "",
|
||||
"authRequired": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"alias": "222", // 资源实例别名
|
||||
"RM_UUID": "xxxx", // 资源实例的RM_UUID
|
||||
"typeAlias": "xxxx", // 资源类型别名
|
||||
"typeCode": "xxxx", // 资源类型代码
|
||||
"projectName": "xxx", // 所属项目名称
|
||||
"projectID": 1, // 所属项目id
|
||||
"status": "running", // 状态
|
||||
"actions": [
|
||||
{
|
||||
"name": "edit",
|
||||
"label": "编辑",
|
||||
"mode": "page",
|
||||
"pageAddr": "前端提供",
|
||||
"actionApi": "",
|
||||
"authRequired": false
|
||||
},
|
||||
{
|
||||
"name": "remove",
|
||||
"label": "卸载",
|
||||
"mode": "api",
|
||||
"pageAddr": "",
|
||||
"actionApi": "/api/objs/RA/Volume/Detach",
|
||||
"authRequired": false
|
||||
},
|
||||
{
|
||||
"name": "extend",
|
||||
"label": "磁盘扩容",
|
||||
"mode": "page",
|
||||
"pageAddr": "前端提供",
|
||||
"actionApi": "",
|
||||
"authRequired": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"alias": "333", // 资源实例别名
|
||||
"RM_UUID": "xxxx",
|
||||
"typeAlias": "xxxx", // 资源类型别名
|
||||
"typeCode": "xxxx", // 资源类型代码
|
||||
"projectName": "xxx", // 所属项目名称
|
||||
"projectID": 1, // 所属项目id
|
||||
"status": "running", // 状态
|
||||
"actions": [
|
||||
{
|
||||
"name": "edit",
|
||||
"label": "编辑",
|
||||
"mode": "page",
|
||||
"pageAddr": "前端提供",
|
||||
"actionApi": "",
|
||||
"authRequired": false
|
||||
},
|
||||
{
|
||||
"name": "remove",
|
||||
"label": "卸载",
|
||||
"mode": "api",
|
||||
"pageAddr": "",
|
||||
"actionApi": "/api/objs/RA/Volume/Detach",
|
||||
"authRequired": false
|
||||
},
|
||||
{
|
||||
"name": "extend",
|
||||
"label": "磁盘扩容",
|
||||
"mode": "page",
|
||||
"pageAddr": "前端提供",
|
||||
"actionApi": "",
|
||||
"authRequired": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
tableState.loadError = false;
|
||||
tableState.loadErrorMessage = '';
|
||||
dataRef.value = res;
|
||||
tableData.value = get(unref(dataRef), props.listField);
|
||||
emit('update:dataSource', tableData.value);
|
||||
clearDelay && setLoading(false);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
const { response, code, message } = error || {};
|
||||
let errMessage = response?.data?.msg;
|
||||
const err: string = error?.toString?.() ?? '';
|
||||
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
|
||||
errMessage = '接口请求超时,请刷新页面重试!';
|
||||
}
|
||||
if (err?.includes('Network Error')) {
|
||||
errMessage = '网络异常,请检查您的网络连接是否正常!';
|
||||
}
|
||||
tableState.loadError = true;
|
||||
tableState.loadErrorMessage = errMessage;
|
||||
clearDelay && setLoading(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测requiredParams是否全部获得数据
|
||||
* @param params
|
||||
*/
|
||||
function checkrequiredParams(params: Recordable) {
|
||||
const { params: dynamicParams } = getTableBindValues.value as any;
|
||||
let { requiredParams } = props;
|
||||
if (requiredParams) {
|
||||
if (requiredParams === true) requiredParams = dynamicParams as any;
|
||||
if (isFunction(requiredParams)) {
|
||||
console.error(
|
||||
'Property dynamicParams of props cannot set to Function when using requiredParams',
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
if (isString(requiredParams)) {
|
||||
if (isUndefined(params[requiredParams])) return false;
|
||||
} else if (isArray(requiredParams)) {
|
||||
for (let i = 0, l = requiredParams.length; i < l; i++) {
|
||||
if (isUndefined(params[requiredParams[i]])) return false;
|
||||
}
|
||||
} else if (isObject(requiredParams)) {
|
||||
const keys = Object.keys(requiredParams);
|
||||
for (let i = 0, l = keys.length; i < l; i++) {
|
||||
if (isUndefined(params[keys[i]])) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function treeSelect(
|
||||
selectedKeys: never[],
|
||||
e: {
|
||||
selected: boolean;
|
||||
selectedNodes: { props: { dataRef: any } }[];
|
||||
node: any;
|
||||
event: any;
|
||||
},
|
||||
) {
|
||||
console.log(selectedKeys, e);
|
||||
const { dataRef } = e.selectedNodes[0].props;
|
||||
treeParamsRef.value = getParams(dataRef, props.params);
|
||||
fetch({
|
||||
page: 1,
|
||||
});
|
||||
}
|
||||
|
||||
const getTreeData = computed(() => {
|
||||
return props?.treeConfig?.treeData || [];
|
||||
});
|
||||
|
||||
const getTreeWidth = computed(() => {
|
||||
return props?.treeConfig?.width || '300px';
|
||||
});
|
||||
|
||||
const getTreeBindValue = computed(() => ({
|
||||
...props?.treeConfig,
|
||||
}));
|
||||
//todo 异步加载|| 树形接口
|
||||
|
||||
function reload(clearDelay = true) {
|
||||
const pagination = unref(getPagination);
|
||||
fetch(
|
||||
{
|
||||
page: pagination === false ? 1 : pagination.current,
|
||||
},
|
||||
clearDelay,
|
||||
);
|
||||
}
|
||||
|
||||
provide('reload', reload); //提供刷新功能
|
||||
|
||||
return {
|
||||
reload,
|
||||
formElRef,
|
||||
tableElRef,
|
||||
getColumnActions,
|
||||
getTableBindValues,
|
||||
formModel,
|
||||
tableState,
|
||||
isEmpty,
|
||||
formFinish,
|
||||
tableChangeEvent,
|
||||
treeSelect,
|
||||
getTreeBindValue,
|
||||
getTreeWidth,
|
||||
getTreeData,
|
||||
customizeRenderEmpty,
|
||||
tableData,
|
||||
treeParamsRef
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-spin-nested-loading > div > .ant-spin) {
|
||||
max-height: none;
|
||||
}
|
||||
.ns-table-search {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.ns-table {
|
||||
position: relative;
|
||||
min-height: 400px;
|
||||
|
||||
.ant-spin-nested-loading {
|
||||
height: 100%;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.ns-table-content {
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fetch-error {
|
||||
p {
|
||||
line-height: 40px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
width: 88px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-ellipsis {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
.tool-tips {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
218
nervui-resource-manage/view/mock-add.vue
Normal file
218
nervui-resource-manage/view/mock-add.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<page-title :title="editId ? '编辑磁盘' : '创建磁盘'" />
|
||||
<a-page-header>
|
||||
<template #extra>
|
||||
<ns-button @click="navigateBack()">返回</ns-button>
|
||||
<ns-button type="primary" @click="submit" :disabled="!mainRef?.validateResult">保存</ns-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<ns-form
|
||||
style="margin-top: 30px; margin-left: 24px"
|
||||
ref="mainRef"
|
||||
formLayout="修改"
|
||||
:schemas="formSchema"
|
||||
:model="data"
|
||||
v-bind="$attrs" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, computed, inject } from 'vue';
|
||||
import { authorizationService } from '/nerv-base/store/modules/authorization-service';
|
||||
import { NsMessage } from '/nerv-lib/component/message';
|
||||
import { http } from '/nerv-lib/util/http';
|
||||
import { useRouter } from 'vue-router';
|
||||
export default defineComponent({
|
||||
name: 'AppAdd',
|
||||
setup() {
|
||||
const mainRef = ref();
|
||||
const formLayout = 'ns-vertical-form';
|
||||
const router = useRouter();
|
||||
const data = ref({});
|
||||
const event = inject('event');
|
||||
const submitData = ref();
|
||||
const navigateBack = (type?: string) => {
|
||||
const msg = {
|
||||
type: type || 'back'
|
||||
}
|
||||
event.value.source.postMessage(msg, event.value.origin);
|
||||
};
|
||||
const onEventData = inject('onEventData')
|
||||
const eventData = computed(function () {
|
||||
return onEventData();
|
||||
})
|
||||
const params = router.currentRoute.value.params;
|
||||
if (params.id) {
|
||||
http.get(`/api/iaas/iaas/objs/RA/Volume/${params.id}`).then((res) => {
|
||||
initRes.value = res;
|
||||
data.value.ID = res.ID;
|
||||
data.value.description = res.description;
|
||||
data.value.name = res.name;
|
||||
data.value.projectName = res.projectName;
|
||||
});
|
||||
}
|
||||
const initRes = ref({});
|
||||
const errorItem = ref({});
|
||||
function submit() {
|
||||
mainRef.value
|
||||
.triggerSubmit()
|
||||
.then((data) => {
|
||||
submitData.value = data
|
||||
let objData = {
|
||||
alias: data['name'],
|
||||
projectID: data['projectID'],
|
||||
projectName: data['projectName'],
|
||||
}
|
||||
// saveData(submitData.value)
|
||||
event.value.source.postMessage({type:'getId', data: objData}, event.value.origin);
|
||||
})
|
||||
.catch(() => ({}));
|
||||
}
|
||||
|
||||
function saveData(data){
|
||||
initRes.value.alias = data.name;
|
||||
initRes.value.RM_UUID = data.RM_UUID;
|
||||
initRes.value.projectID = data.projectID;
|
||||
initRes.value.projectName = data.projectName;
|
||||
http.post('/api/nerv-mock-ra/objs/Resources/Create', initRes.value).then(
|
||||
(res) => {
|
||||
NsMessage.success('操作成功', 1, () => {
|
||||
navigateBack('successBack')
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
if (error?.response?.data?.fieldErrors) {
|
||||
errorItem.value = error?.response?.data?.fieldErrors;
|
||||
mainRef.value.triggerSubmit(['name']);
|
||||
}
|
||||
// 删除RM_UUID
|
||||
let objData = {
|
||||
RM_UUID: data['RM_UUID'],
|
||||
}
|
||||
event.value.source.postMessage({type:'removeId', data: objData}, event.value.origin);
|
||||
},
|
||||
);
|
||||
}
|
||||
let formSchema = ref([
|
||||
{
|
||||
label: '项目',
|
||||
field: 'projectID',
|
||||
addModel: {
|
||||
projectName: 'projectname',
|
||||
},
|
||||
ifShow: params.id ? false : true,
|
||||
viewOnly: params.id ? true : false,
|
||||
component: 'NsSelectApi',
|
||||
componentProps: {
|
||||
api: {
|
||||
url: '/api/passport/passport/objs/Authorization/CheckAuthorization',
|
||||
method: 'POST',
|
||||
},
|
||||
autoSelectFirst: params.id ? false : true,
|
||||
showSearch: true,
|
||||
filterOption: (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
},
|
||||
filterData: (item) => {
|
||||
return authorizationService().checkPermission(
|
||||
'vdisk',
|
||||
'Volume',
|
||||
'add',
|
||||
item.projectname,
|
||||
);
|
||||
},
|
||||
resultField: 'projects',
|
||||
labelField: 'projectname',
|
||||
valueField: 'projectid',
|
||||
immediate: true,
|
||||
placeholder: '请选择项目',
|
||||
},
|
||||
rules: params.id
|
||||
? []
|
||||
: [
|
||||
{
|
||||
type: 'number',
|
||||
required: true,
|
||||
message: '项目必填',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
label: '磁盘名称',
|
||||
component: 'NsInput',
|
||||
componentProps: {
|
||||
placeholder: '请输入',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '磁盘名称必填',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
trigger: 'blur',
|
||||
validator: async (rule, value) => {
|
||||
if (Object.keys(errorItem.value).indexOf('name') !== -1) {
|
||||
const errorInfo = errorItem.value['name'];
|
||||
delete errorItem.value['name'];
|
||||
return Promise.reject(errorInfo);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
pattern: '^[a-zA-Z0-9][a-zA-Z0-9_-]{5,31}$',
|
||||
message:
|
||||
'只能包含字母,数字,短横线(-)和下划线(_),且必须由大写字母、小写字母或数字开头。长度在6-32字符之间。',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
return {
|
||||
errorItem,
|
||||
navigateBack,
|
||||
editId: null,
|
||||
data,
|
||||
mainRef,
|
||||
submit,
|
||||
formSchema,
|
||||
formLayout,
|
||||
event,
|
||||
submitData,
|
||||
saveData,
|
||||
eventData,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
eventData: function (eventData) {
|
||||
const data = this.event?.data;
|
||||
if (data.type === 'returnId' && data.data) {
|
||||
this.submitData['RM_UUID'] = data.data['RM_UUID'];
|
||||
this.saveData(this.submitData);
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeCreate() {},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-col {
|
||||
flex: 0 0 120px !important;
|
||||
}
|
||||
.ant-page-header {
|
||||
padding: 0 24px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
:deep(.ant-page-header-heading-extra) {
|
||||
margin-right: auto !important;
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
146
nervui-resource-manage/view/resource-type-detail.vue
Normal file
146
nervui-resource-manage/view/resource-type-detail.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<Skeleton
|
||||
active
|
||||
:loading="loading">
|
||||
<div v-if="!show" >
|
||||
<page-title :title="data?.alias"/>
|
||||
<a-page-header class="ns-page-header">
|
||||
<template #extra>
|
||||
<ns-button @click="navigateBack">返回</ns-button>
|
||||
<ns-button @click="onAdd" type="primary">创建</ns-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<div class="content-wrapper">
|
||||
<NsMarkDown
|
||||
:value="data?.detail"
|
||||
:previewOnly="true"
|
||||
:htmlPreview="true"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="show" class="frame-wrapper" style="height: 100%">
|
||||
<iframe :src="src" id="frame" height="100%" width="100%" ></iframe>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, nextTick, onUnmounted, ref} from 'vue';
|
||||
import NsMarkDown from '/@/component/markdown.vue';
|
||||
import {http} from "/nerv-lib/util";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {useNavigate} from "/nerv-lib/use/use-navigate";
|
||||
import {NsMessage} from "/nerv-lib/component";
|
||||
import {Skeleton} from "ant-design-vue";
|
||||
import {APP} from "/@/router/index.ts";
|
||||
import Cookie from "js-cookie";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResourceTypeDetail',
|
||||
props: {},
|
||||
components: {NsMarkDown, Skeleton},
|
||||
setup(props, {attrs}) {
|
||||
let loading = ref(true);
|
||||
let data = ref({});
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const params = route.params;
|
||||
const code = params?.id;
|
||||
const { navigateBack } = useNavigate();
|
||||
let src = ref('');
|
||||
let show = ref(false);
|
||||
let frameRef = ref(null);
|
||||
let createPageAddr = '';
|
||||
http.get(`/api/${APP}/objs/ManageResourceTypes/` + params.id).then(res => {
|
||||
loading.value = false;
|
||||
data.value = res;
|
||||
createPageAddr = res['createPageAddr'];
|
||||
src.value = createPageAddr + '?nervsid=' + Cookie.get('nervsid');
|
||||
// src.value = 'http://100.68.2.97:4200/nervui-mock-ra/mr/instance/add?nervsid='+Cookie.get('nervsid');
|
||||
// src.value = 'http://localhost:4200/nervui-mock-ra/mr/instance/add?nervsid='+Cookie.get('nervsid');
|
||||
})
|
||||
function onAdd() {
|
||||
if (createPageAddr) {
|
||||
let flag = !/^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$/.test(createPageAddr);
|
||||
if (flag) {
|
||||
NsMessage.error('不合法地址');
|
||||
} else {
|
||||
show.value = true;
|
||||
}
|
||||
} else {
|
||||
NsMessage.error('无对应资源页面');
|
||||
return;
|
||||
}
|
||||
// loading.value = true;
|
||||
nextTick(() => {
|
||||
load();
|
||||
});
|
||||
}
|
||||
|
||||
function handleMsg(e) {
|
||||
const frame = frameRef.value;
|
||||
const data = e?.data;
|
||||
if(data) {
|
||||
if(data.type === 'back') {
|
||||
window.removeEventListener('message', handleMsg);
|
||||
show.value = false;
|
||||
// router.push({name:'resourceDetail', path: '/rm/resourceManage/list/'+params.id + '/detail'});
|
||||
}
|
||||
if(data.type === 'successBack') {
|
||||
window.removeEventListener('message', handleMsg);
|
||||
router.push({name:'resource', path: '/rm/resourceManage/list'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function load() {
|
||||
frameRef.value = document.getElementById('frame');
|
||||
const frame = frameRef.value;
|
||||
const msg = {
|
||||
type: 'style',
|
||||
showLayout: false,
|
||||
data: {
|
||||
typeCode: params.id
|
||||
}
|
||||
}
|
||||
if(frame) {
|
||||
loading.value = false;
|
||||
frame.onload = function (e) {
|
||||
frame.contentWindow?.postMessage(msg, src.value );
|
||||
}
|
||||
window.addEventListener('message', handleMsg);
|
||||
}
|
||||
}
|
||||
onUnmounted(()=>{
|
||||
window.removeEventListener('message', handleMsg);
|
||||
})
|
||||
return {
|
||||
data,
|
||||
navigateBack,
|
||||
code,
|
||||
loading,
|
||||
onAdd,
|
||||
show,
|
||||
src
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-page-header {
|
||||
padding: 0 24px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
:deep(.ant-page-header-heading-extra) {
|
||||
margin-right: auto !important;
|
||||
margin-left: 0;
|
||||
}
|
||||
.content-wrapper {
|
||||
margin: 0 24px
|
||||
}
|
||||
#frame {
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
660
nervui-resource-manage/view/resource-type-list.vue
Normal file
660
nervui-resource-manage/view/resource-type-list.vue
Normal file
@@ -0,0 +1,660 @@
|
||||
<template>
|
||||
<div class="origin-wrapper">
|
||||
<page-title :title="getTitle"/>
|
||||
<div class="ns-table-wrapper">
|
||||
<div class="ns-table" :class="{ 'ns-table-no-search': !(formConfig?.schemas.length > 0) }">
|
||||
<a-spin :spinning="tableState.loading">
|
||||
<a-row type="flex" class="ns-table-main">
|
||||
<a-col flex="180px">
|
||||
<div class="ns-table-header">
|
||||
<a-button @click="navigateBack" type="secondary">返回</a-button>
|
||||
</div>
|
||||
<Skeleton :loading="loadingTree" active>
|
||||
<div class="type-tree-wrapper">
|
||||
<div class="tree-top-header">
|
||||
<div class="tree-node-content-wrapper" :class="{'selected': allSelected}" @click="onAllSelect">全部类别</div>
|
||||
</div>
|
||||
<a-tree v-model:expandedKeys="expandedKeys" :tree-data="treeData" v-model:selectedKeys="selectedKeys"
|
||||
@expand="onExpand" @select="onTreeSelect">
|
||||
</a-tree>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<!-- {{ formConfig }} -->
|
||||
<div class="ns-table-search">
|
||||
<ns-form
|
||||
ref="formElRef"
|
||||
class="ns-table-form"
|
||||
:showAction="true"
|
||||
v-bind="formConfig"
|
||||
formLayout="flex"
|
||||
:expand="expand"
|
||||
:showExpand="showExpand"
|
||||
:model="formModel"
|
||||
@finish="formFinish"/>
|
||||
</div>
|
||||
<Skeleton :loading="loadingTable" active>
|
||||
<div class="section0">
|
||||
<div class="section-content" v-if="tableData && tableData.length>0">
|
||||
<template v-for="(item) in tableData">
|
||||
<div class="item card-item" @click="onCLickCard(item)">
|
||||
<header class="item-header">
|
||||
<div class="info-card-header">
|
||||
<div class="card-title">{{ item['alias'] }}</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="info-item-block">
|
||||
<div class="card-icon"><img :src="imgSrc + item['iconID']" alt=""></div>
|
||||
<div class="card-description-wrapper">
|
||||
<div class="card-description"><span>{{ item['desc'] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="item-footer">
|
||||
<div class="card-item-footer">
|
||||
<div class="card-footer-left">
|
||||
{{ item['typeClassAlias'] }}
|
||||
</div>
|
||||
<div class="card-footer-right">
|
||||
<div class="card-status">
|
||||
<span v-if="item['shelfState']"> <i class="iconfont iconGreen font-12"></i> 已上架
|
||||
</span>
|
||||
<span v-if="!item['shelfState']"><i class="iconfont iconRed font-12"></i> 待上架
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="section-content" v-if="tableData && tableData.length === 0 || !tableData">
|
||||
<div class="ns-table-content"> <a-empty /> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination" v-if="tableData && tableData.length === 0 || tableData!=null">
|
||||
<a-pagination
|
||||
v-bind="getPagination"
|
||||
show-quick-jumper
|
||||
show-size-changer
|
||||
@change=" (current,pageSize) => onChange({current,pageSize})"
|
||||
size="small"
|
||||
:show-total="(total, range) => `显示 ${range[0]} 到 ${range[1]} 条数据,共计${total}条`"
|
||||
/>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-spin>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent, reactive, ref, unref, watch} from "vue";
|
||||
import {Skeleton, Pagination} from "ant-design-vue";
|
||||
import {debounce, cloneDeep, isArray, get} from "lodash-es";
|
||||
import {tableProps} from "../../lib/component/table/props";
|
||||
import {http, PropTypes, stringUtil} from "../../lib/util";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {PlusCircleOutlined, EditOutlined, DeleteOutlined, ExclamationCircleOutlined} from "@ant-design/icons-vue";
|
||||
import {NsMessage, NsModal} from "../../lib/component";
|
||||
import {RequestParams} from "/nerv-lib/component/table/table";
|
||||
import {AxiosRequestConfig} from "axios";
|
||||
import {useApi, useParams} from "/nerv-lib/use";
|
||||
import {useTableRefresh} from "/nerv-lib/component/table/use-table-refresh";
|
||||
import {useTableSession} from "/nerv-lib/component/table/use-table-session";
|
||||
import {APP} from "/@/router/index.ts";
|
||||
import {useNavigate} from "../../lib/use/use-navigate";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TreeAndTableList',
|
||||
components: {
|
||||
Skeleton,
|
||||
PlusCircleOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
'a-pagination': Pagination,
|
||||
},
|
||||
props: {
|
||||
...tableProps,
|
||||
title: PropTypes.string,
|
||||
tableTitle: PropTypes.func,
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
resultField: PropTypes.string
|
||||
},
|
||||
setup: (props, {attrs, emit}) => {
|
||||
const nsTableRef = ref();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const formModel = reactive({});
|
||||
// 搜索form参数
|
||||
const formParamsRef = ref({});
|
||||
// table参数
|
||||
const tableState = reactive({
|
||||
selectedRowKeys: [],
|
||||
selectedRows: [],
|
||||
loading: false,
|
||||
loadError: false,
|
||||
loadErrorMessage: '',
|
||||
});
|
||||
const expandedKeys = ref()
|
||||
const { delayFetch } = useTableRefresh({ props, reload });
|
||||
const defaultPageRef = ref(0);
|
||||
const { getParams } = useParams();
|
||||
const treeParamsRef = ref({});
|
||||
const dataRef = ref([]);
|
||||
const orderRef = ref({});
|
||||
const { setTableSession } = useTableSession(formModel, formParamsRef, defaultPageRef);
|
||||
const tableData = ref<Recordable[]>([]);
|
||||
let allSelected = ref(true);
|
||||
const selectedKeys = ref([]);
|
||||
let loadingTable = ref<boolean>(false);
|
||||
let loadingTree = ref<boolean>(false);
|
||||
const imgSrc = ref(`/api/${APP}/objs/Images/`)
|
||||
const { navigateBack } = useNavigate();
|
||||
// 缓存
|
||||
function initTableSession() {
|
||||
const { fullPath } = route;
|
||||
const tableSession = JSON.parse(sessionStorage[fullPath] || '{}');
|
||||
if (!props.enableTableSession) return;
|
||||
if (tableSession['formModel']) {
|
||||
Object.assign(formModel, tableSession['formModel']);
|
||||
let code = formModel['typeClassCode'];
|
||||
if(code){
|
||||
treeParamsRef.value['typeClassCode'] = code;
|
||||
selectedKeys.value =[ code];
|
||||
allSelected.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
initTableSession();
|
||||
|
||||
// 页面标题
|
||||
const getTitle = computed(() => {
|
||||
const {title} = props;
|
||||
if (title) return title;
|
||||
const {
|
||||
params: {pageTitle},
|
||||
} = route;
|
||||
if (pageTitle) return pageTitle;
|
||||
});
|
||||
// 搜索参数
|
||||
const formFinish = debounce((data) => {
|
||||
formParamsRef.value = data;
|
||||
fetch({
|
||||
page: 1,
|
||||
});
|
||||
}, 300);
|
||||
const tableChangeEvent = (pagination: Props, filters: [], sorter?: any) => {
|
||||
// console.log('params', pagination, filters, sorter);
|
||||
if (sorter?.field) {
|
||||
if (sorter.order) {
|
||||
orderRef.value = {
|
||||
[props.paramsOrderField]: stringUtil.toLine(
|
||||
`${sorter.field} ${sorter.order.replace('end', '')}`,
|
||||
),
|
||||
};
|
||||
} else {
|
||||
orderRef.value = { [props.paramsOrderField]: '' }; //覆盖默认params
|
||||
}
|
||||
fetch({
|
||||
page: pagination?.current || getPagination.value?.current || 1,
|
||||
pageSize: pagination?.pageSize,
|
||||
});
|
||||
} else if (pagination?.current) {
|
||||
fetch({
|
||||
page: pagination?.current,
|
||||
pageSize: pagination.pageSize,
|
||||
});
|
||||
}
|
||||
};
|
||||
// pagination
|
||||
const getPagination: Recordable | Boolean = computed(() => {
|
||||
const { pagination } = props;
|
||||
if (pagination) {
|
||||
const current = get(dataRef.value, props.pageField);
|
||||
return {
|
||||
showQuickJumper: true,
|
||||
showLessItems: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total: number, range: Array<number>) =>
|
||||
`显示第${range[0]}到${range[1]}条记录 ,共 ${total} 条记录`,
|
||||
...(pagination as Props),
|
||||
total: get(dataRef.value, props.totalField),
|
||||
current: (current >= 0 ? current : 0) + props.pageFieldOffset, // 后端0 开始
|
||||
pageSize: get(dataRef.value, props.sizeField),
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const getTableBindValues = computed(() => {
|
||||
const { params, dynamicParams } = props;
|
||||
return {
|
||||
...attrs,
|
||||
...props,
|
||||
params: dynamicParams
|
||||
? getParams({ ...route.params, ...route.query }, dynamicParams, params)
|
||||
: params || {},
|
||||
pagination: getPagination.value,
|
||||
onChange: tableChangeEvent,
|
||||
};
|
||||
});
|
||||
|
||||
watch(
|
||||
() => getTableBindValues.value.api,
|
||||
() => {
|
||||
fetch();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
function fetch(requestParams: RequestParams = {}, clearDelay = true) {
|
||||
clearDelay && delayFetch();
|
||||
const { api, pagination } = props;
|
||||
const { page, pageSize } = requestParams;
|
||||
if (api) {
|
||||
let pageParams: Recordable = {};
|
||||
|
||||
if (pagination !== false) {
|
||||
pageParams = {
|
||||
[props.paramsPageField]: page ? page - props.pageFieldOffset : defaultPageRef.value, // 后端0 开始
|
||||
[props.paramsPageSizeField]:
|
||||
pageSize || getPagination.value?.pageSize || props.defaultPageSize,
|
||||
};
|
||||
} else {
|
||||
pageParams = {
|
||||
[props.paramsPageField]: defaultPageRef.value, // 后端0 开始
|
||||
[props.paramsPageSizeField]:
|
||||
pageSize || getPagination.value?.pageSize || props.defaultPageSize,
|
||||
};
|
||||
}
|
||||
const httpParams = {
|
||||
...getTableBindValues.value.params,
|
||||
...pageParams,
|
||||
...formParamsRef.value,
|
||||
...treeParamsRef.value,
|
||||
...orderRef.value,
|
||||
|
||||
};
|
||||
|
||||
setTableSession(pageParams[props.paramsPageField]);
|
||||
|
||||
clearDelay && setLoading(true);
|
||||
|
||||
const requestConfig: AxiosRequestConfig = { method: 'get' };
|
||||
const { httpRequest } = useApi();
|
||||
httpRequest({
|
||||
api,
|
||||
params: httpParams,
|
||||
pathParams: { ...route.params, ...route.query },
|
||||
requestConfig,
|
||||
}).then((res: any) => {
|
||||
|
||||
tableState.loadError = false;
|
||||
tableState.loadErrorMessage = '';
|
||||
dataRef.value = res;
|
||||
tableData.value = get(unref(dataRef), props.listField);
|
||||
emit('update:dataSource', tableData.value);
|
||||
clearDelay && setLoading(false);
|
||||
loadingTable.value = false;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
const { response, code, message } = error || {};
|
||||
let errMessage = response?.data?.msg;
|
||||
const err: string = error?.toString?.() ?? '';
|
||||
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
|
||||
errMessage = '接口请求超时,请刷新页面重试!';
|
||||
}
|
||||
if (err?.includes('Network Error')) {
|
||||
errMessage = '网络异常,请检查您的网络连接是否正常!';
|
||||
}
|
||||
tableState.loadError = true;
|
||||
tableState.loadErrorMessage = errMessage;
|
||||
clearDelay && setLoading(false);
|
||||
loadingTable.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setLoading(loading: boolean) {
|
||||
tableState.loading = loading;
|
||||
}
|
||||
|
||||
|
||||
// 右侧树形结构
|
||||
const treeData = ref( []);
|
||||
// 树形结构添加
|
||||
const typeData = ref({});
|
||||
// 搜索
|
||||
const formElRef = ref(null);
|
||||
const getFormConfig = computed(() => {
|
||||
const formConfig = cloneDeep(props.formConfig);
|
||||
if (formConfig) {
|
||||
formConfig.showAction = false;
|
||||
if (!isArray(formConfig.schemas)) {
|
||||
formConfig.schemas = [];
|
||||
}
|
||||
if (formConfig.keySearch !== false) {
|
||||
formConfig.schemas.push({
|
||||
field: 'alias',
|
||||
label: '关键字',
|
||||
component: 'NsInputSearch',
|
||||
rules: [
|
||||
{
|
||||
message: '请输入关键字',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
componentProps: {
|
||||
maxlength: 50,
|
||||
onSearch: () => {
|
||||
unref(formElRef)?.triggerSubmit()
|
||||
// unref(nsTableRef)?.formElRef?.triggerSubmit();
|
||||
},
|
||||
onKeydown: (event) => {
|
||||
//fix 单个input回车会提交表单 造成重复提交
|
||||
if (event.key === 'Enter' || event.code === 'Enter') {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
placeholder: '请输入关键字',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return formConfig;
|
||||
});
|
||||
// 卡片列表分页信息
|
||||
|
||||
function onTreeSelect(selectedKeys: never[],
|
||||
e: {
|
||||
selected: boolean;
|
||||
selectedNodes: { props: { dataRef: any } }[];
|
||||
node: any;
|
||||
event: any;
|
||||
})
|
||||
{
|
||||
allSelected.value = false;
|
||||
const dataRef = e.selectedNodes[0];
|
||||
treeParamsRef.value = getParams(dataRef, {typeClassCode: 'code'});
|
||||
unref(formElRef)?.triggerSubmit();
|
||||
let key = 'typeClassCode';
|
||||
if (e.selected) {
|
||||
formModel[key] = treeParamsRef.value[key];
|
||||
} else if(Object.keys(formModel).includes(key)) {
|
||||
delete formModel[key];
|
||||
}
|
||||
}
|
||||
function reload(clearDelay = true) {
|
||||
const pagination = unref(getPagination);
|
||||
fetch(
|
||||
{
|
||||
page: pagination === false ? 1 : pagination.current,
|
||||
},
|
||||
clearDelay,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
tableState,
|
||||
getTitle,
|
||||
treeData,
|
||||
expandedKeys,
|
||||
formModel,
|
||||
formFinish,
|
||||
tableData,
|
||||
typeData,
|
||||
formConfig: getFormConfig.value,
|
||||
nsTableRef,
|
||||
formElRef,
|
||||
getParams,
|
||||
onTreeSelect,
|
||||
onChange: tableChangeEvent,
|
||||
allSelected,
|
||||
selectedKeys,
|
||||
treeParamsRef,
|
||||
loadingTable,
|
||||
loadingTree,
|
||||
imgSrc,
|
||||
getPagination,
|
||||
navigateBack
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getDataTree()
|
||||
},
|
||||
methods: {
|
||||
onCLickCard(item) {
|
||||
this.$router.push(item['code'] + '/detail' );
|
||||
},
|
||||
// 转换树结构数据
|
||||
transferTree(data) {
|
||||
if( isArray(data) && data.length > 0) {
|
||||
data.forEach(it=>{
|
||||
it['title'] = it['alias'];
|
||||
it['key']=it['code'];
|
||||
if(it['children'] ) {
|
||||
this.transferTree(it['children']);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
getDataTree() {
|
||||
http.get(`/api/${APP}/objs/ManageResourceTypeClasses`).then(res => {
|
||||
this.transferTree(res)
|
||||
this.treeData = res;
|
||||
|
||||
this.loadingTree = false;
|
||||
}).catch( err => {
|
||||
this.loadingTree = false;
|
||||
})
|
||||
},
|
||||
onAllSelect() {
|
||||
this.allSelected = !this.allSelected;
|
||||
this.selectedKeys = [];
|
||||
this.treeParamsRef = null;
|
||||
if(Object.keys(this.formModel).includes('typeClassCode')) {
|
||||
delete this.formModel['typeClassCode'];
|
||||
}
|
||||
this.formElRef.triggerSubmit();
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ns-table-main {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
:deep(.ns-table-header) {
|
||||
min-width: unset;
|
||||
margin-bottom: 16px;
|
||||
.ant-btn {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ns-table-wrapper {
|
||||
margin: 16px 24px 0 24px;
|
||||
|
||||
}
|
||||
|
||||
:deep(.ns-table-search .ns-form-item label) {
|
||||
display: none;
|
||||
}
|
||||
.type-tree-wrapper {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
||||
height: 800px;
|
||||
overflow-y: auto;
|
||||
|
||||
.tree-top-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
margin-bottom: 4px;
|
||||
.tree-node-content-wrapper {
|
||||
padding: 0 4px;
|
||||
line-height: 30px;
|
||||
&.selected {
|
||||
background-color: rgba(0, 172, 255, 0.1);
|
||||
color: #00acff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tree ) {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
|
||||
.ant-tree-node-content-wrapper.ant-tree-node-selected {
|
||||
background-color: rgba(0, 172, 255, 0.1);
|
||||
color: #00acff
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片列表
|
||||
.section0 {
|
||||
height: 100%;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
.iconRed {
|
||||
color: #EB5757;
|
||||
}
|
||||
.iconGreen {
|
||||
color: #0DCB70;
|
||||
}
|
||||
.font-12 {
|
||||
font-size: 12px;
|
||||
}
|
||||
margin: 0 6px 0 24px;
|
||||
|
||||
.section-content {
|
||||
.item {
|
||||
width: 24%;
|
||||
//border: 1px solid #dedede;
|
||||
margin-right: 8px;
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
//transition: box-shadow 400ms;
|
||||
//box-shadow: none;s
|
||||
|
||||
transition-property: box-shadow, border;
|
||||
transition-duration: 400ms, 400ms;
|
||||
|
||||
|
||||
border: 1px solid #F1F3F5;
|
||||
box-shadow: 0 0 16px rgba(0, 0, 0, 0.09);
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover {
|
||||
//box-shadow: 0 2px 12px rgba(0, 172, 255, .7);
|
||||
border: 1px solid #6DCFFF;
|
||||
box-shadow: 0 0 16px rgba(0, 172, 255, 0.3);
|
||||
}
|
||||
|
||||
.item-header {
|
||||
background-color: inherit;
|
||||
display: flex;
|
||||
height: auto;
|
||||
padding: 10px 14px 0 14px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
//border-bottom: .05rem solid #dedede;
|
||||
.info-card-header {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
max-height: 32px;
|
||||
overflow: hidden;
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
text-overflow: ellipsis
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-item-block {
|
||||
height: 90px;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
//border-bottom: .05rem solid #dedede;
|
||||
padding: 4px 14px;
|
||||
display: flex;
|
||||
|
||||
.card-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
//width: 54px;
|
||||
max-height: 88px;
|
||||
img {
|
||||
width: 64px;
|
||||
max-height: 88px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-description-wrapper {
|
||||
display: grid;
|
||||
height: 88px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
.card-description {
|
||||
max-height: 88px;
|
||||
padding-left: 6px;
|
||||
|
||||
span {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-footer {
|
||||
padding: 10px 14px;
|
||||
background-color: #fafbfe;
|
||||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
|
||||
.card-item-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
|
||||
.card-footer-left {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 6px;
|
||||
}
|
||||
// 无数据
|
||||
.ant-empty-description {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
</style>
|
||||
380
nervui-resource-manage/view/tree-and-table-list.vue
Normal file
380
nervui-resource-manage/view/tree-and-table-list.vue
Normal file
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<div claas="origin-wrapper" v-if="!show">
|
||||
<page-title title="资源管理"/>
|
||||
<div class="ns-table-wrapper">
|
||||
<div class="ns-table">
|
||||
<a-row type="flex" class="ns-table-main">
|
||||
<a-col flex="180px">
|
||||
<ns-table-header
|
||||
:headerActions="headerActions"
|
||||
:tableTitle="tableTitle">
|
||||
<template #header="data">
|
||||
<slot name="header" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</ns-table-header>
|
||||
<div class="type-tree-wrapper">
|
||||
<div class="tree-top-header">
|
||||
<div class="tree-node-content-wrapper" :class="{'selected': allSelected}" @click="onAllSelect">全部资源</div>
|
||||
</div>
|
||||
<a-tree v-model:expandedKeys="expandedKeys" :tree-data="treeData" v-model:selectedKeys="selectedKeys"
|
||||
@expand="onExpand" @select="onTreeSelect">
|
||||
</a-tree>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<!-- {{ formConfig }} -->
|
||||
<ns-view-list-table v-bind="getTableBindValues" :model="data" rowKey="code" ref="tableRef">
|
||||
<template #bodyCell="{ record, column }">
|
||||
<template v-if="column.dataIndex === 'tableAction'">
|
||||
<ns-table-action
|
||||
:data="record"
|
||||
:searchData="{}"
|
||||
:columnActions="record && getColumnActions(record)" />
|
||||
</template>
|
||||
</template>
|
||||
</ns-view-list-table>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="show" class="frame-wrapper" style="height: 100%">
|
||||
<iframe :src="src" id="frame" height="100%" width="100%" ></iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent, nextTick, ref, unref} from "vue";
|
||||
import {Skeleton} from "ant-design-vue";
|
||||
import {tableProps} from "../../lib/component/table/props";
|
||||
import {http} from "../../lib/util";
|
||||
import {PlusCircleOutlined, EditOutlined, DeleteOutlined} from "@ant-design/icons-vue";
|
||||
import {useParams} from "../../lib/use";
|
||||
import {RequestParams} from "../../lib/component/table/table";
|
||||
import { isArray} from "lodash-es";
|
||||
import {NsModal} from "../../lib/component";
|
||||
import {APP} from "/@/router/index.ts";
|
||||
import NsCustomListTable from './custom-list-table.vue'
|
||||
import NsTableAction from '/nerv-lib/component/table/table-action.vue';
|
||||
import { useRouter } from "vue-router";
|
||||
import Cookie from "js-cookie";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TreeAndTableList',
|
||||
components: {
|
||||
Skeleton,
|
||||
PlusCircleOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
NsCustomListTable,
|
||||
'ns-table-action': NsTableAction
|
||||
},
|
||||
props: {
|
||||
...tableProps,
|
||||
title: String,
|
||||
tableTitle: Function,
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
resultField: String
|
||||
},
|
||||
setup: (props, {attrs, emit}) => {
|
||||
let allSelected = ref(true);
|
||||
const expandedKeys = ref(['1', '2'])
|
||||
const { getParams } = useParams();
|
||||
const treeParamsRef = ref({});
|
||||
const selectedKeys = ref([]);
|
||||
const tableRef = ref();
|
||||
// let loadingTable = ref<boolean>(false);
|
||||
let loadingTree = ref<boolean>(false);
|
||||
const router = useRouter();
|
||||
let show = ref(false);
|
||||
let src = ref('');
|
||||
let frameRef = ref(null);
|
||||
|
||||
const headerActions = [
|
||||
{
|
||||
label: '创建资源',
|
||||
name: 'resourceAdd',
|
||||
type: 'primary',
|
||||
route: 'add',
|
||||
}
|
||||
]
|
||||
|
||||
const handleAction = (action, record, reload)=>{
|
||||
const mode = action?.mode;
|
||||
if(mode === 'api') {
|
||||
NsModal.confirm({
|
||||
title: '警告',
|
||||
content: '确定执行该操作吗',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: function () {
|
||||
http.post(`/api/${APP}/objs/Resources/Operator`, {
|
||||
RM_UUID: record.RM_UUID,
|
||||
typeCode: record.typeCode,
|
||||
actionApi: action['actionApi']
|
||||
}).then(res=>{
|
||||
reload();
|
||||
})
|
||||
},
|
||||
});
|
||||
}
|
||||
if(mode === 'page') {
|
||||
// action['pageAddr'] = 'http://100.68.2.97:4200/nervui-mock-ra/mr/instance/manage';
|
||||
const template = action['pageAddr'];
|
||||
if(template.indexOf('$') !== -1) { // 模版参数值替换
|
||||
action['pageAddr'] = template.replace(/\$(\w+)\b/g, function (match, key) {
|
||||
return record[key.trim()] ? record[key.trim()] : match;
|
||||
});
|
||||
}
|
||||
src.value = `${action['pageAddr']}?nervsid=${Cookie.get('nervsid')}`;
|
||||
show.value = true;
|
||||
nextTick(() => {
|
||||
load(record, action['name']);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleMsg(e) {
|
||||
const frame = frameRef.value;
|
||||
const data = e?.data;
|
||||
if(data) {
|
||||
if(data.type === 'back') {
|
||||
window.removeEventListener('message', handleMsg);
|
||||
show.value = false;
|
||||
}
|
||||
if(data.type === 'successBack') {
|
||||
window.removeEventListener('message', handleMsg);
|
||||
router.push({name:'resource', path: '/rm/resourceManage/list'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function load(record, actionName) {
|
||||
frameRef.value = document.getElementById('frame');
|
||||
console.log(frameRef.value)
|
||||
const frame = frameRef.value;
|
||||
let msg = {
|
||||
type: 'style',
|
||||
mode: 'page',
|
||||
routerPath: actionName,
|
||||
showLayout: false,
|
||||
data: { pageParams: {}}
|
||||
}
|
||||
if(['manage', 'edit'].includes(actionName)) {
|
||||
msg['data'] = {
|
||||
pageParams: {
|
||||
ID: record['RM_UUID'],
|
||||
pageTitle: record['alias']
|
||||
}
|
||||
}
|
||||
}
|
||||
if(frame) {
|
||||
frame.onload = function (e) {
|
||||
frame.contentWindow?.postMessage(msg, src.value );
|
||||
}
|
||||
window.addEventListener('message', handleMsg);
|
||||
}
|
||||
}
|
||||
const getColumnActions =(record)=>{
|
||||
if(isArray(record?.actions)) {
|
||||
record?.actions.forEach(action => {
|
||||
action['openPermission'] = true;
|
||||
action['handle'] = (record, name, {reload, action}) =>{
|
||||
handleAction(action, record, reload );
|
||||
}
|
||||
})
|
||||
} else {
|
||||
record['actions'] = [];
|
||||
}
|
||||
return record;
|
||||
}
|
||||
const tableConfig = ref(
|
||||
{
|
||||
// title: '资源管理',
|
||||
showTitle: false,
|
||||
api: `/api/${APP}/objs/Resources`,
|
||||
formConfig: {},
|
||||
rowSelection: null,
|
||||
resultField: 'data',
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'alias',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'typeAlias',
|
||||
},
|
||||
{
|
||||
title: '项目',
|
||||
dataIndex: 'projectName',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
transform: {
|
||||
pipe: 'state',
|
||||
statusField: (record: object) => {
|
||||
return record.status;
|
||||
},
|
||||
// textField: (record: object) => {
|
||||
// return record.statusInCN;
|
||||
// },
|
||||
messageField: 'reason',
|
||||
},
|
||||
},
|
||||
],
|
||||
columnActions: {
|
||||
title: '操作',
|
||||
width: 100,
|
||||
actions: [],
|
||||
},
|
||||
rowKey: 'id'
|
||||
}
|
||||
)
|
||||
// 右侧树形结构
|
||||
const treeData = ref([])
|
||||
const data = []
|
||||
// 树形结构添加
|
||||
let typeSchema = ref([]);
|
||||
const typeData = ref({});
|
||||
|
||||
function onTreeSelect(selectedKeys: never[],
|
||||
e: {
|
||||
selected: boolean;
|
||||
selectedNodes: { props: { dataRef: any } }[];
|
||||
node: any;
|
||||
event: any;
|
||||
})
|
||||
{
|
||||
allSelected.value = false;
|
||||
const dataRef = e.selectedNodes[0];
|
||||
treeParamsRef.value = getParams(dataRef, {typeClassCode: 'code'});
|
||||
console.log(treeParamsRef.value)
|
||||
unref(tableRef).nsTableRef.treeParamsRef = {
|
||||
...treeParamsRef.value
|
||||
}
|
||||
unref(tableRef).nsTableRef.formElRef.triggerSubmit();
|
||||
}
|
||||
|
||||
const getTableBindValues = computed(() => {
|
||||
const { params, dynamicParams } = props;
|
||||
return {
|
||||
...props,
|
||||
...tableConfig.value
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
treeData,
|
||||
expandedKeys,
|
||||
typeSchema,
|
||||
typeData,
|
||||
onTreeSelect,
|
||||
getTableBindValues,
|
||||
headerActions,
|
||||
data,
|
||||
allSelected,
|
||||
selectedKeys,
|
||||
tableRef,
|
||||
loadingTree,
|
||||
getColumnActions,
|
||||
show,
|
||||
src
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getDataTree();
|
||||
},
|
||||
methods: {
|
||||
// 转换树结构数据
|
||||
transferTree(data) {
|
||||
if( isArray(data) && data.length > 0) {
|
||||
data.forEach(it=>{
|
||||
it['title'] = it['alias'];
|
||||
it['key']=it['code'];
|
||||
if(it['children'] ) {
|
||||
this.transferTree(it['children']);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
getDataTree() {
|
||||
http.get(`/api/${APP}/objs/ManageResourceTypeClasses`).then(res => {
|
||||
this.transferTree(res)
|
||||
this.treeData = res;
|
||||
this.loadingTree = false;
|
||||
}).catch( ()=>{
|
||||
this.loadingTree = false;
|
||||
})
|
||||
},
|
||||
onAllSelect() {
|
||||
this.allSelected = !this.allSelected;
|
||||
this.selectedKeys = [];
|
||||
this.tableRef.nsTableRef.treeParamsRef = {};
|
||||
this.tableRef.nsTableRef.formElRef.triggerSubmit();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.ns-table-main {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
:deep(.ns-table-header) {
|
||||
min-width: unset;
|
||||
.ant-btn {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ns-table-wrapper {
|
||||
margin: 0 24px;
|
||||
}
|
||||
|
||||
.type-tree-wrapper {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
||||
height: 800px;
|
||||
overflow-y: auto;
|
||||
|
||||
.tree-top-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
margin-bottom: 4px;
|
||||
.tree-node-content-wrapper {
|
||||
padding: 0 4px;
|
||||
&.selected {
|
||||
background-color: rgba(0, 172, 255, 0.1);
|
||||
color: #00acff;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tree ) {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
|
||||
.ant-tree-node-content-wrapper.ant-tree-node-selected {
|
||||
background-color: rgba(0, 172, 255, 0.1);
|
||||
color: #00acff
|
||||
}
|
||||
}
|
||||
|
||||
// iframe 样式
|
||||
#frame {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
10
nervui-resource-manage/vite.config.ts
Normal file
10
nervui-resource-manage/vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @format */
|
||||
|
||||
import configFun from '../build/vite-default.config';
|
||||
|
||||
const dirname = __dirname;
|
||||
const proxy = {
|
||||
'/api': { target: 'http://portal.cloud.sh.dingcloud.com:30080/', changeOrigin: true },
|
||||
'/assets': { target: 'http://portal.cloud.sh.dingcloud.com:30080/', changeOrigin: true },
|
||||
};
|
||||
export default configFun({ dirname, serviceMode: 'paas', baseDir: '../', proxy });
|
||||
3
nervui-resource-manage/vue.config.ts
Normal file
3
nervui-resource-manage/vue.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
runtimeCompiler: true,
|
||||
};
|
||||
Reference in New Issue
Block a user