push
This commit is contained in:
8
nervui-resource-repository/.env
Normal file
8
nervui-resource-repository/.env
Normal file
@@ -0,0 +1,8 @@
|
||||
# port
|
||||
VITE_PORT = 4200
|
||||
|
||||
# spa-title
|
||||
VITE_GLOB_APP_TITLE = nervui-resource-repository
|
||||
|
||||
# spa shortname
|
||||
VITE_GLOB_APP_SHORT_NAME = nervui-resource-repository
|
||||
22
nervui-resource-repository/.env.development
Normal file
22
nervui-resource-repository/.env.development
Normal file
@@ -0,0 +1,22 @@
|
||||
# Whether to open mock
|
||||
VITE_USE_MOCK = true
|
||||
|
||||
# public path
|
||||
VITE_PUBLIC_PATH = /nervui-resource-repository/
|
||||
|
||||
# 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-repository/.env.production
Normal file
35
nervui-resource-repository/.env.production
Normal file
@@ -0,0 +1,35 @@
|
||||
# Whether to open mock
|
||||
VITE_USE_MOCK = true
|
||||
|
||||
# public path
|
||||
VITE_PUBLIC_PATH = /nervui-resource-repository/
|
||||
|
||||
# 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-repository/.version
Normal file
1
nervui-resource-repository/.version
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
74
nervui-resource-repository/build.sh
Normal file
74
nervui-resource-repository/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 rr-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-repository/index.html
Normal file
13
nervui-resource-repository/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>
|
||||
36
nervui-resource-repository/module.json
Normal file
36
nervui-resource-repository/module.json
Normal file
@@ -0,0 +1,36 @@
|
||||
[
|
||||
{
|
||||
"catalog": "计算",
|
||||
"icon": "",
|
||||
"name": "resourceRepo",
|
||||
"label": "资源仓库",
|
||||
"menuSort": [
|
||||
"resourceType"
|
||||
],
|
||||
"menus": [
|
||||
{
|
||||
"name": "resourceType",
|
||||
"url": "/rr/resourceRepository",
|
||||
"label": "资源仓库",
|
||||
"operation": {
|
||||
"resource": "resourceType",
|
||||
"method": "list"
|
||||
},
|
||||
"submenus": [
|
||||
{
|
||||
"name": "resourceTypeClass",
|
||||
"url": "/rr/resourceRepository",
|
||||
"label": "资源类型分类",
|
||||
"operation": {
|
||||
"resource": "resourceTypeClass",
|
||||
"method": "list"
|
||||
},
|
||||
"submenus": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
11
nervui-resource-repository/offline-release.json
Normal file
11
nervui-resource-repository/offline-release.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"release": [
|
||||
{
|
||||
"src": "nervui-resource-repository/release",
|
||||
"dest": "/upload/pkg",
|
||||
"include": [
|
||||
"nervui-resource-repository-(\\d+).(\\d+).(\\d+)(|-\\w+).tar.gz"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
31
nervui-resource-repository/offline.sh
Normal file
31
nervui-resource-repository/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-repository-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-repository \
|
||||
-e version="$version" \
|
||||
--pull=always \
|
||||
registry.nervhub.nervstack.io/nerv3/deploy:latest
|
||||
|
||||
rm -rf $releaseDir/output
|
||||
5
nervui-resource-repository/offline.tpl.yaml
Normal file
5
nervui-resource-repository/offline.tpl.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
images:
|
||||
helm:
|
||||
- name: nervui-resource-repository
|
||||
version: "${VERSION}"
|
||||
repository: "https://registry.nervhub.nervstack.io/chartrepo/nerv3-ui"
|
||||
1
nervui-resource-repository/offline.version
Normal file
1
nervui-resource-repository/offline.version
Normal file
@@ -0,0 +1 @@
|
||||
alpha1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
BIN
nervui-resource-repository/public/favicon.ico
Normal file
BIN
nervui-resource-repository/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
12
nervui-resource-repository/release.yaml
Normal file
12
nervui-resource-repository/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-repository
|
||||
version: 1.0.0
|
||||
components:
|
||||
- type: nervui-resource-repository
|
||||
resources:
|
||||
- {type: template, relativePath: /nervui-resource-repository/deploy.json}
|
||||
|
||||
27
nervui-resource-repository/src/App.vue
Normal file
27
nervui-resource-repository/src/App.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<ns-application/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" type="module">
|
||||
import {defineComponent} from 'vue';
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||||
import {useRoute} from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
return {
|
||||
locale: zhCN,
|
||||
route
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
50
nervui-resource-repository/src/api/index.ts
Normal file
50
nervui-resource-repository/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-repository/src/api/user.ts
Normal file
15
nervui-resource-repository/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;
|
||||
}
|
||||
106
nervui-resource-repository/src/component/markdown.vue
Normal file
106
nervui-resource-repository/src/component/markdown.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<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) => {
|
||||
text.value = val;
|
||||
};
|
||||
|
||||
return {
|
||||
getBindValue,
|
||||
text,
|
||||
change,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
text: {
|
||||
handler(val) {
|
||||
this.$emit('change', val);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
value: {
|
||||
handler(val) {
|
||||
this.text = 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>
|
||||
531
nervui-resource-repository/src/component/upload-custom.vue
Normal file
531
nervui-resource-repository/src/component/upload-custom.vue
Normal file
@@ -0,0 +1,531 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container clearfix">
|
||||
<!-- 图片显示框 -->
|
||||
<template v-if="currentImg.length">
|
||||
<div class="imgList" v-for="(item, index) in fileList" :key="item">
|
||||
<div class="imgContainer" @mouseenter="mouseEnter(index)" @mouseleave="mouseLeave(index)">
|
||||
<img :src="currentImg[index]" class="imgCover" />
|
||||
<span v-if="maskShow[index]" class="mask">
|
||||
<EyeOutlined v-if="false" @click="handlePreview(item, index)" />
|
||||
<DeleteOutlined @click="deleteImg(index)" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 图片显示框 /-->
|
||||
<!-- 上传图片框/内置了input -->
|
||||
<a-upload
|
||||
v-if="count === 1 ? 'true' : fileList.length < count"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
@change="handleChange"
|
||||
:multiple="count != 1"
|
||||
:customRequest="selfUpload"
|
||||
:showUploadList="false">
|
||||
<!-- :disabled="count == 1 && fileUuid ? true : false" -->
|
||||
<div>
|
||||
<template v-if="isLt5M && isJpgOrPngOrJpeg">
|
||||
<UploadOutlined :style="{ fontSize: '14px' }" />
|
||||
<div class="ant-upload-text">上传图片</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<LoadingOutlined />
|
||||
<div class="ant-upload-text">校验失败</div>
|
||||
</template>
|
||||
</div>
|
||||
</a-upload>
|
||||
<!-- 上传图片框/内置input /-->
|
||||
</div>
|
||||
<!-- 预览图片弹窗 -->
|
||||
<ns-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
|
||||
<img v-if="previewVisible" alt="example" style="width: 100%" :src="previewImage" />
|
||||
</ns-modal>
|
||||
<!-- 预览图片弹窗 /-->
|
||||
<!-- 错误消息提示 -->
|
||||
<div class="err-msg" v-if="!isJpgOrPngOrJpeg">
|
||||
<p>文件上传失败请选择{{ fileType.join(',') }}类型、大小不超过{{maxSizeLabel}}的图片</p>
|
||||
<!-- <p :style="{ color: 'red' }">请选择{{ fileType.join(',') }}的图片</p> -->
|
||||
</div>
|
||||
<div class="err-msg" v-if="isJpgOrPngOrJpeg ? !isLt5M : ''">
|
||||
<p>{{ fileName }} 文件上传失败 请选择 {{ fileType.join(',') }}类型、大小不超过{{maxSizeLabel}}的图片</p>
|
||||
<!-- <p :style="{ color: 'red' }">请选择{{ maxSize / 1024 / 1024 }}M内的图片</p> -->
|
||||
</div>
|
||||
<!-- 错误消息提示 /-->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {
|
||||
UploadOutlined,
|
||||
LoadingOutlined,
|
||||
EyeOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { defineComponent, ref, computed, reactive, watch, toRaw } from 'vue';
|
||||
import { http } from '/nerv-lib/util/http';
|
||||
import { NsMessage } from '/nerv-lib/component/message';
|
||||
import {basicSetup} from "codemirror";
|
||||
interface FileItem {
|
||||
uid: string;
|
||||
type: string;
|
||||
size: number;
|
||||
name?: string;
|
||||
status?: string;
|
||||
response?: string;
|
||||
percent?: number;
|
||||
url?: string;
|
||||
preview?: string;
|
||||
originFileObj?: any;
|
||||
}
|
||||
interface FileInfo {
|
||||
file: FileItem;
|
||||
fileList: FileItem[];
|
||||
}
|
||||
// 转base64
|
||||
function getBase64(file: File) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// console.log(file);
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
}
|
||||
export default defineComponent({
|
||||
name: 'NsUploadCustom',
|
||||
components: {
|
||||
UploadOutlined,
|
||||
LoadingOutlined,
|
||||
DeleteOutlined,
|
||||
EyeOutlined,
|
||||
},
|
||||
props: {
|
||||
value: {},
|
||||
// 上传的地址
|
||||
url: {
|
||||
type: String,
|
||||
// require: true,
|
||||
},
|
||||
// 上传的图片大小
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 5242880,
|
||||
},
|
||||
maxSizeLabel: {
|
||||
type: String,
|
||||
default: '5M',
|
||||
},
|
||||
// 上传的图片类型
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return ['jpg', 'png', 'jpeg'];
|
||||
},
|
||||
},
|
||||
// 展示图片数量
|
||||
count: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
// 上传文件类型,0-证书,1-图片,2-身份证件
|
||||
uploadType: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
fileName: {
|
||||
type: String,
|
||||
default: 'file',
|
||||
},
|
||||
baseImageUrl: {
|
||||
type: [String, Object],
|
||||
},
|
||||
compatibilityUuid: {
|
||||
type: [String, Object],
|
||||
},
|
||||
// 图片归类所需
|
||||
params: {
|
||||
type: [Object],
|
||||
},
|
||||
// 是否两个都emit
|
||||
whetherTwo: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
saveType: {
|
||||
type: String, // 'base64', 'formData'
|
||||
},
|
||||
// baseImageUrlParam: {
|
||||
// // type: String
|
||||
// }
|
||||
},
|
||||
emits: ['change'],
|
||||
setup(props, { emit }) {
|
||||
// console.log(props);
|
||||
const previewVisible = ref<boolean>(false);
|
||||
const isLt5M = ref<boolean>(true);
|
||||
const isJpgOrPngOrJpeg = ref<boolean>(true);
|
||||
const previewImage = ref<string | undefined>('');
|
||||
const fileList = ref<FileItem[]>([]);
|
||||
const fileUuid = ref<string>('');
|
||||
const uuidList = ref([]);
|
||||
const uuidString = ref('');
|
||||
const currentImg = ref<string[]>([]);
|
||||
const isIntImg = ref(false);
|
||||
const filterValue = (value) => {
|
||||
if (typeof value == 'object') {
|
||||
let arr = [];
|
||||
value.forEach((item) => {
|
||||
if (item.includes('ParkPic/')) {
|
||||
arr.push(item.slice(item.lastIndexOf('/') + 1));
|
||||
} else {
|
||||
arr.push(item);
|
||||
}
|
||||
});
|
||||
return arr;
|
||||
} else {
|
||||
let str = '';
|
||||
if (value) {
|
||||
if (value.includes('ParkPic/')) {
|
||||
str = value.slice(value.lastIndexOf('/') + 1);
|
||||
} else {
|
||||
str = value;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
};
|
||||
let baseImageUrl = ref();
|
||||
baseImageUrl.value = props.baseImageUrl;
|
||||
if (baseImageUrl.value) {
|
||||
isIntImg.value = true;
|
||||
if (typeof baseImageUrl.value == 'object') {
|
||||
uuidList.value = props.compatibilityUuid;
|
||||
// currentImg.value.concat(props.baseImageUrl);
|
||||
baseImageUrl.value.map((item, index) => {
|
||||
console.log(item);
|
||||
fileList.value.push(item);
|
||||
currentImg.value.push(item);
|
||||
// previewImage.value = props.baseImageUrl[index];
|
||||
});
|
||||
// currentImg.value.unshift(...props.baseImageUrl);
|
||||
emit(
|
||||
'change',
|
||||
props.whetherTwo
|
||||
? [filterValue(fileList.value), filterValue(uuidList.value)]
|
||||
: filterValue(fileList.value),
|
||||
);
|
||||
} else {
|
||||
fileList.value.push({});
|
||||
uuidString.value = props.compatibilityUuid;
|
||||
fileUuid.value = baseImageUrl.value;
|
||||
currentImg.value.unshift(baseImageUrl.value);
|
||||
previewImage.value = baseImageUrl.value;
|
||||
if(props.saveType === 'base64') {
|
||||
toDataUrl(baseImageUrl.value, function(myBase64) {
|
||||
currentImg.value = [myBase64];
|
||||
emit('change', currentImg.value[0]);
|
||||
});
|
||||
} else {
|
||||
emit(
|
||||
'change',
|
||||
props.whetherTwo
|
||||
? [filterValue(fileUuid.value), filterValue(uuidString.value)]
|
||||
: filterValue(fileUuid.value),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fileName = ref<string | undefined>('');
|
||||
const maskShow = ref<boolean[]>([]);
|
||||
const acceptType = computed(() =>
|
||||
props.fileType.map((item: String) => {
|
||||
return 'image/' + item;
|
||||
}),
|
||||
);
|
||||
watch(
|
||||
() => baseImageUrl,
|
||||
(e) => {
|
||||
if (e) {
|
||||
isIntImg.value = true;
|
||||
if (typeof e.value == 'object') {
|
||||
e.value.map((item, index) => {
|
||||
fileList.value.push(item);
|
||||
currentImg.value.push(item);
|
||||
});
|
||||
emit(
|
||||
'change',
|
||||
props.whetherTwo
|
||||
? [filterValue(fileList.value), filterValue(uuidList.value)]
|
||||
: filterValue(fileList.value),
|
||||
);
|
||||
} else {
|
||||
fileList.value.push({});
|
||||
uuidString.value = props.compatibilityUuid;
|
||||
fileUuid.value = baseImageUrl.value;
|
||||
currentImg.value.unshift(baseImageUrl.value);
|
||||
previewImage.value = baseImageUrl.value;
|
||||
|
||||
if(props.saveType === 'base64') {
|
||||
http.get(baseImageUrl.value).then(res=>{
|
||||
const transfer = async () => {
|
||||
currentImg.value = [await getBase64(res)];
|
||||
emit('change', currentImg.value[0]);
|
||||
}
|
||||
transfer();
|
||||
})
|
||||
} else {
|
||||
emit(
|
||||
'change',
|
||||
props.whetherTwo
|
||||
? [filterValue(fileUuid.value), filterValue(uuidString.value)]
|
||||
: filterValue(fileUuid.value),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{deep: true}
|
||||
);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
if( typeof val === 'number') {
|
||||
baseImageUrl.value = baseImageUrl.value+ JSON.stringify(val);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function toDataUrl(url, callback) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onload = function() {
|
||||
let reader = new FileReader();
|
||||
reader.onloadend = function() {
|
||||
callback(reader.result);
|
||||
}
|
||||
reader.readAsDataURL(xhr.response);
|
||||
};
|
||||
xhr.open('GET', url);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
|
||||
const beforeUpload = (file: FileItem) => {
|
||||
// 上传出错后,下次上传图片前,重置为true,让图片可以上传
|
||||
isLt5M.value = true;
|
||||
isJpgOrPngOrJpeg.value = true;
|
||||
// 限制图片格式,服务器不支持gif图片
|
||||
if (file.type === 'image/gif') {
|
||||
NsMessage.warn('不支持gif图片');
|
||||
return false;
|
||||
}
|
||||
isJpgOrPngOrJpeg.value = acceptType.value.includes(file.type);
|
||||
// 如果大于指定的大小,显示错误信息
|
||||
if (file.size > props.maxSize) {
|
||||
isLt5M.value = false;
|
||||
if(currentImg.value?.length>0) {
|
||||
deleteImg(0);
|
||||
}
|
||||
}
|
||||
fileName.value = file.name;
|
||||
return isLt5M.value && isJpgOrPngOrJpeg.value;
|
||||
};
|
||||
const handleChange = ({ fileList: newFileList }: FileInfo) => {
|
||||
// 单图上传
|
||||
if (props.count === 1) {
|
||||
// 删除图片时,newFileList.length = 0
|
||||
// 图片大小不符合规范时,!isLt5M.value为true
|
||||
if (!isLt5M.value || !isJpgOrPngOrJpeg.value || newFileList.length - 1 < 0) {
|
||||
// 让图片不显示,也不上传
|
||||
fileList.value = [];
|
||||
} else {
|
||||
// 添加\更换图片
|
||||
// newFileList[newFileList.length - 1]的目的是为了只显示最新一张图片
|
||||
fileList.value = [newFileList[newFileList.length - 1]];
|
||||
}
|
||||
}
|
||||
};
|
||||
const selfUpload = async ({ file }) => {
|
||||
if (props.count !== 1) {
|
||||
|
||||
emit(
|
||||
'change',
|
||||
props.whetherTwo
|
||||
? [filterValue(fileList.value), filterValue(uuidList.value)]
|
||||
: filterValue(fileList.value),
|
||||
);
|
||||
currentImg.value.push(await getBase64(file));
|
||||
} else {
|
||||
currentImg.value = [await getBase64(file)];
|
||||
if(props.saveType === 'base64') {
|
||||
emit('change', currentImg.value[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const params = {
|
||||
uploadType: props.uploadType,
|
||||
};
|
||||
const formData = new FormData();
|
||||
formData.append(props.fileName, file);
|
||||
// formData.append('uploadType', props.uploadType);
|
||||
Object.keys(props.params).map((item) => {
|
||||
formData.append(item, props.params[item]);
|
||||
});
|
||||
//formData.append('uploadType', 1);
|
||||
const config = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
// params: params,
|
||||
};
|
||||
http.post(props.url, formData, config).then((res) => {
|
||||
emit('change', res.logoImage);
|
||||
});
|
||||
};
|
||||
const handlePreview = async (item, index: number) => {
|
||||
if (props.count > 1) {
|
||||
previewImage.value = fileList.value[index];
|
||||
} else {
|
||||
previewImage.value = fileUuid.value;
|
||||
}
|
||||
previewVisible.value = true;
|
||||
};
|
||||
const deleteImg = (index: number) => {
|
||||
currentImg.value.splice(index, 1);
|
||||
fileList.value.splice(index, 1);
|
||||
uuidList.value && uuidList.value.splice(index, 1);
|
||||
uuidString.value = '';
|
||||
fileUuid.value = '';
|
||||
isIntImg.value = false;
|
||||
if (props.count == 1) {
|
||||
emit(
|
||||
'change',
|
||||
props.whetherTwo
|
||||
? [filterValue(fileUuid.value), filterValue(uuidString.value)]
|
||||
: filterValue(fileUuid.value),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
'change',
|
||||
props.whetherTwo
|
||||
? [filterValue(fileList.value), filterValue(uuidList.value)]
|
||||
: filterValue(fileList.value),
|
||||
);
|
||||
}
|
||||
};
|
||||
const handleCancel = () => {
|
||||
previewVisible.value = false;
|
||||
};
|
||||
const mouseEnter = (index: number) => {
|
||||
maskShow.value[index] = true;
|
||||
};
|
||||
const mouseLeave = (index: number) => {
|
||||
maskShow.value[index] = false;
|
||||
};
|
||||
return {
|
||||
previewVisible,
|
||||
previewImage,
|
||||
fileList,
|
||||
isLt5M,
|
||||
isJpgOrPngOrJpeg,
|
||||
fileName,
|
||||
currentImg,
|
||||
maskShow,
|
||||
fileUuid,
|
||||
baseImageUrl,
|
||||
selfUpload,
|
||||
handleCancel,
|
||||
handlePreview,
|
||||
handleChange,
|
||||
beforeUpload,
|
||||
mouseEnter,
|
||||
mouseLeave,
|
||||
deleteImg,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-upload-picture-card-wrapper) {
|
||||
width: unset !important;
|
||||
}
|
||||
:deep(.ant-upload-picture-card-wrapper .ant-upload.ant-upload-select-picture-card) {
|
||||
margin: 0;
|
||||
width: 64px !important;
|
||||
height: 64px !important;
|
||||
border: 1px solid #d9d9d9;
|
||||
}
|
||||
:deep(.ant-upload-picture-card-wrapper .ant-upload.ant-upload-select-picture-card:hover) {
|
||||
border-color: #d9d9d9;
|
||||
}
|
||||
:deep(.ant-upload-select-picture-card i) {
|
||||
font-size: 32px;
|
||||
color: #999;
|
||||
}
|
||||
:deep(.ant-upload-select-picture-card .ant-upload-text) {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
:deep(.ant-upload-picture-card-wrapper .ant-upload-list-picture-card-container) {
|
||||
width: 88px;
|
||||
height: 64px !important;
|
||||
}
|
||||
:deep(.ant-upload-picture-card-wrapper .ant-upload-list-picture-card .ant-upload-list-item) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 64px !important;
|
||||
height: 64px !important;
|
||||
}
|
||||
.title,
|
||||
.err-msg {
|
||||
text-align: left;
|
||||
}
|
||||
.err-msg p {
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.imgList {
|
||||
display: flex;
|
||||
}
|
||||
.imgContainer {
|
||||
margin-right: 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
position: relative;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.imgCover {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.imgContainer .mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 64px !important;
|
||||
height: 64px !important;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
text-align: center;
|
||||
line-height: 64px !important;
|
||||
}
|
||||
.mask .anticon-eye {
|
||||
color: white;
|
||||
margin-right: 18px;
|
||||
}
|
||||
.mask .anticon-eye:hover {
|
||||
color: #00acff;
|
||||
margin-right: 18px;
|
||||
}
|
||||
.mask .anticon-delete {
|
||||
color: white;
|
||||
}
|
||||
.mask .anticon-delete:hover {
|
||||
color: #00acff;
|
||||
}
|
||||
</style>
|
||||
7
nervui-resource-repository/src/config/app.config.ts
Normal file
7
nervui-resource-repository/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-repository/src/config/index.ts
Normal file
3
nervui-resource-repository/src/config/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import {appConfig} from '/@/config/app.config';
|
||||
|
||||
export {appConfig};
|
||||
18
nervui-resource-repository/src/main.ts
Normal file
18
nervui-resource-repository/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-repository",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "/nervui/nervui-resource-repository",
|
||||
"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-repository",
|
||||
"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-repository/${version}/nervui-resource-repository-${version}.tgz"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"type": "contained",
|
||||
"target": "host"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "host",
|
||||
"type": "/nerv/nerv-orchestrator/compute/Host",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "address",
|
||||
"value": "${server_ip}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
360
nervui-resource-repository/src/router/index.ts
Normal file
360
nervui-resource-repository/src/router/index.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
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 resourceTypeAddForm = () => import('../view/resource-type-add.vue');
|
||||
const resourceTypeDetailForm = () => import('../view/resource-type-detail.vue');
|
||||
const resourceTypeReleaseForm = () => import('../view/resource-type-release.vue');
|
||||
const resourceTypeEditForm = () => import('../view/resource-type-edit.vue');
|
||||
export const APP = 'nerv-rm-server'
|
||||
|
||||
|
||||
const rrRoute = {
|
||||
path: '/rr',
|
||||
name: 'resourceRepo',
|
||||
meta: {
|
||||
sideMenus: {
|
||||
title: '资源仓库',
|
||||
name: 'resourceRepo',
|
||||
root: true,
|
||||
menus: [
|
||||
{
|
||||
name: 'resourceType',
|
||||
label: '资源仓库',
|
||||
url: 'resourceType',
|
||||
module: '',
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
children: [
|
||||
/****** 资源仓库 start *********** */
|
||||
{
|
||||
path: 'resourceRepository',
|
||||
name: 'resourceTypeModule',
|
||||
redirect: '/rr/resourceRepository/list',
|
||||
component: Base,
|
||||
children: [
|
||||
// 资源仓库-列表
|
||||
{
|
||||
path: 'list',
|
||||
name: 'resourceType',
|
||||
component: treeAndTableList,
|
||||
props: {
|
||||
title: '资源仓库',
|
||||
enableTableSession: true,
|
||||
api: `/api/${APP}/objs/ResourceTypes`,
|
||||
formConfig: {
|
||||
schemas: [],
|
||||
showAction: false,
|
||||
},
|
||||
rowSelection: null,
|
||||
resultField: 'data',
|
||||
columns: [],
|
||||
columnActions: {
|
||||
title: '操作',
|
||||
width: 100,
|
||||
actions: [],
|
||||
},
|
||||
headerActions: [
|
||||
{
|
||||
label: '注册资源类型',
|
||||
name: 'resourceTypeAdd',
|
||||
type: 'primary',
|
||||
route: 'add',
|
||||
}
|
||||
],
|
||||
rowKey: 'ID',
|
||||
},
|
||||
},
|
||||
// 资源仓库-添加
|
||||
{
|
||||
name: 'resourceTypeAdd',
|
||||
path: 'add',
|
||||
component: resourceTypeAddForm,
|
||||
props: {
|
||||
title: '配置资源类型',
|
||||
formLayout: 'ns-vertical-form',
|
||||
api: `/api/${APP}/objs/ResourceTypes`,
|
||||
schemas: [
|
||||
{
|
||||
label: '名称',
|
||||
field: 'alias',
|
||||
component: 'NsInput',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
validator: async (rule: any, value: any) => {
|
||||
if (!value) {
|
||||
return Promise.reject('名称不能为空');
|
||||
}
|
||||
if (!/^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9-_\u4e00-\u9fa5]{0,63}$/.test(value)) {
|
||||
return Promise.reject(
|
||||
'只能包括中文、大小写字母、数字和短横线(-)、下划线(_),必须以字母、数字、或中文开头,长度必须在1–64字节之间。',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '资源代码',
|
||||
field: 'code',
|
||||
component: 'NsInput',
|
||||
componentProps: {},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '资源代码不能为空',
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '类别',
|
||||
field: 'typeClassCode',
|
||||
component: 'NsSelectTreeApi',
|
||||
componentProps: {
|
||||
showCheckedStrategy: 'TreeSelect.SHOW_ALL',
|
||||
selectType: 'tree',
|
||||
api: `/api/${APP}/objs/ResourceTypeClasses`,
|
||||
labelField: 'alias',
|
||||
valueField: 'code',
|
||||
resultField: '',
|
||||
immediate: true,
|
||||
placeholder: '请选择类型',
|
||||
unSelectable: {
|
||||
level: -1,
|
||||
},
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择类型',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '介绍',
|
||||
field: 'desc',
|
||||
component: 'NsTextarea',
|
||||
componentProps: {
|
||||
showCount: true,
|
||||
maxlength: 300,
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '介绍内容不能为空',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
field: 'logo',
|
||||
component: 'NsUploadCustom',
|
||||
label: 'icon',
|
||||
componentProps: {
|
||||
saveType: 'base64',
|
||||
// 上传的图中片大小
|
||||
maxSize: 15360,
|
||||
maxSizeLabel: "15KB",
|
||||
// 上传的图片类型
|
||||
fileType: ['jpg', 'png', 'jpeg'],
|
||||
// 展示图片数量
|
||||
count: 1,
|
||||
imagePreview: true
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择图片',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
field: 'detail',
|
||||
component: 'NsMarkDown',
|
||||
class: 'ns-form-item-label',
|
||||
componentProps: {
|
||||
// previewOnly: true
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '详情不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'resourceTypeDetail',
|
||||
path: ':id/detail',
|
||||
component: resourceTypeDetailForm,
|
||||
},
|
||||
{
|
||||
name: 'resourceTypeEdit',
|
||||
path: ':id/edit',
|
||||
component: resourceTypeEditForm,
|
||||
props: {
|
||||
title: '编辑资源类型',
|
||||
formLayout: 'ns-vertical-form',
|
||||
getApi: `/api/${APP}/objs/ResourceTypes`,
|
||||
api: `/api/${APP}/objs/ResourceTypes`,
|
||||
schemas: [
|
||||
{
|
||||
label: '名称',
|
||||
field: 'alias',
|
||||
component: 'NsInput',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
validator: async (rule: any, value: any) => {
|
||||
if (!value) {
|
||||
return Promise.reject('名称不能为空');
|
||||
}
|
||||
if (!/^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9-_\u4e00-\u9fa5]{0,63}$/.test(value)) {
|
||||
return Promise.reject(
|
||||
'只能包括中文、大小写字母、数字和短横线(-)、下划线(_),必须以字母、数字、或中文开头,长度必须在1–64字节之间。',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '资源代码',
|
||||
field: 'code',
|
||||
component: 'NsInputText',
|
||||
componentProps: {},
|
||||
},
|
||||
{
|
||||
label: '类型',
|
||||
field: 'typeClassCode',
|
||||
component: 'NsSelectTreeApi',
|
||||
componentProps: {
|
||||
showCheckedStrategy: 'TreeSelect.SHOW_ALL',
|
||||
selectType: 'tree',
|
||||
api: `/api/${APP}/objs/ResourceTypeClasses`,
|
||||
labelField: 'alias',
|
||||
valueField: 'code',
|
||||
resultField: '',
|
||||
immediate: true,
|
||||
placeholder: '请选择类型',
|
||||
unSelectable: {
|
||||
level: -1,
|
||||
},
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择类型',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '介绍',
|
||||
field: 'desc',
|
||||
component: 'NsTextarea',
|
||||
componentProps: {
|
||||
showCount: true,
|
||||
maxlength: 300,
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '介绍内容不能为空',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
field: 'logo',
|
||||
component: 'NsUploadCustom',
|
||||
label: 'icon',
|
||||
componentProps: {
|
||||
saveType: 'base64',
|
||||
// 上传的图中片大小
|
||||
maxSize: 15360,
|
||||
maxSizeLabel: "15KB",
|
||||
// 上传的图片类型
|
||||
fileType: ['jpg', 'png', 'jpeg'],
|
||||
// 展示图片数量
|
||||
count: 1,
|
||||
imagePreview: true,
|
||||
baseImageUrl: '',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择图片',
|
||||
trigger: 'blur',
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
field: 'detail',
|
||||
component: 'NsMarkDown',
|
||||
class: 'ns-form-item-label',
|
||||
componentProps: {
|
||||
// previewOnly: true
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '详情不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'resourceTypePutAway',
|
||||
path: ':id/release',
|
||||
component: resourceTypeReleaseForm,
|
||||
props: {
|
||||
title: '配置资源类型',
|
||||
formLayout: 'ns-vertical-form',
|
||||
api: `/api/${APP}/objs/ResourceTypes`,
|
||||
schemas: [
|
||||
{
|
||||
label: '资源服务地址',
|
||||
field: 'resourceProviderAddr',
|
||||
component: 'NsInput',
|
||||
componentProps: {
|
||||
placeholder: '请输入域名或IP地址(http://dingcloud.com:30080)'
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
validator: async (rule: any, value: any) => {
|
||||
if (!value) {
|
||||
return Promise.reject('资源服务地址不能为空');
|
||||
}
|
||||
if (!/^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?$/.test(value)) {
|
||||
return Promise.reject(
|
||||
'请输入正常的域名和IP地址',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
/****** 资源管理 end *********** */
|
||||
]
|
||||
};
|
||||
export default rrRoute;
|
||||
9
nervui-resource-repository/src/router/redirect.ts
Normal file
9
nervui-resource-repository/src/router/redirect.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
const RootRoute = {
|
||||
path: '/root',
|
||||
name: 'root',
|
||||
redirect: '/rr/resourceRepository',
|
||||
meta: {
|
||||
title: 'Root',
|
||||
},
|
||||
};
|
||||
export default RootRoute;
|
||||
3
nervui-resource-repository/src/theme/global.scss
Normal file
3
nervui-resource-repository/src/theme/global.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.application .contentMenu .content {
|
||||
background-color: #fff;
|
||||
}
|
||||
2
nervui-resource-repository/src/theme/theme.scss
Normal file
2
nervui-resource-repository/src/theme/theme.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
@import "variable";
|
||||
@import "global";
|
||||
0
nervui-resource-repository/src/theme/variable.less
Normal file
0
nervui-resource-repository/src/theme/variable.less
Normal file
0
nervui-resource-repository/src/theme/variable.scss
Normal file
0
nervui-resource-repository/src/theme/variable.scss
Normal file
40
nervui-resource-repository/src/view/resource-type-add.vue
Normal file
40
nervui-resource-repository/src/view/resource-type-add.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<NsViewAddForm v-bind="getBindValue"/>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, provide } from 'vue';
|
||||
import {formProps} from "/nerv-lib/component/form/form/props";
|
||||
import {PropTypes} from "/nerv-lib/util";
|
||||
import NsMarkDown from '../component/markdown.vue';
|
||||
import NsUploadCustom from '../component/upload-custom.vue';
|
||||
export default defineComponent({
|
||||
name: 'ResourceTypeAdd',
|
||||
props: {
|
||||
...formProps,
|
||||
api: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
},
|
||||
components: { NsMarkDown, NsUploadCustom },
|
||||
setup(props, {attrs}) {
|
||||
provide('components', () => {
|
||||
return {
|
||||
NsMarkDown,
|
||||
NsUploadCustom
|
||||
};
|
||||
});
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
}));
|
||||
return {
|
||||
getBindValue,
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
129
nervui-resource-repository/src/view/resource-type-detail.vue
Normal file
129
nervui-resource-repository/src/view/resource-type-detail.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<Skeleton
|
||||
active
|
||||
:loading="loading">
|
||||
<page-title :title="data?.alias"/>
|
||||
<a-page-header class="ns-page-header">
|
||||
<template #extra>
|
||||
<ns-button @click="onBack">返回</ns-button>
|
||||
<ns-button @click="onEdit" type="primary" v-if="authMap['edit']">编辑</ns-button>
|
||||
<ns-button @click="onRelease" type="primary" v-if="!data?.shelfState && authMap['putAway']">上架</ns-button>
|
||||
<ns-button @click="onUnRelease" type="primary" v-if="data?.shelfState && authMap['putAway']">下架</ns-button>
|
||||
<ns-button @click="onRemove" type="primary" v-if="authMap['remove']">删除</ns-button>
|
||||
|
||||
</template>
|
||||
</a-page-header>
|
||||
<div class="content-wrapper">
|
||||
<NsMarkDown
|
||||
:value="data?.detail"
|
||||
:previewOnly="true"
|
||||
:htmlPreview="true"/>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref} from 'vue';
|
||||
import NsMarkDown from '../component/markdown.vue';
|
||||
import {http} from "/nerv-lib/util";
|
||||
import {useRoute} from "vue-router";
|
||||
import {useNavigate} from "/nerv-lib/use/use-navigate";
|
||||
import { NsModal} from "/nerv-lib/component";
|
||||
import {Skeleton} from "ant-design-vue";
|
||||
import {APP} from "/@/router/index.ts";
|
||||
import {authorizationService} from "/nerv-lib/paas";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResourceTypeDetail',
|
||||
props: {},
|
||||
components: {NsMarkDown, Skeleton},
|
||||
setup(props, {attrs}) {
|
||||
let loading = ref(true);
|
||||
let data = ref({});
|
||||
const route = useRoute();
|
||||
const params = route.params;
|
||||
const code = params?.id;
|
||||
const { navigateBack } = useNavigate();
|
||||
const unReleaseData = ref({
|
||||
code: code,
|
||||
shelfState: false
|
||||
})
|
||||
const removeData = ref({
|
||||
code: code,
|
||||
})
|
||||
http.get(`/api/${APP}/objs/ResourceTypes/` + params.id).then(res => {
|
||||
loading.value = false;
|
||||
data.value = res;
|
||||
})
|
||||
//权限部分
|
||||
let authMap = ref({});
|
||||
const resourceTypeClassOp= ['edit', 'putAway', 'remove'];
|
||||
resourceTypeClassOp.forEach( key => {
|
||||
authMap.value[key]= authorizationService().checkPermission('resourceRepo', 'resourceType', key);
|
||||
})
|
||||
return {
|
||||
data,
|
||||
navigateBack,
|
||||
code,
|
||||
removeData,
|
||||
unReleaseData,
|
||||
loading,
|
||||
authMap
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onEdit() {
|
||||
this.$router.push('edit');
|
||||
},
|
||||
onRelease() {
|
||||
this.$router.push('release');
|
||||
},
|
||||
onUnRelease() {
|
||||
const thisObj = this;
|
||||
NsModal.confirm({
|
||||
title: '警告',
|
||||
content: `确定要下架该资源类型吗?\n下架后不可恢复,请谨慎操作!`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk() {
|
||||
http.post(`/api/${APP}/objs/ResourceTypes/PutAway`, thisObj.unReleaseData).then(res=>{
|
||||
// navigateBack()
|
||||
thisObj.$router.push({name:'resourceType', path: '/rr/resourceRepository/list'});
|
||||
})
|
||||
},
|
||||
});
|
||||
},
|
||||
onRemove() {
|
||||
const thisObj = this;
|
||||
NsModal.confirm({
|
||||
title: '警告',
|
||||
content: `确定要删除该资源类型吗?\n删除后不可恢复,请谨慎操作!`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk() {
|
||||
http.delete(`/api/${APP}/objs/ResourceTypes`, thisObj.removeData).then(res=>{
|
||||
thisObj.$router.push({name:'resourceType', path: '/rr/resourceRepository/list'});
|
||||
})
|
||||
},
|
||||
});
|
||||
},
|
||||
onBack() {
|
||||
this.navigateBack();
|
||||
}
|
||||
}
|
||||
})
|
||||
</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
|
||||
}
|
||||
</style>
|
||||
121
nervui-resource-repository/src/view/resource-type-edit.vue
Normal file
121
nervui-resource-repository/src/view/resource-type-edit.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
|
||||
<template>
|
||||
<div class="editResourceType">
|
||||
<page-title title="编辑" />
|
||||
<a-page-header>
|
||||
<template #extra>
|
||||
<ns-button @click="navigateBack">返回</ns-button>
|
||||
<ns-button type="primary" :disabled="!mainRef?.validateResult" @click="submit">保存</ns-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<Skeleton
|
||||
active
|
||||
:loading="loading">
|
||||
<ns-form
|
||||
style="margin-left: 24px"
|
||||
ref="mainRef"
|
||||
formLayout="修改"
|
||||
v-bind="getBindValue"
|
||||
:schemas="schemas"
|
||||
:model ="data"
|
||||
/>
|
||||
</Skeleton>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, provide, ref } from 'vue';
|
||||
import {formProps} from "/nerv-lib/component/form/form/props";
|
||||
import {http, PropTypes} from "/nerv-lib/util";
|
||||
import NsMarkDown from '../component/markdown.vue';
|
||||
import NsUploadCustom from "/@/component/upload-custom.vue";
|
||||
import {useNavigate} from "/nerv-lib/use/use-navigate";
|
||||
import {NsMessage} from "/nerv-lib/component";
|
||||
import {useRouter} from "vue-router";
|
||||
import {Skeleton} from "ant-design-vue";
|
||||
import {APP} from "/@/router/index.ts";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResourceTypeEdit',
|
||||
props: {
|
||||
...formProps,
|
||||
title: PropTypes.string,
|
||||
getApi: Object | String
|
||||
},
|
||||
components: { NsMarkDown, Skeleton },
|
||||
setup(props, {attrs}) {
|
||||
provide('components', () => {
|
||||
return {
|
||||
NsMarkDown,
|
||||
NsUploadCustom
|
||||
};
|
||||
});
|
||||
const { navigateBack } = useNavigate();
|
||||
let loading = ref(false);
|
||||
const router = useRouter();
|
||||
const { params } = router.currentRoute.value;
|
||||
let data = ref({});
|
||||
const baseImageUrl = ref();
|
||||
let schemas = ref([])
|
||||
const initData = async () => {
|
||||
http.get(props.getApi + '/' + params.id).then(res=>{
|
||||
data.value = res;
|
||||
baseImageUrl.value = `/api/${APP}/objs/Images/${res?.iconID}`;
|
||||
schemas.value = props.schemas;
|
||||
schemas.value?.forEach(fc=>{
|
||||
if( fc['field']=='logo') {
|
||||
fc.componentProps.baseImageUrl = baseImageUrl.value;
|
||||
}
|
||||
})
|
||||
loading.value = false;
|
||||
})
|
||||
};
|
||||
initData();
|
||||
const mainRef = ref();
|
||||
function submit() {
|
||||
mainRef.value
|
||||
.triggerSubmit()
|
||||
.then((formData) => {
|
||||
http.put(`/api/${APP}/objs/ResourceTypes`, formData).then((res) => {
|
||||
NsMessage.success('操作成功', 1, () => {
|
||||
router.push({name:'resourceType', path: '/rr/resourceRepository/list'});
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => ({}));
|
||||
}
|
||||
|
||||
const getBindValue = computed(() => ({
|
||||
...attrs,
|
||||
...props,
|
||||
|
||||
}));
|
||||
return {
|
||||
getBindValue,
|
||||
navigateBack,
|
||||
schemas,
|
||||
data,
|
||||
mainRef,
|
||||
submit
|
||||
};
|
||||
}
|
||||
});
|
||||
</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;
|
||||
}
|
||||
|
||||
.customConfig {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
113
nervui-resource-repository/src/view/resource-type-release.vue
Normal file
113
nervui-resource-repository/src/view/resource-type-release.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<page-title :title="title"/>
|
||||
<a-page-header class="ns-page-header">
|
||||
<template #extra>
|
||||
<ns-button @click="navigateBack">返回</ns-button>
|
||||
<ns-button @click="submit" type="primary" :disabled="!testFlag">保存</ns-button>
|
||||
<ns-button @click="testConnect" type="primary" :disabled="mainRef?.validateResult ? !mainRef?.validateResult : true"
|
||||
>测试连接</ns-button>
|
||||
</template>
|
||||
</a-page-header>
|
||||
<ns-form
|
||||
style="margin-top: 30px; margin-left: 24px"
|
||||
ref="mainRef"
|
||||
formLayout="配置"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<div class="ant-row ant-form-item" v-if="testFlag">
|
||||
<div class="ant-form-item-label item-label">
|
||||
<label title="可执行操作">可执行操作</label>
|
||||
</div>
|
||||
<div class="ant-col ant-col-20 ant-form-item-control">
|
||||
<div class="item-result">
|
||||
{{testResult}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, ref} from 'vue';
|
||||
import NsMarkDown from '../component/markdown.vue';
|
||||
import {http} from "/nerv-lib/util";
|
||||
import {useRoute} from "vue-router";
|
||||
import {useNavigate} from "/nerv-lib/use/use-navigate";
|
||||
import {APP} from "/@/router/index.ts";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ResourceTypeRelease',
|
||||
props: {},
|
||||
components: {NsMarkDown},
|
||||
setup(props, {attrs}) {
|
||||
const mainRef = ref('');
|
||||
const title = ref('配置资源服务地址');
|
||||
const route = useRoute();
|
||||
const code = route.params.id
|
||||
const testFlag = ref(false);
|
||||
const testResult = ref();
|
||||
const { navigateBack } = useNavigate();
|
||||
let releaseData = ref({
|
||||
code: code,
|
||||
shelfState: true
|
||||
});
|
||||
return {
|
||||
title,
|
||||
navigateBack,
|
||||
testFlag,
|
||||
mainRef,
|
||||
testResult,
|
||||
releaseData,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
const thisObj = this;
|
||||
this.mainRef.triggerSubmit().then((data:any)=>{
|
||||
http.post(`/api/${APP}/objs/ResourceTypes/PutAway`, this.releaseData).then(res=>{
|
||||
thisObj.$router.push({name:'resourceType', path: '/rr/resourceRepository/list'})
|
||||
})
|
||||
})
|
||||
},
|
||||
testConnect() {
|
||||
this.testFlag = false;
|
||||
this.mainRef.triggerSubmit().then((data:any)=>{
|
||||
const resourceProviderAddr = data['resourceProviderAddr']
|
||||
http.get(`/api/${APP}/objs/ResourceTypes/TestConnect?resourceProviderAddr=` + resourceProviderAddr ).then(res=>{
|
||||
this.testResult = res;
|
||||
this.testFlag = true;
|
||||
this.releaseData['resourceProviderAddr']= resourceProviderAddr;
|
||||
this.releaseData['configs']= this.testResult;
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</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
|
||||
}
|
||||
.ant-form-item {
|
||||
margin-left: 24px;
|
||||
.item-label {
|
||||
text-align: left;
|
||||
flex: 0 0 100px;
|
||||
}
|
||||
.item-result {
|
||||
white-space: break-spaces;
|
||||
height: 500px;
|
||||
overflow: auto;
|
||||
background-color: #fbfbfb;
|
||||
padding: 4px
|
||||
}
|
||||
}
|
||||
</style>
|
||||
942
nervui-resource-repository/src/view/tree-and-table-list.vue
Normal file
942
nervui-resource-repository/src/view/tree-and-table-list.vue
Normal file
@@ -0,0 +1,942 @@
|
||||
<template>
|
||||
<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">
|
||||
<ns-table-header
|
||||
:headerActions="headerActions"
|
||||
:searchData="formModel"
|
||||
:tableTitle="tableTitle"
|
||||
:data="tableState.selectedRows">
|
||||
<template #header="data">
|
||||
<slot name="header" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</ns-table-header>
|
||||
<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>
|
||||
<ns-button type="link" @click="onContextMenuClick(null, 'add')" v-if="authMap['add']">
|
||||
<PlusCircleOutlined/>
|
||||
添加类别
|
||||
</ns-button>
|
||||
</div>
|
||||
<a-tree v-model:expandedKeys="expandedKeys" :tree-data="treeData" v-model:selectedKeys="selectedKeys"
|
||||
@expand="onExpand" @select="onTreeSelect">
|
||||
<template #title="{ key: treeKey, title, dataRef }">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<span style="min-width: 80px; display: block">{{ title }}</span>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key: menuKey }) => onContextMenuClick(treeKey, menuKey, dataRef)">
|
||||
<a-menu-item key="edit" v-if="authMap['edit']">
|
||||
<EditOutlined/>
|
||||
编辑
|
||||
</a-menu-item>
|
||||
<a-menu-item key="add" v-if="authMap['add']">
|
||||
<PlusCircleOutlined/>
|
||||
添加
|
||||
</a-menu-item>
|
||||
<a-menu-item key="remove" v-if="authMap['remove']">
|
||||
<DeleteOutlined/>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</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>
|
||||
<a-modal v-model:visible="modalVisible" :title="modalOperation.title" @ok="handleOk" @cancel="cancel" v-if="modalVisible">
|
||||
<div class="add-form" id="app" v-if="typeSchema.length">
|
||||
<ns-form
|
||||
ref="configRef"
|
||||
formLayout="ns-vertical-form"
|
||||
:schemas="typeSchema"
|
||||
:model="typeData"
|
||||
/>
|
||||
</div>
|
||||
</a-modal>
|
||||
</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} 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 {authorizationService} from "/nerv-lib/paas";
|
||||
|
||||
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 formModel = ref({});
|
||||
// 搜索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.value, 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/`)
|
||||
let typeFieldErrors = ref({});
|
||||
// 缓存
|
||||
function initTableSession() {
|
||||
const { fullPath } = route;
|
||||
const tableSession = JSON.parse(sessionStorage[fullPath] || '{}');
|
||||
if (!props.enableTableSession) return;
|
||||
if (tableSession['formModel']) {
|
||||
Object.assign(formModel.value, tableSession['formModel']);
|
||||
let code = formModel.value['typeClassCode'];
|
||||
if(code){
|
||||
treeParamsRef.value['typeClassCode'] = code;
|
||||
selectedKeys.value =[ code];
|
||||
allSelected.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
initTableSession();
|
||||
//权限部分
|
||||
let authMap = ref({});
|
||||
const resourceTypeClassOp= ['add', 'edit', 'remove'];
|
||||
resourceTypeClassOp.forEach( key => {
|
||||
authMap.value[key]= authorizationService().checkPermission('resourceRepo', 'resourceTypeClass', key);
|
||||
})
|
||||
// 页面标题
|
||||
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( []);
|
||||
// 树形结构添加
|
||||
let typeSchema = ref([]);
|
||||
const getTypeSchema = (mode) => {
|
||||
let schema = [
|
||||
{
|
||||
field: 'alias',
|
||||
component: 'NsInput',
|
||||
label: '类别名称',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
validator: async (rule, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject('类别名称不能为空');
|
||||
}
|
||||
if (!/^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9-_\u4e00-\u9fa5]{0,50}$/.test(value)) {
|
||||
return Promise.reject(
|
||||
'只能包括大小写字母、中文、数字和短横线(-)、下划线(_)。必须以字母、中文、数字开头。长度必须在1–50字符之间。'
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: 'change',
|
||||
validator: async (rule, value) => {
|
||||
if (Object.keys(typeFieldErrors.value).indexOf('alias') !== -1) {
|
||||
const errorInfo = typeFieldErrors.value['alias'];
|
||||
delete typeFieldErrors.value['alias'];
|
||||
return Promise.reject(errorInfo);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
if (mode === 'add') {
|
||||
schema.push(
|
||||
{
|
||||
field: 'code',
|
||||
component: 'NsInput',
|
||||
label: '类别代码',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
validator: async (rule, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject('类别代码不能为空');
|
||||
}
|
||||
if (!/^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9-_\u4e00-\u9fa5]{0,50}$/.test(value)) {
|
||||
return Promise.reject(
|
||||
'只能包括大小写字母、中文、数字和短横线(-)、下划线(_)。必须以字母、中文、数字开头。长度必须在1–50字符之间。'
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: 'change',
|
||||
validator: async (rule, value) => {
|
||||
if (Object.keys(typeFieldErrors.value).indexOf('code') !== -1) {
|
||||
const errorInfo = typeFieldErrors.value['code'];
|
||||
delete typeFieldErrors.value['code'];
|
||||
return Promise.reject(errorInfo);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
} else if (mode === 'edit') {
|
||||
schema.push(
|
||||
{
|
||||
field: 'code',
|
||||
component: 'NsInputText',
|
||||
label: '类别代码',
|
||||
})
|
||||
}
|
||||
return schema || [];
|
||||
}
|
||||
const typeData = ref({});
|
||||
let modalVisible = ref(false);
|
||||
let modalOperation = ref({
|
||||
title: '添加类别',
|
||||
key: 'add'
|
||||
});
|
||||
// 搜索
|
||||
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.value[key] = treeParamsRef.value[key];
|
||||
} else {
|
||||
updateFormModel();
|
||||
}
|
||||
}
|
||||
function reload(clearDelay = true) {
|
||||
const pagination = unref(getPagination);
|
||||
fetch(
|
||||
{
|
||||
page: pagination === false ? 1 : pagination.current,
|
||||
},
|
||||
clearDelay,
|
||||
);
|
||||
}
|
||||
const updateFormModel = function (value?:any) {
|
||||
const key = 'typeClassCode';
|
||||
const model: object = formModel.value;
|
||||
if (value) {
|
||||
if (Object.keys(model).includes(key) && model[key] === value) {
|
||||
delete model[key];
|
||||
}
|
||||
} else if (Object.keys(model).includes(key)){
|
||||
delete model[key];
|
||||
}
|
||||
}
|
||||
return {
|
||||
tableState,
|
||||
getTitle,
|
||||
treeData,
|
||||
expandedKeys,
|
||||
formModel,
|
||||
formFinish,
|
||||
tableData,
|
||||
typeSchema,
|
||||
typeData,
|
||||
modalVisible,
|
||||
modalOperation,
|
||||
typeFieldErrors,
|
||||
getTypeSchema,
|
||||
formConfig: getFormConfig.value,
|
||||
nsTableRef,
|
||||
formElRef,
|
||||
getParams,
|
||||
onTreeSelect,
|
||||
|
||||
onChange: tableChangeEvent,
|
||||
allSelected,
|
||||
selectedKeys,
|
||||
treeParamsRef,
|
||||
loadingTable,
|
||||
loadingTree,
|
||||
imgSrc,
|
||||
getTableBindValues,
|
||||
getPagination,
|
||||
authMap,
|
||||
updateFormModel,
|
||||
setTableSession
|
||||
}
|
||||
},
|
||||
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/ResourceTypeClasses`).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;
|
||||
this.updateFormModel();
|
||||
this.formElRef.triggerSubmit();
|
||||
},
|
||||
// onExpand(keys) {
|
||||
// this.expandedKeys = keys;
|
||||
// },
|
||||
onContextMenuClick(treeKey, menuKey, data) {
|
||||
this.modalOperation.key = menuKey
|
||||
if (menuKey === 'add') {
|
||||
this.modalOperation.title = '添加类别';
|
||||
this.modalVisible = true;
|
||||
this.typeData['parentID'] = data?.id || null;
|
||||
this.typeSchema = this.getTypeSchema('add')
|
||||
}
|
||||
if (menuKey === 'edit') {
|
||||
this.modalOperation.title = '编辑类别';
|
||||
this.modalVisible = true;
|
||||
this.typeData = {
|
||||
alias: data['title'],
|
||||
code: data['code'],
|
||||
id: data['id'],
|
||||
parentID: data['parentID']
|
||||
}
|
||||
this.typeSchema = [
|
||||
{
|
||||
field: 'alias',
|
||||
component: 'NsInput',
|
||||
label: '类别名称',
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'change',
|
||||
validator: async (rule, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject('类别名称不能为空');
|
||||
}
|
||||
if (!/^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9-_\u4e00-\u9fa5]{0,50}$/.test(value)) {
|
||||
return Promise.reject(
|
||||
'只能包括大小写字母、中文、数字和短横线(-)、下划线(_)。必须以字母、中文、数字开头。长度必须在1–50字符之间。'
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
component: 'NsInputText',
|
||||
label: '类别代码'
|
||||
},
|
||||
{
|
||||
field: 'parentID',
|
||||
label: '类别',
|
||||
component: 'NsSelectTreeApi',
|
||||
componentProps: {
|
||||
showCheckedStrategy: 'TreeSelect.SHOW_ALL',
|
||||
selectType: 'tree',
|
||||
api: `/api/${APP}/objs/ResourceTypeClasses`,
|
||||
labelField: 'alias',
|
||||
valueField: 'id',
|
||||
resultField: '',
|
||||
immediate: true,
|
||||
placeholder: '请选择类型',
|
||||
allowClear: true,
|
||||
unSelectable: {
|
||||
level: -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
if (menuKey === 'remove') {
|
||||
const thisObj = this;
|
||||
NsModal.confirm(
|
||||
{
|
||||
title: '警告',
|
||||
content: `确定要删除该项吗?\n删除后不可恢复,请谨慎操作!`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk() {
|
||||
http.delete(`/api/${APP}/objs/ResourceTypeClasses`, {id: data['id']}).then(() => {
|
||||
NsMessage.success('删除成功');
|
||||
if (data['code']) {
|
||||
thisObj.updateFormModel( data['code']);
|
||||
thisObj.setTableSession(thisObj.getPagination.current);
|
||||
}
|
||||
thisObj.getDataTree();
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
handleArgumentError(data) {
|
||||
const error = data?.response?.data;
|
||||
if(error && error['errType'] === 'ArgumentError') {
|
||||
this.typeFieldErrors = error['fieldErrors'];
|
||||
this.$refs.configRef.triggerSubmit();
|
||||
}
|
||||
},
|
||||
addType() {
|
||||
http.post(`/api/${APP}/objs/ResourceTypeClasses`, this.typeData).then((res) => {
|
||||
NsMessage.success('操作成功');
|
||||
this.getDataTree();
|
||||
this.typeData = {};
|
||||
this.modalVisible = false;
|
||||
}).catch((e)=>{
|
||||
this.handleArgumentError(e)
|
||||
})
|
||||
},
|
||||
editType() {
|
||||
if (Object.keys(this.typeData).includes('parentID')) {
|
||||
this.typeData['parentID'] = 0;
|
||||
}
|
||||
http.put(`/api/${APP}/objs/ResourceTypeClasses`, this.typeData).then((res) => {
|
||||
NsMessage.success('操作成功');
|
||||
this.getDataTree();
|
||||
this.typeData = {};
|
||||
this.modalVisible = false;
|
||||
}).catch((e)=>{
|
||||
this.handleArgumentError(e)
|
||||
})
|
||||
},
|
||||
removeType() {
|
||||
console.log(this.typeData)
|
||||
},
|
||||
handleOk() {
|
||||
if (['add'].includes(this.modalOperation.key)) {
|
||||
this.$refs.configRef.triggerSubmit().then(() => {
|
||||
this.addType();
|
||||
});
|
||||
}
|
||||
if (['edit'].includes(this.modalOperation.key)) {
|
||||
this.$refs.configRef.triggerSubmit().then(() => {
|
||||
this.editType();
|
||||
});
|
||||
}
|
||||
if (['remove'].includes(this.modalOperation.key)) {
|
||||
this.removeType();
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
this.typeData = {};
|
||||
this.modalVisible = false;
|
||||
this.typeFieldErrors = {};
|
||||
}
|
||||
},
|
||||
});
|
||||
</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;
|
||||
}
|
||||
|
||||
: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>
|
||||
48
nervui-resource-repository/tsconfig.json
Normal file
48
nervui-resource-repository/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"
|
||||
]
|
||||
}
|
||||
10
nervui-resource-repository/vite.config.ts
Normal file
10
nervui-resource-repository/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-repository/vue.config.ts
Normal file
3
nervui-resource-repository/vue.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
runtimeCompiler: true,
|
||||
};
|
||||
Reference in New Issue
Block a user