初始化magic-boot项目
|
@ -0,0 +1,8 @@
|
||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile name="Maven default annotation processors profile" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<module name="magic-boot" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
<component name="JavacSettings">
|
||||||
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
|
<module name="magic-boot" options="-parameters" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="magic-boot@manage.zh-cc.top" uuid="f6959f5a-43fe-4eb9-aae5-284f2c2dfea4">
|
||||||
|
<driver-ref>mysql.8</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<imported>true</imported>
|
||||||
|
<remarks>$PROJECT_DIR$/magic-boot/src/main/resources/application.yml</remarks>
|
||||||
|
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:mysql://manage.zh-cc.top:14725/magic-boot?useSSL=false&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai</jdbc-url>
|
||||||
|
<jdbc-additional-properties>
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
|
</jdbc-additional-properties>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/magic-boot/src/main/java" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Central Repository" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/magic-boot/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_22" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/magic.iml" filepath="$PROJECT_DIR$/.idea/magic.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="PROJECT" dialect="MySQL" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1 @@
|
||||||
|
VITE_APP_BASE_API = 'http://127.0.0.1:8089/'
|
|
@ -0,0 +1,2 @@
|
||||||
|
#VITE_APP_BASE_API = 'http://localhost:8089/'
|
||||||
|
VITE_APP_BASE_API = 'http://192.168.1.9:8089/'
|
|
@ -0,0 +1,53 @@
|
||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
package-lock.json
|
||||||
|
tests/**/coverage/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
|
||||||
|
|
||||||
|
*.log
|
||||||
|
|
||||||
|
logs/
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 吕金泽
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,52 @@
|
||||||
|
##
|
||||||
|
|
||||||
|
后端:
|
||||||
|
<span style="margin-right: 5px">
|
||||||
|
<a href="https://gitee.com/ssssssss-team/magic-boot" target="_blank"><img src="https://gitee.com/ssssssss-team/magic-boot/badge/star.svg?theme=white"></a>
|
||||||
|
</span>
|
||||||
|
<a href="https://github.com/zegezy/magic-boot" target="_blank"><img src="https://img.shields.io/github/stars/zegezy/magic-boot.svg?style=social"></a>
|
||||||
|
前端:
|
||||||
|
<span style="margin-right: 5px">
|
||||||
|
<a href="https://gitee.com/ssssssss-team/magic-boot-naive" target="_blank"><img src="https://gitee.com/ssssssss-team/magic-boot-naive/badge/star.svg?theme=white"></a>
|
||||||
|
</span>
|
||||||
|
<a href="https://github.com/zegezy/magic-boot-naive" target="_blank"><img src="https://img.shields.io/github/stars/zegezy/magic-boot-naive.svg?style=social"></a>
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
基于[ **magic-api** ](https://gitee.com/ssssssss-team/magic-api)搭建的快速开发平台,前端采用Vue3 + naive-ui最新版本搭建,依赖较少,运行速度快。对常用组件进行封装。利用Vue3的`@vue/compiler-sfc`单文件编译,动态编译组件,可以实现在浏览器编写Vue代码,既改即生效快速开发。
|
||||||
|
QQ群([ **576433387** ](https://jq.qq.com/?_wv=1027&k=KD6DPvB0))
|
||||||
|
|
||||||
|
|
||||||
|
| 代码 | 效果 |
|
||||||
|
|----|----|
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
- 菜单管理:树结构,配置菜单、权限按钮、选择关联组件等,支持全局模糊搜索
|
||||||
|
- 组织机构:树结构,配置组织机构,类型:部门、公司(选择项存在数据字典),支持全局模糊搜索
|
||||||
|
- 角色管理:角色菜单权限分配、设置数据范围等。
|
||||||
|
- 用户管理:用户添加、导入用户(支持导入前预览数据)、登录状态更改等
|
||||||
|
- 数据字典:分为`系统类`和`业务类`两类数据维护
|
||||||
|
- 动态组件:在线编写Vue3业务页面代码,保存编译生效
|
||||||
|
- 操作日志:接口调用操作日志查询
|
||||||
|
- 登录日志:系统登录日志查询,包含登录失败日志
|
||||||
|
- 数据库监控:Druid Monitor SQL监控、数据源信息查看等
|
||||||
|
- 在线用户:当前活跃在系统内的用户,可以选择踢人下线
|
||||||
|
|
||||||
|
## 在线体验
|
||||||
|
- 演示地址:
|
||||||
|
前台:[ **https://preview.magicboot.net/** ](https://preview.magicboot.net/)
|
||||||
|
后台:[ **https://api.magicboot.net:8443/magic/web/index.html** ](https://api.magicboot.net:8443/magic/web/index.html)
|
||||||
|
- 文档地址:[ **https://magicboot.net/** ](https://magicboot.net/)
|
||||||
|
- 账号:system/123456
|
||||||
|
|
||||||
|
## 系统截图
|
||||||
|
|  |  |
|
||||||
|
|---|---|
|
||||||
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
del /f/s/q node_modules > nul;
|
||||||
|
rmdir /s/q node_modules;
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<link rel="icon" href="/favicon.ico"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title></title>
|
||||||
|
<style>
|
||||||
|
html, body, #app {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"name": "magic-boot-vite",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"demo": "vite build --mode demo"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@layui/layer-vue": "^2.3.2",
|
||||||
|
"@volar/monaco": "1.7.9",
|
||||||
|
"@vueuse/core": "^10.1.0",
|
||||||
|
"ali-oss": "^6.17.1",
|
||||||
|
"axios": "^0.24.0",
|
||||||
|
"js-sha256": "^0.9.0",
|
||||||
|
"less": "^4.1.3",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"monaco-editor-core": "^0.46.0",
|
||||||
|
"monaco-editor-textmate": "^4.0.0",
|
||||||
|
"monaco-textmate": "^3.0.1",
|
||||||
|
"monaco-volar": "^0.4.0",
|
||||||
|
"onigasm": "^2.2.5",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
|
"pinia": "^2.0.33",
|
||||||
|
"vue": "^3.3.11",
|
||||||
|
"vue-cropper": "^1.0.2",
|
||||||
|
"vue-router": "^4.1.6",
|
||||||
|
"vuedraggable": "^4.1.0",
|
||||||
|
"xlsx": "^0.18.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vicons/antd": "^0.12.0",
|
||||||
|
"@vicons/fluent": "^0.12.0",
|
||||||
|
"@vicons/ionicons5": "^0.12.0",
|
||||||
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
|
"autoprefixer": "^10.4.13",
|
||||||
|
"naive-ui": "^2.38.1",
|
||||||
|
"postcss": "^8.4.21",
|
||||||
|
"sortablejs": "^1.15.0",
|
||||||
|
"tailwindcss": "^3.2.7",
|
||||||
|
"vite": "^4.1.0",
|
||||||
|
"vite-plugin-svg-icons": "^1.1.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,29 @@
|
||||||
|
<template>
|
||||||
|
<n-config-provider
|
||||||
|
:locale="zhCN"
|
||||||
|
:date-locale="dateZhCN"
|
||||||
|
v-bind="configProvider"
|
||||||
|
>
|
||||||
|
<n-message-provider>
|
||||||
|
<n-dialog-provider>
|
||||||
|
<router-view/>
|
||||||
|
</n-dialog-provider>
|
||||||
|
</n-message-provider>
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import {NConfigProvider, zhCN, dateZhCN} from 'naive-ui'
|
||||||
|
import global from '@/scripts/global'
|
||||||
|
const configProvider = reactive({})
|
||||||
|
configProvider.themeOverrides = global.selectTheme.themeOverrides
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
@import 'styles/index.less';
|
||||||
|
.n-config-provider{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {useDictStore} from "@/store/modules/dictStore";
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
export function getCheckboxData(props){
|
||||||
|
let handlerData = (dictData) => {
|
||||||
|
if(props.optionsFilter){
|
||||||
|
dictData = dictData.filter(props.optionsFilter)
|
||||||
|
}
|
||||||
|
return dictData
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (props.type) {
|
||||||
|
resolve(handlerData(dictStore.getDictType(props.type)))
|
||||||
|
} else if (props.url) {
|
||||||
|
$common.get(props.url, props.params).then(res => {
|
||||||
|
resolve(handlerData($common.mapLabelValue((res.data.list || res.data), props.labelField, props.valueField)))
|
||||||
|
})
|
||||||
|
} else if (props.options && props.options.length > 0) {
|
||||||
|
resolve(handlerData($common.mapLabelValue(props.options, props.labelField, props.valueField)))
|
||||||
|
} else{
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import {useDictStore} from "@/store/modules/dictStore";
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
export function getSelectData(props){
|
||||||
|
let listConcat = (dictData) => {
|
||||||
|
let selectList = []
|
||||||
|
if(props.optionsFilter){
|
||||||
|
dictData = dictData.filter(props.optionsFilter)
|
||||||
|
}
|
||||||
|
if (props.allOption) {
|
||||||
|
selectList = [{
|
||||||
|
value: '',
|
||||||
|
label: '全部'
|
||||||
|
}]
|
||||||
|
selectList = selectList.concat(dictData)
|
||||||
|
} else {
|
||||||
|
selectList = dictData
|
||||||
|
}
|
||||||
|
return selectList
|
||||||
|
}
|
||||||
|
let handlerData = (data) => {
|
||||||
|
let newData = []
|
||||||
|
data.forEach(it => {
|
||||||
|
newData.push({
|
||||||
|
label: it[props.labelField || 'label'],
|
||||||
|
value: it[props.valueField || 'value'].toString()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return newData
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (props.type) {
|
||||||
|
resolve(listConcat(dictStore.getDictType(props.type)))
|
||||||
|
} else if (props.url) {
|
||||||
|
$common.get(props.url, props.params).then(res => {
|
||||||
|
resolve(listConcat(handlerData(res.data.list || res.data)))
|
||||||
|
})
|
||||||
|
} else if (props.options && props.options.length > 0) {
|
||||||
|
resolve(listConcat(handlerData(props.options)))
|
||||||
|
} else{
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import treeTable from "@/scripts/treeTable";
|
||||||
|
|
||||||
|
export function getTreeSelectData(props){
|
||||||
|
return new Promise(resolve => {
|
||||||
|
$common.get(props.url).then(res => {
|
||||||
|
let options = res.data.list
|
||||||
|
treeTable.deleteEmptyChildren(options)
|
||||||
|
resolve(options)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
overflow: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
min-height: 25px;
|
||||||
|
min-width: 25px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 99px
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
border: 1px solid #efefef
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
--mb-main-icon-color: #909399;
|
||||||
|
}
|
||||||
|
.clear{
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import {defineAsyncComponent} from 'vue'
|
||||||
|
|
||||||
|
const components = import.meta.glob('./**/*.vue')
|
||||||
|
|
||||||
|
export function setupComponents(app) {
|
||||||
|
for (const [key, value] of Object.entries(components)) {
|
||||||
|
const name = key.substring(key.lastIndexOf('/') + 1, key.lastIndexOf('.'))
|
||||||
|
app.component(name, defineAsyncComponent(value))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
export default {
|
||||||
|
table: {
|
||||||
|
// 选中行颜色
|
||||||
|
selectedRowColor: '#D9DDE2',
|
||||||
|
// 单元格内容不换行显示
|
||||||
|
nowrap: true,
|
||||||
|
// todo 拖拽列之后回调此方法,用保存列
|
||||||
|
// saveCols(tableId, columns) {
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// todo 远程加载列
|
||||||
|
// remoteLoadColumn() {
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// todo 保存页码数
|
||||||
|
// savePage(pageSize,tableId){
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// todo 获取远程页码数
|
||||||
|
// async getPage(tableId, callback){
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// 下拉表格选项
|
||||||
|
dropMenus: [],
|
||||||
|
// 表头提示
|
||||||
|
titleTooltip:{
|
||||||
|
iconProps: {
|
||||||
|
icon: 'QuestionCircleFilled',
|
||||||
|
color: '#4b6fa7'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 单元格显示的图片
|
||||||
|
image:{
|
||||||
|
width: 30,
|
||||||
|
height: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
v-if="dynamic"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mb-icon"
|
||||||
|
:fill="color"
|
||||||
|
:style="{ width: size, height: size }"
|
||||||
|
>
|
||||||
|
<use :xlink:href="symbolId" :class="className"/>
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
</svg>
|
||||||
|
<n-icon
|
||||||
|
v-else
|
||||||
|
:color="color"
|
||||||
|
:size="size"
|
||||||
|
:title="title"
|
||||||
|
>
|
||||||
|
<component :is="xicons[icon]" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import xicons from '@/scripts/xicons'
|
||||||
|
import svgIcons from '@/scripts/svg-icons'
|
||||||
|
const props = defineProps({
|
||||||
|
prefix: {
|
||||||
|
type: String,
|
||||||
|
default: 'mb-icon'
|
||||||
|
},
|
||||||
|
icon: String,
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '1em'
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'currentColor'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const symbolId = computed(() => props.icon&&props.icon.startsWith('#') ? props.icon : `#${props.prefix}-${props.icon}`)
|
||||||
|
const className = computed(() => props.icon&&props.icon.startsWith('#') ? props.icon.substring(1) : `${props.prefix}-${props.icon}`)
|
||||||
|
const dynamic = ref(false)
|
||||||
|
if(svgIcons.indexOf(props.icon) != -1){
|
||||||
|
dynamic.value = true
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.mb-icon {
|
||||||
|
vertical-align: -0.25em;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,640 @@
|
||||||
|
<style scoped>
|
||||||
|
.edit-text{
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
:deep(.n-data-table-tr:hover .edit-text){
|
||||||
|
border: var(--mb-editor-table-tr-hover-border)
|
||||||
|
}
|
||||||
|
.edit-text:hover{
|
||||||
|
border: 1px dashed #ccc !important;
|
||||||
|
}
|
||||||
|
.edit-text-not-allowed{
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
:deep(.n-data-table-td){
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
:deep(.n-data-table .n-data-table-expand-trigger),
|
||||||
|
:deep(.n-data-table .n-data-table-indent),
|
||||||
|
:deep(.n-data-table .n-data-table-expand-placeholder){
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.copy-text{
|
||||||
|
margin: 0px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<mb-table
|
||||||
|
ref="magicTable"
|
||||||
|
v-bind="tableOptions"
|
||||||
|
@scroll="onScroll"
|
||||||
|
@dynamicSettingContextmenu="dynamicSettingContextmenu"
|
||||||
|
@contextmenuSelect="contextmenuSelect"
|
||||||
|
>
|
||||||
|
<template v-for="(col, colIndex) in cols" #[col.field]="{ row }">
|
||||||
|
<template v-if="row">
|
||||||
|
<template v-if="col.component">
|
||||||
|
<!-- 设置了组件并且是非编辑模式下 -->
|
||||||
|
<template v-if="col.alwaysEdit !== true && !edits[row._index_ + '' + colIndex]">
|
||||||
|
<div v-if="getIsEdit(col.edit, row)" class="flex items-center h-4/5">
|
||||||
|
<!-- 如果是可以编辑,则鼠标悬浮显示边框 -->
|
||||||
|
<div
|
||||||
|
@click="editMode(row._index_, colIndex, col, row)"
|
||||||
|
class="edit-text h-full"
|
||||||
|
:style="col.labelStyle"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
:name="col.field + '-view'"
|
||||||
|
:row="row"
|
||||||
|
:col="col"
|
||||||
|
:row-index="row._index_"
|
||||||
|
:col-index="colIndex"
|
||||||
|
>
|
||||||
|
<span v-if="col.show == undefined || (col.show && col.show(row))">
|
||||||
|
{{ getLabel(row[col.field], col) }}
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<mb-icon v-if="col.copyText" class="copy-text" icon="CopyOutline" @click="common.copyText(getLabel(row[col.field], col))" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex items-center">
|
||||||
|
<!-- 如果不可以编辑,则鼠标悬浮显示禁止标志 -->
|
||||||
|
<div class="edit-text-not-allowed flex-1" :style="col.labelStyle">
|
||||||
|
<slot
|
||||||
|
:name="col.field + '-view'"
|
||||||
|
:row="row"
|
||||||
|
:col="col"
|
||||||
|
:row-index="row._index_"
|
||||||
|
:col-index="colIndex"
|
||||||
|
>
|
||||||
|
<span v-if="col.show == undefined || (col.show && col.show(row))">
|
||||||
|
{{ common.getValidValue(getLabel(row[col.field], col), '-') }}
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<mb-icon v-if="col.copyText" class="copy-text" icon="CopyOutline" @click="common.copyText(common.getValidValue(getLabel(row[col.field], col), '-'))" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="flex items-center" v-if="(col.edit != undefined && col.edit(row) && col.alwaysEdit) || (col.edit == undefined && col.alwaysEdit) || (edits[row._index_ + '' + colIndex] && currentRowIndex == row._index_ && currentColIndex == colIndex)">
|
||||||
|
<div class="flex-1">
|
||||||
|
<!-- edit 和 alwaysEdit 可配合使用 比如符合条件的 可以一直保持编辑模式 -->
|
||||||
|
<slot
|
||||||
|
:name="col.field + '-edit'"
|
||||||
|
:row="row"
|
||||||
|
:col="col"
|
||||||
|
:row-index="row._index_"
|
||||||
|
:col-index="colIndex"
|
||||||
|
>
|
||||||
|
<!-- edit = true(始终编辑模式) 或者激活编辑模式 显示组件 -->
|
||||||
|
<component
|
||||||
|
v-if="!col.component.startsWith('n-')"
|
||||||
|
:ref="(el) => setComponentRef(row._index_, colIndex, el, col)"
|
||||||
|
:is="col.component.startsWith('#') ? col.component.substring(1) : 'mb-' + col.component"
|
||||||
|
v-model="row[col.field]"
|
||||||
|
v-bind="componentDynamicBind(row, col)"
|
||||||
|
:style="col.componentStyle"
|
||||||
|
@blur="componentBlur(row._index_, colIndex, col, row)"
|
||||||
|
:close-current-col-edit-mode="closeCurrentColEditMode"
|
||||||
|
/>
|
||||||
|
<!-- naive组件 大多使用 v-model:value 绑定,主要是兼容这个 -->
|
||||||
|
<component
|
||||||
|
v-else
|
||||||
|
:ref="(el) => setComponentRef(row._index_, colIndex, el, col)"
|
||||||
|
:is="col.component"
|
||||||
|
v-model:value="row[col.field]"
|
||||||
|
v-bind="componentDynamicBind(row, col)"
|
||||||
|
:style="col.componentStyle"
|
||||||
|
@blur="componentBlur(row._index_, colIndex, col, row)"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<mb-icon v-if="col.copyText" class="copy-text" icon="CopyOutline" @click="common.copyText(row[col.field])" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="flex items-center">
|
||||||
|
<!-- 如果没有设置组件 直接显示数据 -->
|
||||||
|
<div :style="col.labelStyle" class="flex-1 w-full">
|
||||||
|
<slot
|
||||||
|
:name="col.field + '-view'"
|
||||||
|
:row="row"
|
||||||
|
:col="col"
|
||||||
|
:row-index="row._index_"
|
||||||
|
:col-index="colIndex"
|
||||||
|
>
|
||||||
|
<mb-table-tooltip v-if="col.show == undefined || (col.show && col.show(row))">
|
||||||
|
{{ row[col.field] }}
|
||||||
|
</mb-table-tooltip>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<mb-icon v-if="col.copyText" class="copy-text" icon="CopyOutline" @click="common.copyText(row[col.field])" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</mb-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
import { reactive, ref, nextTick, toRaw, watch } from 'vue'
|
||||||
|
import { getSelectData } from "@/api/components/mb-select.js";
|
||||||
|
import { getTreeSelectData } from "@/api/components/mb-tree-select";
|
||||||
|
import { omit, cloneDeep } from 'lodash-es'
|
||||||
|
import treeTable from "@/scripts/treeTable";
|
||||||
|
import common from '@/scripts/common'
|
||||||
|
const magicTable = ref()
|
||||||
|
const props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
cols: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
showNo: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
operationWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 85
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
rowKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
rowHoverEdit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
keepCurrentPage: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 深拷贝对象,取消对象引用
|
||||||
|
const tableOptions = reactive({
|
||||||
|
id: props.id,
|
||||||
|
page: props.page,
|
||||||
|
showNo: props.showNo,
|
||||||
|
selectedRowEnable: false,
|
||||||
|
contextmenuEnable: true,
|
||||||
|
rowKey: props.rowKey,
|
||||||
|
keepCurrentPage: props.keepCurrentPage,
|
||||||
|
data: [],
|
||||||
|
cols: [],
|
||||||
|
props: props.props
|
||||||
|
})
|
||||||
|
const currentColIndex = ref(0)
|
||||||
|
const currentRowIndex = ref(0)
|
||||||
|
const currentCol = ref()
|
||||||
|
const currentRow = ref()
|
||||||
|
// 是否可编辑单元格,可以控制某行某列
|
||||||
|
const edits = ref({})
|
||||||
|
const showLabelData = reactive({})
|
||||||
|
const disableComponentCallbackFields = ref([])
|
||||||
|
|
||||||
|
function setData(data){
|
||||||
|
let newData = cloneDeep(data)
|
||||||
|
tableOptions.data = dataAddIndex(newData)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i in props.cols) {
|
||||||
|
let col = props.cols[i]
|
||||||
|
getShowLabelData(col)
|
||||||
|
if(!col.type){
|
||||||
|
col.type = 'dynamic'
|
||||||
|
}
|
||||||
|
if(!col.editIcon){
|
||||||
|
col.editIcon = col.component ? true : false
|
||||||
|
}
|
||||||
|
if(col.copyAll){
|
||||||
|
col.copyAllCallback = (col) => {
|
||||||
|
let labels = []
|
||||||
|
treeTable.recursionRearrange(getData()).forEach(it => {
|
||||||
|
labels.push(common.getValidValue(getLabel(it[col.field], col), '-'))
|
||||||
|
})
|
||||||
|
$common.copyText(labels.join('\n'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableOptions.cols.push(col)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getShowLabelData(col){
|
||||||
|
switch (col.component) {
|
||||||
|
case 'select':
|
||||||
|
getSelectData(col.componentProps).then(data => {
|
||||||
|
showLabelData[col.field] = data
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case 'tree-select':
|
||||||
|
getTreeSelectData(col.componentProps).then(data => {
|
||||||
|
showLabelData[col.field] = treeTable.recursionRearrange(data)
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons = []
|
||||||
|
if(props.operation && !props.preview){
|
||||||
|
let deleteClick = (row, confirm) => {
|
||||||
|
if(confirm){
|
||||||
|
$common.warning('此操作将永久删除该数据, 是否继续?', () => {
|
||||||
|
deleteRow(row)
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
deleteRow(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let deleteType = props.operation.delete instanceof Object
|
||||||
|
let subType = props.operation.sub instanceof Object
|
||||||
|
let sameType = props.operation.same instanceof Object
|
||||||
|
buttons.push(...[{
|
||||||
|
label: '删除',
|
||||||
|
link: true,
|
||||||
|
icon: 'Delete24Regular',
|
||||||
|
if: (row) => {
|
||||||
|
return deleteType ? props.operation.delete.if === undefined || (props.operation.delete.if && props.operation.delete.if(row)) : props.operation.delete
|
||||||
|
},
|
||||||
|
click: (row) => {
|
||||||
|
deleteType ? deleteClick(row, props.operation.delete.confirm) : deleteClick(row)
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: '添加下级',
|
||||||
|
link: true,
|
||||||
|
icon: 'sub-level',
|
||||||
|
if: (row) => {
|
||||||
|
return subType ? props.operation.sub.if === undefined || (props.operation.sub.if && props.operation.sub.if(row)) : props.operation.sub
|
||||||
|
},
|
||||||
|
click: (row) => {
|
||||||
|
addChildrenRow(row)
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
label: '添加同级',
|
||||||
|
link: true,
|
||||||
|
icon: 'same-level',
|
||||||
|
if: (row) => {
|
||||||
|
return sameType ? props.operation.same.if === undefined || (props.operation.same.if && props.operation.same.if(row)) : props.operation.same
|
||||||
|
},
|
||||||
|
click: (row) => {
|
||||||
|
addRow(row)
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
if(props.operation.buttons){
|
||||||
|
buttons.push(...props.operation.buttons)
|
||||||
|
}
|
||||||
|
tableOptions.cols.push({
|
||||||
|
label: '操作',
|
||||||
|
type: 'buttons',
|
||||||
|
width: props.operationWidth,
|
||||||
|
fixed: 'right',
|
||||||
|
buttons: buttons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function componentDynamicBind(row, col){
|
||||||
|
let bind = {...col.componentProps}
|
||||||
|
if(col.componentProps){
|
||||||
|
for(let key in col.componentProps){
|
||||||
|
if(key.startsWith('on') && typeof(col.componentProps[key]) == 'function'){
|
||||||
|
bind[key] = (...data) => {
|
||||||
|
if(disableComponentCallbackFields.value.indexOf(col.field) == -1){
|
||||||
|
let _data = {
|
||||||
|
editorCurrentRow: row
|
||||||
|
}
|
||||||
|
for(let key in data){
|
||||||
|
// 如果标识了需要解构,把key赋值给_data
|
||||||
|
if(data[key] instanceof Object && data[key]['_deconstruction_']){
|
||||||
|
for(let key2 in data[key]){
|
||||||
|
_data[key2] = data[key][key2]
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
_data[key] = data[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
col.componentProps[key](_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(col.handlerComponentProps){
|
||||||
|
col.handlerComponentProps(bind, row)
|
||||||
|
}
|
||||||
|
return bind
|
||||||
|
}
|
||||||
|
|
||||||
|
// 增加同级
|
||||||
|
function addRow(row){
|
||||||
|
// 递归树结构数据 查到当前行数据 在父级push
|
||||||
|
recursionAddRow(tableOptions.data, row._index_)
|
||||||
|
tableDataAddIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
function recursionAddRow(children, index){
|
||||||
|
children.forEach(it => {
|
||||||
|
if(it._index_ == index){
|
||||||
|
children.push({[props.rowKey]: $common.uuid()})
|
||||||
|
}
|
||||||
|
if(it.children && it.children.length > 0){
|
||||||
|
recursionAddRow(it.children, index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加下级
|
||||||
|
function addChildrenRow(row){
|
||||||
|
if(row.children && row.children.length > 0){
|
||||||
|
row.children.push({[props.rowKey]: $common.uuid()})
|
||||||
|
}else{
|
||||||
|
row.children = [{[props.rowKey]: $common.uuid()}]
|
||||||
|
}
|
||||||
|
tableDataAddIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归增加index(主要是为了解决树形结构数据,naive-ui的index不包含子级的问题)
|
||||||
|
* https://www.naiveui.com/zh-CN/os-theme/components/data-table#expand.vue
|
||||||
|
*/
|
||||||
|
function dataAddIndex(children){
|
||||||
|
let index = { index: 0 }
|
||||||
|
// 深拷贝解除对象引用
|
||||||
|
let data = cloneDeep(children)
|
||||||
|
recursionAddIndex(data, index)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function tableDataAddIndex(){
|
||||||
|
tableOptions.data = dataAddIndex(tableOptions.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
function recursionAddIndex(children, index){
|
||||||
|
children.forEach(it => {
|
||||||
|
it._index_ = index.index
|
||||||
|
index.index++
|
||||||
|
if(it.children && it.children.length > 0){
|
||||||
|
recursionAddIndex(it.children, index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除本行及以下子节点
|
||||||
|
function deleteRow(row){
|
||||||
|
recursionDelete(tableOptions.data, row._index_)
|
||||||
|
tableDataAddIndex()
|
||||||
|
let deleteAfter = props.operation?.delete?.deleteAfter;
|
||||||
|
deleteAfter && deleteAfter()
|
||||||
|
}
|
||||||
|
|
||||||
|
function recursionDelete(children, index){
|
||||||
|
children.forEach((it, i) => {
|
||||||
|
if(it._index_ == index){
|
||||||
|
children.splice(i, 1)
|
||||||
|
}
|
||||||
|
if(it.children && it.children.length > 0){
|
||||||
|
recursionDelete(it.children, index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据edit(boolean) 或者 edit(function)判断此列是否可以编辑,不能编辑的话 显示文字
|
||||||
|
function getIsEdit(edit, row){
|
||||||
|
if(props.preview){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(typeof(edit) == 'function'){
|
||||||
|
return edit(row)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLabelByData({col, value, data, valueField, labelField}){
|
||||||
|
let dataList = data || showLabelData[col.field]
|
||||||
|
if(dataList && dataList.length > 0){
|
||||||
|
let labels = []
|
||||||
|
let values = value.toString().split(',')
|
||||||
|
for(let value of values){
|
||||||
|
let data = dataList.filter(it => it[col.showLabel?.valueField || valueField] == value)[0];
|
||||||
|
labels.push(data && data[col.showLabel?.labelField || labelField])
|
||||||
|
}
|
||||||
|
return labels.join(',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反显方法
|
||||||
|
function getLabel(value, col){
|
||||||
|
if($common.notEmptyNot01(value)){
|
||||||
|
let valueField = col.componentProps?.valueField || 'value'
|
||||||
|
let labelField = col.componentProps?.labelField || 'label'
|
||||||
|
if(['select', 'tree-select'].indexOf(col.component) != -1){
|
||||||
|
if(col.component == 'tree-select'){
|
||||||
|
valueField = col.componentProps?.valueField || 'key'
|
||||||
|
labelField = col.componentProps?.labelField || 'label'
|
||||||
|
}else if(col.component == 'select'){
|
||||||
|
valueField = 'value'
|
||||||
|
labelField = 'label'
|
||||||
|
}
|
||||||
|
return getLabelByData({col, value, valueField, labelField})
|
||||||
|
}else if(col.showLabel){
|
||||||
|
return getLabelByData({col, value, data: col.showLabel.data, valueField, labelField})
|
||||||
|
}else{
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentEditRef = null
|
||||||
|
function componentBlur(rowIndex, colIndex, col, row){
|
||||||
|
edits.value[rowIndex + '' + colIndex] = false
|
||||||
|
if(disableComponentCallbackFields.value.indexOf(col.field) == -1){
|
||||||
|
col.blur && col.blur(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭当前单元格编辑模式
|
||||||
|
function closeCurrentColEditMode(){
|
||||||
|
componentBlur(currentRowIndex.value, currentColIndex.value, currentCol.value, currentRow.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态设置ref
|
||||||
|
function setComponentRef(rowIndex, colIndex, el, col){
|
||||||
|
if(el && edits.value[rowIndex + '' + colIndex]){
|
||||||
|
currentEditRef = el
|
||||||
|
nextTick(() => {
|
||||||
|
if(el.focus){
|
||||||
|
el.focus()
|
||||||
|
}else{
|
||||||
|
let key = Object.keys(toRaw(el.$refs))[0]
|
||||||
|
// 执行组件的focus方法,如果组件内template根节点内有focus方法 直接设置ref属性即可,比如mb-input
|
||||||
|
if(key){
|
||||||
|
if(el.$refs[key].focus){
|
||||||
|
el.$refs[key].focus()
|
||||||
|
}else{
|
||||||
|
el.$refs[key].$el.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentInit(el, col)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件初始化时
|
||||||
|
function componentInit(el, col){
|
||||||
|
switch (col.component){
|
||||||
|
case 'textarea':
|
||||||
|
// 放大显示
|
||||||
|
textareaInit(el, col)
|
||||||
|
break;
|
||||||
|
case 'tree-select':
|
||||||
|
// 展开
|
||||||
|
el.expand()
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// textarea初始化
|
||||||
|
function textareaInit(el, col){
|
||||||
|
col.componentStyle = col.componentStyle || {}
|
||||||
|
let parentNodeRect = el.$el.parentNode.getBoundingClientRect()
|
||||||
|
let tableRect = magicTable.value.$el.getBoundingClientRect()
|
||||||
|
let tableWidth = magicTable.value.$el.clientWidth
|
||||||
|
let left = parentNodeRect.left - tableRect.left
|
||||||
|
let top = parentNodeRect.top - tableRect.top - magicTable.value.$el.querySelector('.n-data-table-base-table-header').offsetHeight
|
||||||
|
col.componentStyle.position = 'absolute'
|
||||||
|
col.componentStyle['z-index'] = 999999
|
||||||
|
col.componentStyle.width = col.componentStyle.width || parentNodeRect.width + 'px'
|
||||||
|
if(tableWidth - left - col.componentStyle.width.match(/\d+/)[0] < 1){
|
||||||
|
col.componentStyle.right = `1px`
|
||||||
|
col.componentStyle.left = 'unset'
|
||||||
|
}else{
|
||||||
|
col.componentStyle.left = `${left}px`
|
||||||
|
col.componentStyle.right = 'unset'
|
||||||
|
}
|
||||||
|
col.componentStyle.top = `${top}px`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 让单元格变成预览模式
|
||||||
|
function previewMode(rowIndex, colIndex){
|
||||||
|
edits.value[rowIndex + '' + colIndex] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 让单元格变成编辑模式
|
||||||
|
function editMode(rowIndex, colIndex, col, row){
|
||||||
|
currentRowIndex.value = rowIndex
|
||||||
|
currentColIndex.value = colIndex
|
||||||
|
edits.value[rowIndex + '' + colIndex] = true
|
||||||
|
if(col){
|
||||||
|
currentCol.value = col
|
||||||
|
}
|
||||||
|
if(row){
|
||||||
|
currentRow.value = row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onScroll(){
|
||||||
|
if(currentCol.value && currentCol.value.component == 'textarea' && edits.value[currentRowIndex.value + '' + currentColIndex.value]){
|
||||||
|
edits.value[currentRowIndex.value + '' + currentColIndex.value] = false
|
||||||
|
currentCol.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let contextmenus = []
|
||||||
|
function dynamicSettingContextmenu(row){
|
||||||
|
contextmenus = []
|
||||||
|
for(let button of buttons){
|
||||||
|
if((button.if && button.if(row)) || button.if === undefined){
|
||||||
|
contextmenus.push({
|
||||||
|
key: button.label,
|
||||||
|
title: button.label,
|
||||||
|
row: row,
|
||||||
|
click: button.click,
|
||||||
|
icon: $common.renderIcon(button.icon)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contextmenus
|
||||||
|
}
|
||||||
|
|
||||||
|
function contextmenuSelect(key){
|
||||||
|
let menu = contextmenus.filter(it => it.key == key)[0]
|
||||||
|
if(menu.click){
|
||||||
|
menu.click(menu.row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(){
|
||||||
|
return tableOptions.data.map(it => omit({...it}, '_index_'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function push(data){
|
||||||
|
tableOptions.data.push(data)
|
||||||
|
tableDataAddIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
function unshift(data){
|
||||||
|
tableOptions.data.unshift(data)
|
||||||
|
tableDataAddIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTableRef(){
|
||||||
|
return magicTable.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableComponentCallback(fields){
|
||||||
|
disableComponentCallbackFields.value.push(...fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableComponentCallback(fields){
|
||||||
|
for(let field of fields){
|
||||||
|
let index = disableComponentCallbackFields.value.indexOf(field)
|
||||||
|
if(index != -1){
|
||||||
|
disableComponentCallbackFields.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(magicTable, () => {
|
||||||
|
if(props.rowHoverEdit){
|
||||||
|
magicTable.value.$el.style.setProperty('--mb-editor-table-tr-hover-border', '1px dashed #ccc')
|
||||||
|
}else{
|
||||||
|
magicTable.value.$el.style.setProperty('--mb-editor-table-tr-hover-border', 'unset')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getTableRef,
|
||||||
|
previewMode,
|
||||||
|
editMode,
|
||||||
|
getData,
|
||||||
|
setData,
|
||||||
|
push,
|
||||||
|
unshift,
|
||||||
|
disableComponentCallback,
|
||||||
|
enableComponentCallback,
|
||||||
|
closeCurrentColEditMode
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<div class="pagination-container" style="height: 50px">
|
||||||
|
<n-pagination
|
||||||
|
:page="page"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:item-count="itemCount"
|
||||||
|
:page-sizes="[10, 20, 50, 100, 200]"
|
||||||
|
@updatePage="emit('update-page', $event)"
|
||||||
|
@updatePageSize="emit('update-page-size', $event)"
|
||||||
|
show-quick-jumper
|
||||||
|
show-size-picker
|
||||||
|
style="margin: 0 auto;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
page: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 10
|
||||||
|
},
|
||||||
|
itemCount: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update-page', 'update-page-size'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pagination-container{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,69 @@
|
||||||
|
<!--
|
||||||
|
Magic-Boot 单选字典组件
|
||||||
|
@author Yean
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<n-radio-group :size="$global.uiSize.value" :value="modelValue" @change="handleChange">
|
||||||
|
<n-radio-button v-for="item in dictList" :key="item.value" :value="item.value" v-if="isButton">
|
||||||
|
{{ item.label }}
|
||||||
|
</n-radio-button>
|
||||||
|
<n-radio v-for="item in dictList" :key="item.value" :value="item.value" v-else>{{ item.label }}</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, watch} from "vue";
|
||||||
|
import {useDictStore} from "@/store/modules/dictStore";
|
||||||
|
|
||||||
|
const emits = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
|
||||||
|
const modelValue = ref();
|
||||||
|
|
||||||
|
const dictList = ref([]);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 选中值
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 字典Key值
|
||||||
|
dictKey: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 是否为按钮模式
|
||||||
|
isButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (props.dictKey) {
|
||||||
|
dictList.value = dictStore.getDictType(props.dictKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.modelValue) {
|
||||||
|
modelValue.value = props.modelValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.modelValue, value => {
|
||||||
|
modelValue.value = value;
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.dictKey, value => {
|
||||||
|
if (value) {
|
||||||
|
dictList.value = dictStore.getDictType(value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
emits("update:modelValue", e.target.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,136 @@
|
||||||
|
<template>
|
||||||
|
<n-form :size="$global.uiSize.value" inline label-placement="left" @keyup.enter="search" style="flex-wrap:wrap">
|
||||||
|
<template v-for="(it, i) in where">
|
||||||
|
<n-form-item v-if="formItemIf(it)" :label="it.label" :key="i" :show-feedback="false">
|
||||||
|
<component
|
||||||
|
:is="!it.component ? 'mb-input' : it.component.startsWith('n-') || 'mb-' + it.component"
|
||||||
|
v-model="it.value"
|
||||||
|
:item-label="it.label"
|
||||||
|
v-bind="it.componentProps || it.props"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</template>
|
||||||
|
<n-form-item :show-feedback="false">
|
||||||
|
<n-space>
|
||||||
|
<n-button :size="$global.uiSize.value" type="primary" @click="search">
|
||||||
|
<mb-icon icon="Search" />
|
||||||
|
搜索
|
||||||
|
</n-button>
|
||||||
|
<n-button :size="$global.uiSize.value" @click="reset">
|
||||||
|
<mb-icon icon="TrashOutline" />
|
||||||
|
清空
|
||||||
|
</n-button>
|
||||||
|
<slot name="buttons"/>
|
||||||
|
</n-space>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {nextTick, watch} from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
where: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
notReset: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function formItemIf(it){
|
||||||
|
if(it && it instanceof Object){
|
||||||
|
it.show = it.show === undefined ? !!it.label : it.show
|
||||||
|
return it.show
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function reRenderComponent(key){
|
||||||
|
props.where[key].show = false
|
||||||
|
nextTick(() => {
|
||||||
|
props.where[key].show = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in props.where) {
|
||||||
|
if (props.where[key] instanceof Object) {
|
||||||
|
if (props.where[key].value === undefined) {
|
||||||
|
props.where[key].value = null
|
||||||
|
}
|
||||||
|
watch(() => props.where[key].value, (value) => {
|
||||||
|
props.where[key].valueChange && props.where[key].valueChange({value, reRenderComponent, where: props.where})
|
||||||
|
})
|
||||||
|
if (props.where[key].component === 'date') {
|
||||||
|
let isResetValue = false
|
||||||
|
for (let k in props.where[key]) {
|
||||||
|
if (k === 'resetValue') {
|
||||||
|
isResetValue = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isResetValue) {
|
||||||
|
// date 组件 要reset为 null
|
||||||
|
props.where[key].resetValue = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(['search'])
|
||||||
|
|
||||||
|
function input(input) {
|
||||||
|
if (input) {
|
||||||
|
emit('search')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
for (let key in props.where) {
|
||||||
|
if (props.where[key] instanceof Object) {
|
||||||
|
if (props.where[key].component && props.where[key].component.startsWith('date') && props.where[key].value instanceof Array && props.where[key].value.join(',')) {
|
||||||
|
props.where[key].value = props.where[key].value.join(',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
emit('search')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
for (let key in props.where) {
|
||||||
|
if (props.notReset.indexOf(key) === -1) {
|
||||||
|
if (props.where[key] instanceof Object) {
|
||||||
|
let isResetValue = false
|
||||||
|
for (let k in props.where[key]) {
|
||||||
|
if (k === 'resetValue') {
|
||||||
|
isResetValue = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isResetValue) {
|
||||||
|
props.where[key].value = props.where[key].resetValue
|
||||||
|
} else {
|
||||||
|
if (props.where[key].value instanceof Array) {
|
||||||
|
props.where[key].value = []
|
||||||
|
} else {
|
||||||
|
props.where[key].value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.where[key] instanceof Array) {
|
||||||
|
props.where[key] = []
|
||||||
|
} else {
|
||||||
|
props.where[key] = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextTick(() => emit('search'))
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ reRenderComponent })
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,112 @@
|
||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<template v-if="type === 'switch'">
|
||||||
|
<mb-switch
|
||||||
|
v-model="row[col.field]"
|
||||||
|
@change="col.change(row)"
|
||||||
|
v-if="col.if != undefined ? col.if(row) : true"
|
||||||
|
:checked-value="col.checkedValue"
|
||||||
|
:unchecked-value="col.uncheckedValue"
|
||||||
|
v-bind="col.props"
|
||||||
|
/>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type === 'html'">
|
||||||
|
<mb-table-tooltip :nowrap="nowrap">
|
||||||
|
<span v-html="getLabel(row, col)"></span>
|
||||||
|
</mb-table-tooltip>
|
||||||
|
<mb-icon v-if="col.copyText" class="copy-text" icon="CopyOutline" @click="copyText(getValueByPath(row, col.field))" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type === 'templet'">
|
||||||
|
<mb-table-tooltip :nowrap="nowrap">
|
||||||
|
<span v-html="col.templet(row, col, index)"></span>
|
||||||
|
</mb-table-tooltip>
|
||||||
|
<mb-icon v-if="col.copyText" class="copy-text" icon="CopyOutline" @click="copyText(col.templet(row, col, index))" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type === 'buttons'">
|
||||||
|
<n-space>
|
||||||
|
<template v-for="it in col.buttons">
|
||||||
|
<slot v-if="it.type == 'dynamic'" :name="it.field" :row="row" :index="index" />
|
||||||
|
<n-button
|
||||||
|
v-else-if="it.if != undefined ? it.if(row) : true"
|
||||||
|
v-permission="it.permission"
|
||||||
|
:type="it.type"
|
||||||
|
:text="it.link"
|
||||||
|
:dashed="it.dashed"
|
||||||
|
:href="it.href"
|
||||||
|
:color="it.color"
|
||||||
|
:target="it.target"
|
||||||
|
:tag="it.tag || (it.link ? 'a' : 'button')"
|
||||||
|
:text-color="it.textColor || '#2D8CF0'"
|
||||||
|
@click="it.click(row)"
|
||||||
|
>
|
||||||
|
<template #icon v-if="it.icon">
|
||||||
|
<mb-icon :icon="it.icon" />
|
||||||
|
</template>
|
||||||
|
{{ it.label }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type === 'dictType'">
|
||||||
|
<mb-table-tooltip :nowrap="nowrap">
|
||||||
|
<span>{{ dictStore.getDictLabel(col.dictType, getValueByPath(row, col.field) + '') }}</span>
|
||||||
|
</mb-table-tooltip>
|
||||||
|
<mb-icon v-if="col.copyText" class="copy-text" icon="CopyOutline" @click="copyText(dictStore.getDictLabel(col.dictType, getValueByPath(row, col.field) + ''))" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="type === 'image'">
|
||||||
|
<n-image-group v-if="row[col.field]">
|
||||||
|
<n-space>
|
||||||
|
<n-image
|
||||||
|
v-for="it in row[col.field].split(',')"
|
||||||
|
:width="componentProperties.table.image.width"
|
||||||
|
:height="componentProperties.table.image.height"
|
||||||
|
:src="it && it.startsWith('http') ? it : $global.filePrefix + encodeURIComponent(it)"
|
||||||
|
/>
|
||||||
|
</n-space>
|
||||||
|
</n-image-group>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<mb-table-tooltip :nowrap="nowrap">
|
||||||
|
<span v-html="getLabel(row, col)"></span>
|
||||||
|
</mb-table-tooltip>
|
||||||
|
<mb-icon v-if="col.copyText" class="copy-text" icon="CopyOutline" @click="copyText(getLabel(row, col))" />
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {get as getValueByPath} from "lodash-es";
|
||||||
|
import componentProperties from '@/components/magic-component-properties'
|
||||||
|
import {useDictStore} from "@/store/modules/dictStore";
|
||||||
|
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
nowrap: {
|
||||||
|
type: Boolean,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
function copyText(text){
|
||||||
|
$common.copyText(text)
|
||||||
|
}
|
||||||
|
function getLabel(row, col){
|
||||||
|
return $common.notEmptyNot01(getValueByPath(row, col.field)) ? getValueByPath(row, col.field) : $common.notEmptyNot01(col.defaultValue) ? col.defaultValue : ''
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,68 @@
|
||||||
|
<template>
|
||||||
|
<n-tree-select
|
||||||
|
ref="magicTreeSelect"
|
||||||
|
v-model:value="selectValue"
|
||||||
|
:options="options"
|
||||||
|
:placeholder="placeholder || (itemLabel && '请选择' + itemLabel)"
|
||||||
|
:multiple="multiple"
|
||||||
|
:key-field="valueField"
|
||||||
|
:label-field="labelField"
|
||||||
|
v-bind="props.props"
|
||||||
|
default-expand-all
|
||||||
|
filterable
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref,nextTick } from "vue"
|
||||||
|
import { watchValue } from '@/components/magic/scripts/watch-join-update.js'
|
||||||
|
import { getTreeSelectData } from '@/api/components/mb-tree-select.js'
|
||||||
|
|
||||||
|
const magicTreeSelect = ref()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
itemLabel: String,
|
||||||
|
placeholder: String,
|
||||||
|
props: Object,
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
join: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label'
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'key'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectValue = ref(props.multiple ? [] : null)
|
||||||
|
const options = ref([])
|
||||||
|
|
||||||
|
getTreeSelectData(props).then(data => {
|
||||||
|
options.value = data
|
||||||
|
watchValue(selectValue, props, emit)
|
||||||
|
})
|
||||||
|
|
||||||
|
function expand(){
|
||||||
|
nextTick(() => magicTreeSelect.value.handleTriggerClick())
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ expand })
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,399 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full w-full" :style="style">
|
||||||
|
<div class="flex flex-col h-full w-full">
|
||||||
|
<div style="margin-bottom: 5px;" v-if="expand || checked">
|
||||||
|
<n-space>
|
||||||
|
<n-button :size="$global.uiSize.value" v-if="expand" type="primary" @click="doExpand">展开/折叠</n-button>
|
||||||
|
<n-button :size="$global.uiSize.value" v-if="checked" type="primary"
|
||||||
|
@click="() => { treeAllChecked = !treeAllChecked; checkedAll(treeAllChecked) }">全选/全不选
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 5px" v-if="search">
|
||||||
|
<n-input :size="$global.uiSize.value" v-model:value="searchValue" placeholder="输入关键字进行过滤"/>
|
||||||
|
</div>
|
||||||
|
<n-tree
|
||||||
|
class="flex-1"
|
||||||
|
v-bind="props.props"
|
||||||
|
virtual-scroll
|
||||||
|
block-line
|
||||||
|
:cascade="cascade"
|
||||||
|
:checkable="checkable"
|
||||||
|
:show-line="showLine"
|
||||||
|
:style="treeStyle"
|
||||||
|
key-field="id"
|
||||||
|
label-field="name"
|
||||||
|
:data="treeData"
|
||||||
|
:checked-keys="checkedKeys"
|
||||||
|
:pattern="searchValue"
|
||||||
|
:show-irrelevant-nodes="false"
|
||||||
|
:default-expand-all="defaultExpandAll"
|
||||||
|
:node-props="nodeProps"
|
||||||
|
@update:checked-keys="updateCheckedKeys"
|
||||||
|
@update:expanded-keys="updatePrefixWithExpaned"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<n-dropdown
|
||||||
|
placement="bottom-start"
|
||||||
|
trigger="manual"
|
||||||
|
:show="showDropdown"
|
||||||
|
:options="contextmenu"
|
||||||
|
:x="dropdownX"
|
||||||
|
:y="dropdownY"
|
||||||
|
@select="dropdownSelect"
|
||||||
|
@clickoutside="() => showDropdown = false"
|
||||||
|
>
|
||||||
|
<!-- 需要有一个空的节点,不然报错-->
|
||||||
|
<div></div>
|
||||||
|
</n-dropdown>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {watch, ref, nextTick, onBeforeMount, h} from 'vue'
|
||||||
|
import treeTable from '@/scripts/treeTable'
|
||||||
|
import { clone, uniq, pull, pullAll, isEmpty } from 'lodash-es'
|
||||||
|
import MbIcon from "@/components/magic/basic/mb-icon.vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'check-change', 'node-click'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
treeStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expand: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
checked: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
searchWidth: {
|
||||||
|
type: String,
|
||||||
|
default: '230px'
|
||||||
|
},
|
||||||
|
keyAll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
cascade: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
checkable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showLine: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
contextmenu: {
|
||||||
|
type: Array,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
defaultExpandAll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const showDropdown = ref(false)
|
||||||
|
const dropdownX = ref()
|
||||||
|
const dropdownY = ref()
|
||||||
|
const checkedAllKeys = ref([])
|
||||||
|
const checkedKeys = ref([])
|
||||||
|
|
||||||
|
function updateCheckedKeys(keys, option, meta) {
|
||||||
|
if(!props.cascade){
|
||||||
|
checkedKeys.value = keys
|
||||||
|
}else{
|
||||||
|
let action = meta.action
|
||||||
|
let node = meta.node
|
||||||
|
let id = node.id
|
||||||
|
let pid = node.pid
|
||||||
|
let children = node.children
|
||||||
|
// 去重
|
||||||
|
checkedAllKeys.value = uniq(checkedAllKeys.value)
|
||||||
|
if (isEmpty(children)) {
|
||||||
|
if (action == 'check') {
|
||||||
|
// 添加"当前节点"
|
||||||
|
checkedKeys.value.push(id)
|
||||||
|
|
||||||
|
// 添加"所有父级"和"当前节点"
|
||||||
|
checkedAllKeys.value.push(...getParentIds(id))
|
||||||
|
checkedAllKeys.value.push(id)
|
||||||
|
} else {
|
||||||
|
// 删除"当前节点"
|
||||||
|
pull(checkedKeys.value, id)
|
||||||
|
|
||||||
|
// 删除"当前节点"和"所有子级未全选的父级"
|
||||||
|
pull(checkedAllKeys.value, id)
|
||||||
|
upRecursionCheck(pid)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 获取所有子级(不包含任何父级)
|
||||||
|
let selectedKeys = getIds(children)
|
||||||
|
// 获取所有子级(包含所有父级)
|
||||||
|
let selectedAllKeys = getIds(children, true)
|
||||||
|
if (action == 'check') {
|
||||||
|
// 添加当前节点下所有的子级(不包含任何父级)
|
||||||
|
checkedKeys.value.push(...selectedKeys)
|
||||||
|
|
||||||
|
// 添加"当前节点"和"所有子级"和"所有父级"
|
||||||
|
checkedAllKeys.value.push(id)
|
||||||
|
checkedAllKeys.value.push(...getParentIds(id))
|
||||||
|
checkedAllKeys.value.push(...selectedAllKeys)
|
||||||
|
} else {
|
||||||
|
// 删除当前节点下所有的子级(不包含任何父级)
|
||||||
|
pullAll(checkedKeys.value, selectedKeys)
|
||||||
|
// 删除"当前节点"和"所有子级"和"所有父级"
|
||||||
|
pullAll(checkedAllKeys.value, selectedAllKeys)
|
||||||
|
pull(checkedAllKeys.value, id)
|
||||||
|
upRecursionCheck(pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 去重
|
||||||
|
checkedAllKeys.value = uniq(checkedAllKeys.value)
|
||||||
|
}
|
||||||
|
updateKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePrefixWithExpaned(_keys, _option, meta){
|
||||||
|
if(props.icon){
|
||||||
|
if (!meta.node)
|
||||||
|
return;
|
||||||
|
switch (meta.action) {
|
||||||
|
case "expand":
|
||||||
|
meta.node.prefix = () => h(MbIcon, { icon: props.icon.expand })
|
||||||
|
break;
|
||||||
|
case "collapse":
|
||||||
|
meta.node.prefix = () => h(MbIcon, { icon: props.icon.collapse })
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function upRecursionCheck(pid){
|
||||||
|
if(pid != '0'){
|
||||||
|
// 获取所有同级
|
||||||
|
let siblings = sourceData.value.filter(it => it.pid == pid)
|
||||||
|
// 如果同级都没有选中则取消父级id
|
||||||
|
if(!siblings.some(it => checkedAllKeys.value.indexOf(it.id) != -1)){
|
||||||
|
pull(checkedAllKeys.value, pid)
|
||||||
|
// 获取父级
|
||||||
|
let parent = sourceData.value.filter(it => it.id == pid)[0]
|
||||||
|
upRecursionCheck(parent.pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIds(children, all) {
|
||||||
|
all = all == undefined ? false : true
|
||||||
|
let ids = []
|
||||||
|
getAllSubs(children, ids, all)
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllSubs(children, ids, all){
|
||||||
|
children.forEach(it => {
|
||||||
|
if (it.children && it.children.length > 0) {
|
||||||
|
if(all){
|
||||||
|
ids.push(it.id)
|
||||||
|
}
|
||||||
|
getAllSubs(it.children, ids, all)
|
||||||
|
}else{
|
||||||
|
ids.push(it.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有父级的id
|
||||||
|
function getParentIds(id){
|
||||||
|
let ids = []
|
||||||
|
upRecursion(id, ids)
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
function upRecursion(id, ids){
|
||||||
|
let menu = sourceData.value.filter(it => it.id == id)[0]
|
||||||
|
if(menu && menu.pid != '0'){
|
||||||
|
ids.push(menu.pid)
|
||||||
|
let parentMenu = sourceData.value.filter(it => it.id == menu.pid)[0]
|
||||||
|
if(parentMenu){
|
||||||
|
upRecursion(parentMenu.id, ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSourceData(children){
|
||||||
|
children.forEach(it => {
|
||||||
|
let chi = clone(it)
|
||||||
|
delete chi.children
|
||||||
|
sourceData.value.push(chi)
|
||||||
|
if(it.children && it.children.length > 0){
|
||||||
|
loadSourceData(it.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const tree = ref()
|
||||||
|
const treeData = ref([])
|
||||||
|
const sourceData = ref([])
|
||||||
|
const defaultExpandAll = ref(props.defaultExpandAll)
|
||||||
|
const refreshTree = ref(false)
|
||||||
|
const treeAllChecked = ref(false)
|
||||||
|
const searchValue = ref('')
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
loadTreeData()
|
||||||
|
})
|
||||||
|
|
||||||
|
function updateKeys(){
|
||||||
|
// 如果开启级联 并且需要返回带父级的值
|
||||||
|
if(props.cascade && props.keyAll){
|
||||||
|
emit('update:modelValue', checkedAllKeys.value.join(','))
|
||||||
|
emit('check-change', checkedAllKeys.value.join(','))
|
||||||
|
}else{
|
||||||
|
emit('update:modelValue', checkedKeys.value.join(','))
|
||||||
|
emit('check-change', checkedKeys.value.join(','))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectIds(value) {
|
||||||
|
if(value){
|
||||||
|
let ids = []
|
||||||
|
let values = value.split(',')
|
||||||
|
checkedAllKeys.value = values
|
||||||
|
values.forEach(id => {
|
||||||
|
// 如果当前id没有子级
|
||||||
|
if(!sourceData.value.some(it => it.pid == id)){
|
||||||
|
ids.push(id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(props.cascade){
|
||||||
|
checkedKeys.value = ids
|
||||||
|
}else{
|
||||||
|
checkedKeys.value = values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doExpand() {
|
||||||
|
refreshTree.value = false
|
||||||
|
defaultExpandAll.value = !defaultExpandAll.value
|
||||||
|
nextTick(() => refreshTree.value = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentNode = ref()
|
||||||
|
function nodeProps({option}) {
|
||||||
|
return {
|
||||||
|
onClick() {
|
||||||
|
emit('node-click', option)
|
||||||
|
},
|
||||||
|
onContextmenu(e) {
|
||||||
|
currentNode.value = option
|
||||||
|
props.contextmenu.forEach(it => {
|
||||||
|
it.show = (it.if && it.if(currentNode.value))
|
||||||
|
})
|
||||||
|
showDropdown.value = true;
|
||||||
|
dropdownX.value = e.clientX;
|
||||||
|
dropdownY.value = e.clientY;
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function recursionRenderIcon(children){
|
||||||
|
children.forEach(it => {
|
||||||
|
if(it.isGroup){
|
||||||
|
if(it.children && it.children.length){
|
||||||
|
it.prefix = () => h(MbIcon, { icon: defaultExpandAll.value ? props.icon.expand : props.icon.collapse })
|
||||||
|
recursionRenderIcon(it.children)
|
||||||
|
}else{
|
||||||
|
it.prefix = () => h(MbIcon, { icon: props.icon.collapse })
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
it.prefix = () => h(MbIcon, { icon: props.icon.node, color: '#42b883' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTreeData() {
|
||||||
|
$common.get(props.url, props.params).then((res) => {
|
||||||
|
treeTable.deleteEmptyChildren(res.data.list)
|
||||||
|
if(props.icon){
|
||||||
|
recursionRenderIcon(res.data.list)
|
||||||
|
}
|
||||||
|
treeData.value = res.data.list
|
||||||
|
loadSourceData(treeData.value)
|
||||||
|
|
||||||
|
refreshTree.value = true
|
||||||
|
nextTick(() => selectIds(props.modelValue))
|
||||||
|
watch(() => props.modelValue, (value) => {
|
||||||
|
nextTick(() => selectIds(value))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload(){
|
||||||
|
loadTreeData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTree() {
|
||||||
|
return tree.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkedAll(checked) {
|
||||||
|
if (checked) {
|
||||||
|
checkedKeys.value = getIds(treeData.value)
|
||||||
|
checkedAllKeys.value = sourceData.value.map(it => it.id)
|
||||||
|
} else {
|
||||||
|
checkedKeys.value = []
|
||||||
|
checkedAllKeys.value = []
|
||||||
|
}
|
||||||
|
updateKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
function dropdownSelect(key){
|
||||||
|
showDropdown.value = false
|
||||||
|
props.contextmenu && props.contextmenu.filter(it => it.key == key)[0].click(currentNode.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({getTree, reload})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
<template>
|
||||||
|
<lay-layer v-model="$global.modal.modalMap[modalId].value" v-bind="layerOptions">
|
||||||
|
<div style="padding: 20px">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</lay-layer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import { layer } from '@layui/layer-vue';
|
||||||
|
const modalId = $common.uuid()
|
||||||
|
$global.modal.create(modalId);
|
||||||
|
const emit = defineEmits(['confirm'])
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
|
shadeClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
shade: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
resize: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
maxmin: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
shadeOpacity: {
|
||||||
|
type: String,
|
||||||
|
default: '.4'
|
||||||
|
},
|
||||||
|
showFooter: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
type: Array,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const layerOptions = reactive({
|
||||||
|
title: props.title,
|
||||||
|
zIndex: $global.modal.getIndex(modalId),
|
||||||
|
shade: props.shade,
|
||||||
|
resize: props.resize,
|
||||||
|
maxmin: props.maxmin,
|
||||||
|
shadeOpacity: props.shadeOpacity,
|
||||||
|
shadeClose: props.shadeClose,
|
||||||
|
area: [props.width, props.height],
|
||||||
|
btn: !props.showFooter ? [] : props.buttons !== undefined ? props.buttons : [
|
||||||
|
{
|
||||||
|
text: "确定",
|
||||||
|
callback: () => {
|
||||||
|
confirm()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "取消",
|
||||||
|
callback: () => {
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
watch(() => props.title, (value) => {
|
||||||
|
layerOptions.title = value
|
||||||
|
})
|
||||||
|
|
||||||
|
const confirmLoading = ref(false)
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
$global.modal.show(modalId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
$global.modal.hide(modalId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loading() {
|
||||||
|
confirmLoading.value = layer.load(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoading() {
|
||||||
|
layer.close(confirmLoading.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirm() {
|
||||||
|
emit('confirm', {
|
||||||
|
loading,
|
||||||
|
hideLoading,
|
||||||
|
hide,
|
||||||
|
title: props.title
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
defineExpose({show, hide, loading, hideLoading})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.layui-layer-title{
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.layui-layer-btn .layui-layer-btn0 {
|
||||||
|
background-color: #2D8CF0FF;
|
||||||
|
border-color: #2D8CF0FF;
|
||||||
|
}
|
||||||
|
.layui-layer-setwin i:hover {
|
||||||
|
color: #2D8CF0FF
|
||||||
|
}
|
||||||
|
.layui-layer{
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,41 @@
|
||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<span v-if="getNowrap" @mouseover="onMouseover">
|
||||||
|
<n-ellipsis :tooltip="tooltip">
|
||||||
|
<slot />
|
||||||
|
</n-ellipsis>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {computed, ref} from 'vue'
|
||||||
|
import componentProperties from "@/components/magic-component-properties.js";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
nowrap: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const getNowrap = computed(() => props.nowrap !== undefined ? props.nowrap : componentProperties.table.nowrap !== undefined ? componentProperties.table.nowrap : false)
|
||||||
|
const tooltip = ref()
|
||||||
|
tooltip.value = false
|
||||||
|
let timers
|
||||||
|
let duration
|
||||||
|
function onMouseover(){
|
||||||
|
clearTimeout(timers)
|
||||||
|
const onUpdateShow = (value) => {
|
||||||
|
if (!value) {
|
||||||
|
timers = setTimeout(() => {
|
||||||
|
tooltip.value = false
|
||||||
|
}, (duration ?? 100) + 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tooltip.value = { onUpdateShow }
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,112 @@
|
||||||
|
<template>
|
||||||
|
<n-checkbox-group ref="magicCheckbox" v-model:value="checkboxValue">
|
||||||
|
<n-space item-style="display: flex;">
|
||||||
|
<n-checkbox v-for="it in options" :value="it.value" :label="it.label" :key="it.value" />
|
||||||
|
</n-space>
|
||||||
|
</n-checkbox-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
import { ref, watch, onMounted } from 'vue'
|
||||||
|
import { getCheckboxData } from '@/api/components/mb-checkbox.js'
|
||||||
|
import { watchValue } from "@/components/magic/scripts/watch-join-update";
|
||||||
|
import { isArray } from "lodash-es";
|
||||||
|
|
||||||
|
const magicCheckbox = ref()
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
optionsFilter: {
|
||||||
|
type: Function,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label'
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value'
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
join: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
defaultFirstItem: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
done: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const options = ref([])
|
||||||
|
const checkboxValue = ref([])
|
||||||
|
|
||||||
|
watch(() => [props.type, props.url, props.options], () => {
|
||||||
|
loadData()
|
||||||
|
}, {deep: true})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
|
||||||
|
let watchList = []
|
||||||
|
function loadData() {
|
||||||
|
getCheckboxData({
|
||||||
|
type: props.type,
|
||||||
|
url: props.url,
|
||||||
|
params: props.params,
|
||||||
|
options: props.options,
|
||||||
|
optionsFilter: props.optionsFilter,
|
||||||
|
labelField: props.labelField,
|
||||||
|
valueField: props.valueField
|
||||||
|
}).then(data => {
|
||||||
|
options.value = data
|
||||||
|
props.done(data)
|
||||||
|
$common.stopWatchList(watchList)
|
||||||
|
watchList = watchValue(checkboxValue, props, emit)
|
||||||
|
if(props.defaultFirstItem && options.value && options.value[0]){
|
||||||
|
let defaultValue = options.value[0].value
|
||||||
|
checkboxValue.value = isArray(checkboxValue.value) ? [defaultValue] : defaultValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions(){
|
||||||
|
return options.value
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ getOptions })
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<n-date-picker
|
||||||
|
:size="$global.uiSize.value"
|
||||||
|
v-model:formatted-value="selectValue"
|
||||||
|
:type="type"
|
||||||
|
:format="valueFormat"
|
||||||
|
:value-format="valueFormat"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:start-placeholder="startPlaceholder"
|
||||||
|
:end-placeholder="endPlaceholder"
|
||||||
|
update-value-on-close
|
||||||
|
v-bind="props.props"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {watch, ref} from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const selectValue = ref('')
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: String,
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'date'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '请选择时间'
|
||||||
|
},
|
||||||
|
format: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
startPlaceholder: {
|
||||||
|
type: String,
|
||||||
|
default: '开始时间'
|
||||||
|
},
|
||||||
|
endPlaceholder: {
|
||||||
|
type: String,
|
||||||
|
default: '结束时间'
|
||||||
|
},
|
||||||
|
props: Object
|
||||||
|
})
|
||||||
|
|
||||||
|
function handlerValue(value){
|
||||||
|
if(value && value.indexOf(',') !== -1){
|
||||||
|
return value.split(',')
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
selectValue.value = handlerValue(props.modelValue)
|
||||||
|
watch(() => props.modelValue, (value) => {
|
||||||
|
if(!$common.arrayStringEq(value, selectValue.value)){
|
||||||
|
selectValue.value = handlerValue(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const valueFormat = ref()
|
||||||
|
if (!props.format) {
|
||||||
|
if (props.type.startsWith('date')) {
|
||||||
|
valueFormat.value = 'yyyy-MM-dd'
|
||||||
|
}
|
||||||
|
if (props.type.startsWith('datetime')) {
|
||||||
|
valueFormat.value = 'yyyy-MM-dd HH:mm:ss'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valueFormat.value = props.format
|
||||||
|
}
|
||||||
|
watch(selectValue, (value) => {
|
||||||
|
emit('update:modelValue', value instanceof Array ? value.join(',') : value)
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<n-input
|
||||||
|
ref="magicInput"
|
||||||
|
:size="$global.uiSize.value"
|
||||||
|
v-model:value="selectValue"
|
||||||
|
:type="type"
|
||||||
|
:placeholder="placeholder || (itemLabel && '请输入' + itemLabel)"
|
||||||
|
v-bind="props.props"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useVModel } from "@vueuse/core";
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: String,
|
||||||
|
itemLabel: String,
|
||||||
|
placeholder: String,
|
||||||
|
type: String,
|
||||||
|
props: Object
|
||||||
|
})
|
||||||
|
const selectValue = useVModel(props, 'modelValue', emit)
|
||||||
|
</script>
|
|
@ -0,0 +1,206 @@
|
||||||
|
<style scoped>
|
||||||
|
.mb-list{
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background: white;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="list-container" ref="listContainerRef">
|
||||||
|
<mb-input ref="magicInput" @click="inputClick" v-model="inputValue" />
|
||||||
|
<div class="mb-list" :style="{ width: width + 'px', height: height + 'px', ...componentStyle }" v-if="showList">
|
||||||
|
<div class="mb-search" v-if="!search">
|
||||||
|
<mb-search :where="selectTableOptions.where" @search="reloadTable" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-toolbar">
|
||||||
|
<n-button v-if="multiple" :size="$global.uiSize.value" type="primary" @click="selectDataList">
|
||||||
|
选择数据
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div class="mb-table">
|
||||||
|
<mb-table
|
||||||
|
ref="magicTable"
|
||||||
|
v-bind="selectTableOptions"
|
||||||
|
v-model:checked-row-keys="checkedRowKeys"
|
||||||
|
@dblclick="tableDblclick"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onBeforeUnmount, ref, watch, toRaw } from 'vue'
|
||||||
|
import { onClickOutside } from '@vueuse/core'
|
||||||
|
import { clone, cloneDeep } from 'lodash-es'
|
||||||
|
const magicTable = ref()
|
||||||
|
const magicInput = ref()
|
||||||
|
const listContainerRef = ref()
|
||||||
|
const checkedRowKeys = ref()
|
||||||
|
const showList = ref(false)
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 500
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 300
|
||||||
|
},
|
||||||
|
tableOptions: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
onSelectData: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
closeCurrentColEditMode: {
|
||||||
|
type: Function,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
type: Object,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectTableOptions = ref(cloneDeep(props.tableOptions))
|
||||||
|
const sourceData = ref()
|
||||||
|
selectTableOptions.value.done = (data) => {
|
||||||
|
sourceData.value = data
|
||||||
|
}
|
||||||
|
initTableOptions()
|
||||||
|
watch(() => props.tableOptions, (value) => {
|
||||||
|
selectTableOptions.value = value
|
||||||
|
initTableOptions()
|
||||||
|
}, { deep: true })
|
||||||
|
function initTableOptions(){
|
||||||
|
selectTableOptions.value.selection = props.multiple
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputValue = ref(clone(props.modelValue))
|
||||||
|
if(props.search){
|
||||||
|
watch(inputValue, (value) => {
|
||||||
|
let fields = props.search.fields
|
||||||
|
if(props.search.static){
|
||||||
|
selectTableOptions.value.data = sourceData.value.filter((it) => {
|
||||||
|
for(let i = 0; i<fields.length; i++){
|
||||||
|
if(it[fields[i]].indexOf(value) != -1){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
selectTableOptions.value.where = selectTableOptions.value.where || {}
|
||||||
|
fields.forEach(field => {
|
||||||
|
selectTableOptions.value.where[field] = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watch(() => selectTableOptions.value.where, () => reloadTable(), { deep: true })
|
||||||
|
}
|
||||||
|
watch(() => props.modelValue, (value) => {
|
||||||
|
inputValue.value = value
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => listContainerRef.value, () => {
|
||||||
|
addEventListener()
|
||||||
|
onClickOutside(listContainerRef, () => closeTable())
|
||||||
|
})
|
||||||
|
|
||||||
|
function tableDblclick({ row }){
|
||||||
|
props.onSelectData({
|
||||||
|
selectData: row,
|
||||||
|
multiple: false,
|
||||||
|
_deconstruction_: true
|
||||||
|
})
|
||||||
|
closeTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectDataList(){
|
||||||
|
let rowKey = selectTableOptions.value['rowKey'] || 'id'
|
||||||
|
props.onSelectData({
|
||||||
|
selectData: magicTable.value.getData().filter(it => checkedRowKeys.value.indexOf(it[rowKey]) != -1),
|
||||||
|
multiple: true,
|
||||||
|
_deconstruction_: true
|
||||||
|
})
|
||||||
|
closeTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentStyle = ref()
|
||||||
|
function inputClick(){
|
||||||
|
let style = {}
|
||||||
|
let inputRect = magicInput.value.$el.getBoundingClientRect()
|
||||||
|
let bodyClientWidth = document.body.clientWidth
|
||||||
|
let bodyClientHeight = document.body.clientHeight
|
||||||
|
let top = inputRect.y + inputRect.height
|
||||||
|
let left = inputRect.x
|
||||||
|
style.position = 'fixed'
|
||||||
|
style['z-index'] = 999999
|
||||||
|
if((bodyClientWidth - props.width - left) < 1){
|
||||||
|
style.right = `1px`
|
||||||
|
style.left = 'unset'
|
||||||
|
}else{
|
||||||
|
style.left = `${left}px`
|
||||||
|
style.right = 'unset'
|
||||||
|
}
|
||||||
|
|
||||||
|
if((bodyClientHeight - props.height - top) < 1){
|
||||||
|
style.bottom = `${bodyClientHeight - inputRect.y}px`
|
||||||
|
style.top = 'unset'
|
||||||
|
}else{
|
||||||
|
style.top = `${top}px`
|
||||||
|
style.bottom = 'unset'
|
||||||
|
}
|
||||||
|
componentStyle.value = style
|
||||||
|
showList.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadTable() {
|
||||||
|
magicTable.value && magicTable.value.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
function keydown(e){
|
||||||
|
// esc
|
||||||
|
if(e && e.keyCode == 27){
|
||||||
|
closeTable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeTable(){
|
||||||
|
props.closeCurrentColEditMode && props.closeCurrentColEditMode()
|
||||||
|
showList.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEventListener() {
|
||||||
|
listContainerRef.value.addEventListener('keydown', keydown)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeListener() {
|
||||||
|
listContainerRef.value.removeEventListener('keydown', keydown)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
removeListener()
|
||||||
|
})
|
||||||
|
|
||||||
|
function focus(){
|
||||||
|
magicInput.value.$refs[Object.keys(toRaw(magicInput.value.$refs))[0]].focus()
|
||||||
|
inputClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ focus })
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,143 @@
|
||||||
|
<template>
|
||||||
|
<n-select
|
||||||
|
ref="magicSelect"
|
||||||
|
:size="$global.uiSize.value"
|
||||||
|
v-bind="props.props"
|
||||||
|
v-model:value="selectValue"
|
||||||
|
:multiple="multiple"
|
||||||
|
:options="options"
|
||||||
|
:style="{ width }"
|
||||||
|
:placeholder="placeholder || (itemLabel && '请输入' + itemLabel)"
|
||||||
|
:clearable="clearable"
|
||||||
|
:show-on-focus="showOnFocus"
|
||||||
|
filterable
|
||||||
|
max-tag-count="responsive"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
import { ref, watch, onMounted } from 'vue'
|
||||||
|
import { getSelectData } from '@/api/components/mb-select.js'
|
||||||
|
import { watchValue } from "@/components/magic/scripts/watch-join-update";
|
||||||
|
import { isArray } from "lodash-es";
|
||||||
|
|
||||||
|
const magicSelect = ref()
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
optionsFilter: {
|
||||||
|
type: Function,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label'
|
||||||
|
},
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: 'value'
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
allOption: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
itemLabel: String,
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
join: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showOnFocus: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
defaultFirstItem: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
done: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const options = ref([])
|
||||||
|
const selectValue = ref(props.multiple ? [] : null)
|
||||||
|
|
||||||
|
watch(() => [props.type, props.url, props.options], () => {
|
||||||
|
loadData()
|
||||||
|
}, {deep: true})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
|
||||||
|
let watchList = []
|
||||||
|
function loadData() {
|
||||||
|
getSelectData({
|
||||||
|
type: props.type,
|
||||||
|
url: props.url,
|
||||||
|
params: props.params,
|
||||||
|
options: props.options,
|
||||||
|
optionsFilter: props.optionsFilter,
|
||||||
|
allOption: props.allOption,
|
||||||
|
labelField: props.labelField,
|
||||||
|
valueField: props.valueField
|
||||||
|
}).then(data => {
|
||||||
|
options.value = data
|
||||||
|
props.done(data)
|
||||||
|
$common.stopWatchList(watchList)
|
||||||
|
watchList = watchValue(selectValue, props, emit)
|
||||||
|
if(props.defaultFirstItem && options.value && options.value[0]){
|
||||||
|
let defaultValue = options.value[0].value
|
||||||
|
selectValue.value = isArray(selectValue.value) ? [defaultValue] : defaultValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions(){
|
||||||
|
return options.value
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ getOptions })
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,81 @@
|
||||||
|
<template>
|
||||||
|
<n-switch
|
||||||
|
ref="magicSwitch"
|
||||||
|
:size="$global.uiSize.value"
|
||||||
|
v-model:value="selectValue"
|
||||||
|
:checked-value="_checkedValue"
|
||||||
|
:unchecked-value="_uncheckedValue"
|
||||||
|
v-bind="props.props"
|
||||||
|
@update:value="change"
|
||||||
|
>
|
||||||
|
<template #checked>
|
||||||
|
<slot name="checked"></slot>
|
||||||
|
</template>
|
||||||
|
<template #unchecked>
|
||||||
|
<slot name="unchecked"></slot>
|
||||||
|
</template>
|
||||||
|
</n-switch>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, watch} from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
const selectValue = ref('')
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: Boolean | String | Number,
|
||||||
|
checkedValue: Boolean | String | Number,
|
||||||
|
uncheckedValue: Boolean | String | Number,
|
||||||
|
props: Object
|
||||||
|
})
|
||||||
|
const _checkedValue = ref(true)
|
||||||
|
const _uncheckedValue = ref(false)
|
||||||
|
|
||||||
|
function change() {
|
||||||
|
emit('update:modelValue', selectValue.value)
|
||||||
|
emit('change', selectValue.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActive(value) {
|
||||||
|
if($common.notEmptyNot01(value)){
|
||||||
|
if (typeof (value) == 'boolean') {
|
||||||
|
_checkedValue.value = true
|
||||||
|
_uncheckedValue.value = false
|
||||||
|
} else {
|
||||||
|
if (props.checkedValue == undefined && props.uncheckedValue == undefined) {
|
||||||
|
_checkedValue.value = '1'
|
||||||
|
_uncheckedValue.value = '0'
|
||||||
|
} else {
|
||||||
|
_checkedValue.value = props.checkedValue + ''
|
||||||
|
_uncheckedValue.value = props.uncheckedValue + ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
_checkedValue.value = props.checkedValue || true
|
||||||
|
_uncheckedValue.value = props.uncheckedValue || false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicSetValue(props.modelValue)
|
||||||
|
setActive(props.modelValue)
|
||||||
|
watch(() => props.modelValue, (value) => {
|
||||||
|
dynamicSetValue(value)
|
||||||
|
setActive(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
function dynamicSetValue(value) {
|
||||||
|
if (typeof (value) == 'boolean') {
|
||||||
|
selectValue.value = value
|
||||||
|
} else {
|
||||||
|
if (value || value == 0) {
|
||||||
|
selectValue.value = value + ''
|
||||||
|
} else {
|
||||||
|
selectValue.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(selectValue, (value) => {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<n-input
|
||||||
|
ref="magicTextarea"
|
||||||
|
:size="$global.uiSize.value"
|
||||||
|
v-model:value="selectValue"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="placeholder || (itemLabel && '请输入' + itemLabel)"
|
||||||
|
v-bind="props.props"
|
||||||
|
:rows="rows"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useVModel } from "@vueuse/core";
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: String,
|
||||||
|
props: Object,
|
||||||
|
itemLabel: String,
|
||||||
|
placeholder: String,
|
||||||
|
rows: Number
|
||||||
|
})
|
||||||
|
const selectValue = useVModel(props, 'modelValue', emit)
|
||||||
|
</script>
|
|
@ -0,0 +1,372 @@
|
||||||
|
<template>
|
||||||
|
<n-upload
|
||||||
|
:id="uploadDomId"
|
||||||
|
class="upload-demo"
|
||||||
|
ref="uploadRef"
|
||||||
|
:action="actionUrl"
|
||||||
|
:headers="headers"
|
||||||
|
:directory-dnd="directoryDnd"
|
||||||
|
@preview="onPreview"
|
||||||
|
@remove="onRemove"
|
||||||
|
:multiple="multiple"
|
||||||
|
:limit="limit"
|
||||||
|
:show-file-list="showFileList"
|
||||||
|
@before-upload="beforeUpload"
|
||||||
|
@finish="onFinish"
|
||||||
|
@change="onChange"
|
||||||
|
@error="onError"
|
||||||
|
:file-list="fileList"
|
||||||
|
:default-upload="defaultUpload"
|
||||||
|
>
|
||||||
|
<n-upload-dragger v-if="directoryDnd">
|
||||||
|
<div>可拖拽上传</div>
|
||||||
|
<n-button type="primary" :loading="uploadLoading" :disabled="!multiple && fileList.length == 1">
|
||||||
|
{{
|
||||||
|
label
|
||||||
|
}}
|
||||||
|
</n-button>
|
||||||
|
<div slot="tip" v-if="showTip" class="el-upload__tip">支持上传
|
||||||
|
{{
|
||||||
|
getSettingSuffixs().replaceAll(',', ',')
|
||||||
|
}}文件,且不超过{{ maxFileSize }}MB
|
||||||
|
</div>
|
||||||
|
</n-upload-dragger>
|
||||||
|
<template v-else>
|
||||||
|
<n-button type="primary" :loading="uploadLoading" :disabled="!multiple && fileList.length == 1">
|
||||||
|
{{
|
||||||
|
label
|
||||||
|
}}
|
||||||
|
</n-button>
|
||||||
|
<div slot="tip" v-if="showTip" class="el-upload__tip">支持上传
|
||||||
|
{{
|
||||||
|
getSettingSuffixs().replaceAll(',', ',')
|
||||||
|
}}文件,且不超过{{ maxFileSize }}MB
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-upload>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, watch, onMounted} from 'vue'
|
||||||
|
import {useUserStore} from "@/store/modules/userStore";
|
||||||
|
import global from '@/scripts/global'
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const emit = defineEmits(['change', 'update:modelValue'])
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
maxFileSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 200
|
||||||
|
},
|
||||||
|
accept: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
externalId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
externalType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
formats: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: '点击上传'
|
||||||
|
},
|
||||||
|
showTip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => true
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
showFileList: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => true
|
||||||
|
},
|
||||||
|
onSuccess: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteTip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => true
|
||||||
|
},
|
||||||
|
join: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
directoryDnd: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
defaultUpload: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const acceptList = {
|
||||||
|
image: 'png,jpg,gif,jpeg',
|
||||||
|
wps: 'pdf,pptx,xls,xlsx,csv,docx,doc',
|
||||||
|
compress: 'zip,rar,7z',
|
||||||
|
video: 'avi,flv,mp4,mpeg,mov'
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionUrl = ref(global.baseApi + '/system/file/upload')
|
||||||
|
const headers = {
|
||||||
|
token: userStore.getToken()
|
||||||
|
}
|
||||||
|
const uploadRef = ref()
|
||||||
|
const urls = ref([])
|
||||||
|
const uploadDomId = $common.uuid()
|
||||||
|
const fileList = ref([])
|
||||||
|
const uploadLoading = ref(false)
|
||||||
|
const emitUpdate = ref(true)
|
||||||
|
|
||||||
|
watch(() => props.modelValue, () => {
|
||||||
|
if (emitUpdate.value) {
|
||||||
|
emitUpdate.value = false
|
||||||
|
if (fileList.value.length == 0) {
|
||||||
|
renderFile()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderFile()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.externalId) {
|
||||||
|
$common.get('/system/file/files', {
|
||||||
|
externalId: props.externalId,
|
||||||
|
externalType: props.externalType
|
||||||
|
}).then(res => {
|
||||||
|
const {data} = res
|
||||||
|
fileList.value = data
|
||||||
|
})
|
||||||
|
actionUrl.value = actionUrl.value + `?externalId=${props.externalId}&externalType=${props.externalType}`
|
||||||
|
} else {
|
||||||
|
renderFile()
|
||||||
|
}
|
||||||
|
if (props.action) {
|
||||||
|
actionUrl.value = global.baseApi + props.action
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function setFileList() {
|
||||||
|
if (urls.value.length > 0) {
|
||||||
|
fileList.value = urls.value.map(it => {
|
||||||
|
return {
|
||||||
|
name: it.substring(it.lastIndexOf('/') + 1),
|
||||||
|
fullPath: it
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFile() {
|
||||||
|
if (props.multiple && props.join && props.modelValue) {
|
||||||
|
urls.value = props.modelValue.split(',')
|
||||||
|
} else {
|
||||||
|
if (props.modelValue instanceof Array && props.modelValue.length > 0) {
|
||||||
|
urls.value = props.modelValue
|
||||||
|
} else {
|
||||||
|
if (props.modelValue) {
|
||||||
|
urls.value = [props.modelValue]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFileList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(data) {
|
||||||
|
if (data.file.status != "removed") {
|
||||||
|
fileList.value = data.fileList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError() {
|
||||||
|
uploadLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateValue(value) {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emitUpdate.value = true
|
||||||
|
emit('change', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRemove({file}) {
|
||||||
|
let deleteFile = () => {
|
||||||
|
let url = file.fullPath
|
||||||
|
urls.value.splice(urls.value.indexOf(url), 1)
|
||||||
|
fileList.value.forEach((it, i) => {
|
||||||
|
if (it && url == it.fullPath) {
|
||||||
|
fileList.value.splice(i, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (props.multiple) {
|
||||||
|
if (props.join) {
|
||||||
|
updateValue(urls.value.join(','))
|
||||||
|
} else {
|
||||||
|
updateValue(urls.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById(uploadDomId).getElementsByClassName('n-upload-file-input')[0].removeAttribute('disabled')
|
||||||
|
updateValue('')
|
||||||
|
}
|
||||||
|
$common.delete('/system/file/delete', {url: encodeURI(url)})
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!props.deleteTip) {
|
||||||
|
deleteFile()
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
$dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要删除此文件吗?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: () => {
|
||||||
|
deleteFile()
|
||||||
|
resolve()
|
||||||
|
},
|
||||||
|
onClose: () => {
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPreview(file) {
|
||||||
|
window.open(global.baseApi + file.fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFinish({file, event}) {
|
||||||
|
let res = JSON.parse((event?.target).response)
|
||||||
|
file.fullPath = res.data.url
|
||||||
|
uploadLoading.value = false
|
||||||
|
if (res.data) {
|
||||||
|
urls.value.push(res.data.url)
|
||||||
|
if (props.multiple) {
|
||||||
|
if (props.join) {
|
||||||
|
updateValue(urls.value.join(','))
|
||||||
|
} else {
|
||||||
|
updateValue(urls.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById(uploadDomId).getElementsByClassName('n-upload-file-input')[0].setAttribute('disabled', '')
|
||||||
|
updateValue(res.data.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (props.onSuccess) {
|
||||||
|
props.onSuccess({file, event})
|
||||||
|
}
|
||||||
|
if (props.action) {
|
||||||
|
urls.value = []
|
||||||
|
fileList.value = []
|
||||||
|
updateValue(urls.value.join(','))
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSettingSuffixs() {
|
||||||
|
if (props.formats) {
|
||||||
|
return props.formats
|
||||||
|
}
|
||||||
|
let suffixs = acceptList[props.accept]
|
||||||
|
if (!suffixs) {
|
||||||
|
suffixs = getAllSuffixs()
|
||||||
|
}
|
||||||
|
return suffixs
|
||||||
|
}
|
||||||
|
|
||||||
|
function beforeUpload({file}) {
|
||||||
|
let fileName = file.name
|
||||||
|
let accepts = props.accept.split(',')
|
||||||
|
if (accepts) {
|
||||||
|
for (let i = 0; i < accepts.length; i++) {
|
||||||
|
if (!validAccept(fileName, accepts[i])) {
|
||||||
|
$message.error('上传文件格式只能为:' + getSettingSuffixs().replaceAll(',', ','))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!validAccept(fileName, 'null')) {
|
||||||
|
$message.error('上传文件格式只能为:' + getAllSuffixs().replaceAll(',', ','))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLt2M = file.file.size / 1024 / 1024 < props.maxFileSize
|
||||||
|
if (!isLt2M) {
|
||||||
|
$message.error(`上传文件大小不能超过 ${props.maxFileSize}MB!`)
|
||||||
|
return isLt2M
|
||||||
|
}
|
||||||
|
if (props.defaultUpload) {
|
||||||
|
uploadLoading.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllSuffixs() {
|
||||||
|
let suffixs = ''
|
||||||
|
for (const key in acceptList) {
|
||||||
|
suffixs += acceptList[key] + ','
|
||||||
|
}
|
||||||
|
suffixs = suffixs.substring(0, suffixs.length - 1)
|
||||||
|
return suffixs
|
||||||
|
}
|
||||||
|
|
||||||
|
function validAccept(fileName, accept) {
|
||||||
|
if (props.formats) {
|
||||||
|
return validEndsWith(fileName, props.formats)
|
||||||
|
}
|
||||||
|
if (accept && acceptList[accept]) {
|
||||||
|
return validEndsWith(fileName, acceptList[accept])
|
||||||
|
} else {
|
||||||
|
return validEndsWith(fileName, getAllSuffixs())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validEndsWith(fileName, suffixs) {
|
||||||
|
suffixs = suffixs.split(',')
|
||||||
|
for (let i = 0; i < suffixs.length; i++) {
|
||||||
|
const suffix = suffixs[i]
|
||||||
|
if (fileName.toLowerCase().endsWith('.' + suffix)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileList() {
|
||||||
|
return fileList.value
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({getFileList})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.n-upload) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,423 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-image-group>
|
||||||
|
<draggable
|
||||||
|
v-model="urls"
|
||||||
|
class="vue-draggable"
|
||||||
|
tag="div"
|
||||||
|
draggable=".draggable-item"
|
||||||
|
@end="onDragEnd"
|
||||||
|
item-key="id"
|
||||||
|
>
|
||||||
|
<template #item="{ element }">
|
||||||
|
<div class="draggable-item" :style="{ width: width + 'px', height: height + 'px' }">
|
||||||
|
<n-image :src="$global.baseApi + element"/>
|
||||||
|
<div class="tools">
|
||||||
|
<div class="shadow" @click="handleDelete(element)">
|
||||||
|
<n-icon size="20">
|
||||||
|
<Trash/>
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
<div class="shadow" @click="beforeCropper(element)">
|
||||||
|
<n-icon size="20">
|
||||||
|
<Crop/>
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<n-upload
|
||||||
|
v-if="(!multiple && urls.length == 0) || (multiple && urls.length < limit)"
|
||||||
|
class="uploadBox"
|
||||||
|
:style="{ width: width + 'px', height: height + 'px' }"
|
||||||
|
:action="action"
|
||||||
|
:headers="headers"
|
||||||
|
accept=".jpg,.jpeg,.png,.gif"
|
||||||
|
directory-dnd
|
||||||
|
:show-file-list="false"
|
||||||
|
:multiple="multiple"
|
||||||
|
:max="limit"
|
||||||
|
@change="onChange"
|
||||||
|
@finish="onFinish"
|
||||||
|
@error="onError"
|
||||||
|
@before-upload="beforeUpload"
|
||||||
|
:file-list="fileList"
|
||||||
|
:disabled="disabled"
|
||||||
|
>
|
||||||
|
<n-upload-dragger>
|
||||||
|
<n-icon size="30" class="uploadIcon">
|
||||||
|
<span class="draggable-text">可拖拽上传</span>
|
||||||
|
<Add/>
|
||||||
|
<span v-show="isUploading" class="uploading">正在上传...</span>
|
||||||
|
<span v-if="!isUploading && limit && limit!==99 && multiple"
|
||||||
|
class="limitTxt">最多{{ limit }}张</span>
|
||||||
|
</n-icon>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
</n-image-group>
|
||||||
|
<div v-if="tip" :style="{ color: tipColor }">{{ tip }}</div>
|
||||||
|
<mb-modal ref="cropperDialog" @confirm="cropper">
|
||||||
|
<div class="cropper-content">
|
||||||
|
<div class="cropper" style="text-align:center">
|
||||||
|
<vueCropper
|
||||||
|
ref="cropperRef"
|
||||||
|
v-bind="cropperOption"
|
||||||
|
:outputSize="cropperOption.outputSize === undefined ? 0.8 : cropperOption.outputSize"
|
||||||
|
:outputType="cropperOption.outputType === undefined ? 'jpeg' : cropperOption.outputType"
|
||||||
|
:canMove="cropperOption.canMove === undefined ? true : cropperOption.canMove"
|
||||||
|
:canMoveBox="cropperOption.canMoveBox === undefined ? true : cropperOption.canMoveBox"
|
||||||
|
:autoCrop="cropperOption.autoCrop === undefined ? true : cropperOption.autoCrop"
|
||||||
|
:centerBox="cropperOption.centerBox === undefined ? true : cropperOption.centerBox"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mb-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import 'vue-cropper/dist/index.css'
|
||||||
|
import {ref, watch, onMounted} from 'vue'
|
||||||
|
import {VueCropper} from 'vue-cropper'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
import {Trash, Crop, Add} from "@vicons/ionicons5";
|
||||||
|
import {useUserStore} from "@/store/modules/userStore";
|
||||||
|
import global from '@/scripts/global'
|
||||||
|
import request from '@/scripts/request'
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
externalId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
externalType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
cropperConfig: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
tip: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
tipColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
deleteTip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => true
|
||||||
|
},
|
||||||
|
join: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const action = ref(global.baseApi + '/system/file/upload')
|
||||||
|
const headers = {token: userStore.getToken()}
|
||||||
|
const disabled = ref(false)
|
||||||
|
const isUploading = ref(false)
|
||||||
|
const cropperOption = ref({})
|
||||||
|
const urls = ref([])
|
||||||
|
const fileList = ref([])
|
||||||
|
const emitUpdate = ref(true)
|
||||||
|
const cropperDialog = ref()
|
||||||
|
const cropperRef = ref()
|
||||||
|
|
||||||
|
watch(() => props.modelValue, () => {
|
||||||
|
if (emitUpdate.value) {
|
||||||
|
emitUpdate.value = false
|
||||||
|
if (fileList.value.length == 0) {
|
||||||
|
renderFile()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderFile()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
cropperOption.value = props.cropperConfig || {}
|
||||||
|
cropperOption.value.img = ''
|
||||||
|
if (props.externalId) {
|
||||||
|
$common.get('/system/file/files', {
|
||||||
|
externalId: props.externalId,
|
||||||
|
externalType: props.externalType
|
||||||
|
}).then(res => {
|
||||||
|
urls.value = res.data
|
||||||
|
})
|
||||||
|
action.value = action.value + `?externalId=${props.externalId}&externalType=${props.externalType}`
|
||||||
|
} else {
|
||||||
|
renderFile()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function renderFile() {
|
||||||
|
if (props.multiple && props.join && props.modelValue) {
|
||||||
|
urls.value = props.modelValue.split(',')
|
||||||
|
} else {
|
||||||
|
if (props.modelValue instanceof Array) {
|
||||||
|
urls.value = props.modelValue
|
||||||
|
fileList.value = urls.value.map(it => {
|
||||||
|
return {fullPath: it}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (props.modelValue) {
|
||||||
|
urls.value.push(props.modelValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateValue(value) {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emitUpdate.value = true
|
||||||
|
emit('change', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(url) {
|
||||||
|
let deleteFile = () => {
|
||||||
|
urls.value = urls.value.filter(it => it != url)
|
||||||
|
fileList.value = fileList.value.filter(it => it.fullPath != url)
|
||||||
|
$common.delete('/system/file/delete', {url: encodeURI(url)})
|
||||||
|
if (props.multiple) {
|
||||||
|
if (props.join) {
|
||||||
|
updateValue(urls.value.join(','))
|
||||||
|
} else {
|
||||||
|
updateValue(urls.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateValue('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!props.deleteTip) {
|
||||||
|
deleteFile()
|
||||||
|
} else {
|
||||||
|
$dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要删除此图片吗?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: () => {
|
||||||
|
deleteFile()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(data) {
|
||||||
|
fileList.value = data.fileList
|
||||||
|
}
|
||||||
|
|
||||||
|
function beforeUpload() {
|
||||||
|
isUploading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError() {
|
||||||
|
isUploading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFinish({file, event}) {
|
||||||
|
let res = JSON.parse((event?.target).response)
|
||||||
|
file.fullPath = res.data.url
|
||||||
|
urls.value.push(res.data.url)
|
||||||
|
if (props.multiple) {
|
||||||
|
if (props.join) {
|
||||||
|
updateValue(urls.value.join(','))
|
||||||
|
} else {
|
||||||
|
updateValue(urls.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateValue(res.data.url)
|
||||||
|
}
|
||||||
|
onDragEnd()
|
||||||
|
isUploading.value = false
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragEnd() {
|
||||||
|
$common.get('/system/file/resort', {urls: urls.value.map(url => encodeURI(url)).join(',')})
|
||||||
|
}
|
||||||
|
|
||||||
|
function beforeCropper(url) {
|
||||||
|
cropperOption.value.img = global.baseApi + url
|
||||||
|
cropperOption.value.relativeImg = url
|
||||||
|
cropperDialog.value.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function cropper() {
|
||||||
|
let relativeImg = cropperOption.value.relativeImg
|
||||||
|
cropperRef.value.getCropBlob((data) => {
|
||||||
|
let dataFile = new File([data], relativeImg.substring(relativeImg.lastIndexOf('/') + 1), {
|
||||||
|
type: data.type,
|
||||||
|
lastModified: Date.now()
|
||||||
|
})
|
||||||
|
let formData = new FormData()
|
||||||
|
formData.append('file', dataFile)
|
||||||
|
formData.append('url', encodeURI(relativeImg))
|
||||||
|
request({
|
||||||
|
url: '/system/file/cropper',
|
||||||
|
method: 'post',
|
||||||
|
data: formData
|
||||||
|
}).then(res => {
|
||||||
|
fileList.value[fileList.value.map(fl => fl.fullPath).indexOf(relativeImg)].fullPath = res.data.url
|
||||||
|
urls.value[urls.value.indexOf(relativeImg)] = res.data.url
|
||||||
|
cropperDialog.value.hide()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.uploadIcon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #fbfdff;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
.limitTxt,
|
||||||
|
.uploading {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draggable-text {
|
||||||
|
position: absolute;
|
||||||
|
top: 10%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-draggable {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.draggable-item {
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
:deep(.n-image), :deep(.n-image img) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-upload) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tools {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: rgba(0, 0, 0, .5);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .3s;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.shadow {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hideShadow {
|
||||||
|
.shadow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.single {
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.draggable-item {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.maxHidden {
|
||||||
|
.uploadBox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cropper-content {
|
||||||
|
.cropper {
|
||||||
|
width: auto;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-upload-trigger) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-upload-dragger) {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,303 @@
|
||||||
|
<style scoped>
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-progress__text) {
|
||||||
|
min-width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover {
|
||||||
|
background: #F5F7FA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
display: inline-block;
|
||||||
|
width: calc(100% - 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-top: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="!readonly">
|
||||||
|
<n-button @click="btnClick">点击上传</n-button>
|
||||||
|
<div slot="tip" class="el-upload__tip">支持上传{{ getSettingSuffixs().replaceAll(',', ',') }}文件</div>
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 16px; font-weight: bold" v-if="readonly && (!fileList || fileList.length == 0)">
|
||||||
|
没有文件
|
||||||
|
</div>
|
||||||
|
<input ref="fileInput" type="file" multiple @change="fileChange" :accept="accept" style="display: none"/>
|
||||||
|
<ul :style="{ width, height }">
|
||||||
|
<li v-for="file in fileList" :title="file.name">
|
||||||
|
<div class="file-name">
|
||||||
|
{{ file.name }}
|
||||||
|
</div>
|
||||||
|
<div class="progress">
|
||||||
|
<n-progress :percentage="file.progress" :status="file.progress == 100 ? 'success' : ''"/>
|
||||||
|
</div>
|
||||||
|
<div class="delete" v-if="file.progress == 100 && !readonly">
|
||||||
|
<n-icon color="red" @click="deleteFile(file.key)">
|
||||||
|
<Trash/>
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
<div v-if="file.progress == 100">
|
||||||
|
<a style="color: blue;cursor: pointer" @click="preview(file.key)" target="_blank">点击查看</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<mb-modal ref="videoDialog" title="预览视频" :show-footer="false">
|
||||||
|
<mb-video :url="currentUrl"/>
|
||||||
|
</mb-modal>
|
||||||
|
<mb-modal ref="imageDialog" title="预览图片" :show-footer="false">
|
||||||
|
<n-image style="margin: 0 auto;display: table" :src="currentUrl"/>
|
||||||
|
</mb-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import ossutil from '@/scripts/ossutil'
|
||||||
|
import global from '@/scripts/global'
|
||||||
|
import {Trash, Crop, Add} from "@vicons/ionicons5";
|
||||||
|
import {ref, onMounted, watch, nextTick} from 'vue'
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: String,
|
||||||
|
width: String,
|
||||||
|
height: String,
|
||||||
|
accept: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
formats: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
readonly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const videoDialog = ref()
|
||||||
|
const imageDialog = ref()
|
||||||
|
const currentUrl = ref()
|
||||||
|
const emitUpdate = ref(true)
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (value) => {
|
||||||
|
if (emitUpdate.value) {
|
||||||
|
emitUpdate.value = false
|
||||||
|
if (fileList.value.length == 0) {
|
||||||
|
fileList.value = renderFileList(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fileList.value = renderFileList(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function preview(key) {
|
||||||
|
key = key.substring(0, key.lastIndexOf('/') + 1) + encodeURIComponent(key.substring(key.lastIndexOf('/') + 1))
|
||||||
|
let fileUrl = global.filePrefix + key
|
||||||
|
currentUrl.value = fileUrl
|
||||||
|
let suffix = key.substring(key.lastIndexOf('\.') + 1)
|
||||||
|
suffix = suffix.toLocaleLowerCase()
|
||||||
|
if (suffix == 'mp4') {
|
||||||
|
videoDialog.value.hide()
|
||||||
|
nextTick(() => {
|
||||||
|
videoDialog.value.show()
|
||||||
|
})
|
||||||
|
} else if (acceptList.value['image'].indexOf(suffix) != -1) {
|
||||||
|
imageDialog.value.hide()
|
||||||
|
nextTick(() => {
|
||||||
|
imageDialog.value.show()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
window.open(fileUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFileList(urls) {
|
||||||
|
let fileList = []
|
||||||
|
if (urls) {
|
||||||
|
let urlArray = urls.split(',')
|
||||||
|
for (let i = 0; i < urlArray.length; i++) {
|
||||||
|
fileList.push({
|
||||||
|
key: urlArray[i],
|
||||||
|
name: urlArray[i].substring(urlArray[i].lastIndexOf('/') + 1),
|
||||||
|
progress: 100
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileList
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateModelValue() {
|
||||||
|
let urls = fileList.value.filter(it => it.progress == 100).map(it => it.key).join(',')
|
||||||
|
emit('update:modelValue', urls)
|
||||||
|
emitUpdate.value = true
|
||||||
|
emit('change', urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress = ref()
|
||||||
|
const fileList = ref(renderFileList(props.modelValue))
|
||||||
|
const fileInput = ref()
|
||||||
|
|
||||||
|
let ossClient = {}
|
||||||
|
onMounted(async () => {
|
||||||
|
ossClient = await ossutil.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
function btnClick() {
|
||||||
|
fileInput.value.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除文件
|
||||||
|
function deleteFile(url) {
|
||||||
|
fileList.value.forEach((it, i) => {
|
||||||
|
if (it.key == url) {
|
||||||
|
fileList.value.splice(i, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
updateModelValue()
|
||||||
|
ossClient.delete(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
//上传属性
|
||||||
|
const options = {
|
||||||
|
// 获取分片上传进度、断点和返回值。
|
||||||
|
progress: (p, cpt, res) => {
|
||||||
|
if (p && p == 1) {// 小文件 上传很快的情况
|
||||||
|
updateModelValue()
|
||||||
|
}
|
||||||
|
if (cpt) {
|
||||||
|
fileList.value.forEach(file => {
|
||||||
|
if (file.key == cpt.name) {
|
||||||
|
file.progress = (Math.round(p * 10000) / 100)
|
||||||
|
if (file.progress == 100) {
|
||||||
|
updateModelValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 设置并发上传的分片数量。
|
||||||
|
parallel: 1,
|
||||||
|
// 设置分片大小。默认值为1 MB,最小值为100 KB。
|
||||||
|
partSize: 1024 * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 文件上传
|
||||||
|
function fileChange() {
|
||||||
|
const files = fileInput.value.files;
|
||||||
|
let valid = true
|
||||||
|
files.forEach(file => {
|
||||||
|
let fileName = file.name
|
||||||
|
let accepts = props.accept.split(',')
|
||||||
|
if (accepts) {
|
||||||
|
for (let i = 0; i < accepts.length; i++) {
|
||||||
|
if (!validAccept(fileName, accepts[i]) && valid) {
|
||||||
|
alert('上传文件格式只能为:' + getSettingSuffixs().replaceAll(',', ','))
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!validAccept(fileName, 'null') && valid) {
|
||||||
|
alert('上传文件格式只能为:' + getAllSuffixs().replaceAll(',', ','))
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let uploadDirs = []
|
||||||
|
$common.get('/system/file/getFileUploadDirectory', {length: files.length}).then(dirRes => {
|
||||||
|
uploadDirs = dirRes.data
|
||||||
|
let _fileList = []
|
||||||
|
uploadDirs.forEach((uploadDir, i) => {
|
||||||
|
let fileUrl = uploadDir + files[i].name;
|
||||||
|
_fileList.push({key: fileUrl, name: files[i].name, progress: 100, file: files[i]})
|
||||||
|
})
|
||||||
|
fileList.value.push(..._fileList)
|
||||||
|
_fileList.forEach(file => {
|
||||||
|
ossClient.multipartUpload(file.key, file.file, {
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
fileInput.value.value = ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const acceptList = ref({
|
||||||
|
image: 'png,jpg,gif,jpeg',
|
||||||
|
wps: 'pdf,pptx,xls,xlsx,csv,docx,doc',
|
||||||
|
compress: 'zip,rar,7z',
|
||||||
|
video: 'avi,flv,mp4,mpeg'
|
||||||
|
})
|
||||||
|
|
||||||
|
function getSettingSuffixs() {
|
||||||
|
if (props.formats) {
|
||||||
|
return props.formats
|
||||||
|
}
|
||||||
|
let suffixs = acceptList.value[props.accept]
|
||||||
|
if (!suffixs) {
|
||||||
|
suffixs = getAllSuffixs()
|
||||||
|
}
|
||||||
|
return suffixs
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllSuffixs() {
|
||||||
|
let suffixs = ''
|
||||||
|
for (const key in acceptList.value) {
|
||||||
|
suffixs += acceptList.value[key] + ','
|
||||||
|
}
|
||||||
|
suffixs = suffixs.substring(0, suffixs.length - 1)
|
||||||
|
return suffixs
|
||||||
|
}
|
||||||
|
|
||||||
|
function validAccept(fileName, accept) {
|
||||||
|
if (props.formats) {
|
||||||
|
return validEndsWith(fileName, props.formats)
|
||||||
|
}
|
||||||
|
if (accept && acceptList.value[accept]) {
|
||||||
|
return validEndsWith(fileName, acceptList.value[accept])
|
||||||
|
} else {
|
||||||
|
return validEndsWith(fileName, getAllSuffixs())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validEndsWith(fileName, suffixs) {
|
||||||
|
suffixs = suffixs.split(',')
|
||||||
|
for (let i = 0; i < suffixs.length; i++) {
|
||||||
|
const suffix = suffixs[i]
|
||||||
|
if (fileName.toLowerCase().endsWith('.' + suffix)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<style scoped>
|
||||||
|
.video {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<video width="300" height="300" controls class="video">
|
||||||
|
<source :src="url" type="video/mp4">
|
||||||
|
</video>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
url: String
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -0,0 +1,53 @@
|
||||||
|
import {computed, watch} from 'vue'
|
||||||
|
import {isArray, isNumber, isString} from "lodash-es";
|
||||||
|
|
||||||
|
export function watchValue(componentValue, props, emit){
|
||||||
|
let watchList = []
|
||||||
|
let multiple = props.multiple
|
||||||
|
let join = props.join
|
||||||
|
if(!multiple){
|
||||||
|
join = false
|
||||||
|
}
|
||||||
|
const getComponentValue = computed(() => {
|
||||||
|
if (join) {
|
||||||
|
return componentValue.value && componentValue.value.join(',')
|
||||||
|
} else {
|
||||||
|
return componentValue.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let componentValueWatch = false
|
||||||
|
let setValue = (value) => {
|
||||||
|
if(isArray(value)){
|
||||||
|
value = value.map(v => v.toString())
|
||||||
|
componentValue.value = value
|
||||||
|
}else if(isNumber(value)){
|
||||||
|
join = multiple
|
||||||
|
componentValue.value = props.multiple ? value.toString().split(',') : value.toString()
|
||||||
|
}else if(isString(value)){
|
||||||
|
join = multiple
|
||||||
|
componentValue.value = props.multiple ? value.split(',') : value
|
||||||
|
}else{
|
||||||
|
componentValue.value = value
|
||||||
|
}
|
||||||
|
if (!componentValueWatch) {
|
||||||
|
watchList.push(watch(componentValue, (value) => {
|
||||||
|
if (join) {
|
||||||
|
emit('update:modelValue', value && value.join(','))
|
||||||
|
emit('change', value && value.join(','))
|
||||||
|
} else {
|
||||||
|
emit('update:modelValue', value)
|
||||||
|
emit('change', value)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
componentValueWatch = true
|
||||||
|
}
|
||||||
|
setValue(props.modelValue)
|
||||||
|
watchList.push(watch(() => props.modelValue, (value) => {
|
||||||
|
// 如果传过来的值和选择的值不一样则更新
|
||||||
|
if(!$common.arrayStringEq(value, getComponentValue.value)){
|
||||||
|
setValue(value)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return watchList
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
<template>
|
||||||
|
<div :class="monacoVolarClass" style="width: 100%;height: 100%;"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
import * as monaco from "monaco-editor-core";
|
||||||
|
import {loadGrammars, loadTheme} from "monaco-volar";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
theme: {
|
||||||
|
type: String,
|
||||||
|
default: 'dark'// dark or light
|
||||||
|
},
|
||||||
|
fileName: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
oldCode: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
onSave: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
onDidChangeModelContent: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
compare: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
watch(() => props.code, (value) => {
|
||||||
|
setValue(value)
|
||||||
|
})
|
||||||
|
watch(() => props.oldCode, (value) => {
|
||||||
|
setOldValue(value)
|
||||||
|
})
|
||||||
|
const monacoVolarClass = ref('monaco-volar' + $common.uuid())
|
||||||
|
|
||||||
|
function getModelUri(){
|
||||||
|
let fileName = props.compare ? (props.fileName + 'compare') : props.fileName
|
||||||
|
return monaco.Uri.parse(`file:///${fileName}.vue`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOldModelUri(){
|
||||||
|
return monaco.Uri.parse(`file:///${props.fileName}-old.vue`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModel(){
|
||||||
|
return monaco.editor.getModel(getModelUri())
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOldModel(){
|
||||||
|
return monaco.editor.getModel(getOldModelUri())
|
||||||
|
}
|
||||||
|
let editorInstance = null
|
||||||
|
let editorModel = null
|
||||||
|
let editorOldModel = null
|
||||||
|
function afterReady(theme) {
|
||||||
|
editorModel = monaco.editor.createModel(props.code, 'vue', getModelUri());
|
||||||
|
if(props.compare){
|
||||||
|
editorInstance = monaco.editor.createDiffEditor(document.querySelector(`.${monacoVolarClass.value}`), {
|
||||||
|
theme,
|
||||||
|
automaticLayout: true,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
minimap: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
inlineSuggest: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
"semanticHighlighting.enabled": true,
|
||||||
|
})
|
||||||
|
editorOldModel = monaco.editor.createModel(props.oldCode, 'vue', getOldModelUri());
|
||||||
|
editorInstance.setModel({
|
||||||
|
modified: editorModel,
|
||||||
|
original: editorOldModel
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
editorInstance = monaco.editor.create(document.querySelector(`.${monacoVolarClass.value}`), {
|
||||||
|
theme,
|
||||||
|
model: editorModel,
|
||||||
|
automaticLayout: true,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
minimap: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
inlineSuggest: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
"semanticHighlighting.enabled": true,
|
||||||
|
})
|
||||||
|
editorInstance.onDidChangeModelContent((e) => {
|
||||||
|
props.onDidChangeModelContent(e)
|
||||||
|
})
|
||||||
|
addCommands()
|
||||||
|
loadGrammars(monaco, editorInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCommands(){
|
||||||
|
editorInstance.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function(ed) {
|
||||||
|
props.onSave()
|
||||||
|
// props.onSave(props.fileName)
|
||||||
|
// 不能在 addCommand 回调里面 获取组件的变量并传出去 会有问题
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadTheme(monaco.editor).then(theme => {
|
||||||
|
afterReady(theme[props.theme]);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function setValue(value){
|
||||||
|
getModel().setValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOldValue(value){
|
||||||
|
getOldModel().setValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(){
|
||||||
|
return getModel().getValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOldValue(){
|
||||||
|
return getOldModel().getValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispose(){
|
||||||
|
editorModel && editorModel.dispose()
|
||||||
|
editorOldModel && editorOldModel.dispose()
|
||||||
|
editorInstance.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ setValue, setOldValue, getValue, getOldValue, dispose })
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<template>
|
||||||
|
<div>{{ msg }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const msg = ref('hello magic-boot')
|
||||||
|
</script>
|
|
@ -0,0 +1,145 @@
|
||||||
|
<template>
|
||||||
|
<n-form
|
||||||
|
ref="dataForm"
|
||||||
|
:rules="rules"
|
||||||
|
:model="formData"
|
||||||
|
v-bind="form.props"
|
||||||
|
>
|
||||||
|
<n-grid v-for="(row,i) in form.rows" :key="i" :cols="row.gutter">
|
||||||
|
<n-gi v-for="(col,j) in row.cols" :key="j" :span="col.span" v-bind="col.colProps">
|
||||||
|
<n-form-item :label="col.label" :label-width="col.labelWidth" :path="col.name"
|
||||||
|
v-bind="col.formItemProps">
|
||||||
|
<slot v-if="col.component == 'dynamic'" :name="col.name" :form-data="formData" :col="col"></slot>
|
||||||
|
<component
|
||||||
|
v-else
|
||||||
|
:is="!col.component ? 'mb-input' : col.component.startsWith('n-') || $global.dynamicComponentNames.indexOf(col.component) != -1 ? col.component : 'mb-' + col.component"
|
||||||
|
v-model="formData[col.name]"
|
||||||
|
:item-label="col.label"
|
||||||
|
v-bind="col.props"
|
||||||
|
@change="col.change"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, reactive, watch} from 'vue'
|
||||||
|
|
||||||
|
const rules = reactive(getRules())
|
||||||
|
const formData = ref({})
|
||||||
|
const dataForm = ref()
|
||||||
|
const props = defineProps({
|
||||||
|
form: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
add: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
primaryField: {
|
||||||
|
type: String,
|
||||||
|
default: 'id'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['reload'])
|
||||||
|
watch(() => [props.detail && props.detail.formData, props.add && props.add.formData], (value) => {
|
||||||
|
value.forEach(it => {
|
||||||
|
if (it) {
|
||||||
|
formData.value = $common.objectAssign(formData.value, it)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, {deep: true})
|
||||||
|
|
||||||
|
props.form.props = props.form.props || {}
|
||||||
|
$common.setDefaultValue(props.form.props, 'labelPosition', 'right')
|
||||||
|
$common.setDefaultValue(props.form.props, 'labelWidth', '')
|
||||||
|
|
||||||
|
if (props.add && props.add.formData) {
|
||||||
|
formData.value = $common.objectAssign(formData.value, props.add.formData)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRules() {
|
||||||
|
let _rules = {}
|
||||||
|
props.form.rows.forEach(row => {
|
||||||
|
row.cols.forEach(col => {
|
||||||
|
if (col.rules) {
|
||||||
|
_rules[col.name] = col.rules
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return _rules
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData() {
|
||||||
|
let data = {}
|
||||||
|
props.form.rows.forEach(row => {
|
||||||
|
row.cols.forEach(col => {
|
||||||
|
data[col.name] = col.defaultValue || undefined
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function initFormData() {
|
||||||
|
formData.value = getData()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormData() {
|
||||||
|
return formData.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(d) {
|
||||||
|
dataForm.value.validate((errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
d.loading()
|
||||||
|
$common.post(props.form.request.url, formData.value).then(res => {
|
||||||
|
d.hideLoading()
|
||||||
|
$message.success((!formData.value[props.primaryField] ? '创建' : '修改') + '成功')
|
||||||
|
d.hide()
|
||||||
|
emit('reload')
|
||||||
|
}).catch(() => d.hideLoading())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDetail(id) {
|
||||||
|
formData.value = props.detail.formData || {}
|
||||||
|
if (props.detail && props.detail.request) {
|
||||||
|
let _formData = getData()
|
||||||
|
_formData[props.primaryField] = id
|
||||||
|
$common.get(props.detail.request.url, {[props.primaryField]: id}).then(res => {
|
||||||
|
const {data} = res
|
||||||
|
for (let t in _formData) {
|
||||||
|
if ((data[t] || data[t] === 0) && (!props.detail.excludeAssign || props.detail.excludeAssign.indexOf(t) === -1)) {
|
||||||
|
_formData[t] = data[t]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (formData.value) {
|
||||||
|
formData.value = $common.objectAssign(_formData, formData.value)
|
||||||
|
} else {
|
||||||
|
formData.value = _formData
|
||||||
|
}
|
||||||
|
if (props.detail.handlerFormData) {
|
||||||
|
props.detail.handlerFormData(formData.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (props.detail.handlerFormData) {
|
||||||
|
props.detail.handlerFormData(formData.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({save, getDetail, getFormData, initFormData})
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1,63 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-list">
|
||||||
|
<div class="mb-search">
|
||||||
|
<mb-search v-if="table.where" :where="table.where" :no-reset="search && search.noReset" @search="reload"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-toolbar" v-if="tools && tools.length > 0">
|
||||||
|
<template v-for="(it, i) in tools" :key="i">
|
||||||
|
<n-button v-if="it.type == 'add'" v-permission="it.permission" type="primary" @click="it.click">
|
||||||
|
{{ it.label || '添加' }}
|
||||||
|
</n-button>
|
||||||
|
<!-- <mb-button v-else-if="it.type == 'delete'" v-permission="it.permission" :plain="true" :request-url="it.url" :btn-type="'delete'" :request-data="{ id: ids }" :after-handler="reload" />-->
|
||||||
|
<n-button v-else :icon="it.icon" :key="it.label" v-permission="it.permission" :type="it.type"
|
||||||
|
:size="it.size" :class="it.class" @click="it.click(ids)">
|
||||||
|
{{ it.label }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="mb-table">
|
||||||
|
<mb-table ref="tableRef" v-bind="table" @selection-change="selectionChange"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref} from 'vue'
|
||||||
|
|
||||||
|
const tableRef = ref()
|
||||||
|
const ids = ref([])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
search: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tools: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
props.tools.forEach(it => {
|
||||||
|
if (it.type == 'delete') {
|
||||||
|
props.table.selection = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
tableRef.value.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectionChange(columns) {
|
||||||
|
ids.value = columns.map(it => it['id']).join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({reload})
|
||||||
|
|
||||||
|
</script>
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1646452992174" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4758" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M914.5 653.5c-5.5 0-11 1.1-16 3.3l-0.2 0.1h-0.2L510.2 822.2 122.2 657h-0.2l-0.2-0.1c-5-2.1-10.3-3.3-16-3.3-23.1 0-41.8 19.3-41.8 43.1 0 18 10.7 33.3 25.8 39.8l403.9 172.1 0.4 0.1c10.2 4.4 21.8 4.4 32 0l0.2-0.1c0.1 0 0.1-0.1 0.2-0.1l403.9-172.1c15.1-6.5 25.8-21.8 25.8-39.8 0.1-23.8-18.6-43.1-41.7-43.1z m0-186.5c-7.9-0.2-16 3.2-16 3.2L510.2 635.6 121.8 470.2s-10.3-3.2-16-3.2C82.7 467 64 486.2 64 510c0 17.9 10.7 33.3 25.8 39.7l403.9 172c0.1 0 0.1 0.1 0.2 0.1l0.1 0.1c5 2.1 10.3 3.3 16 3.3 5.7 0 11.1-1.2 16-3.3l0.2-0.1c0.1 0 0.1 0 0.2-0.1l403.9-172c15.1-6.4 25.8-21.8 25.9-39.7 0.1-23.8-18.6-43-41.7-43zM89.8 363.2l403.9 172.1c0.1 0 0.1 0 0.2 0.1l0.1 0.1c5 2.1 10.3 3.2 16 3.2 5.5 0 10.9-1.1 16-3.2l0.2-0.1 0.2-0.1 403.9-172c15.1-6.5 25.8-21.8 25.9-39.7 0-18-10.7-33.3-25.8-39.8L526.5 111.6c-0.1 0-0.1 0-0.2-0.1l-0.2-0.1c-10.2-4.4-21.8-4.4-32 0l-0.1 0.1L89.8 283.7C74.7 290.1 64 305.5 64 323.5c0 17.9 10.7 33.2 25.8 39.7z" p-id="4759"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1653135674579" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5558" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="240"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||||
|
</style></defs><path d="M170.6752 0h682.6496A170.6752 170.6752 0 0 1 1024 170.6752v682.6496A170.6752 170.6752 0 0 1 853.3248 1024H170.6752A170.6752 170.6752 0 0 1 0 853.3248V170.6752A170.6752 170.6752 0 0 1 170.6752 0z m350.72 682.6752H195.0464a42.6752 42.6752 0 1 0 0 85.3248h326.2976a128 128 0 0 0 241.536 0h72.2432a42.6752 42.6752 0 1 0 0-85.3248H762.88a128 128 0 0 0-241.4336 0zM263.2704 256H192.5376a42.6752 42.6752 0 1 0 0 85.3248h70.7584a128 128 0 0 0 241.4336 0h327.68a42.6752 42.6752 0 0 0 0-85.3248h-327.68a128 128 0 0 0-241.4336 0z" p-id="5559"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1649004377679" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2837" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||||
|
</style></defs><path d="M511.850044 0.299912C229.332813 0.299912 0.299912 107.568486 0 239.929708v544.140584c0 132.461193 229.132871 239.929708 511.850044 239.929708s511.850044-107.468515 511.850044-239.929708V239.929708C1023.400176 107.568486 794.367275 0.299912 511.850044 0.299912zM167.950796 895.737577c-22.093527 0-39.988285-17.894757-39.988285-39.988285s17.894757-39.988285 39.988285-39.988284 39.988285 17.894757 39.988284 39.988284-17.894757 39.988285-39.988284 39.988285z m791.768036-188.644733c-17.894757 11.496632-37.489017 22.193498-58.782778 32.190569-104.969247 49.18559-243.228742 76.277653-389.08601 76.277653s-284.116763-27.092063-389.08601-76.277653c-21.293762-9.997071-40.888021-20.693937-58.782779-32.190569v-79.176804c87.274431 73.778385 255.125256 123.66377 447.868789 123.663771s360.594357-49.885385 447.868788-123.663771v79.176804zM127.962511 583.828956c0-22.093527 17.894757-39.988285 39.988285-39.988284s39.988285 17.894757 39.988284 39.988284-17.894757 39.988285-39.988284 39.988285-39.988285-17.894757-39.988285-39.988285z m831.756321-148.156594c-17.894757 11.496632-37.489017 22.193498-58.782778 32.190569-104.969247 49.18559-243.228742 76.277653-389.08601 76.277653S227.733281 517.048521 122.764034 467.862931c-21.293762-9.997071-40.888021-20.693937-58.782779-32.190569v-79.176804c87.274431 73.778385 255.125256 123.66377 447.868789 123.66377s360.594357-49.885385 447.868788-123.66377v79.176804z" p-id="2838"></path></svg>
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017183540" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5317" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M259.2 0H896a64 64 0 0 1 64 64v748.608a64 64 0 0 1-64 64H259.2a64 64 0 0 1-64-64V64a64 64 0 0 1 64-64z m132.8 183.04V640h171.264c76.096 0 133.12-20.48 172.544-61.44 37.376-39.04 56.384-94.72 56.384-167.04 0-72.96-19.008-128.64-56.384-167.04-39.36-40.96-96.448-61.44-172.544-61.44H392z m76.8 64h80c58.432 0 101.056 12.8 128 39.04 26.24 25.6 39.296 67.84 39.296 125.44 0 56.32-13.12 97.92-39.36 124.8-26.88 26.24-69.504 39.68-127.936 39.68h-80V247.04zM64 384h75.968v568.512h580.096V1024H128a64 64 0 0 1-64-64V384z" p-id="5318"></path></svg>
|
After Width: | Height: | Size: 915 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg class="icon" viewBox="0 0 1028 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M875.086452 153.730058C676.053818-45.302575 353.260522-45.302575 154.128323 153.730058s-199.032634 521.825929 0 720.958129 521.825929 199.032634 720.958129 0 199.032634-521.825929 0-720.958129zM725.836868 725.438604c-9.757478 9.757478-25.488922 9.757478-35.246399 0L514.557604 549.405739 338.624306 725.438604c-9.757478 9.757478-25.488922 9.757478-35.2464 0s-9.757478-25.488922 0-35.2464l176.032865-176.032864-176.032865-175.933299c-9.757478-9.757478-9.757478-25.488922 0-35.246399 9.757478-9.757478 25.488922-9.757478 35.2464 0l176.032864 176.032865 176.032865-176.032865c9.757478-9.757478 25.488922-9.757478 35.246399 0 9.757478 9.757478 9.757478 25.488922 0 35.246399L549.804004 514.15934 725.836868 690.192204c9.657912 9.757478 9.657912 25.488922 0 35.2464z"></path></svg>
|
After Width: | Height: | Size: 899 B |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017365938" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6713" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M922.317764 0H101.64469A100.312307 100.312307 0 0 0 1.634036 100.010654V923.989346a100.312307 100.312307 0 0 0 100.010654 100.010654h820.673074a100.299739 100.299739 0 0 0 99.998085-100.010654V100.06093A100.299739 100.299739 0 0 0 922.317764 0zM427.933063 402.04107l-177.321894 103.064894 177.321894 97.094672v81.257871L179.684925 534.831388v-57.477532l248.248138-160.529857z m70.938812 364.661192h-53.970812l78.643542-509.303973h53.958243z m345.393085-231.845736L596.004253 683.445938v-81.257871l177.321894-97.094672-177.321894-103.064894v-85.217071l248.260707 160.542426z" p-id="6714"></path></svg>
|
After Width: | Height: | Size: 977 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
|
After Width: | Height: | Size: 944 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M38.47 52L52 38.462l-23.648-23.67L43.209 0H.035L0 43.137l14.757-14.865L38.47 52zm74.773 47.726L89.526 76 76 89.536l23.648 23.672L84.795 128h43.174L128 84.863l-14.757 14.863zM89.538 52l23.668-23.648L128 43.207V.038L84.866 0 99.73 14.76 76 38.472 89.538 52zM38.46 76L14.792 99.651 0 84.794v43.173l43.137.033-14.865-14.757L52 89.53 38.46 76z"/></svg>
|
After Width: | Height: | Size: 421 B |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017648725" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19289" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M555.541333 117.994667l312.874667 224.565333A117.333333 117.333333 0 0 1 917.333333 437.866667V800c0 64.8-52.533333 117.333333-117.333333 117.333333H640V746.666667c0-70.688-57.312-128-128-128s-128 57.312-128 128v170.666666H224c-64.8 0-117.333333-52.533333-117.333333-117.333333V437.877333a117.333333 117.333333 0 0 1 48.917333-95.317333l312.874667-224.565333a74.666667 74.666667 0 0 1 87.082666 0z" p-id="19290"></path></svg>
|
After Width: | Height: | Size: 803 B |
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1649004239313" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1045" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||||
|
</style></defs><path d="M870.4 256h-153.6V153.6a51.2 51.2 0 0 0-102.4 0v102.4H409.6V153.6a51.2 51.2 0 0 0-102.4 0v102.4H153.6a51.2 51.2 0 0 0 0 102.4h51.2v102.4a51.2 51.2 0 0 0 0 12.8A322.56 322.56 0 0 0 204.8 512a307.2 307.2 0 0 0 256 302.592V921.6h102.4v-107.008A307.2 307.2 0 0 0 819.2 512a322.56 322.56 0 0 0 0-38.4A51.2 51.2 0 0 0 819.2 460.8V358.4h51.2a51.2 51.2 0 0 0 0-102.4z m-153.6 204.8h-7.168a209.408 209.408 0 0 1 7.168 51.2 204.8 204.8 0 0 1-409.6 0 209.408 209.408 0 0 1 7.168-51.2H307.2V358.4h409.6z" p-id="1046"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1646472156835" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5189" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M956.8 828.7c3.2 20-0.8 38-12 54-11.1 16-26.4 25.7-45.5 29l-115.1 19c-18.5 3.3-35.7-0.9-51.3-12.5-15.6-11.6-25.1-27.5-28.3-47.5l-97.8-646c-1.3-10-0.8-19.5 1.4-28.5s6-17.4 11-25c5.1-7.7 11.7-14 19.7-19s16.8-8.5 26.4-10.5l114.1-19c19.2-3.3 36.4 1 51.8 13 15.4 12 24.6 28 27.8 48l97.8 645zM510 99.6c19.2 0 35.5 7 48.9 21 13.4 14 20.1 31 20.1 51v667.9c0 20-6.7 37-20.1 51-13.4 14-29.7 21-48.9 21h-99.7c-19.2 0-35.5-7-48.9-21-13.4-14-20.1-31-20.1-51V171.6c0-20 5.1-37 15.4-51s25.3-21 45-21H510z m23.4 504c6.4 0 11.8-3.1 16.3-9.5 4.5-6.3 6.7-13.9 6.7-22.5 0-9.4-2.3-17-6.7-23-4.5-6-9.9-9-16.3-9h-99.7c-6.4 0-11.8 3-16.3 9s-6.7 13.7-6.7 23c0 8.7 2.2 16.2 6.7 22.5 4.5 6.4 9.9 9.5 16.3 9.5h99.7z m0-127.1c6.4 0 11.8-3.1 16.3-9.5 4.5-6.3 6.7-14.2 6.7-23.5s-2.3-17-6.7-23c-4.5-6.1-9.9-9-16.3-9h-99.7c-6.4 0-11.8 3-16.3 9s-6.7 13.7-6.7 23 2.2 17.2 6.7 23.5c4.5 6.3 9.9 9.5 16.3 9.5h99.7z m-301-376.9c19.2 0 35.7 7 49.3 21 13.7 14 20.6 31 20.6 51v667.9c0 20-6.8 37-20.6 51s-30.2 21-49.3 21h-99.7c-19.2 0-35.7-7-49.3-21-13.8-14-20.6-31-20.6-51V171.6c0-20 6.8-37 20.6-51s30.2-21 49.3-21h99.7z m-95.9 250c-7 0-12.8 3-17.2 9-4.5 6-6.7 13.7-6.7 23 0 8.7 2.2 16.2 6.7 22.5 4.5 6.2 10.2 9.5 17.2 9.5h92c7 0 12.6-3.1 16.8-9.5s6.3-13.9 6.3-22.5c0-9.4-2.1-17-6.3-23-4.1-6-9.7-9-16.8-9h-92z m95.9 313.9c7 0 12.6-3 16.8-9 4.1-6 6.3-13.4 6.3-22 0-9.4-2.1-16.8-6.3-22.5s-9.7-8.5-16.8-8.5h-95.9c-7 0-12.8 2.8-17.2 8.5-4.4 5.7-6.7 13.2-6.7 22.5 0 8.7 2.2 16 6.7 22s10.2 9 17.2 9h95.9z m0-122.9c7 0 12.6-3 16.8-9 4.1-6 6.3-13.7 6.3-23 0-9.4-2.1-17-6.3-23-4.1-6-9.7-9-16.8-9h-95.9c-7 0-12.8 3-17.2 9-4.5 6-6.7 13.7-6.7 23 0 9.4 2.2 17 6.7 23 4.5 5.9 10.2 9 17.2 9h95.9z" p-id="5190"></path></svg>
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1649004325581" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2700" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||||
|
</style></defs><path d="M665.729808 153.544368V0h-102.733792v153.544368h-255.90728V0H204.725824v153.544368H0v153.544368h870.455632V153.544368h-204.725824zM0 358.270192h255.90728v255.90728H0zM562.996016 430.220934a205.096704 205.096704 0 0 0-51.181456 132.775082 170.604853 170.604853 0 0 0 5.192322 51.181456H307.088736v-255.90728h255.90728zM0 665.358928h255.90728v255.90728H0zM562.996016 696.141978a363.833394 363.833394 0 0 0-189.148859 225.49511h-66.758421v-256.27816h230.316552z" p-id="2701"></path><path d="M562.996016 562.996016A153.915248 153.915248 0 1 0 716.911264 407.968127a153.915248 153.915248 0 0 0-153.915248 153.544368z" p-id="2702"></path><path d="M716.911264 716.911264A308.201376 308.201376 0 0 0 407.968127 1024h616.031873a307.830496 307.830496 0 0 0-307.088736-307.088736z" p-id="2703"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017024921" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2935" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M892.928 128q28.672 0 48.64 19.968t19.968 48.64l0 52.224q0 28.672-19.968 48.64t-48.64 19.968l-759.808 0q-28.672 0-48.64-19.968t-19.968-48.64l0-52.224q0-28.672 19.968-48.64t48.64-19.968l759.808 0zM892.928 448.512q28.672 0 48.64 19.968t19.968 48.64l0 52.224q0 28.672-19.968 48.64t-48.64 19.968l-759.808 0q-28.672 0-48.64-19.968t-19.968-48.64l0-52.224q0-28.672 19.968-48.64t48.64-19.968l759.808 0zM892.928 769.024q28.672 0 48.64 19.968t19.968 48.64l0 52.224q0 28.672-19.968 48.64t-48.64 19.968l-759.808 0q-28.672 0-48.64-19.968t-19.968-48.64l0-52.224q0-28.672 19.968-48.64t48.64-19.968l759.808 0z" p-id="2936"></path></svg>
|
After Width: | Height: | Size: 997 B |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1646494062534" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2291" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M759.3 525.4h-200v199.4c0 110.2 89.6 199.4 200 199.4 110.5 0 199.9-89.3 199.9-199.4 0.1-110.2-89.4-199.4-199.9-199.4zM98 724.8c0 110.2 89.6 199.4 199.9 199.4 110.5 0 199.9-89.2 199.9-199.4V525.4H297.9C187.6 525.4 98 614.6 98 724.8z m861.3-460.3c0-110.2-89.6-199.4-199.9-199.4-110.4 0-200 89.3-200 199.4v199.4h200c110.4 0 199.9-89.2 199.9-199.4zM297.9 65.1C187.5 65.1 98 154.5 98 264.5c0 110.2 89.6 199.4 199.9 199.4h200V264.5c0-110.1-89.5-199.4-200-199.4z" p-id="2292"></path></svg>
|
After Width: | Height: | Size: 860 B |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641720023979" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15481" width="240" height="240" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M960 1024h-320v-384h128V576H256v64h128v384H0v-384h128V448h320V384H320V0h384v384H576v64h320v192h128v384h-64zM128 896h128v-128H128v128zM576 128H448v128h128V128z m320 640h-128v128h128v-128z" p-id="15482"></path></svg>
|
After Width: | Height: | Size: 592 B |
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1649004383477" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2972" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||||
|
</style></defs><path d="M12.795383 550.784512h92.979783a11.942357 11.942357 0 0 1 12.795383 11.942357v12.795383a12.795383 12.795383 0 0 1-12.795383 11.942358H12.795383a12.36887 12.36887 0 0 1-12.795383-11.942358v-12.795383a11.942357 11.942357 0 0 1 12.795383-11.942357z m0-365.521441h92.979783a11.942357 11.942357 0 0 1 12.795383 11.942357v12.795383a11.942357 11.942357 0 0 1-12.795383 11.942358H12.795383a11.942357 11.942357 0 0 1-12.795383-11.942358v-12.795383a11.942357 11.942357 0 0 1 12.795383-11.942357z m0 182.973977h92.979783a11.942357 11.942357 0 0 1 12.795383 11.515844v13.221896a11.942357 11.942357 0 0 1-12.795383 11.515845H12.795383a11.942357 11.942357 0 0 1-12.795383-11.515845V379.752892a11.942357 11.942357 0 0 1 12.795383-11.515844z m0 365.094928h92.979783a12.36887 12.36887 0 0 1 12.795383 11.942358v12.795383a11.942357 11.942357 0 0 1-12.795383 11.942357H12.795383a11.942357 11.942357 0 0 1-12.795383-11.942357v-12.795383a12.36887 12.36887 0 0 1 12.795383-11.942358z" p-id="2973"></path><path d="M945.578804 73.090213h-69.521581a10.236306 10.236306 0 0 1-10.236307-8.103742A76.772298 76.772298 0 0 0 787.76908 0.15653H127.95383A85.302553 85.302553 0 0 0 37.533123 82.473494v54.593634a9.383281 9.383281 0 0 0 9.809794 9.383281H106.628192a47.76943 47.76943 0 0 1 49.475481 45.210353v27.296817a38.386149 38.386149 0 0 1-39.665688 37.106611H47.342917a9.383281 9.383281 0 0 0-9.809794 8.956768v54.593634a9.383281 9.383281 0 0 0 9.809794 9.383281H106.628192a47.76943 47.76943 0 0 1 49.475481 46.063379v17.913536a48.195943 48.195943 0 0 1-49.475481 46.063379H47.342917a9.383281 9.383281 0 0 0-9.809794 8.956768v54.593634a9.383281 9.383281 0 0 0 9.809794 9.383281H106.628192a47.76943 47.76943 0 0 1 49.048968 45.636866v27.296817a38.386149 38.386149 0 0 1-39.665687 36.680098H47.342917a9.809794 9.809794 0 0 0-10.236306 9.383281v54.593634a9.809794 9.809794 0 0 0 10.236306 9.383281h58.858762a47.342917 47.342917 0 0 1 49.048968 45.636866V767.87951a37.959636 37.959636 0 0 1-39.239174 36.253585H46.916404a10.236306 10.236306 0 0 0-10.236306 9.383281v127.95383A85.302553 85.302553 0 0 0 127.95383 1023.78717h810.374257a85.302553 85.302553 0 0 0 85.302553-82.316964V146.450409a76.345785 76.345785 0 0 0-78.904862-73.360196z m-343.342777 91.273732a47.76943 47.76943 0 0 1 55.446659-13.648408l94.259322 49.048968A37.959636 37.959636 0 0 1 767.72298 249.239986l-298.558937 466.178454a10.662819 10.662819 0 0 1-13.221895 3.412102l-147.573418-76.345785a8.956768 8.956768 0 0 1-3.838615-12.36887z m-178.70885 597.117874l-119.850087 42.651276a10.662819 10.662819 0 0 1-12.795383-5.544666v-2.559076l-9.809794-107.90773a9.383281 9.383281 0 0 1 8.956768-9.809794 13.648409 13.648409 0 0 1 5.971179 0l127.95383 66.962505a8.956768 8.956768 0 0 1 3.838615 12.36887 8.530255 8.530255 0 0 1-4.69164 4.69164z m522.051627 152.265057c0 40.0922-2.559077 72.933683-78.904862 72.933683H156.103673a76.772298 76.772298 0 0 1-78.051837-62.697376 8.956768 8.956768 0 0 1 7.250717-10.236307h692.230221a85.302553 85.302553 0 0 0 88.714655-82.316964V118.727079a9.809794 9.809794 0 0 1 10.236306-8.956768c38.812662 0 68.668555 5.118153 68.668556 72.933683v731.042882z" p-id="2974"></path></svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>
|
After Width: | Height: | Size: 623 B |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641017706517" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20110" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M792.703177 534.452774c-49.288352 33.217439-108.257215 52.515189-171.718341 52.515189-63.271312 0-122.050361-19.171208-171.1489-52.135561-132.996298 65.042909-224.866244 203.290726-224.866244 363.303875 0 169.314031 793.232441 166.972993 793.232441 0C1018.138862 737.7435 926.079103 599.305869 792.703177 534.452774L792.703177 534.452774 792.703177 534.452774zM354.359526 270.99103c0-149.636653 119.329695-270.99103 266.62531-270.99103 147.295615 0 266.62531 121.291105 266.62531 270.99103 0 149.636653-119.329695 270.864488-266.62531 270.864488C473.68922 541.855518 354.359526 420.627684 354.359526 270.99103L354.359526 270.99103 354.359526 270.99103 354.359526 270.99103zM177.959107 912.182508c0 6.200589 0.94907 12.084821 2.783938 17.905781C80.584558 913.827562 5.797867 881.749007 5.797867 833.409724c0-138.500902 79.468768-258.146954 194.622556-314.458422 40.177283 27.01685 88.010395 43.340849 139.576515 45.049174C241.040605 646.696082 177.959107 771.97328 177.959107 912.182508L177.959107 912.182508 177.959107 912.182508zM310.006336 270.99103c0-68.775916 21.638789-132.426856 58.399421-184.309332C361.952083 86.112256 355.561681 85.859171 349.108007 85.859171c-121.227834 0-219.488182 99.842131-219.488182 223.031376s98.197077 223.031376 219.424911 223.031376c27.269936 0 53.400988-5.061705 77.444086-14.362588C355.498409 459.666083 310.006336 370.76989 310.006336 270.99103L310.006336 270.99103zM310.006336 270.99103" p-id="20111"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703302857634" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2481" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.1953125" height="200"><path d="M651.8 775.5h-20.3v-527h178V305c0 19.3 15.7 35 35 35s35-15.7 35-35v-91.5c0-19.3-15.7-35-35-35h-248c-19.3 0-35 15.7-35 35V477H340.8V236.1c0-32-26-58-58-58H58c-32 0-58 26-58 58v551.8c0 32 26 58 58 58h224.8c32 0 58-26 58-58V547h220.7v263.5c0 19.3 15.7 35 35 35h55.3c19.3 0 35-15.7 35-35s-15.7-35-35-35z m-381 0.4H70V248.1h200.8v527.8z" p-id="2482"></path><path d="M990 651.5h-89v-89c0-19.3-15.7-35-35-35s-35 15.7-35 35v89h-89c-19.3 0-35 15.7-35 35s15.7 35 35 35h89v89c0 19.3 15.7 35 35 35s35-15.7 35-35v-89h89c19.3 0 35-15.7 35-35s-15.7-35-35-35z" p-id="2483"></path></svg>
|
After Width: | Height: | Size: 911 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>
|
After Width: | Height: | Size: 600 B |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1641015979047" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2139" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M519.9 647.4c-44.8 7.4-90.4-6.9-122.5-38.3-32.1-31.4-46.6-76.2-39-120 9.4-56.8 56.2-102.7 114.2-112.1 44.9-7.6 90.7 6.7 122.9 38.2 32.2 31.6 46.7 76.5 38.9 120.4-9.7 56.9-56.5 102.6-114.5 111.8M848.4 559l66-40.4c10.5-6.4 15.6-18.6 12.9-30.4L895 349.3c-2.7-11.8-12.8-20.6-25.1-21.9l-77.5-8.4c-7.7-0.8-14.7-4.6-19.5-10.5l-13-16c-4.8-5.9-7-13.5-6.1-21l8.9-75.7c1.4-12-5.1-23.6-16.2-28.9l-131-61.8c-11.1-5.2-24.4-3-33.2 5.5l-55 54c-5.4 5.3-12.8 8.4-20.6 8.4H486c-7.7 0-15.1-3-20.6-8.4l-55-54c-8.7-8.6-22-10.8-33.2-5.5l-130.9 61.8c-11.1 5.3-17.6 16.8-16.2 28.9l8.9 75.8c0.9 7.5-1.3 15-6.1 21L220 308.4c-4.8 5.9-11.8 9.7-19.5 10.5l-77.5 8.5c-12.3 1.3-22.3 10.1-25.1 21.9L65.5 488.1c-2.7 11.8 2.4 24 12.9 30.4l66 40.5c6.5 4 11.2 10.4 12.9 17.8l4.6 19.9c1.7 7.4 0.4 15.1-3.7 21.5l-41.6 64.5c-6.6 10.2-5.8 23.4 1.9 32.9l90.7 111.3c7.7 9.5 20.7 13.1 32.3 9.1l73.5-25.3c7.3-2.5 15.3-2.1 22.2 1.2l18.7 8.8c7 3.3 12.3 9.1 14.8 16.3l25.6 72c4.1 11.4 15.1 19.1 27.4 19.1H569c12.4 0 23.4-7.7 27.4-19.1l25.6-72c2.5-7.1 7.9-13 14.8-16.3l18.7-8.8c6.9-3.3 14.9-3.7 22.2-1.2l73.5 25.3c11.7 4 24.6 0.4 32.3-9.1l90.7-111.3c7.7-9.5 8.4-22.7 1.9-32.9l-41.6-64.5c-4.1-6.4-5.5-14.1-3.8-21.5l4.6-19.8c1.9-7.5 6.5-13.9 13.1-17.9" fill="" p-id="2140"></path></svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703302854435" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2343" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M928 701c-8.8 0-16-7.2-16-16v-77c0-53-43-96-96-96H560c-8.8 0-16-7.2-16-16v-96c0-8.8 7.2-16 16-16h48c53 0 96-43 96-96V96c0-53-43-96-96-96H416c-53 0-96 43-96 96v192c0 53 43 96 96 96h48c8.8 0 16 7.2 16 16v96c0 8.8-7.2 16-16 16H208c-53 0-96 43-96 96v77c0 8.8-7.2 16-16 16-53 0-96 43-96 96v131c0 53 43 96 96 96h96c53 0 96-43 96-96V797c0-53-43-96-96-96-8.8 0-16-7.2-16-16v-77c0-17.7 14.3-32 32-32h256c8.8 0 16 7.2 16 16v94c0 8.3-6.7 15-15 15h-1c-53 0-96 43-96 96v131c0 53 43 96 96 96h96c53 0 96-43 96-96V797c0-53-43-96-96-96h-1c-8.3 0-15-6.7-15-15v-94c0-8.8 7.2-16 16-16h256c17.7 0 32 14.3 32 32v77c0 8.8-7.2 16-16 16-53 0-96 43-96 96v131c0 53 43 96 96 96h96c53 0 96-43 96-96V797c0-53-43-96-96-96z m-736 64c17.6 0 32 14.4 32 32v131c0 17.6-14.4 32-32 32H96c-17.6 0-32-14.4-32-32V797c0-17.6 14.4-32 32-32h96z m368 0c17.6 0 32 14.4 32 32v131c0 17.6-14.4 32-32 32h-96c-17.6 0-32-14.4-32-32V797c0-17.6 14.4-32 32-32h96zM416 320c-17.6 0-32-14.4-32-32V96c0-17.6 14.4-32 32-32h192c17.6 0 32 14.4 32 32v192c0 17.6-14.4 32-32 32H416z m544 608c0 17.6-14.4 32-32 32h-96c-17.6 0-32-14.4-32-32V797c0-17.6 14.4-32 32-32h96c17.6 0 32 14.4 32 32v131z" p-id="2344"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z"/></svg>
|
After Width: | Height: | Size: 424 B |
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1649004142311" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3942" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||||
|
</style></defs><path d="M522.854 33.884L902.47 164.732c14.1 4.85 23.54 18.113 23.54 33.011v402.218c-10.714 238.49-386.18 383.703-402.118 389.764A34.91 34.91 0 0 1 511.505 992c-4.217 0-8.439-0.767-12.387-2.275C483.147 983.664 107.648 838.452 97 601.502V197.743c0-14.898 9.442-28.16 23.503-33.011L500.121 33.884a35.24 35.24 0 0 1 22.733 0z m243.984 299.804c-29.29-29.29-76.777-29.29-106.066 0L459.246 535.213l-95.46-95.46c-29.289-29.289-76.776-29.289-106.065 0-29.29 29.29-29.29 76.777 0 106.067l148.492 148.492c14.645 14.645 33.839 21.967 53.033 21.967l1.152-0.009c18.808-0.287 37.53-7.606 51.881-21.958l254.559-254.558c29.289-29.29 29.289-76.777 0-106.066z" p-id="3943"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<n-result status="404" title="404 资源不存在" description="生活总归带点荒谬" />
|
||||||
|
</template>
|
|
@ -0,0 +1,176 @@
|
||||||
|
<template>
|
||||||
|
<n-layout position="absolute" has-sider>
|
||||||
|
<n-layout-sider
|
||||||
|
bordered
|
||||||
|
collapse-mode="width"
|
||||||
|
:collapsed-width="64"
|
||||||
|
:width="200"
|
||||||
|
show-trigger
|
||||||
|
@update:collapsed="updateCollapsed"
|
||||||
|
@after-enter="() => isCollapsed = false"
|
||||||
|
>
|
||||||
|
<n-layout position="absolute">
|
||||||
|
<n-layout-header class="nav-bg">
|
||||||
|
<p class="text-center text-2xl m-0 pt-5 pb-5 text-white title" v-if="!isCollapsed">
|
||||||
|
{{ $global.title }}
|
||||||
|
</p>
|
||||||
|
<p class="text-center text-2xl m-0 pt-5 pb-5 text-white title" v-else>
|
||||||
|
{{ $global.title.substring(0, 1) }}
|
||||||
|
</p>
|
||||||
|
</n-layout-header>
|
||||||
|
<n-layout-content class="absolute top-16 right-0 bottom-0 left-0 nav-bg" :native-scrollbar="false">
|
||||||
|
<n-menu
|
||||||
|
ref="menuRef"
|
||||||
|
v-model:value="selectedKey"
|
||||||
|
:indent="24"
|
||||||
|
:collapsed-width="64"
|
||||||
|
:collapsed-icon-size="22"
|
||||||
|
:options="menuOptions"
|
||||||
|
inverted
|
||||||
|
accordion
|
||||||
|
/>
|
||||||
|
</n-layout-content>
|
||||||
|
</n-layout>
|
||||||
|
</n-layout-sider>
|
||||||
|
<n-layout>
|
||||||
|
<n-layout-header class="h-16" style="box-shadow: 1px 1px 6px #c6c6c6">
|
||||||
|
<layout-header/>
|
||||||
|
</n-layout-header>
|
||||||
|
<n-layout-content class="absolute right-0 bottom-0 left-0 bg-lightgray" style="top:4.3rem;">
|
||||||
|
<n-layout position="absolute">
|
||||||
|
<n-layout-header class="h-12 p-2 bg-lightgray">
|
||||||
|
<tabs/>
|
||||||
|
</n-layout-header>
|
||||||
|
<n-layout-content class="absolute top-12 right-0 bottom-0 left-0 px-4 router-view-content p-1 bg-lightgray">
|
||||||
|
<div style="width: 100%;height: 100%">
|
||||||
|
<component
|
||||||
|
v-for="com in keepaliveIframes"
|
||||||
|
:key="com.path"
|
||||||
|
:is="IframeComponent"
|
||||||
|
:url="common.getUrlType(com.meta.path) == 2 ? '/#' + com.meta.path : com.meta.path"
|
||||||
|
v-show="com.path == $route.path"
|
||||||
|
/>
|
||||||
|
<component
|
||||||
|
v-for="com in keepaliveDynamicComponents"
|
||||||
|
:key="com.path"
|
||||||
|
:is="ShowComponent"
|
||||||
|
:name="com.meta.componentName"
|
||||||
|
v-show="com.path == $route.path"
|
||||||
|
/>
|
||||||
|
<nested-router />
|
||||||
|
</div>
|
||||||
|
</n-layout-content>
|
||||||
|
</n-layout>
|
||||||
|
</n-layout-content>
|
||||||
|
</n-layout>
|
||||||
|
</n-layout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, h, watch, computed} from 'vue';
|
||||||
|
import tabs from './tabs.vue'
|
||||||
|
import NestedRouter from './nested-router.vue'
|
||||||
|
import {RouterLink} from 'vue-router'
|
||||||
|
// 此页面template内 不能直接使用$common 需要这里导入 才能使用 原因未知
|
||||||
|
import common from '@/scripts/common'
|
||||||
|
import {useUserStore} from "@/store/modules/userStore"
|
||||||
|
import {useTabsStore} from "@/store/modules/tabsStore"
|
||||||
|
import LayoutHeader from "@/layout/layout-header.vue";
|
||||||
|
import IframeComponent from '@/views/common/iframe.vue'
|
||||||
|
import ShowComponent from '@/views/common/show-component.vue'
|
||||||
|
import { isEmpty } from 'lodash-es'
|
||||||
|
|
||||||
|
const tabsStore = useTabsStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const menuRef = ref()
|
||||||
|
const currentTab = tabsStore.getCurrentTab
|
||||||
|
const selectedKey = ref(currentTab)
|
||||||
|
selectMenu(currentTab)
|
||||||
|
watch(() => tabsStore.getCurrentTab, (key) => selectMenu(key))
|
||||||
|
// 单独处理 "iframe" 并且开启缓存的页面
|
||||||
|
const keepaliveIframes = computed(() => tabsStore.getTabs.filter(it => $common.filterIframeTabs(it)))
|
||||||
|
// 缓存“动态组件”
|
||||||
|
const keepaliveDynamicComponents = computed(() => tabsStore.getTabs.filter(it => it.meta.componentName && it.meta.keepAlive))
|
||||||
|
|
||||||
|
function selectMenu(key) {
|
||||||
|
selectedKey.value = key
|
||||||
|
menuRef.value?.showOption(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCollapsed = ref(false)
|
||||||
|
|
||||||
|
function updateCollapsed(collapsed) {
|
||||||
|
if (collapsed) {
|
||||||
|
isCollapsed.value = collapsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuOptions = ref(recursionRouters(userStore.getPermissionRouters))
|
||||||
|
|
||||||
|
function recursionRouters(children) {
|
||||||
|
let menus = []
|
||||||
|
children.forEach((chi) => {
|
||||||
|
let menu = {}
|
||||||
|
if (!isEmpty(chi.children)) {
|
||||||
|
if (chi['alwaysShow'] === true) {
|
||||||
|
menu.key = chi.path
|
||||||
|
menu.label = chi.title
|
||||||
|
menu.children = recursionRouters(chi.children)
|
||||||
|
} else {
|
||||||
|
menu.key = chi.children[0].path
|
||||||
|
menu.label = () => h(
|
||||||
|
RouterLink,
|
||||||
|
{
|
||||||
|
to: {
|
||||||
|
path: chi.children[0].path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{default: () => chi.children[0].title}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
menu.key = chi.path
|
||||||
|
if(chi.openMode == '1'){
|
||||||
|
let path = $common.handlerUrlPage(chi.path)
|
||||||
|
menu.label = () => h(
|
||||||
|
'a',
|
||||||
|
{
|
||||||
|
href: path,
|
||||||
|
target: '_blank'
|
||||||
|
},
|
||||||
|
chi.title
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
menu.label = () => h(
|
||||||
|
RouterLink,
|
||||||
|
{
|
||||||
|
to: {
|
||||||
|
path: chi.path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{default: () => chi.title}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chi.icon) {
|
||||||
|
menu.icon = $common.renderIcon(chi.icon)
|
||||||
|
}
|
||||||
|
menus.push(menu)
|
||||||
|
})
|
||||||
|
return menus
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.nav-bg {
|
||||||
|
background-color: #041427;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-family: PoetsenOne;
|
||||||
|
}
|
||||||
|
.bg-lightgray{
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,126 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="header">
|
||||||
|
<div style="width: 95%">
|
||||||
|
<div>
|
||||||
|
<n-breadcrumb>
|
||||||
|
<n-breadcrumb-item>
|
||||||
|
首页
|
||||||
|
</n-breadcrumb-item>
|
||||||
|
<n-breadcrumb-item>
|
||||||
|
{{ $router.currentRoute.value.meta.title }}
|
||||||
|
</n-breadcrumb-item>
|
||||||
|
</n-breadcrumb>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<template v-if="userStore.getInfo.headPortrait">
|
||||||
|
<n-avatar
|
||||||
|
round
|
||||||
|
:size="40"
|
||||||
|
:src="$global.baseApi + userStore.getInfo.headPortrait"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<n-avatar
|
||||||
|
v-else
|
||||||
|
round
|
||||||
|
:size="40"
|
||||||
|
>
|
||||||
|
{{ userStore.getInfo.name?.substring(0, 1) }}
|
||||||
|
</n-avatar>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n-dropdown :options="options" @select="handleSelect">
|
||||||
|
<n-button text style="font-size: 20px;text-align: center">
|
||||||
|
<mb-icon icon="SettingsOutline" />
|
||||||
|
</n-button>
|
||||||
|
</n-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<mb-modal ref="uiDialog" title="UI大小配置" @confirm="handleUiSetting" width="320px" :show-footer="false">
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<n-radio-group v-model:value="$global.uiSize.value" name="uiSize" size="large">
|
||||||
|
<n-radio-button
|
||||||
|
v-for="ui in uiSizeList"
|
||||||
|
:key="ui.value"
|
||||||
|
:value="ui.value"
|
||||||
|
>
|
||||||
|
{{ ui.label }}
|
||||||
|
</n-radio-button>
|
||||||
|
</n-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</mb-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref} from 'vue'
|
||||||
|
import {useUserStore} from "@/store/modules/userStore";
|
||||||
|
import router from '@/scripts/router'
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const uiSizeList = ref([
|
||||||
|
{
|
||||||
|
label: "紧凑(小)",
|
||||||
|
value: "small"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "正常(中)",
|
||||||
|
value: "medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "放大(大)",
|
||||||
|
value: "large"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const options = ref([
|
||||||
|
{
|
||||||
|
label: "个人中心",
|
||||||
|
key: "userCenter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "界面大小",
|
||||||
|
key: "uiSetting",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "退出",
|
||||||
|
key: "logout",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const uiDialog = ref();
|
||||||
|
|
||||||
|
function handleSelect(key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'logout':
|
||||||
|
userStore.logout()
|
||||||
|
break;
|
||||||
|
case 'userCenter':
|
||||||
|
router.push({
|
||||||
|
path: '/user-center'
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case 'uiSetting':
|
||||||
|
uiDialog.value.show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUiSetting() {
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
|
<transition name="fade" mode="out-in" appear>
|
||||||
|
<keep-alive :include="keepAliveInclude">
|
||||||
|
<component v-if="tabsStore.getShow && routerComponents.some(it => it.path === $route.path)" :is="Component" :key="$route.path"/>
|
||||||
|
</keep-alive>
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {useTabsStore} from '@/store/modules/tabsStore'
|
||||||
|
import {computed} from "vue";
|
||||||
|
const tabsStore = useTabsStore()
|
||||||
|
|
||||||
|
const routerComponents = computed(() => tabsStore.getTabs.filter(it => {
|
||||||
|
// 过滤掉配置缓存并且是动态组件
|
||||||
|
if(it.meta.keepAlive && it.meta.componentName){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 过滤掉配置缓存并且需要iframe打开的
|
||||||
|
return !$common.filterIframeTabs(it);
|
||||||
|
}))
|
||||||
|
const keepAliveInclude = computed(() => tabsStore.getTabs.filter(it => it.meta.keepAlive && !it.meta.componentName).map(it => it.path.substring(it.path.lastIndexOf('/') + 1)))
|
||||||
|
</script>
|
|
@ -0,0 +1,183 @@
|
||||||
|
<template>
|
||||||
|
<div class="tabs">
|
||||||
|
<n-tag
|
||||||
|
:size="$global.uiSize.value"
|
||||||
|
@contextmenu="handleContextMenu(tab,$event)"
|
||||||
|
v-for="tab in tabsStore.getTabs"
|
||||||
|
:closable="tab.path!==`/home`"
|
||||||
|
@close="handleClose(tab.path)"
|
||||||
|
@click="jump(tab)"
|
||||||
|
:type="tabsStore.getCurrentTab == tab.path ? 'primary' : 'default'"
|
||||||
|
:class="[tabsStore.getCurrentTab == tab.path?'selected':'',global.uiSize]"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
{{ tab.meta.title }}
|
||||||
|
</n-tag>
|
||||||
|
<n-dropdown
|
||||||
|
placement="bottom-start"
|
||||||
|
trigger="manual"
|
||||||
|
:x="xAxis"
|
||||||
|
:y="yAxis"
|
||||||
|
:options="dropdownOptions"
|
||||||
|
:show="showDropdown"
|
||||||
|
:on-clickoutside="() => showDropdown = false"
|
||||||
|
@select="handleDropdownSelect"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {useTabsStore} from '@/store/modules/tabsStore'
|
||||||
|
import router from '@/scripts/router'
|
||||||
|
import {ref, nextTick} from "vue";
|
||||||
|
import global from "@/scripts/global.js";
|
||||||
|
|
||||||
|
const tabsStore = useTabsStore()
|
||||||
|
const tabs = tabsStore.getTabs
|
||||||
|
|
||||||
|
const showDropdown = ref(false);
|
||||||
|
const dropdownOptions = ref([
|
||||||
|
{
|
||||||
|
label: "刷新",
|
||||||
|
key: 'refresh'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "关闭左侧",
|
||||||
|
key: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "关闭右侧",
|
||||||
|
key: 'right'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "关闭其他",
|
||||||
|
key: 'other'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 弹出菜单展示位置
|
||||||
|
const xAxis = ref(0);
|
||||||
|
const yAxis = ref(0);
|
||||||
|
|
||||||
|
function handleClose(path) {
|
||||||
|
if (tabs.length == 1) {
|
||||||
|
tabs.splice(0, 1)
|
||||||
|
router.push({
|
||||||
|
path: '/home'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
tabs.forEach((it, i) => {
|
||||||
|
if (it.path == path) {
|
||||||
|
tabs.splice(i, 1)
|
||||||
|
router.push({
|
||||||
|
path: tabs[tabs.length - 1].path,
|
||||||
|
query: tabs[tabs.length - 1].query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function jump(item) {
|
||||||
|
router.push({
|
||||||
|
path: item.path,
|
||||||
|
query: tabs.filter(it => it.path == item.path)[0].query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPath = ref()
|
||||||
|
|
||||||
|
function handleContextMenu(item, e) {
|
||||||
|
currentPath.value = item.path
|
||||||
|
e.preventDefault();
|
||||||
|
xAxis.value = e.clientX;
|
||||||
|
yAxis.value = e.clientY;
|
||||||
|
showDropdown.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDropdownSelect(type) {
|
||||||
|
if (type != 'refresh') {
|
||||||
|
close(type)
|
||||||
|
} else {
|
||||||
|
tabsStore.refreshReplace({path: currentPath.value})
|
||||||
|
}
|
||||||
|
showDropdown.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(type) {
|
||||||
|
let path = currentPath.value
|
||||||
|
if (type == 'other') {
|
||||||
|
for (let i = tabs.length - 1; i >= 0; i--) {
|
||||||
|
if (tabs[i].path != path) {
|
||||||
|
tabs.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type == 'right') {
|
||||||
|
for (let i = tabs.length - 1; i >= 0; i--) {
|
||||||
|
if (tabs[i].path != path) {
|
||||||
|
tabs.splice(i, 1)
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0, len = tabs.length; i < len; i++) {
|
||||||
|
if (tabs[0].path != path) {
|
||||||
|
tabs.splice(0, 1)
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
router.push({
|
||||||
|
path: path,
|
||||||
|
query: tabs.filter(it => it.path == path)[0].query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
|
||||||
|
.n-tag {
|
||||||
|
//padding: 17px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
//flex-shrink: 0;
|
||||||
|
transition: box-shadow;
|
||||||
|
transition-duration: 0.25s;
|
||||||
|
}
|
||||||
|
.small:hover{
|
||||||
|
box-shadow: 1px 1px 6px #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium:hover{
|
||||||
|
box-shadow: 1px 1px 3px #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium{
|
||||||
|
padding: 15px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small{
|
||||||
|
padding: 14px 17px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,37 @@
|
||||||
|
import './styles/tailwind.css'
|
||||||
|
import '@/assets/css/common.css'
|
||||||
|
import 'vite-plugin-svg-icons/register'
|
||||||
|
import {createApp} from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import {
|
||||||
|
setupNaive,
|
||||||
|
setupNaiveDiscreteApi,
|
||||||
|
setupDirectives,
|
||||||
|
setupGlobalProperties,
|
||||||
|
setupMonacoVolar,
|
||||||
|
setupTheme,
|
||||||
|
setupLayer
|
||||||
|
} from '@/scripts/plugins'
|
||||||
|
import {setupRouter} from '@/scripts/router'
|
||||||
|
import {setupStore} from '@/store'
|
||||||
|
import {setupComponents} from '@/components'
|
||||||
|
import '@/scripts/compiler/magic-import'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
setupStore(app)
|
||||||
|
setupNaive(app)
|
||||||
|
setupNaiveDiscreteApi()
|
||||||
|
setupDirectives(app)
|
||||||
|
setupGlobalProperties(app)
|
||||||
|
await setupRouter(app)
|
||||||
|
await setupMonacoVolar()
|
||||||
|
setupComponents(app)
|
||||||
|
await setupTheme()
|
||||||
|
setupLayer(app)
|
||||||
|
app.mount('#app')
|
||||||
|
}
|
||||||
|
|
||||||
|
void start()
|
||||||
|
|
|
@ -0,0 +1,376 @@
|
||||||
|
import request from '@/scripts/request'
|
||||||
|
import global from '@/scripts/global'
|
||||||
|
import {utils, writeFile} from 'xlsx'
|
||||||
|
import {useUserStore} from "@/store/modules/userStore";
|
||||||
|
import {isArray, cloneDeep} from "lodash-es";
|
||||||
|
import {h} from 'vue'
|
||||||
|
import MbIcon from "@/components/magic/basic/mb-icon.vue";
|
||||||
|
|
||||||
|
const common = {}
|
||||||
|
|
||||||
|
common.handleDelete = (options) => {
|
||||||
|
const url = options.url
|
||||||
|
const id = options.id
|
||||||
|
$dialog.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '此操作将永久删除该数据, 是否继续?',
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: () => {
|
||||||
|
request({
|
||||||
|
url: url,
|
||||||
|
method: 'delete',
|
||||||
|
params: {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
$message.success('删除成功')
|
||||||
|
options && options.done()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatJson = (list, filterVal) => {
|
||||||
|
return list.map(v => filterVal.map(j => {
|
||||||
|
return v[j]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
common.request = (method) => {
|
||||||
|
return common[common.requestMethod.indexOf(method) !== -1 ? method : 'get']
|
||||||
|
}
|
||||||
|
common.requestMethod = ['get','post','postJson','delete']
|
||||||
|
common.get = (url, data) => request({url, params: data})
|
||||||
|
common.delete = (url, data) => request({url, method: 'delete', params: data})
|
||||||
|
common.post = (url, data) => request.post(url, data, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
transformRequest: [data => data && Object.keys(data).map(it => encodeURIComponent(it) + '=' + encodeURIComponent(data[it] === null || data[it] === undefined ? '' : data[it])).join('&')]
|
||||||
|
})
|
||||||
|
common.postJson = (url, data) => request.post(url, JSON.stringify(data), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
common.renderWhere = (where) => {
|
||||||
|
let newWhere = {}
|
||||||
|
for (let key in where) {
|
||||||
|
if (where[key] instanceof Object) {
|
||||||
|
newWhere[key] = where[key].value
|
||||||
|
} else {
|
||||||
|
newWhere[key] = where[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newWhere
|
||||||
|
}
|
||||||
|
|
||||||
|
// common.exportExcel = (options) => {
|
||||||
|
// let where = options.where || {}
|
||||||
|
// where = common.renderWhere(where)
|
||||||
|
// where.current = 1
|
||||||
|
// where.size = 99999999
|
||||||
|
// const url = options.url
|
||||||
|
// const headers = options.headers
|
||||||
|
// const columns = options.columns
|
||||||
|
// request({
|
||||||
|
// url: url,
|
||||||
|
// method: 'post',
|
||||||
|
// params: where
|
||||||
|
// }).then(res => {
|
||||||
|
// import('@/vendor/Export2Excel').then(excel => {
|
||||||
|
// const data = formatJson(res.data, columns)
|
||||||
|
// excel.export_json_to_excel({
|
||||||
|
// header: headers,
|
||||||
|
// data,
|
||||||
|
// filename: 'table-list'
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
common.handlerTreeData = (data, id, pid, sort, pidVal) => {
|
||||||
|
let treeData = []
|
||||||
|
let addChildren = (it) => {
|
||||||
|
let children = data.filter(d => d[pid] === it[id])
|
||||||
|
if (children && children.length > 0) {
|
||||||
|
children.sort((a, b) => {
|
||||||
|
return a[sort] - b[sort]
|
||||||
|
})
|
||||||
|
it.children = children
|
||||||
|
children.forEach(chi => {
|
||||||
|
addChildren(chi)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.sort((a, b) => {
|
||||||
|
return a[sort] - b[sort]
|
||||||
|
})
|
||||||
|
data.filter(it => it[pid] === pidVal).forEach(it => {
|
||||||
|
addChildren(it)
|
||||||
|
treeData.push(it)
|
||||||
|
})
|
||||||
|
return treeData
|
||||||
|
}
|
||||||
|
|
||||||
|
common.uuid = () => {
|
||||||
|
function S4() {
|
||||||
|
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4());
|
||||||
|
}
|
||||||
|
|
||||||
|
common.objAssign = (obj1, obj2, exclude) => {
|
||||||
|
exclude = exclude || ''
|
||||||
|
for (let o1 in obj1) {
|
||||||
|
for (let o2 in obj2) {
|
||||||
|
if (o1 === o2) {
|
||||||
|
if (exclude.indexOf(o1) == -1) {
|
||||||
|
obj1[o1] = obj2[o2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common.copyNew = (obj) => {
|
||||||
|
return JSON.parse(JSON.stringify(obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
common.getParam = (data) => {
|
||||||
|
let url = ''
|
||||||
|
for (let k in data) {
|
||||||
|
const value = data[k] !== undefined ? data[k] : ''
|
||||||
|
url += `&${k}=${encodeURIComponent(value)}`
|
||||||
|
}
|
||||||
|
return url ? url.substring(1) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
common.getUrl = (url, data) => {
|
||||||
|
url += (url.indexOf('?') < 0 ? '?' : '') + common.getParam(data)
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToken(){
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const token = userStore.getToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
common.downloadMore = (urls, filename) => {
|
||||||
|
let params = {
|
||||||
|
// post只需编码一次,get需要编码两次(encodeURIComponent(encodeURIComponent(urls)))
|
||||||
|
urls: encodeURIComponent(urls),
|
||||||
|
filename: filename || '',
|
||||||
|
token: getToken()
|
||||||
|
}
|
||||||
|
let form = document.createElement("form");
|
||||||
|
form.style.display = 'none';
|
||||||
|
form.action = global.baseApi + '/system/file/download';
|
||||||
|
form.method = 'post';
|
||||||
|
document.body.appendChild(form);
|
||||||
|
for(let key in params){
|
||||||
|
let input = document.createElement("input");
|
||||||
|
input.type = 'hidden';
|
||||||
|
input.name = key;
|
||||||
|
input.value = params[key];
|
||||||
|
form.appendChild(input);
|
||||||
|
}
|
||||||
|
form.submit();
|
||||||
|
form.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
common.download = (urls, filename) => {
|
||||||
|
location.href = common.downloadHref(urls, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
common.downloadHref = (urls, filename) => {
|
||||||
|
return global.baseApi + `/system/file/download?urls=${encodeURIComponent(encodeURIComponent(urls))}&filename=${filename || ''}&token=${getToken()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
common.loadConfig = async () => {
|
||||||
|
await request({
|
||||||
|
url: '/system/config/list'
|
||||||
|
}).then(res => {
|
||||||
|
const {data} = res
|
||||||
|
global.config = data
|
||||||
|
global.filePrefix = global.config.bucketDomain ? global.config.bucketDomain : global.baseApi
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
common.setDefaultValue = (obj, attr, value) => {
|
||||||
|
obj[attr] = obj[attr] === undefined ? value : obj[attr]
|
||||||
|
}
|
||||||
|
|
||||||
|
common.isComma = (value) => {
|
||||||
|
return value.toString().indexOf(',') !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
common.exportExcel = (options) => {
|
||||||
|
options.suffix = options.suffix || 'xlsx'
|
||||||
|
const workBook = utils.json_to_sheet(options.data);
|
||||||
|
const wb = utils.book_new()
|
||||||
|
utils.book_append_sheet(wb, workBook, 'sheet1');
|
||||||
|
writeFile(wb, `${options.fileName}.${options.suffix || 'xlsx'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
common.objectAssign = (target, source) => {
|
||||||
|
return Object.assign({}, JSON.parse(JSON.stringify(target)), JSON.parse(JSON.stringify(source)))
|
||||||
|
}
|
||||||
|
|
||||||
|
common.getUrlType = (url) => {
|
||||||
|
if(!url){
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if(url.startsWith('http')){
|
||||||
|
return 0
|
||||||
|
}else if(url.indexOf('.htm') != -1){
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
common.getLocationHref = () => {
|
||||||
|
return location.href.substring(0, location.href.indexOf('/', location.href.indexOf('/', location.href.indexOf('/') + 1) + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
common.handlerUrlPage = (url) => {
|
||||||
|
let urlType = common.getUrlType(url)
|
||||||
|
if(urlType == 1){
|
||||||
|
return common.getLocationHref() + url
|
||||||
|
}else if(urlType == 2){
|
||||||
|
return common.getLocationHref() + '/#' + url
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
common.filterIframeTabs = (it) => {
|
||||||
|
return it.meta.keepAlive && it.meta.path && (
|
||||||
|
(it.meta.path.startsWith('http') && (it.meta.openMode == '0' || it.meta.openMode == '2'))
|
||||||
|
||
|
||||||
|
(it.meta.path.indexOf('.htm') != -1 && (it.meta.openMode == '0' || it.meta.openMode == '2'))
|
||||||
|
||
|
||||||
|
it.meta.openMode == '2'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串数组和字符串(带逗号)对比
|
||||||
|
*/
|
||||||
|
common.arrayStringEq = (v1, v2) => {
|
||||||
|
let value1 = cloneDeep(v1)
|
||||||
|
let value2 = cloneDeep(v2)
|
||||||
|
value1 = isArray(value1) ? value1.join(',') : value1 && value1.toString()
|
||||||
|
value2 = isArray(value2) ? value2.join(',') : value2 && value2.toString()
|
||||||
|
return value1 == value2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断数据不为null、undefined、空字符串。不包含0和1
|
||||||
|
*/
|
||||||
|
common.notEmptyNot01 = (value) => {
|
||||||
|
if(value !== null && value !== undefined && value !== ''){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取有效数据 并且 可以设置默认值
|
||||||
|
*/
|
||||||
|
common.getValidValue = (value, defaultValue) => {
|
||||||
|
if(common.notEmptyNot01(value)){
|
||||||
|
return value
|
||||||
|
}else{
|
||||||
|
return common.notEmptyNot01(defaultValue) ? defaultValue : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common.stopWatchList = (watchList) => {
|
||||||
|
for(let watchFunction of watchList){
|
||||||
|
watchFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common.copyText = (selection) => {
|
||||||
|
let textarea = document.createElement('textarea')
|
||||||
|
textarea.value = selection
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select()
|
||||||
|
try {
|
||||||
|
document.execCommand("copy");
|
||||||
|
$message.success('复制成功')
|
||||||
|
} catch (err) {
|
||||||
|
$message.error('复制失败')
|
||||||
|
}
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
common.dialog = (type, options) => {
|
||||||
|
$dialog[type]({
|
||||||
|
title: options.title || '提示',
|
||||||
|
content: options.content,
|
||||||
|
positiveText: options.positiveText || '确定',
|
||||||
|
negativeText: options.negativeText || '取消',
|
||||||
|
onPositiveClick: (e) => {
|
||||||
|
if(options.ok){
|
||||||
|
return options.ok(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNegativeClick: (e) => {
|
||||||
|
if(options.cancel){
|
||||||
|
return options.cancel(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
common.warning = (content, ok, options) => {
|
||||||
|
common.dialog('warning', {
|
||||||
|
content,
|
||||||
|
ok,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
common.info = (content, ok, options) => {
|
||||||
|
common.dialog('info', {
|
||||||
|
content,
|
||||||
|
ok,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
common.success = (content, ok, options) => {
|
||||||
|
common.dialog('success', {
|
||||||
|
content,
|
||||||
|
ok,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
common.error = (content, ok, options) => {
|
||||||
|
common.dialog('error', {
|
||||||
|
content,
|
||||||
|
ok,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
common.renderIcon = (icon) => {
|
||||||
|
return () => h(MbIcon, { icon })
|
||||||
|
}
|
||||||
|
|
||||||
|
common.mapLabelValue = (data, labelField, valueField) => {
|
||||||
|
return data.map(it => {
|
||||||
|
return {
|
||||||
|
label: it[labelField || 'label'],
|
||||||
|
value: it[valueField || 'value'].toString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default common
|
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
function appComponent(app, item){
|
||||||
|
item.compileJs = `(function(){
|
||||||
|
${item.compileJs}
|
||||||
|
})()`
|
||||||
|
let componentStyle = document.createElement("style");
|
||||||
|
componentStyle.innerHTML = item.compileCss
|
||||||
|
document.head.appendChild(componentStyle);
|
||||||
|
app.component(item.name, eval(item.compileJs))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadDynamicComponent(app) {
|
||||||
|
await $common.post('/system/component/list').then((res) => {
|
||||||
|
res.data.forEach(it => {
|
||||||
|
appComponent(app, it)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import * as vue from "vue";
|
||||||
|
import * as NaiveUI from 'naive-ui';
|
||||||
|
import * as router from '@/scripts/router'
|
||||||
|
import xicons from '@/scripts/xicons'
|
||||||
|
import svgIcons from '@/scripts/svg-icons'
|
||||||
|
import * as dictStore from "@/store/modules/dictStore";
|
||||||
|
import * as userStore from "@/store/modules/userStore";
|
||||||
|
import MbIcon from '@/components/magic/basic/mb-icon.vue';
|
||||||
|
import * as lodashEs from 'lodash-es'
|
||||||
|
import * as vueRouter from 'vue-router'
|
||||||
|
|
||||||
|
const libs = {
|
||||||
|
vue,
|
||||||
|
'naive-ui': NaiveUI,
|
||||||
|
'@/scripts/xicons': xicons,
|
||||||
|
'@/scripts/svg-icons': svgIcons,
|
||||||
|
'@/scripts/router': router,
|
||||||
|
'@/store/modules/dictStore': dictStore,
|
||||||
|
'@/store/modules/userStore': userStore,
|
||||||
|
'@/components/magic/basic/mb-icon.vue': MbIcon,
|
||||||
|
'lodash-es': lodashEs,
|
||||||
|
'vue-router': vueRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
window.___magic__import__ = function(lib, name){
|
||||||
|
if(Object.prototype.toString.call(libs[lib]) != '[object Module]' && name == '*'){
|
||||||
|
return libs[lib]
|
||||||
|
}
|
||||||
|
return (libs[lib] || {})[name]
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
import * as SFCCompiler from '@vue/compiler-sfc'
|
||||||
|
import {babelParse} from "@vue/compiler-sfc";
|
||||||
|
|
||||||
|
const COMP_IDENTIFIER = `__sfc__`
|
||||||
|
|
||||||
|
export function compileCode(sourceCode){
|
||||||
|
let compiled = {}
|
||||||
|
compileFile(sourceCode, compiled)
|
||||||
|
if(compiled.errors.length){
|
||||||
|
throw compiled.errors[0]
|
||||||
|
}else{
|
||||||
|
let jsCode = compiled.js
|
||||||
|
let ast = babelParse(jsCode, {
|
||||||
|
sourceType: 'module'
|
||||||
|
})
|
||||||
|
let replaceCode = (node, subCode) => jsCode.substring(0, node.start) + subCode + jsCode.substring(node.end);
|
||||||
|
for(let i = ast.program.body.length - 1; i>=0; i--){
|
||||||
|
let node = ast.program.body[i]
|
||||||
|
if(node.type === 'ImportDeclaration'){
|
||||||
|
jsCode = replaceCode(node, node.specifiers.map(it => `const ${it.local?.name || it.imported?.name || '*'} = ___magic__import__('${node.source.value}', '${it.imported?.name || '*'}');`).join('\r\n'));
|
||||||
|
} else if (node.type === 'ExportDefaultDeclaration'){
|
||||||
|
jsCode = replaceCode(node, `return ${node.declaration.name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
compileCss: compiled.css,
|
||||||
|
compileJs: jsCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileFile(code, compiled) {
|
||||||
|
|
||||||
|
const filename = 'mb-sfc-compiler.vue'
|
||||||
|
|
||||||
|
const id = hashId(filename)
|
||||||
|
const { errors, descriptor } = SFCCompiler.parse(code, {
|
||||||
|
filename,
|
||||||
|
sourceMap: true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
compiled.errors = errors
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasVueCompositionFunctions(code) && !hasScriptSetup(code)){
|
||||||
|
compiled.errors = [
|
||||||
|
'defineProps、defineExpose、defineEmits、defineSlots、defineOptions、defineModel需要在<script setup>下使用'
|
||||||
|
]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(descriptor.script && descriptor.script.lang) ||
|
||||||
|
(descriptor.scriptSetup && descriptor.scriptSetup.lang) ||
|
||||||
|
(descriptor.template && descriptor.template.lang)
|
||||||
|
) {
|
||||||
|
compiled.errors = [
|
||||||
|
'lang="x" pre-processors are not supported in the in-browser playground.'
|
||||||
|
]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasScoped = descriptor.styles.some((s) => s.scoped)
|
||||||
|
let clientCode = ''
|
||||||
|
|
||||||
|
const appendSharedCode = (code) => {
|
||||||
|
clientCode += code
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientScriptResult = doCompileScript(descriptor, id, compiled)
|
||||||
|
if (!clientScriptResult) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const [clientScript, bindings] = clientScriptResult
|
||||||
|
clientCode += clientScript
|
||||||
|
|
||||||
|
// template
|
||||||
|
// only need dedicated compilation if not using <script setup>
|
||||||
|
if (descriptor.template && !descriptor.scriptSetup) {
|
||||||
|
const clientTemplateResult = doCompileTemplate(
|
||||||
|
descriptor,
|
||||||
|
id,
|
||||||
|
bindings,
|
||||||
|
compiled
|
||||||
|
)
|
||||||
|
if (!clientTemplateResult) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientCode += clientTemplateResult
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasScoped) {
|
||||||
|
appendSharedCode(
|
||||||
|
`\n${COMP_IDENTIFIER}.__scopeId = ${JSON.stringify(`data-v-${id}`)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientCode) {
|
||||||
|
appendSharedCode(
|
||||||
|
`\n${COMP_IDENTIFIER}.__file = ${JSON.stringify(filename)}` +
|
||||||
|
`\nexport default ${COMP_IDENTIFIER}`
|
||||||
|
)
|
||||||
|
compiled.js = clientCode.trimStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
// styles
|
||||||
|
let css = ''
|
||||||
|
for (const style of descriptor.styles) {
|
||||||
|
if (style.module) {
|
||||||
|
compiled.errors = [`<style module> is not supported in the playground.`]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const styleResult = SFCCompiler.compileStyle({
|
||||||
|
source: style.content,
|
||||||
|
filename,
|
||||||
|
id,
|
||||||
|
scoped: style.scoped,
|
||||||
|
modules: !!style.module
|
||||||
|
})
|
||||||
|
if (styleResult.errors.length) {
|
||||||
|
// postcss uses pathToFileURL which isn't polyfilled in the browser
|
||||||
|
// ignore these errors for now
|
||||||
|
if (!styleResult.errors[0].message.includes('pathToFileURL')) {
|
||||||
|
compiled.errors = styleResult.errors
|
||||||
|
}
|
||||||
|
// proceed even if css compile errors
|
||||||
|
} else {
|
||||||
|
css += styleResult.code + '\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (css) {
|
||||||
|
compiled.css = css.trim()
|
||||||
|
} else {
|
||||||
|
compiled.css = '/* No <style> tags present */'
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear errors
|
||||||
|
compiled.errors = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function doCompileScript(descriptor, id, compiled) {
|
||||||
|
if (descriptor.script || descriptor.scriptSetup) {
|
||||||
|
try {
|
||||||
|
const compiledScript = SFCCompiler.compileScript(descriptor, {
|
||||||
|
id,
|
||||||
|
refSugar: true,
|
||||||
|
inlineTemplate: true
|
||||||
|
})
|
||||||
|
let code = ''
|
||||||
|
if (compiledScript.bindings) {
|
||||||
|
code += `\n/* Analyzed bindings: ${JSON.stringify(
|
||||||
|
compiledScript.bindings,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)} */`
|
||||||
|
}
|
||||||
|
code +=
|
||||||
|
`\n` +
|
||||||
|
SFCCompiler.rewriteDefault(compiledScript.content, COMP_IDENTIFIER)
|
||||||
|
// console.log( SFCCompiler.rewriteDefault(compiledScript.content, COMP_IDENTIFIER))
|
||||||
|
return [code, compiledScript.bindings]
|
||||||
|
} catch (e) {
|
||||||
|
compiled.errors = [e]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return [`\nconst ${COMP_IDENTIFIER} = {}`, undefined]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doCompileTemplate(descriptor, id, bindingMetadata, compiled) {
|
||||||
|
const templateResult = SFCCompiler.compileTemplate({
|
||||||
|
source: descriptor.template && descriptor.template.content,
|
||||||
|
filename: descriptor.filename,
|
||||||
|
id,
|
||||||
|
scoped: descriptor.styles.some(s => s.scoped),
|
||||||
|
slotted: descriptor.slotted,
|
||||||
|
isProd: false,
|
||||||
|
compilerOptions: {
|
||||||
|
bindingMetadata
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (templateResult.errors.length) {
|
||||||
|
compiled.errors = templateResult.errors
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fnName = `render`
|
||||||
|
|
||||||
|
return (
|
||||||
|
`\n${templateResult.code.replace(
|
||||||
|
/\nexport (function|const) (render|ssrRender)/,
|
||||||
|
`$1 ${fnName}`
|
||||||
|
)}` + `\n${COMP_IDENTIFIER}.${fnName} = ${fnName}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashId(filename) {
|
||||||
|
return btoa(filename).slice(0, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasVueCompositionFunctions(content) {
|
||||||
|
const regexPatterns = [
|
||||||
|
/defineExpose\s*/,
|
||||||
|
/defineProps\s*/,
|
||||||
|
/defineEmits\s*/
|
||||||
|
];
|
||||||
|
for (const pattern of regexPatterns) {
|
||||||
|
if (pattern.test(content)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasScriptSetup(content) {
|
||||||
|
const regex = /<script\s+setup[^>]*>/;
|
||||||
|
return regex.test(content);
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import {useUserStore} from "@/store/modules/userStore";
|
||||||
|
import { isEmpty } from 'lodash-es'
|
||||||
|
|
||||||
|
const permission = {
|
||||||
|
mounted(el, binding) {
|
||||||
|
if (binding.value) {
|
||||||
|
const permissionList = useUserStore().getAuths
|
||||||
|
if (!isEmpty(permissionList) && !permissionList.includes(binding.value) && import.meta.env.MODE != 'demo') {
|
||||||
|
el.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default permission
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
|
||||||
|
const baseApi = import.meta.env.VITE_APP_BASE_API;
|
||||||
|
const modalIndex = ref(10)
|
||||||
|
const modalMap = reactive({})
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Magic Boot',
|
||||||
|
baseApi: baseApi,
|
||||||
|
filePrefix: '',
|
||||||
|
dynamicComponentNames: [],
|
||||||
|
config: {},
|
||||||
|
uiSize: ref('medium'),
|
||||||
|
selectTheme: {
|
||||||
|
name: 'default',
|
||||||
|
themeOverrides: {}
|
||||||
|
},
|
||||||
|
themeList: [{
|
||||||
|
name: 'default',
|
||||||
|
themeOverrides: 'defaultOverrides',
|
||||||
|
style: 'defaultStyle'
|
||||||
|
}],
|
||||||
|
modal: {
|
||||||
|
modalMap,
|
||||||
|
create(id){
|
||||||
|
modalMap[id] = {}
|
||||||
|
modalMap[id].vlaue = false
|
||||||
|
},
|
||||||
|
show(id){
|
||||||
|
modalMap[id].value = true
|
||||||
|
},
|
||||||
|
hide(id){
|
||||||
|
modalMap[id].value = false
|
||||||
|
},
|
||||||
|
getIndex(id){
|
||||||
|
modalIndex.value = modalIndex.value + 1
|
||||||
|
modalMap[id].index = modalIndex.value
|
||||||
|
return modalIndex.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* Naive UI Monaco 自动提示
|
||||||
|
* @author Yean
|
||||||
|
* @date 2024-3-27 21:19:04
|
||||||
|
*/
|
||||||
|
import webTypesData from 'naive-ui/web-types.json'; // 替换成你的web-types.json文件路径
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Naive UI组件自动提示项
|
||||||
|
* @returns {*[]}
|
||||||
|
*/
|
||||||
|
export function naiveUiCompletionItems(monaco) {
|
||||||
|
const naiveUiComponents = webTypesData.contributions.html['vue-components'];
|
||||||
|
return naiveUiComponents.flatMap(component => {
|
||||||
|
const componentName = component.name.replace(/^N/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); // 去除首字母"N"并转为小写
|
||||||
|
const tagForm = `<n-${componentName} `;
|
||||||
|
const componentDescription = component.description;
|
||||||
|
|
||||||
|
// 组件自身提示项
|
||||||
|
const componentCompletion = {
|
||||||
|
label: tagForm,
|
||||||
|
kind: monaco.languages.CompletionItemKind.Text,
|
||||||
|
documentation: componentDescription,
|
||||||
|
insertText: tagForm,
|
||||||
|
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件props提示项
|
||||||
|
const componentProps = component.props.map(prop => ({
|
||||||
|
label: prop.name,
|
||||||
|
kind: monaco.languages.CompletionItemKind.Property,
|
||||||
|
documentation: prop.description,
|
||||||
|
detail: prop.type,
|
||||||
|
insertText: `{ ${prop.name}$1 }`, // $1 表示插入点
|
||||||
|
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 组件事件提示项
|
||||||
|
const componentEvents = component.js?.events?.map(event => ({
|
||||||
|
label: `@${event.name}`,
|
||||||
|
kind: monaco.languages.CompletionItemKind.Event,
|
||||||
|
documentation: event.description || '',
|
||||||
|
insertText: `@${event.name}="$1"` // $1 表示插入点
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 返回组件、props和事件的CompletionItems
|
||||||
|
return [componentCompletion, ...componentProps, ...(componentEvents || [])];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册Naive UI Monaco自动提示
|
||||||
|
* @param monaco
|
||||||
|
*/
|
||||||
|
export function registerNaiveMonacoCompletionProvider(monaco){
|
||||||
|
monaco.languages.registerCompletionItemProvider('vue', {
|
||||||
|
triggerCharacters:['<','@','v-on:',' '],
|
||||||
|
provideCompletionItems: (model, position) => {
|
||||||
|
const word = model.getWordUntilPosition(position);
|
||||||
|
const range = {
|
||||||
|
startLineNumber: position.lineNumber,
|
||||||
|
endLineNumber: position.lineNumber,
|
||||||
|
startColumn: word.startColumn,
|
||||||
|
endColumn: word.endColumn,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { suggestions: naiveUiCompletionItems(monaco) };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import OSS from "ali-oss";
|
||||||
|
import global from '@/scripts/global'
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
let stsInfo = {}
|
||||||
|
await $common.get('/oss/sts/service').then(res => {
|
||||||
|
stsInfo = res.data
|
||||||
|
})
|
||||||
|
const client = new OSS({
|
||||||
|
// yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
||||||
|
region: 'oss-cn-beijing',
|
||||||
|
// 从STS服务获取的临时访问密钥(AccessKey ID和AccessKey Secret)。
|
||||||
|
accessKeyId: stsInfo.accessKeyId,
|
||||||
|
accessKeySecret: stsInfo.accessKeySecret,
|
||||||
|
// 从STS服务获取的安全令牌(SecurityToken)。
|
||||||
|
stsToken: stsInfo.stsToken,
|
||||||
|
refreshSTSToken: async () => {
|
||||||
|
// 向您搭建的STS服务获取临时访问凭证。
|
||||||
|
let info = {}
|
||||||
|
await $common.get('/oss/sts/service').then(res => {
|
||||||
|
info = res.data
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
accessKeyId: info.accessKeyId,
|
||||||
|
accessKeySecret: info.accessKeySecret,
|
||||||
|
stsToken: info.stsToken
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 刷新临时访问凭证的时间间隔,单位为毫秒。
|
||||||
|
refreshSTSTokenInterval: 30000,
|
||||||
|
// 填写Bucket名称。
|
||||||
|
bucket: global.config.bucketDomain
|
||||||
|
});
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
init
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
import permission from '@/scripts/directives/permission'
|
||||||
|
|
||||||
|
export function setupDirectives(app) {
|
||||||
|
app.directive('permission', permission)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import global from '@/scripts/global'
|
||||||
|
import common from "@/scripts/common";
|
||||||
|
import treeTable from '@/scripts/treeTable'
|
||||||
|
|
||||||
|
export function setupGlobalProperties(app) {
|
||||||
|
app.config.globalProperties.$global = global
|
||||||
|
window['$global'] = global
|
||||||
|
window['$common'] = common
|
||||||
|
window['$treeTable'] = treeTable
|
||||||
|
document.body.addEventListener('keyup', (e) => {
|
||||||
|
if(e.keyCode === 27){ // esc
|
||||||
|
let maxIndexObject = null;
|
||||||
|
for (let key in global.modal.modalMap) {
|
||||||
|
if (global.modal.modalMap[key].value === true) {
|
||||||
|
if (!maxIndexObject || global.modal.modalMap[key].index > maxIndexObject.index) {
|
||||||
|
maxIndexObject = global.modal.modalMap[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(maxIndexObject){
|
||||||
|
maxIndexObject.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export {setupNaive} from '@/scripts/plugins/naive'
|
||||||
|
export {setupNaiveDiscreteApi} from '@/scripts/plugins/naiveDiscreteApi'
|
||||||
|
export {setupDirectives} from '@/scripts/plugins/directives'
|
||||||
|
export {setupGlobalProperties} from '@/scripts/plugins/globalProperties'
|
||||||
|
export {setupMonacoVolar} from '@/scripts/plugins/monacoVolar'
|
||||||
|
export {setupTheme} from '@/scripts/plugins/theme'
|
||||||
|
export {setupLayer} from '@/scripts/plugins/layer'
|
|
@ -0,0 +1,6 @@
|
||||||
|
import '@layui/layer-vue/lib/index.css';
|
||||||
|
import layer from '@layui/layer-vue';
|
||||||
|
|
||||||
|
export function setupLayer(app) {
|
||||||
|
app.use(layer)
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import * as onigasm from "onigasm";
|
||||||
|
import onigasmWasm from "onigasm/lib/onigasm.wasm?url";
|
||||||
|
import * as monaco from "monaco-editor-core";
|
||||||
|
import * as volar from "@volar/monaco";
|
||||||
|
import editorWorker from "monaco-editor-core/esm/vs/editor/editor.worker?worker";
|
||||||
|
import vueWorker from "monaco-volar/vue.worker?worker";
|
||||||
|
import {registerNaiveMonacoCompletionProvider} from "@/scripts/monaco/naiveui-monaco-prompt";
|
||||||
|
|
||||||
|
// 高亮代码
|
||||||
|
function loadOnigasm() {
|
||||||
|
return onigasm.loadWASM(onigasmWasm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
async function editorInit() {
|
||||||
|
self.MonacoEnvironment ??= {};
|
||||||
|
self.MonacoEnvironment.getWorker ??= () => new editorWorker();
|
||||||
|
|
||||||
|
const getWorker = self.MonacoEnvironment.getWorker;
|
||||||
|
|
||||||
|
self.MonacoEnvironment.getWorker = (_, label) => {
|
||||||
|
if (label === "vue") {
|
||||||
|
return new vueWorker();
|
||||||
|
}
|
||||||
|
return getWorker();
|
||||||
|
};
|
||||||
|
|
||||||
|
const worker = monaco.editor.createWebWorker({
|
||||||
|
moduleId: "vs/language/vue/vueWorker",
|
||||||
|
label: "vue",
|
||||||
|
createData: {},
|
||||||
|
});
|
||||||
|
const languageId = ["vue"];
|
||||||
|
const getSyncUris = () => monaco.editor.getModels().map((model) => model.uri);
|
||||||
|
volar.editor.activateMarkers(
|
||||||
|
worker,
|
||||||
|
languageId,
|
||||||
|
"vue",
|
||||||
|
getSyncUris,
|
||||||
|
monaco.editor
|
||||||
|
);
|
||||||
|
volar.editor.activateAutoInsertion(worker, languageId, getSyncUris, monaco.editor);
|
||||||
|
await volar.languages.registerProvides(
|
||||||
|
worker,
|
||||||
|
languageId,
|
||||||
|
getSyncUris,
|
||||||
|
monaco.languages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setupMonacoVolar(app) {
|
||||||
|
await loadOnigasm()
|
||||||
|
monaco.languages.register({id: "vue", extensions: [".vue"]});
|
||||||
|
monaco.languages.onLanguage("vue", editorInit);
|
||||||
|
// 注册naive-ui组件自动提示
|
||||||
|
registerNaiveMonacoCompletionProvider(monaco);
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
import * as NaiveUI from 'naive-ui';
|
||||||
|
|
||||||
|
const naive = NaiveUI.create({
|
||||||
|
components: [
|
||||||
|
NaiveUI.NMessageProvider,
|
||||||
|
NaiveUI.NDialogProvider,
|
||||||
|
NaiveUI.NConfigProvider,
|
||||||
|
NaiveUI.NInput,
|
||||||
|
NaiveUI.NInputGroup,
|
||||||
|
NaiveUI.NInputGroupLabel,
|
||||||
|
NaiveUI.NButton,
|
||||||
|
NaiveUI.NForm,
|
||||||
|
NaiveUI.NFormItem,
|
||||||
|
NaiveUI.NFormItemGi,
|
||||||
|
NaiveUI.NCheckboxGroup,
|
||||||
|
NaiveUI.NCheckbox,
|
||||||
|
NaiveUI.NIcon,
|
||||||
|
NaiveUI.NLayout,
|
||||||
|
NaiveUI.NLayoutHeader,
|
||||||
|
NaiveUI.NLayoutContent,
|
||||||
|
NaiveUI.NLayoutFooter,
|
||||||
|
NaiveUI.NLayoutSider,
|
||||||
|
NaiveUI.NMenu,
|
||||||
|
NaiveUI.NBreadcrumb,
|
||||||
|
NaiveUI.NBreadcrumbItem,
|
||||||
|
NaiveUI.NDropdown,
|
||||||
|
NaiveUI.NSpace,
|
||||||
|
NaiveUI.NTooltip,
|
||||||
|
NaiveUI.NAvatar,
|
||||||
|
NaiveUI.NTabs,
|
||||||
|
NaiveUI.NTabPane,
|
||||||
|
NaiveUI.NCard,
|
||||||
|
NaiveUI.NRow,
|
||||||
|
NaiveUI.NCol,
|
||||||
|
NaiveUI.NDrawer,
|
||||||
|
NaiveUI.NDrawerContent,
|
||||||
|
NaiveUI.NDivider,
|
||||||
|
NaiveUI.NSwitch,
|
||||||
|
NaiveUI.NBadge,
|
||||||
|
NaiveUI.NAlert,
|
||||||
|
NaiveUI.NElement,
|
||||||
|
NaiveUI.NTag,
|
||||||
|
NaiveUI.NNotificationProvider,
|
||||||
|
NaiveUI.NProgress,
|
||||||
|
NaiveUI.NDatePicker,
|
||||||
|
NaiveUI.NGrid,
|
||||||
|
NaiveUI.NGridItem,
|
||||||
|
NaiveUI.NList,
|
||||||
|
NaiveUI.NListItem,
|
||||||
|
NaiveUI.NThing,
|
||||||
|
NaiveUI.NDataTable,
|
||||||
|
NaiveUI.NPopover,
|
||||||
|
NaiveUI.NPagination,
|
||||||
|
NaiveUI.NSelect,
|
||||||
|
NaiveUI.NRadioGroup,
|
||||||
|
NaiveUI.NRadio,
|
||||||
|
NaiveUI.NRadioButton,
|
||||||
|
NaiveUI.NSteps,
|
||||||
|
NaiveUI.NStep,
|
||||||
|
NaiveUI.NInputGroup,
|
||||||
|
NaiveUI.NResult,
|
||||||
|
NaiveUI.NDescriptions,
|
||||||
|
NaiveUI.NDescriptionsItem,
|
||||||
|
NaiveUI.NTable,
|
||||||
|
NaiveUI.NInputNumber,
|
||||||
|
NaiveUI.NLoadingBarProvider,
|
||||||
|
NaiveUI.NModal,
|
||||||
|
NaiveUI.NUpload,
|
||||||
|
NaiveUI.NUploadDragger,
|
||||||
|
NaiveUI.NTree,
|
||||||
|
NaiveUI.NTreeSelect,
|
||||||
|
NaiveUI.NSpin,
|
||||||
|
NaiveUI.NTimePicker,
|
||||||
|
NaiveUI.NBackTop,
|
||||||
|
NaiveUI.NSkeleton,
|
||||||
|
NaiveUI.NImage,
|
||||||
|
NaiveUI.NImageGroup,
|
||||||
|
NaiveUI.NSplit,
|
||||||
|
NaiveUI.NEllipsis
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
export function setupNaive(app) {
|
||||||
|
app.use(naive)
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import * as NaiveUI from 'naive-ui'
|
||||||
|
|
||||||
|
export function setupNaiveDiscreteApi() {
|
||||||
|
const {message, dialog, notification, loadingBar} = NaiveUI.createDiscreteApi(
|
||||||
|
['message', 'dialog', 'notification', 'loadingBar']
|
||||||
|
);
|
||||||
|
window['$message'] = message;
|
||||||
|
window['$dialog'] = dialog;
|
||||||
|
window['$notification'] = notification;
|
||||||
|
window['$loading'] = loadingBar;
|
||||||
|
}
|