mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-08-28 09:33:52 +08:00
发布代码生成、更新20+表单组件,优化数据字典,gf版本更新到2.3.1
This commit is contained in:
4
web/.env
4
web/.env
@@ -2,10 +2,10 @@
|
||||
VITE_PORT = 8001
|
||||
|
||||
# spa-title
|
||||
VITE_GLOB_APP_TITLE = HotGo
|
||||
VITE_GLOB_APP_TITLE = HG后台管理系统
|
||||
|
||||
# spa shortname
|
||||
VITE_GLOB_APP_SHORT_NAME = HotGo
|
||||
VITE_GLOB_APP_SHORT_NAME = HG
|
||||
|
||||
# 生产环境 开启mock
|
||||
VITE_GLOB_PROD_MOCK = false
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# 只在开发模式中被载入
|
||||
VITE_PORT = 8001
|
||||
|
||||
# 网站根目录
|
||||
VITE_PUBLIC_PATH = /
|
||||
@@ -15,7 +14,7 @@ VITE_DROP_CONSOLE = true
|
||||
|
||||
# 跨域代理,可以配置多个,请注意不要换行,如果是公网运行,请改成公网IP:服务端运行端口
|
||||
#VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]]
|
||||
VITE_PROXY=[["/admin","http://42.194.151.158:8000/admin"]]
|
||||
VITE_PROXY=[["/admin","http://localhost:8000/admin"]]
|
||||
|
||||
# API 接口地址
|
||||
VITE_GLOB_API_URL =
|
||||
|
@@ -1,9 +1,11 @@
|
||||
# 是否开启mock
|
||||
VITE_USE_MOCK = false
|
||||
# 只在生产模式中被载入
|
||||
|
||||
# 网站根目录
|
||||
VITE_PUBLIC_PATH = /admin
|
||||
|
||||
# 是否开启mock
|
||||
VITE_USE_MOCK = false
|
||||
|
||||
# 网站前缀
|
||||
VITE_BASE_URL = /
|
||||
|
||||
|
@@ -76,7 +76,7 @@ const menuList = () => {
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/admin/menu/list__',
|
||||
url: '/admin/menu/list',
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: () => {
|
||||
|
@@ -5,6 +5,7 @@ function getMenuKeys() {
|
||||
const newKeys = [];
|
||||
doCustomTimes(parseInt(Math.random() * 6), () => {
|
||||
const key = keys[Math.floor(Math.random() * keys.length)];
|
||||
// @ts-ignore
|
||||
newKeys.push(key);
|
||||
});
|
||||
return Array.from(new Set(newKeys));
|
||||
@@ -28,7 +29,7 @@ const roleList = (pageSize) => {
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/admin/role/list__',
|
||||
url: '/admin/role/list',
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import {resultSuccess} from '../_util';
|
||||
import {ApiEnum} from "@/enums/apiEnum";
|
||||
import { resultSuccess } from '../_util';
|
||||
import { ApiEnum } from '@/enums/apiEnum';
|
||||
|
||||
const menusList = [
|
||||
{
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import Mock from 'mockjs';
|
||||
import {ApiEnum} from '@/enums/apiEnum';
|
||||
import {resultSuccess} from '../_util';
|
||||
import { ApiEnum } from '@/enums/apiEnum';
|
||||
import { resultSuccess } from '../_util';
|
||||
|
||||
const Random = Mock.Random;
|
||||
|
||||
@@ -44,7 +44,7 @@ export default [
|
||||
timeout: 1000,
|
||||
method: 'post',
|
||||
response: () => {
|
||||
return resultSuccess({token});
|
||||
return resultSuccess({ token });
|
||||
},
|
||||
},
|
||||
{
|
||||
|
26849
web/package-lock.json
generated
26849
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "hotgo",
|
||||
"version": "1.8.1",
|
||||
"version": "2.1.0",
|
||||
"author": {
|
||||
"name": "Ahjung",
|
||||
"email": "735878602@qq.com",
|
||||
"url": "https://github.com/jekip/naive-ui-admin"
|
||||
"name": "MengShuai",
|
||||
"email": "133814250@qq.com",
|
||||
"url": "https://github.com/bufanyun/hotgo"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -42,7 +42,7 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^2.1.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"naive-ui": "^2.28.4",
|
||||
"naive-ui": "^2.34.3",
|
||||
"node-sass": "^7.0.3",
|
||||
"pinia": "^2.0.14",
|
||||
"qs": "^6.10.3",
|
||||
@@ -109,6 +109,10 @@
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"hotgo",
|
||||
"hg",
|
||||
"gf",
|
||||
"goframe",
|
||||
"vue",
|
||||
"naive-ui",
|
||||
"naive-ui-admin",
|
||||
@@ -120,13 +124,13 @@
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jekip/naive-ui-admin.git"
|
||||
"url": "git+https://github.com/bufanyun/hotgo.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jekip/naive-ui-admin/issues"
|
||||
"url": "https://github.com/bufanyun/hotgo/issues"
|
||||
},
|
||||
"homepage": "https://github.com/jekip/naive-ui-admin",
|
||||
"homepage": "https://github.com/bufanyun/hotgo",
|
||||
"engines": {
|
||||
"node": "^12 || >=14"
|
||||
}
|
||||
|
7612
web/pnpm-lock.yaml
generated
7612
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
web/public/onerror.png
Normal file
BIN
web/public/onerror.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
74
web/src/api/curdDemo/index.ts
Normal file
74
web/src/api/curdDemo/index.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { http, jumpExport } from '@/utils/http/axios';
|
||||
|
||||
// 获取生成演示列表
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除/批量删除生成演示
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 新建/编辑生成演示
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 修改生成演示状态
|
||||
export function Status(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/status',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 操作生成演示开关
|
||||
export function Switch(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/switch',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 获取生成演示指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 获取生成演示最大排序
|
||||
export function MaxSort() {
|
||||
return http.request({
|
||||
url: '/curdDemo/maxSort',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 导出生成演示
|
||||
export function Export(params) {
|
||||
jumpExport('/curdDemo/export', params);
|
||||
}
|
87
web/src/api/develop/code.ts
Normal file
87
web/src/api/develop/code.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { http } from '@/utils/http/axios';
|
||||
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function Status(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/status',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
export function Selects(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/selects',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function TableSelect(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/tableSelect',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function ColumnSelect(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/columnSelect',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function ColumnList(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/columnList',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function Preview(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/preview',
|
||||
method: 'post',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function Build(params) {
|
||||
return http.request({
|
||||
url: '/genCodes/build',
|
||||
method: 'post',
|
||||
params,
|
||||
});
|
||||
}
|
@@ -6,7 +6,7 @@ import { http } from '@/utils/http/axios';
|
||||
*/
|
||||
export function getDictTree(params?) {
|
||||
return http.request({
|
||||
url: '/dict_type/tree',
|
||||
url: '/dictType/tree',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
@@ -19,7 +19,7 @@ export function getDictTree(params?) {
|
||||
*/
|
||||
export function EditDict(params?) {
|
||||
return http.request({
|
||||
url: '/dict_type/edit',
|
||||
url: '/dictType/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
@@ -32,7 +32,7 @@ export function EditDict(params?) {
|
||||
*/
|
||||
export function DeleteDict(params?) {
|
||||
return http.request({
|
||||
url: '/dict_type/delete',
|
||||
url: '/dictType/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
@@ -44,7 +44,7 @@ export function DeleteDict(params?) {
|
||||
*/
|
||||
export function getDictSelect(params?) {
|
||||
return http.request({
|
||||
url: '/dict_type/select',
|
||||
url: '/dictType/select',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
@@ -57,7 +57,7 @@ export function getDictSelect(params?) {
|
||||
*/
|
||||
export function EditData(params?) {
|
||||
return http.request({
|
||||
url: '/dict_data/edit',
|
||||
url: '/dictData/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
@@ -70,7 +70,7 @@ export function EditData(params?) {
|
||||
*/
|
||||
export function DeleteData(params?) {
|
||||
return http.request({
|
||||
url: '/dict_data/delete',
|
||||
url: '/dictData/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
@@ -82,7 +82,28 @@ export function DeleteData(params?) {
|
||||
*/
|
||||
export function getDataList(params?) {
|
||||
return http.request({
|
||||
url: '/dict_data/list',
|
||||
url: '/dictData/list',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典数据列表
|
||||
*/
|
||||
export function Dict(type) {
|
||||
return http.request({
|
||||
url: '/dictData/option/' + type,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典数据列表
|
||||
*/
|
||||
export function Dicts(params) {
|
||||
return http.request({
|
||||
url: '/dictData/options',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
|
@@ -31,3 +31,11 @@ export function Delete(params) {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function ResetPwd(params) {
|
||||
return http.request({
|
||||
url: '/member/reset_pwd',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
@@ -15,6 +15,13 @@ export function updateConfig(params) {
|
||||
});
|
||||
}
|
||||
|
||||
export function TypeSelect() {
|
||||
return http.request({
|
||||
url: '/config/typeSelect',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
export function sendTestEmail(params) {
|
||||
return http.request({
|
||||
url: '/ems/sendTest',
|
||||
|
@@ -3,10 +3,11 @@ import { http } from '@/utils/http/axios';
|
||||
/**
|
||||
* @description: 角色列表
|
||||
*/
|
||||
export function getRoleList() {
|
||||
export function getRoleList(params) {
|
||||
return http.request({
|
||||
url: '/role/list',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,3 +42,18 @@ export function GetPermissions(params) {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function DataScopeSelect() {
|
||||
return http.request({
|
||||
url: '/role/dataScope/select',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
export function DataScopeEdit(params) {
|
||||
return http.request({
|
||||
url: '/role/dataScope/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
68
web/src/api/test/index.ts
Normal file
68
web/src/api/test/index.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { http, jumpExport } from '@/utils/http/axios';
|
||||
|
||||
// 列表
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/test/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除/批量删除
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/test/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 新建/编辑
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/test/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 修改状态
|
||||
export function Status(params) {
|
||||
return http.request({
|
||||
url: '/test/status',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 操作开关
|
||||
export function Switch(params) {
|
||||
return http.request({
|
||||
url: '/test/switch',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/test/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取最大排序
|
||||
export function MaxSort() {
|
||||
return http.request({
|
||||
url: '/test/maxSort',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
// 导出
|
||||
export function Export(params) {
|
||||
jumpExport('/test/export', params);
|
||||
}
|
84
web/src/components/DatePicker/datePicker.vue
Normal file
84
web/src/components/DatePicker/datePicker.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<n-date-picker v-bind="$props" v-model:value="modelValue" :shortcuts="shortcuts" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref } from 'vue';
|
||||
import {
|
||||
dateToTimestamp,
|
||||
formatToDate,
|
||||
formatToDateTime,
|
||||
timestampToTime,
|
||||
defShortcuts,
|
||||
defRangeShortcuts,
|
||||
} from '@/utils/dateUtil';
|
||||
import { basicProps } from './props';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicUpload',
|
||||
props: {
|
||||
...basicProps,
|
||||
},
|
||||
emits: ['update:formValue', 'update:startValue', 'update:endValue'],
|
||||
setup(props, { emit }) {
|
||||
const shortcuts = ref<any>({});
|
||||
|
||||
function getTimestamp(value) {
|
||||
let t = dateToTimestamp(value);
|
||||
if (t === 0) {
|
||||
return new Date().getTime();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
function setTimestamp(value) {
|
||||
if (!isTimeType()) {
|
||||
return formatToDate(new Date(Number(value)).toDateString());
|
||||
} else {
|
||||
return formatToDateTime(timestampToTime(Number(value / 1000)));
|
||||
}
|
||||
}
|
||||
|
||||
function isRangeType() {
|
||||
return props.type.indexOf('range') != -1;
|
||||
}
|
||||
|
||||
function isTimeType() {
|
||||
return props.type.indexOf('time') != -1;
|
||||
}
|
||||
|
||||
const modelValue = computed({
|
||||
get() {
|
||||
if (!isRangeType()) {
|
||||
return getTimestamp(props.formValue);
|
||||
} else {
|
||||
return [getTimestamp(props.startValue), getTimestamp(props.endValue)];
|
||||
}
|
||||
},
|
||||
set(value) {
|
||||
if (!isRangeType()) {
|
||||
emit('update:formValue', setTimestamp(value));
|
||||
} else {
|
||||
emit('update:startValue', setTimestamp(value[0]));
|
||||
emit('update:endValue', setTimestamp(value[1]));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (!isRangeType()) {
|
||||
shortcuts.value = defShortcuts();
|
||||
} else {
|
||||
shortcuts.value = defRangeShortcuts();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
modelValue,
|
||||
shortcuts,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
18
web/src/components/DatePicker/props.ts
Normal file
18
web/src/components/DatePicker/props.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { PropType } from 'vue';
|
||||
import { NDatePicker } from 'naive-ui';
|
||||
|
||||
export const basicProps = {
|
||||
...NDatePicker.props,
|
||||
formValue: {
|
||||
type: String as PropType<string> | undefined | Date,
|
||||
default: () => '',
|
||||
},
|
||||
startValue: {
|
||||
type: String as PropType<string> | undefined | Date,
|
||||
default: () => '',
|
||||
},
|
||||
endValue: {
|
||||
type: String as PropType<string> | undefined | Date,
|
||||
default: () => '',
|
||||
},
|
||||
};
|
84
web/src/components/Editor/editor.vue
Normal file
84
web/src/components/Editor/editor.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<QuillEditor
|
||||
ref="quillEditor"
|
||||
:options="options"
|
||||
v-model:content="content"
|
||||
@ready="readyQuill"
|
||||
class="quillEditor"
|
||||
:id="quillEditorId"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import { QuillEditor } from '@vueup/vue-quill';
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||
import { getRandomString } from '@/utils/charset';
|
||||
export interface Props {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
const quillEditorId = ref('quillEditorId-' + getRandomString(16, true));
|
||||
const quillEditor = ref();
|
||||
const content = ref();
|
||||
const props = withDefaults(defineProps<Props>(), { value: '' });
|
||||
const options = ref({
|
||||
modules: {
|
||||
toolbar: [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
|
||||
[{ header: 1 }, { header: 2 }], // custom button values
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
|
||||
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
|
||||
[{ direction: 'rtl' }], // text direction
|
||||
|
||||
[{ size: ['small', false, 'large', 'huge'] }], // custom dropdown
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||
|
||||
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
|
||||
[{ font: [] }],
|
||||
[{ align: [] }],
|
||||
['clean'],
|
||||
['image'],
|
||||
],
|
||||
},
|
||||
theme: 'snow',
|
||||
placeholder: '输入您要编辑的内容!',
|
||||
});
|
||||
|
||||
function readyQuill() {
|
||||
quillEditor.value.setHTML(props.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => content.value,
|
||||
(_newValue, _oldValue) => {
|
||||
if (quillEditor.value !== undefined) {
|
||||
emit('update:value', quillEditor.value.getHTML());
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true, // 深度监听
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
// 兼容表单分组 n-form-item-blank
|
||||
let dom = document.getElementById(quillEditorId.value);
|
||||
if (dom && dom.parentNode) {
|
||||
const parent = dom.parentNode as Element;
|
||||
if ('n-form-item-blank' === parent.className) {
|
||||
parent.setAttribute('style', 'display: block;');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ql-container {
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
@@ -10,7 +10,7 @@ export interface FormSchema {
|
||||
labelMessageStyle?: object | string;
|
||||
defaultValue?: any;
|
||||
component?: ComponentType;
|
||||
componentProps?: object;
|
||||
componentProps?: object | any;
|
||||
slot?: string;
|
||||
rules?: object | object[];
|
||||
giProps?: GridItemProps;
|
||||
|
94
web/src/components/IconSelector/AntdSelector.vue
Normal file
94
web/src/components/IconSelector/AntdSelector.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<n-popover trigger="click" placement="bottom" width="400">
|
||||
<template #trigger>
|
||||
<n-button>
|
||||
<template #icon>
|
||||
<n-icon size="20">
|
||||
<component :is="formValue !== '' ? formValue : 'AntDesignOutlined'" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
<n-scrollbar class="grid-wrapper">
|
||||
<n-grid :cols="8" :collapsed="false" responsive="screen" style="height: 300px">
|
||||
<n-grid-item v-for="(item, index) of icons" :key="index">
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-3 icon-wrapper"
|
||||
@click="onIconClick(item)"
|
||||
>
|
||||
<n-icon size="20">
|
||||
<component :is="item" />
|
||||
</n-icon>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-scrollbar>
|
||||
<div class="flex justify-end mt-2 mb-2">
|
||||
<n-pagination
|
||||
:page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:page-slot="8"
|
||||
:item-count="itemCount"
|
||||
@update-page="onUpdatePage"
|
||||
/>
|
||||
</div>
|
||||
</n-popover>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, shallowReactive } from 'vue';
|
||||
import * as AntdIcons from '@vicons/antd';
|
||||
export default defineComponent({
|
||||
name: 'AntdSelector',
|
||||
components: AntdIcons,
|
||||
props: {
|
||||
value: String,
|
||||
option: String,
|
||||
},
|
||||
emits: ['update:value'],
|
||||
setup(props, { emit }) {
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:value', value);
|
||||
},
|
||||
});
|
||||
|
||||
const iconArray = Object.keys(AntdIcons);
|
||||
const pageSize = 40;
|
||||
const icons = shallowReactive(iconArray.slice(0, 40));
|
||||
const currentPage = ref(1);
|
||||
const itemCount = computed(() => iconArray.length);
|
||||
|
||||
function onUpdatePage(page: number) {
|
||||
currentPage.value = page;
|
||||
icons.length = 0;
|
||||
const start = (currentPage.value - 1) * pageSize;
|
||||
icons.push(...iconArray.slice(start, start + pageSize));
|
||||
}
|
||||
|
||||
function onIconClick(item: any) {
|
||||
formValue.value = item;
|
||||
}
|
||||
return {
|
||||
icons,
|
||||
currentPage,
|
||||
pageSize,
|
||||
itemCount,
|
||||
onUpdatePage,
|
||||
onIconClick,
|
||||
formValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.grid-wrapper {
|
||||
.icon-wrapper {
|
||||
cursor: pointer;
|
||||
border: 1px solid #f5f5f5;
|
||||
}
|
||||
}
|
||||
</style>
|
94
web/src/components/IconSelector/Ionicons5Selector.vue
Normal file
94
web/src/components/IconSelector/Ionicons5Selector.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<n-popover trigger="click" placement="bottom" width="400">
|
||||
<template #trigger>
|
||||
<n-button>
|
||||
<template #icon>
|
||||
<n-icon size="20">
|
||||
<component :is="formValue !== '' ? formValue : 'LogoIonic'" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
<n-scrollbar class="grid-wrapper">
|
||||
<n-grid :cols="8" :collapsed="false" responsive="screen" style="height: 300px">
|
||||
<n-grid-item v-for="(item, index) of icons" :key="index">
|
||||
<div
|
||||
class="flex flex-col items-center justify-center p-3 icon-wrapper"
|
||||
@click="onIconClick(item)"
|
||||
>
|
||||
<n-icon size="20">
|
||||
<component :is="item" />
|
||||
</n-icon>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-scrollbar>
|
||||
<div class="flex justify-end mt-2 mb-2">
|
||||
<n-pagination
|
||||
:page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:page-slot="8"
|
||||
:item-count="itemCount"
|
||||
@update-page="onUpdatePage"
|
||||
/>
|
||||
</div>
|
||||
</n-popover>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, shallowReactive } from 'vue';
|
||||
import * as Ionicons5Icons from '@vicons/ionicons5';
|
||||
export default defineComponent({
|
||||
name: 'Ionicons5Selector',
|
||||
components: Ionicons5Icons,
|
||||
props: {
|
||||
value: String,
|
||||
option: String,
|
||||
},
|
||||
emits: ['update:value'],
|
||||
setup(props, { emit }) {
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:value', value);
|
||||
},
|
||||
});
|
||||
|
||||
const iconArray = Object.keys(Ionicons5Icons);
|
||||
const pageSize = 40;
|
||||
const icons = shallowReactive(iconArray.slice(0, 40));
|
||||
const currentPage = ref(1);
|
||||
const itemCount = computed(() => iconArray.length);
|
||||
|
||||
function onUpdatePage(page: number) {
|
||||
currentPage.value = page;
|
||||
icons.length = 0;
|
||||
const start = (currentPage.value - 1) * pageSize;
|
||||
icons.push(...iconArray.slice(start, start + pageSize));
|
||||
}
|
||||
|
||||
function onIconClick(item: any) {
|
||||
formValue.value = item;
|
||||
}
|
||||
return {
|
||||
icons,
|
||||
currentPage,
|
||||
pageSize,
|
||||
itemCount,
|
||||
onUpdatePage,
|
||||
onIconClick,
|
||||
formValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.grid-wrapper {
|
||||
.icon-wrapper {
|
||||
cursor: pointer;
|
||||
border: 1px solid #f5f5f5;
|
||||
}
|
||||
}
|
||||
</style>
|
44
web/src/components/IconSelector/index.vue
Normal file
44
web/src/components/IconSelector/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-input-group>
|
||||
<n-input v-bind="$props" :value="formValue" :style="{ width: '70%' }" />
|
||||
<template v-if="option === 'ionicons5'">
|
||||
<Ionicons5Selector v-model:value="formValue" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<AntdSelector v-model:value="formValue" />
|
||||
</template>
|
||||
</n-input-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { basicProps } from '@/components/IconSelector/props';
|
||||
import Ionicons5Selector from '@/components/IconSelector/Ionicons5Selector.vue';
|
||||
import AntdSelector from '@/components/IconSelector/AntdSelector.vue';
|
||||
export default defineComponent({
|
||||
name: 'BasicUpload',
|
||||
components: { Ionicons5Selector, AntdSelector },
|
||||
props: {
|
||||
...basicProps,
|
||||
},
|
||||
emits: ['update:value'],
|
||||
setup(props, { emit }) {
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:value', value);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
formValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
14
web/src/components/IconSelector/props.ts
Normal file
14
web/src/components/IconSelector/props.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { PropType } from 'vue';
|
||||
import { NInput } from 'naive-ui';
|
||||
|
||||
export const basicProps = {
|
||||
...NInput.props,
|
||||
option: {
|
||||
type: String as PropType<string>,
|
||||
default: 'antd', // ionicons5 | antd
|
||||
},
|
||||
value: {
|
||||
type: String as PropType<string>,
|
||||
default: () => '',
|
||||
},
|
||||
};
|
@@ -18,7 +18,7 @@
|
||||
<slot name="tableTitle"></slot>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center table-toolbar-right">
|
||||
<div class="flex items-center table-toolbar-right" v-show="showTopRight">
|
||||
<!--顶部右侧区域-->
|
||||
<slot name="toolbar"></slot>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</n-tooltip>
|
||||
|
||||
<!--表格设置单独抽离成组件-->
|
||||
<ColumnSetting />
|
||||
<ColumnSetting :openChecked="openChecked" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="s-table">
|
||||
|
@@ -12,14 +12,14 @@
|
||||
<div class="table-toolbar-inner-popover-title">
|
||||
<n-space>
|
||||
<n-checkbox v-model:checked="checkAll" @update:checked="onCheckAll"
|
||||
>列展示
|
||||
</n-checkbox>
|
||||
>列展示</n-checkbox
|
||||
>
|
||||
<n-checkbox v-model:checked="selection" @update:checked="onSelection"
|
||||
>勾选列
|
||||
</n-checkbox>
|
||||
>勾选列</n-checkbox
|
||||
>
|
||||
<n-button text type="info" size="small" class="mt-1" @click="resetColumns"
|
||||
>重置
|
||||
</n-button>
|
||||
>重置</n-button
|
||||
>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
@@ -92,24 +92,22 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, ref, toRaw, toRefs, unref, watchEffect } from 'vue';
|
||||
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs, watchEffect } from 'vue';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import {
|
||||
DragOutlined,
|
||||
SettingOutlined,
|
||||
VerticalLeftOutlined,
|
||||
DragOutlined,
|
||||
VerticalRightOutlined,
|
||||
VerticalLeftOutlined,
|
||||
} from '@vicons/antd';
|
||||
import Draggable from 'vuedraggable/src/vuedraggable';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||
|
||||
interface Options {
|
||||
title: string;
|
||||
key: string;
|
||||
fixed?: boolean | 'left' | 'right';
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ColumnSetting',
|
||||
components: {
|
||||
@@ -119,30 +117,32 @@
|
||||
VerticalRightOutlined,
|
||||
VerticalLeftOutlined,
|
||||
},
|
||||
setup() {
|
||||
props: {
|
||||
openChecked: {
|
||||
type: Boolean as PropType<Boolean>,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { getDarkTheme } = useDesignSetting();
|
||||
const table: any = useTableContext();
|
||||
const columnsList = ref<Options[]>([]);
|
||||
const cacheColumnsList = ref<Options[]>([]);
|
||||
|
||||
const state = reactive({
|
||||
selection: true,
|
||||
selection: false,
|
||||
checkAll: true,
|
||||
checkList: [],
|
||||
defaultCheckList: [],
|
||||
});
|
||||
|
||||
const getSelection = computed(() => {
|
||||
return state.selection;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const columns = table.getColumns();
|
||||
if (columns.length) {
|
||||
init();
|
||||
}
|
||||
});
|
||||
|
||||
//初始化
|
||||
function init() {
|
||||
const columns: any[] = getColumns();
|
||||
@@ -153,23 +153,27 @@
|
||||
if (!columnsList.value.length) {
|
||||
columnsList.value = cloneDeep(newColumns);
|
||||
cacheColumnsList.value = cloneDeep(newColumns);
|
||||
// 只有首次加载时需要执行,重复执行会导致生成多个选项
|
||||
if (props.openChecked) {
|
||||
state.selection = true;
|
||||
if (newColumns[0].type != 'selection' && newColumns[0].key != 'selection') {
|
||||
onSelection(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
onSelection(true);
|
||||
}
|
||||
|
||||
//切换
|
||||
function onChange(checkList) {
|
||||
console.log('checkList:' + JSON.stringify(checkList));
|
||||
if (state.selection) {
|
||||
checkList.unshift('selection');
|
||||
}
|
||||
setColumns(checkList);
|
||||
}
|
||||
|
||||
//设置
|
||||
function setColumns(columns) {
|
||||
table.setColumns(columns);
|
||||
}
|
||||
|
||||
//获取
|
||||
function getColumns() {
|
||||
let newRet: any[] = [];
|
||||
@@ -178,7 +182,6 @@
|
||||
});
|
||||
return newRet;
|
||||
}
|
||||
|
||||
//重置
|
||||
function resetColumns() {
|
||||
state.checkList = [...state.defaultCheckList];
|
||||
@@ -193,7 +196,6 @@
|
||||
setColumns(newColumns);
|
||||
columnsList.value = newColumns;
|
||||
}
|
||||
|
||||
//全选
|
||||
function onCheckAll(e) {
|
||||
let checkList = table.getCacheColumns(true);
|
||||
@@ -205,33 +207,28 @@
|
||||
state.checkList = [];
|
||||
}
|
||||
}
|
||||
|
||||
//拖拽排序
|
||||
function draggableEnd() {
|
||||
const newColumns = toRaw(unref(columnsList));
|
||||
columnsList.value = newColumns;
|
||||
setColumns(newColumns);
|
||||
}
|
||||
|
||||
//勾选列
|
||||
function onSelection(e) {
|
||||
console.log('onSelection:' + JSON.stringify(e));
|
||||
let checkList = table.getCacheColumns();
|
||||
if (e) {
|
||||
if (checkList[0].type === undefined || checkList[0].type !== 'selection') {
|
||||
checkList.unshift({ type: 'selection', key: 'selection' });
|
||||
setColumns(checkList);
|
||||
}
|
||||
checkList.unshift({ type: 'selection', key: 'selection' });
|
||||
setColumns(checkList);
|
||||
} else {
|
||||
checkList.splice(0, 1);
|
||||
setColumns(checkList);
|
||||
}
|
||||
}
|
||||
|
||||
function onMove(e) {
|
||||
if (e.draggedContext.element.draggable === false) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//固定
|
||||
function fixedColumn(item, fixed) {
|
||||
if (!state.checkList.includes(item.key)) return;
|
||||
@@ -245,7 +242,6 @@
|
||||
columnsList.value[index].fixed = isFixed;
|
||||
setColumns(columns);
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
columnsList,
|
||||
@@ -268,65 +264,54 @@
|
||||
&-inner-popover-title {
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
&-right {
|
||||
&-icon {
|
||||
margin-left: 12px;
|
||||
font-size: 16px;
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
|
||||
:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-toolbar-inner {
|
||||
&-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
|
||||
&:hover {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
|
||||
.drag-icon {
|
||||
display: inline-flex;
|
||||
margin-right: 8px;
|
||||
cursor: move;
|
||||
|
||||
&-hidden {
|
||||
visibility: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.ant-checkbox-wrapper {
|
||||
flex: 1;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-checkbox-dark {
|
||||
&:hover {
|
||||
background: hsla(0, 0%, 100%, 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-popover {
|
||||
.n-popover__content {
|
||||
padding: 0;
|
||||
|
@@ -12,6 +12,14 @@ export const basicProps = {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
showTopRight: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
openChecked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'medium',
|
||||
|
@@ -11,12 +11,24 @@
|
||||
>
|
||||
<div class="upload-card-item-info">
|
||||
<div class="img-box">
|
||||
<img :src="item" />
|
||||
<template v-if="fileType === 'image'">
|
||||
<img :src="item" @error="errorImg($event)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-avatar :style="fileAvatarCSS">{{ getFileExt(item) }}</n-avatar>
|
||||
</template>
|
||||
</div>
|
||||
<div class="img-box-actions">
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="preview(item)">
|
||||
<EyeOutlined />
|
||||
</n-icon>
|
||||
<template v-if="fileType === 'image'">
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="preview(item)">
|
||||
<EyeOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="download(item)">
|
||||
<CloudDownloadOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="remove(index)">
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
@@ -40,7 +52,7 @@
|
||||
<n-icon size="18" class="m-auto">
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
<span class="upload-title">上传图片</span>
|
||||
<span class="upload-title">{{ uploadTitle }}</span>
|
||||
</div>
|
||||
</n-upload>
|
||||
</div>
|
||||
@@ -68,21 +80,21 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
|
||||
import { EyeOutlined, DeleteOutlined, PlusOutlined } from '@vicons/antd';
|
||||
import { defineComponent, toRefs, reactive, computed, watch, onMounted, ref } from 'vue';
|
||||
import { EyeOutlined, DeleteOutlined, PlusOutlined, CloudDownloadOutlined } from '@vicons/antd';
|
||||
import { basicProps } from './props';
|
||||
import { useMessage, useDialog } from 'naive-ui';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import componentSetting from '@/settings/componentSetting';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { isString } from '@/utils/is';
|
||||
|
||||
import { isJsonString, isNullOrUnDef } from '@/utils/is';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
const globSetting = useGlobSetting();
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicUpload',
|
||||
|
||||
components: { EyeOutlined, DeleteOutlined, PlusOutlined },
|
||||
components: { EyeOutlined, DeleteOutlined, PlusOutlined, CloudDownloadOutlined },
|
||||
props: {
|
||||
...basicProps,
|
||||
},
|
||||
@@ -97,6 +109,13 @@
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const uploadTitle = ref(props.fileType === 'image' ? '上传图片' : '上传附件');
|
||||
const fileAvatarCSS = computed(() => {
|
||||
return {
|
||||
'--n-merged-size': `var(--n-avatar-size-override, ${props.width * 0.8}px)`,
|
||||
'--n-font-size': `18px`,
|
||||
};
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
showModal: false,
|
||||
@@ -109,32 +128,55 @@
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
// console.log('props.value:' + props.value);
|
||||
// 单图模式
|
||||
if (typeof props.value === 'string') {
|
||||
let data: string[] = [];
|
||||
if (props.value !== '') {
|
||||
data.push(props.value);
|
||||
}
|
||||
|
||||
state.imgList = data.map((item) => {
|
||||
return getImgUrl(item);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 多图模式
|
||||
state.imgList = props.value.map((item) => {
|
||||
return getImgUrl(item);
|
||||
});
|
||||
loadValue(props.value);
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.values,
|
||||
() => {
|
||||
loadValue(props.values);
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
// 加载默认
|
||||
function loadValue(value: any) {
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data: string[] = [];
|
||||
if (isJsonString(value)) {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
|
||||
// 单图模式
|
||||
if (typeof value === 'string') {
|
||||
if (value !== '') {
|
||||
data.push(value);
|
||||
}
|
||||
} else {
|
||||
// 多图模式
|
||||
data = value;
|
||||
}
|
||||
|
||||
state.imgList = data.map((item) => {
|
||||
return getImgUrl(item);
|
||||
});
|
||||
state.originalImgList = state.imgList;
|
||||
}
|
||||
|
||||
//预览
|
||||
function preview(url: string) {
|
||||
state.showModal = true;
|
||||
state.previewUrl = url;
|
||||
}
|
||||
//下载
|
||||
function download(url: string) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
//删除
|
||||
function remove(index: number) {
|
||||
@@ -146,7 +188,11 @@
|
||||
onPositiveClick: () => {
|
||||
state.imgList.splice(index, 1);
|
||||
state.originalImgList.splice(index, 1);
|
||||
emit('uploadChange', state.originalImgList);
|
||||
if (props.maxNumber === 1) {
|
||||
emit('uploadChange', '');
|
||||
} else {
|
||||
emit('uploadChange', state.originalImgList);
|
||||
}
|
||||
emit('delete', state.originalImgList);
|
||||
},
|
||||
onNegativeClick: () => {},
|
||||
@@ -159,25 +205,29 @@
|
||||
return /(^http|https:\/\/)/g.test(url) ? url : `${imgUrl}${url}`;
|
||||
}
|
||||
|
||||
function checkFileType(fileType: string) {
|
||||
return componentSetting.upload.fileType.includes(fileType);
|
||||
function checkFileType(map: string[], fileType: string) {
|
||||
if (isNullOrUnDef(map)) {
|
||||
return true;
|
||||
}
|
||||
return map.includes(fileType);
|
||||
}
|
||||
|
||||
//上传之前
|
||||
function beforeUpload({ file }) {
|
||||
const fileInfo = file.file;
|
||||
const { maxSize, accept } = props;
|
||||
const acceptRef = (isString(accept) && accept.split(',')) || [];
|
||||
|
||||
// 设置最大值,则判断
|
||||
if (maxSize && fileInfo.size / 1024 / 1024 >= maxSize) {
|
||||
message.error(`上传文件最大值不能超过${maxSize}M`);
|
||||
if (props.maxSize && fileInfo.size / 1024 / 1024 >= props.maxSize) {
|
||||
message.error(`上传文件最大值不能超过${props.maxSize}M`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置类型,则判断
|
||||
const fileType = componentSetting.upload.fileType;
|
||||
if (acceptRef.length > 0 && !checkFileType(fileInfo.type)) {
|
||||
const fileType =
|
||||
props.fileType === 'image'
|
||||
? componentSetting.upload.imageType
|
||||
: componentSetting.upload.fileType;
|
||||
if (!checkFileType(fileType, fileInfo.type)) {
|
||||
console.log('checkFileType fileInfo.type:' + fileInfo.type);
|
||||
message.error(`只能上传文件类型为${fileType.join(',')}`);
|
||||
return false;
|
||||
}
|
||||
@@ -197,20 +247,45 @@
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
let imgUrl: string = getImgUrl(result[imgField]);
|
||||
state.imgList.push(imgUrl);
|
||||
state.originalImgList.push(result[imgField]);
|
||||
emit('uploadChange', state.originalImgList);
|
||||
state.originalImgList = state.imgList;
|
||||
if (props.maxNumber === 1) {
|
||||
emit('uploadChange', imgUrl);
|
||||
} else {
|
||||
emit('uploadChange', state.originalImgList);
|
||||
}
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**图片加载失败显示自定义默认图片(缺省图)*/
|
||||
function errorImg(e) {
|
||||
e.srcElement.src = '/onerror.png';
|
||||
//这一句没用,如果默认图片的路径错了还是会一直闪屏,在方法的前面加个.once只让它执行一次也没用
|
||||
e.srcElement.onerror = null; //防止闪图
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
setTimeout(function () {
|
||||
if (props.maxNumber === 1) {
|
||||
loadValue(props.value);
|
||||
} else {
|
||||
loadValue(props.values);
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
return {
|
||||
errorImg,
|
||||
...toRefs(state),
|
||||
finish,
|
||||
preview,
|
||||
download,
|
||||
remove,
|
||||
beforeUpload,
|
||||
getCSSProperties,
|
||||
uploadTitle,
|
||||
fileAvatarCSS,
|
||||
getFileExt,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -3,6 +3,10 @@ import { NUpload } from 'naive-ui';
|
||||
|
||||
export const basicProps = {
|
||||
...NUpload.props,
|
||||
fileType: {
|
||||
type: String,
|
||||
default: 'image',
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: '.jpg,.png,.jpeg,.svg,.gif',
|
||||
@@ -24,7 +28,7 @@ export const basicProps = {
|
||||
default: () => '',
|
||||
},
|
||||
values: {
|
||||
type: (Array as PropType<string[]>) || (String as PropType<string>),
|
||||
type: (Array as PropType<string[]>) || (Object as PropType<object>),
|
||||
default: () => [],
|
||||
},
|
||||
width: {
|
||||
|
59
web/src/components/Upload/uploadFile.vue
Normal file
59
web/src/components/Upload/uploadFile.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<BasicUpload
|
||||
:action="`${uploadUrl}${urlPrefix}/upload/file`"
|
||||
:headers="uploadHeaders"
|
||||
:data="{ type: 0 }"
|
||||
name="file"
|
||||
:width="100"
|
||||
:height="100"
|
||||
fileType="file"
|
||||
:maxNumber="maxNumber"
|
||||
@uploadChange="uploadChange"
|
||||
v-model:value="image"
|
||||
v-model:values="images"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, unref, reactive } from 'vue';
|
||||
import { BasicUpload } from '@/components/Upload';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
|
||||
export interface Props {
|
||||
value: string | string[] | null;
|
||||
maxNumber: number;
|
||||
}
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
const { uploadUrl } = globSetting;
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
const uploadHeaders = reactive({
|
||||
Authorization: useUserStore.token,
|
||||
});
|
||||
const emit = defineEmits(['update:value']);
|
||||
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1 });
|
||||
const image = ref<string>('');
|
||||
const images = ref<string[] | object>([]);
|
||||
|
||||
function uploadChange(list: string | string[]) {
|
||||
if (props.maxNumber === 1) {
|
||||
image.value = unref(list as string);
|
||||
emit('update:value', image.value);
|
||||
} else {
|
||||
images.value = unref(list as string[]);
|
||||
emit('update:value', images.value);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.maxNumber === 1) {
|
||||
image.value = props.value as string;
|
||||
} else {
|
||||
images.value = props.value as string[];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
58
web/src/components/Upload/uploadImage.vue
Normal file
58
web/src/components/Upload/uploadImage.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<BasicUpload
|
||||
:action="`${uploadUrl}${urlPrefix}/upload/image`"
|
||||
:headers="uploadHeaders"
|
||||
:data="{ type: 0 }"
|
||||
name="file"
|
||||
:width="100"
|
||||
:height="100"
|
||||
:maxNumber="maxNumber"
|
||||
@uploadChange="uploadChange"
|
||||
v-model:value="image"
|
||||
v-model:values="images"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, unref, reactive } from 'vue';
|
||||
import { BasicUpload } from '@/components/Upload';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
|
||||
export interface Props {
|
||||
value: string | string[] | null;
|
||||
maxNumber: number;
|
||||
}
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
const { uploadUrl } = globSetting;
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
const uploadHeaders = reactive({
|
||||
Authorization: useUserStore.token,
|
||||
});
|
||||
const emit = defineEmits(['update:value']);
|
||||
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1 });
|
||||
const image = ref<string>('');
|
||||
const images = ref<string[]>([]);
|
||||
|
||||
function uploadChange(list: string | string[]) {
|
||||
if (props.maxNumber === 1) {
|
||||
image.value = unref(list as string);
|
||||
emit('update:value', image.value);
|
||||
} else {
|
||||
images.value = unref(list as string[]);
|
||||
emit('update:value', images.value);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.maxNumber === 1) {
|
||||
image.value = props.value as string;
|
||||
} else {
|
||||
images.value = props.value as string[];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
@@ -65,3 +65,31 @@ export const statusActions = [
|
||||
key: 2,
|
||||
},
|
||||
];
|
||||
|
||||
// 标签
|
||||
export const tagOptions = [
|
||||
{
|
||||
label: '灰色',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
label: '主色',
|
||||
value: 'primary',
|
||||
},
|
||||
{
|
||||
label: '蓝色',
|
||||
value: 'info',
|
||||
},
|
||||
{
|
||||
label: '绿色',
|
||||
value: 'success',
|
||||
},
|
||||
{
|
||||
label: '黄色',
|
||||
value: 'warning',
|
||||
},
|
||||
{
|
||||
label: '红色',
|
||||
value: 'error',
|
||||
},
|
||||
];
|
||||
|
@@ -29,17 +29,6 @@ export function useTime() {
|
||||
second.value = date.getSeconds();
|
||||
};
|
||||
|
||||
// 原生时间格式化
|
||||
// new Intl.DateTimeFormat('zh', {
|
||||
// year: 'numeric',
|
||||
// month: '2-digit',
|
||||
// day: '2-digit',
|
||||
// hour: '2-digit',
|
||||
// minute: '2-digit',
|
||||
// second: '2-digit',
|
||||
// hour12: false
|
||||
// }).format(new Date())
|
||||
|
||||
updateTime();
|
||||
|
||||
onMounted(() => {
|
||||
|
@@ -1,15 +1,12 @@
|
||||
import type { EChartsOption } from 'echarts';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { useTimeoutFn } from '@/hooks/core/useTimeout';
|
||||
import { tryOnUnmounted } from '@vueuse/core';
|
||||
import { unref, nextTick, watch, computed, ref } from 'vue';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { useEventListener } from '@/hooks/event/useEventListener';
|
||||
import { useBreakpoint } from '@/hooks/event/useBreakpoint';
|
||||
|
||||
import echarts from '@/utils/lib/echarts';
|
||||
|
||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||
|
||||
export function useECharts(
|
||||
|
@@ -555,13 +555,13 @@
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
//::v-deep(.menu-server-link) {
|
||||
// color: #515a6e;
|
||||
//
|
||||
// &:hover {
|
||||
// color: #1890ff;
|
||||
// }
|
||||
//}
|
||||
::v-deep(.menu-server-link) {
|
||||
color: #515a6e;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.action-items-wrapper {
|
||||
position: relative;
|
||||
@@ -600,7 +600,7 @@
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/deep/ sup {
|
||||
:deep(sup) {
|
||||
top: 1.3em;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="logo">
|
||||
<img src="~@/assets/images/logo.png" alt="" :class="{ 'mr-2': !collapsed }" />
|
||||
<h2 v-show="!collapsed" class="title">后台管理系统</h2>
|
||||
<h2 v-show="!collapsed" class="title">HG后台管理系统</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@@ -44,7 +44,7 @@ async function bootstrap() {
|
||||
f.call(null, event);
|
||||
});
|
||||
};
|
||||
app.config.globalProperties.$websocket = Websocket(onMessage);
|
||||
Websocket(onMessage);
|
||||
|
||||
app.mount('#app', true);
|
||||
}
|
||||
|
@@ -1,7 +1,4 @@
|
||||
export const RedirectName = 'Redirect';
|
||||
|
||||
export const ErrorPage = () => import('@/views/exception/404.vue');
|
||||
|
||||
export const Layout = () => import('@/layout/index.vue');
|
||||
|
||||
export const ParentLayout = () => import('@/layout/parentLayout.vue');
|
||||
|
@@ -51,7 +51,6 @@ export const routerGenerator = (routerMap, parent?): any[] => {
|
||||
|
||||
/**
|
||||
* 动态生成菜单
|
||||
* @returns {Promise<Router>}
|
||||
*/
|
||||
export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@@ -1,32 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { ProjectOutlined } from '@vicons/antd';
|
||||
import { renderIcon, renderNew } from '@/utils/index';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
component: Layout,
|
||||
meta: {
|
||||
sort: 10,
|
||||
isRoot: true,
|
||||
activeMenu: 'about_index',
|
||||
icon: renderIcon(ProjectOutlined),
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: `about_index`,
|
||||
meta: {
|
||||
title: '关于',
|
||||
extra: renderNew(),
|
||||
activeMenu: 'about_index',
|
||||
},
|
||||
component: () => import('@/views/about/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,131 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout, ParentLayout } from '@/router/constant';
|
||||
import { WalletOutlined } from '@vicons/antd';
|
||||
import { renderIcon, renderNew } from '@/utils';
|
||||
|
||||
const routeName = 'comp';
|
||||
|
||||
/**
|
||||
* @param name 路由名称, 必须设置,且不能重名
|
||||
* @param meta 路由元信息(路由附带扩展信息)
|
||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
||||
* @param meta.disabled 禁用整个菜单
|
||||
* @param meta.title 菜单名称
|
||||
* @param meta.icon 菜单图标
|
||||
* @param meta.keepAlive 缓存该路由
|
||||
* @param meta.sort 排序越小越排前
|
||||
*
|
||||
* */
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/comp',
|
||||
name: routeName,
|
||||
component: Layout,
|
||||
redirect: '/comp/table',
|
||||
meta: {
|
||||
title: '组件示例',
|
||||
icon: renderIcon(WalletOutlined),
|
||||
sort: 8,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'table',
|
||||
name: `${routeName}_table`,
|
||||
redirect: '/comp/table/basic',
|
||||
component: ParentLayout,
|
||||
meta: {
|
||||
title: '表格',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'basic',
|
||||
name: `${routeName}_table_basic`,
|
||||
meta: {
|
||||
title: '基础表格',
|
||||
},
|
||||
component: () => import('@/views/comp/table/basic.vue'),
|
||||
},
|
||||
{
|
||||
path: 'editCell',
|
||||
name: `${routeName}_table_editCell`,
|
||||
meta: {
|
||||
title: '单元格编辑',
|
||||
},
|
||||
component: () => import('@/views/comp/table/editCell.vue'),
|
||||
},
|
||||
{
|
||||
path: 'editRow',
|
||||
name: `${routeName}_table_editRow`,
|
||||
meta: {
|
||||
title: '整行编辑',
|
||||
},
|
||||
component: () => import('@/views/comp/table/editRow.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'form',
|
||||
name: `${routeName}_form`,
|
||||
redirect: '/comp/form/basic',
|
||||
component: ParentLayout,
|
||||
meta: {
|
||||
title: '表单',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'basic',
|
||||
name: `${routeName}_form_basic`,
|
||||
meta: {
|
||||
title: '基础使用',
|
||||
},
|
||||
component: () => import('@/views/comp/form/basic.vue'),
|
||||
},
|
||||
{
|
||||
path: 'useForm',
|
||||
name: `useForm`,
|
||||
meta: {
|
||||
title: 'useForm',
|
||||
},
|
||||
component: () => import('@/views/comp/form/useForm.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'upload',
|
||||
name: `${routeName}_upload`,
|
||||
meta: {
|
||||
title: '上传图片',
|
||||
},
|
||||
component: () => import('@/views/comp/upload/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'modal',
|
||||
name: `${routeName}_modal`,
|
||||
meta: {
|
||||
title: '弹窗扩展',
|
||||
},
|
||||
component: () => import('@/views/comp/modal/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'richtext',
|
||||
name: `richtext`,
|
||||
meta: {
|
||||
title: '富文本',
|
||||
extra: renderNew(),
|
||||
},
|
||||
component: () => import('@/views/comp/richtext/vue-quill.vue'),
|
||||
},
|
||||
{
|
||||
path: 'drag',
|
||||
name: `Drag`,
|
||||
meta: {
|
||||
title: '拖拽',
|
||||
extra: renderNew(),
|
||||
},
|
||||
component: () => import('@/views/comp/drag/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,64 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { DashboardOutlined } from '@vicons/antd';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
const routeName = 'dashboard';
|
||||
|
||||
/**
|
||||
* @param name 路由名称, 必须设置,且不能重名
|
||||
* @param meta 路由元信息(路由附带扩展信息)
|
||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
||||
* @param meta.disabled 禁用整个菜单
|
||||
* @param meta.title 菜单名称
|
||||
* @param meta.icon 菜单图标
|
||||
* @param meta.keepAlive 缓存该路由
|
||||
* @param meta.sort 排序越小越排前
|
||||
* */
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: routeName,
|
||||
redirect: '/dashboard/console',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: 'Dashboard',
|
||||
icon: renderIcon(DashboardOutlined),
|
||||
permissions: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
|
||||
sort: 0,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'console',
|
||||
name: `${routeName}_console`,
|
||||
meta: {
|
||||
title: '主控台',
|
||||
permissions: ['dashboard_console'],
|
||||
affix: true,
|
||||
},
|
||||
component: () => import('@/views/dashboard/console/console.vue'),
|
||||
},
|
||||
// {
|
||||
// path: 'monitor',
|
||||
// name: `${ routeName }_monitor`,
|
||||
// meta: {
|
||||
// title: '监控页',
|
||||
// permissions: ['dashboard_monitor']
|
||||
// },
|
||||
// component: () => import('@/views/dashboard/monitor/monitor.vue')
|
||||
// },
|
||||
{
|
||||
path: 'workplace',
|
||||
name: `${routeName}_workplace`,
|
||||
meta: {
|
||||
title: '工作台',
|
||||
keepAlive: true,
|
||||
permissions: ['dashboard_workplace'],
|
||||
},
|
||||
component: () => import('@/views/dashboard/workplace/workplace.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,19 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { DocumentTextOutline } from '@vicons/ionicons5';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/external',
|
||||
name: 'https://naive-ui-admin-docs.vercel.app',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '项目文档',
|
||||
icon: renderIcon(DocumentTextOutline),
|
||||
sort: 9,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,57 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { ExclamationCircleOutlined } from '@vicons/antd';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
/**
|
||||
* @param name 路由名称, 必须设置,且不能重名
|
||||
* @param meta 路由元信息(路由附带扩展信息)
|
||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
||||
* @param meta.disabled 禁用整个菜单
|
||||
* @param meta.title 菜单名称
|
||||
* @param meta.icon 菜单图标
|
||||
* @param meta.keepAlive 缓存该路由
|
||||
* @param meta.sort 排序越小越排前
|
||||
*
|
||||
* */
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/exception',
|
||||
name: 'Exception',
|
||||
redirect: '/exception/403',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '异常页面',
|
||||
icon: renderIcon(ExclamationCircleOutlined),
|
||||
sort: 3,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '403',
|
||||
name: 'exception-403',
|
||||
meta: {
|
||||
title: '403',
|
||||
},
|
||||
component: () => import('@/views/exception/403.vue'),
|
||||
},
|
||||
{
|
||||
path: '404',
|
||||
name: 'exception-404',
|
||||
meta: {
|
||||
title: '404',
|
||||
},
|
||||
component: () => import('@/views/exception/404.vue'),
|
||||
},
|
||||
{
|
||||
path: '500',
|
||||
name: 'exception-500',
|
||||
meta: {
|
||||
title: '500',
|
||||
},
|
||||
component: () => import('@/views/exception/500.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,57 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { ProfileOutlined } from '@vicons/antd';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
/**
|
||||
* @param name 路由名称, 必须设置,且不能重名
|
||||
* @param meta 路由元信息(路由附带扩展信息)
|
||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
||||
* @param meta.disabled 禁用整个菜单
|
||||
* @param meta.title 菜单名称
|
||||
* @param meta.icon 菜单图标
|
||||
* @param meta.keepAlive 缓存该路由
|
||||
* @param meta.sort 排序越小越排前
|
||||
*
|
||||
* */
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/form',
|
||||
name: 'Form',
|
||||
redirect: '/form/basic-form',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '表单页面',
|
||||
icon: renderIcon(ProfileOutlined),
|
||||
sort: 3,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'basic-form',
|
||||
name: 'form-basic-form',
|
||||
meta: {
|
||||
title: '基础表单',
|
||||
},
|
||||
component: () => import('@/views/form/basicForm/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'step-form',
|
||||
name: 'form-step-form',
|
||||
meta: {
|
||||
title: '分步表单',
|
||||
},
|
||||
component: () => import('@/views/form/stepForm/stepForm.vue'),
|
||||
},
|
||||
{
|
||||
path: 'detail',
|
||||
name: 'form-detail',
|
||||
meta: {
|
||||
title: '表单详情',
|
||||
},
|
||||
component: () => import('@/views/form/detail/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,24 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { DesktopOutline } from '@vicons/ionicons5';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
const IFrame = () => import('@/views/iframe/index.vue');
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/frame',
|
||||
name: 'Frame',
|
||||
redirect: '/frame/docs',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '外部页面',
|
||||
sort: 8,
|
||||
icon: renderIcon(DesktopOutline),
|
||||
},
|
||||
children: [
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,51 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { TableOutlined } from '@vicons/antd';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
/**
|
||||
* @param name 路由名称, 必须设置,且不能重名
|
||||
* @param meta 路由元信息(路由附带扩展信息)
|
||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
||||
* @param meta.disabled 禁用整个菜单
|
||||
* @param meta.title 菜单名称
|
||||
* @param meta.icon 菜单图标
|
||||
* @param meta.keepAlive 缓存该路由
|
||||
* @param meta.sort 排序越小越排前
|
||||
*
|
||||
* */
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/list',
|
||||
name: 'List',
|
||||
redirect: '/list/basic-list',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '列表页面',
|
||||
icon: renderIcon(TableOutlined),
|
||||
sort: 2,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'basic-list',
|
||||
name: 'basic-list',
|
||||
meta: {
|
||||
title: '基础列表',
|
||||
},
|
||||
component: () => import('@/views/list/basicList/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'basic-info/:id?',
|
||||
name: 'basic-info',
|
||||
meta: {
|
||||
title: '基础详情',
|
||||
hidden: true,
|
||||
activeMenu: 'basic-list',
|
||||
},
|
||||
component: () => import('@/views/list/basicList/info.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,57 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { CheckCircleOutlined } from '@vicons/antd';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
/**
|
||||
* @param name 路由名称, 必须设置,且不能重名
|
||||
* @param meta 路由元信息(路由附带扩展信息)
|
||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
||||
* @param meta.disabled 禁用整个菜单
|
||||
* @param meta.title 菜单名称
|
||||
* @param meta.icon 菜单图标
|
||||
* @param meta.keepAlive 缓存该路由
|
||||
* @param meta.sort 排序越小越排前
|
||||
*
|
||||
* */
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/result',
|
||||
name: 'Result',
|
||||
redirect: '/result/success',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '结果页面',
|
||||
icon: renderIcon(CheckCircleOutlined),
|
||||
sort: 4,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'success',
|
||||
name: 'result-success',
|
||||
meta: {
|
||||
title: '成功页',
|
||||
},
|
||||
component: () => import('@/views/result/success.vue'),
|
||||
},
|
||||
{
|
||||
path: 'fail',
|
||||
name: 'result-fail',
|
||||
meta: {
|
||||
title: '失败页',
|
||||
},
|
||||
component: () => import('@/views/result/fail.vue'),
|
||||
},
|
||||
{
|
||||
path: 'info',
|
||||
name: 'result-info',
|
||||
meta: {
|
||||
title: '信息页',
|
||||
},
|
||||
component: () => import('@/views/result/info.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,49 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { SettingOutlined } from '@vicons/antd';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
/**
|
||||
* @param name 路由名称, 必须设置,且不能重名
|
||||
* @param meta 路由元信息(路由附带扩展信息)
|
||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
||||
* @param meta.disabled 禁用整个菜单
|
||||
* @param meta.title 菜单名称
|
||||
* @param meta.icon 菜单图标
|
||||
* @param meta.keepAlive 缓存该路由
|
||||
* @param meta.sort 排序越小越排前
|
||||
*
|
||||
* */
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/setting',
|
||||
name: 'Setting',
|
||||
redirect: '/setting/account',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '设置页面',
|
||||
icon: renderIcon(SettingOutlined),
|
||||
sort: 5,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'account',
|
||||
name: 'setting-account',
|
||||
meta: {
|
||||
title: '个人设置',
|
||||
},
|
||||
component: () => import('@/views/setting/account/account.vue'),
|
||||
},
|
||||
{
|
||||
path: 'system',
|
||||
name: 'setting-system',
|
||||
meta: {
|
||||
title: '系统设置',
|
||||
},
|
||||
component: () => import('@/views/setting/system/system.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -1,49 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { OptionsSharp } from '@vicons/ionicons5';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
/**
|
||||
* @param name 路由名称, 必须设置,且不能重名
|
||||
* @param meta 路由元信息(路由附带扩展信息)
|
||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
||||
* @param meta.disabled 禁用整个菜单
|
||||
* @param meta.title 菜单名称
|
||||
* @param meta.icon 菜单图标
|
||||
* @param meta.keepAlive 缓存该路由
|
||||
* @param meta.sort 排序越小越排前
|
||||
*
|
||||
* */
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/system',
|
||||
name: 'System',
|
||||
redirect: '/system/menu',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '系统设置',
|
||||
icon: renderIcon(OptionsSharp),
|
||||
sort: 1,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu',
|
||||
name: 'system_menu',
|
||||
meta: {
|
||||
title: '菜单权限管理',
|
||||
},
|
||||
component: () => import('@/views/permission/menu/menu.vue'),
|
||||
},
|
||||
{
|
||||
path: 'role',
|
||||
name: 'system_role',
|
||||
meta: {
|
||||
title: '角色权限管理',
|
||||
},
|
||||
component: () => import('@/views/permission/role/role.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
@@ -8,7 +8,6 @@ import { PageEnum } from '@/enums/pageEnum';
|
||||
import { ErrorPageRoute } from '@/router/base';
|
||||
|
||||
const LOGIN_PATH = PageEnum.BASE_LOGIN;
|
||||
|
||||
const whitePathList = [LOGIN_PATH]; // no redirect whitelist
|
||||
|
||||
export function createRouterGuards(router: Router) {
|
||||
|
@@ -1,53 +1,12 @@
|
||||
import { renderIcon } from '@/utils/index';
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
CheckCircleOutlined,
|
||||
DashboardOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
ProfileOutlined,
|
||||
ProjectOutlined,
|
||||
SettingOutlined,
|
||||
TableOutlined,
|
||||
WalletOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
} from '@vicons/antd';
|
||||
import {
|
||||
DesktopOutline,
|
||||
DocumentTextOutline,
|
||||
OptionsSharp,
|
||||
SettingsOutline,
|
||||
LogoBuffer,
|
||||
KeyOutline,
|
||||
VolumeLowOutline,
|
||||
CodeSlashOutline,
|
||||
EaselOutline,
|
||||
TimerOutline,
|
||||
OptionsOutline,
|
||||
} from '@vicons/ionicons5';
|
||||
import * as antdIcons from '@vicons/antd';
|
||||
// import * as x5Icons from '@vicons/ionicons5';
|
||||
|
||||
//前端路由图标映射表
|
||||
export const constantRouterIcon = {
|
||||
EditOutlined: renderIcon(EditOutlined),
|
||||
DeleteOutlined: renderIcon(DeleteOutlined),
|
||||
OptionsOutline: renderIcon(OptionsOutline),
|
||||
TimerOutline: renderIcon(TimerOutline),
|
||||
EaselOutline: renderIcon(EaselOutline),
|
||||
CodeSlashOutline: renderIcon(CodeSlashOutline),
|
||||
VolumeLowOutline: renderIcon(VolumeLowOutline),
|
||||
DashboardOutlined: renderIcon(DashboardOutlined),
|
||||
SettingsOutline: renderIcon(SettingsOutline),
|
||||
OptionsSharp: renderIcon(OptionsSharp),
|
||||
TableOutlined: renderIcon(TableOutlined),
|
||||
ExclamationCircleOutlined: renderIcon(ExclamationCircleOutlined),
|
||||
ProfileOutlined: renderIcon(ProfileOutlined),
|
||||
CheckCircleOutlined: renderIcon(CheckCircleOutlined),
|
||||
SettingOutlined: renderIcon(SettingOutlined),
|
||||
WalletOutlined: renderIcon(WalletOutlined),
|
||||
DesktopOutline: renderIcon(DesktopOutline),
|
||||
DocumentTextOutline: renderIcon(DocumentTextOutline),
|
||||
ProjectOutlined: renderIcon(ProjectOutlined),
|
||||
AppstoreOutlined: renderIcon(AppstoreOutlined),
|
||||
LogoBuffer: renderIcon(LogoBuffer),
|
||||
KeyOutline: renderIcon(KeyOutline),
|
||||
};
|
||||
export const constantRouterIcon = {};
|
||||
for (const element of Object.keys(antdIcons)) {
|
||||
constantRouterIcon[element] = renderIcon(antdIcons[element]);
|
||||
}
|
||||
|
||||
// for (const element of Object.keys(x5Icons)) {
|
||||
// constantRouterIcon[element] = renderIcon(x5Icons[element]);
|
||||
// }
|
||||
|
@@ -6,6 +6,7 @@ export type Component<T extends any = any> =
|
||||
| (() => Promise<typeof import('*.vue')>)
|
||||
| (() => Promise<T>);
|
||||
|
||||
// @ts-ignore
|
||||
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||
name: string;
|
||||
meta: RouteMeta;
|
||||
|
@@ -13,7 +13,7 @@ export default {
|
||||
//默认分页数量
|
||||
defaultPageSize: 10,
|
||||
//可切换每页数量集合
|
||||
pageSizes: [10, 20, 30, 40, 50],
|
||||
pageSizes: [10, 20, 30, 40, 50, 100, 200],
|
||||
},
|
||||
upload: {
|
||||
//考虑接口规范不同
|
||||
@@ -26,6 +26,29 @@ export default {
|
||||
//最大上传图片大小
|
||||
maxSize: 10,
|
||||
//图片上传类型
|
||||
fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'],
|
||||
imageType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'],
|
||||
//文件上传类型
|
||||
fileType: [
|
||||
// 图片
|
||||
'image/png',
|
||||
'image/jpg',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/svg+xml',
|
||||
// 文档
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
// 音频
|
||||
'audio/mpeg',
|
||||
'audio/midi',
|
||||
// 视频
|
||||
'audio/mp4',
|
||||
'video/webm',
|
||||
'video/x-flv',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@@ -2,8 +2,8 @@ import { toRaw, unref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { store } from '@/store';
|
||||
import { asyncRoutes, constantRouter } from '@/router/index';
|
||||
import { generatorDynamicRouter, removeHiddenMenus } from '@/router/generator-routers';
|
||||
import { asyncRoutes, constantRouter } from '@/router';
|
||||
import { generatorDynamicRouter } from '@/router/generator-routers';
|
||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||
|
||||
interface TreeHelperConfig {
|
||||
|
@@ -6,9 +6,13 @@ Object.keys(allModules).forEach((path) => {
|
||||
});
|
||||
|
||||
// export default modules
|
||||
// @ts-ignore
|
||||
import asyncRoute from './async-route';
|
||||
// @ts-ignore
|
||||
import user from './user';
|
||||
// @ts-ignore
|
||||
import tabsView from './tabs-view';
|
||||
// @ts-ignore
|
||||
import lockscreen from './lockscreen';
|
||||
|
||||
export default {
|
||||
|
@@ -21,7 +21,6 @@ export const notificationStore = defineStore({
|
||||
},
|
||||
addMessages(message) {
|
||||
message = JSON.parse(message);
|
||||
console.log('message:' + JSON.stringify(message));
|
||||
if (
|
||||
message.event !== undefined &&
|
||||
message.event === 'notice' &&
|
||||
|
@@ -55,7 +55,9 @@ export const useTabsViewStore = defineStore({
|
||||
},
|
||||
closeOtherTabs(route) {
|
||||
// 关闭其他
|
||||
this.tabsList = this.tabsList.filter((item) => item.fullPath == route.fullPath || (item?.meta?.affix ?? false));
|
||||
this.tabsList = this.tabsList.filter(
|
||||
(item) => item.fullPath == route.fullPath || (item?.meta?.affix ?? false)
|
||||
);
|
||||
},
|
||||
closeCurrentTab(route) {
|
||||
// 关闭当前页
|
||||
|
@@ -86,6 +86,7 @@ export const useUserStore = defineStore({
|
||||
|
||||
// 获取用户信息
|
||||
GetInfo() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const that = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfo()
|
||||
@@ -108,6 +109,7 @@ export const useUserStore = defineStore({
|
||||
},
|
||||
// 获取用户信息
|
||||
GetConfig() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const that = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
getConfig()
|
||||
|
3
web/src/styles/hotgo.less
Normal file
3
web/src/styles/hotgo.less
Normal file
@@ -0,0 +1,3 @@
|
||||
.min-left-space {
|
||||
margin-left: 5px;
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
@import 'transition/index.less';
|
||||
@import './var.less';
|
||||
@import './common.less';
|
||||
@import './hotgo.less';
|
||||
|
@@ -121,13 +121,6 @@ export function encodeParams(obj) {
|
||||
return arr.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* 去重追加
|
||||
* @param array
|
||||
* @param son
|
||||
*/
|
||||
export function onlyPush(array: any, son: any) {}
|
||||
|
||||
/**
|
||||
* 对象拷贝
|
||||
* @param obj2
|
||||
|
74
web/src/utils/charset.ts
Normal file
74
web/src/utils/charset.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 随机生成字符串
|
||||
*/
|
||||
export function getRandomString(len = 12, isSmall = false) {
|
||||
const _charStr = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789';
|
||||
const _smallCharStr = 'abacdefghjklmnopqrstuvwxyz0123456789';
|
||||
const charStr = isSmall ? _smallCharStr : _charStr;
|
||||
const min = 0;
|
||||
const max = charStr.length - 1;
|
||||
let _str = '';
|
||||
//循环生成字符串
|
||||
for (let i = 0, index; i < len; i++) {
|
||||
index = (function (randomIndexFunc, i) {
|
||||
return randomIndexFunc(min, max, i, randomIndexFunc);
|
||||
})(function (min, max, i, _self) {
|
||||
const numStart = _charStr.length - 10;
|
||||
let indexTemp = Math.floor(Math.random() * (max - min + 1) + min);
|
||||
if (i == 0 && indexTemp >= numStart) {
|
||||
indexTemp = _self(min, max, i, _self);
|
||||
}
|
||||
return indexTemp;
|
||||
}, i);
|
||||
_str += _charStr[index];
|
||||
}
|
||||
return _str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏中间几位字符
|
||||
*/
|
||||
export function structure(array) {
|
||||
// 将字符串转化成数组
|
||||
const arrBox = [...array];
|
||||
const count = arrBox.length;
|
||||
if (count == 1) {
|
||||
return '*';
|
||||
}
|
||||
let min = 1;
|
||||
let max = count;
|
||||
// 两位姓名
|
||||
if (count == 2) {
|
||||
min = 0;
|
||||
max = count;
|
||||
}
|
||||
// 三位姓名
|
||||
if (count == 3) {
|
||||
min = 0;
|
||||
max = count - 1;
|
||||
}
|
||||
// if (count >= 2 && count <= 8) {
|
||||
// min = 1;
|
||||
// }
|
||||
|
||||
// 手机号
|
||||
if (count == 11) {
|
||||
min = 3;
|
||||
max = 7;
|
||||
}
|
||||
// 身份证号码
|
||||
if (count >= 15) {
|
||||
min = 9;
|
||||
max = count - 4;
|
||||
}
|
||||
// 2.将数组中的4-7位变成*
|
||||
let str = '';
|
||||
arrBox.map((res, index) => {
|
||||
if (index > min && index < max) {
|
||||
str += '*';
|
||||
} else {
|
||||
str += res;
|
||||
}
|
||||
});
|
||||
return str;
|
||||
}
|
@@ -1,21 +1,38 @@
|
||||
import { format } from 'date-fns';
|
||||
import {
|
||||
endOfMonth,
|
||||
endOfToday,
|
||||
endOfWeek,
|
||||
endOfYesterday,
|
||||
format,
|
||||
startOfMonth,
|
||||
startOfToday,
|
||||
startOfTomorrow,
|
||||
startOfWeek,
|
||||
startOfYesterday,
|
||||
subMonths,
|
||||
} from 'date-fns';
|
||||
|
||||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
|
||||
const DATE_FORMAT = 'YYYY-MM-DD ';
|
||||
const DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm:ss';
|
||||
const DATE_FORMAT = 'yyyy-MM-dd';
|
||||
|
||||
export function formatToDateTime(date: Date, formatStr = DATE_TIME_FORMAT): string {
|
||||
const date2 = new Date(date);
|
||||
return format(date2, formatStr);
|
||||
export function formatToDateTime(date: string, formatStr = DATE_TIME_FORMAT): string {
|
||||
if (date === null || date === undefined || date === '') {
|
||||
return ``;
|
||||
}
|
||||
return format(new Date(Date.parse(date)), formatStr);
|
||||
}
|
||||
|
||||
export function formatToDate(date: Date, formatStr = DATE_FORMAT): string {
|
||||
return format(date, formatStr);
|
||||
export function formatToDate(date: string, formatStr = DATE_FORMAT): string {
|
||||
if (date === null || date === undefined || date === '') {
|
||||
return ``;
|
||||
}
|
||||
return format(new Date(Date.parse(date)), formatStr);
|
||||
}
|
||||
|
||||
export function timestampToTime(timestamp) {
|
||||
const date = new Date(timestamp * 1000);
|
||||
const Y = date.getFullYear() + '-';
|
||||
const M = (date.getMonth() + 1 <= 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
|
||||
const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
|
||||
const D = (date.getDate() + 1 <= 10 ? '0' + date.getDate() : date.getDate()) + ' ';
|
||||
const h = (date.getHours() + 1 <= 10 ? '0' + date.getHours() : date.getHours()) + ':';
|
||||
const m = (date.getMinutes() + 1 <= 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
|
||||
@@ -26,7 +43,7 @@ export function timestampToTime(timestamp) {
|
||||
export function timestampToTimeNF(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
const Y = date.getFullYear();
|
||||
const M = date.getMonth() + 1 <= 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
|
||||
const M = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
|
||||
const D = date.getDate() + 1 <= 10 ? '0' + date.getDate() : date.getDate();
|
||||
const h = date.getHours() + 1 <= 10 ? '0' + date.getHours() : date.getHours();
|
||||
const m = date.getMinutes() + 1 <= 10 ? '0' + date.getMinutes() : date.getMinutes();
|
||||
@@ -34,6 +51,13 @@ export function timestampToTimeNF(timestamp) {
|
||||
return Y.toString() + M.toString() + D.toString() + h.toString() + m.toString() + s.toString();
|
||||
}
|
||||
|
||||
export function dateToTimestamp(date: string) {
|
||||
if (date === null || date === undefined || date === '') {
|
||||
return 0;
|
||||
}
|
||||
return new Date(date).getTime();
|
||||
}
|
||||
|
||||
export function timestampToDate(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
const Y = date.getFullYear() + '-';
|
||||
@@ -44,23 +68,16 @@ export function timestampToDate(timestamp) {
|
||||
|
||||
export function getTime() {
|
||||
const myDate = new Date();
|
||||
|
||||
const hour = myDate.getHours().toString().padStart(2, '0');
|
||||
|
||||
const minutes = myDate.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
const seconed = myDate.getSeconds().toString().padStart(2, '0');
|
||||
|
||||
return hour + ':' + minutes + ':' + seconed;
|
||||
}
|
||||
|
||||
export function getDate() {
|
||||
const myDate = new Date();
|
||||
|
||||
const month = (myDate.getMonth() + 1).toString().padStart(2, '0');
|
||||
|
||||
const day = myDate.getDate().toString().padStart(2, '0');
|
||||
|
||||
return myDate.getFullYear() + '-' + month + '-' + day;
|
||||
}
|
||||
|
||||
@@ -129,15 +146,11 @@ export function formatAfter(end): string {
|
||||
if (end.getTime() - start.getTime() > 0) {
|
||||
sjc = end.getTime() - start.getTime(); //时间差的毫秒数
|
||||
}
|
||||
|
||||
const days = Math.floor(sjc / (24 * 3600 * 1000)); //计算出相差天数
|
||||
|
||||
const leave1 = sjc % (24 * 3600 * 1000); //计算天数后剩余的毫秒数
|
||||
const hours = Math.floor(leave1 / (3600 * 1000)); //计算出小时数
|
||||
|
||||
const leave2 = leave1 % (3600 * 1000); //计算小时数后剩余的毫秒数
|
||||
const minutes = Math.floor(leave2 / (60 * 1000)); //计算相差分钟数
|
||||
|
||||
const leave3 = leave2 % (60 * 1000); //计算分钟数后剩余的毫秒数
|
||||
const seconds = Math.round(leave3 / 1000); //计算相差秒数
|
||||
if (days > 0) {
|
||||
@@ -152,6 +165,38 @@ export function formatAfter(end): string {
|
||||
if (seconds > 0) {
|
||||
return seconds + '秒后';
|
||||
}
|
||||
|
||||
return '刚刚';
|
||||
}
|
||||
|
||||
export function defShortcuts() {
|
||||
return {
|
||||
今天: startOfToday().getTime(),
|
||||
昨天: startOfYesterday().getTime(),
|
||||
明天: startOfTomorrow().getTime(),
|
||||
};
|
||||
}
|
||||
|
||||
export function defRangeShortcuts() {
|
||||
const nowDate = new Date();
|
||||
return {
|
||||
今天: [startOfToday().getTime(), endOfToday().getTime()] as const,
|
||||
昨天: () => {
|
||||
return [startOfYesterday().getTime(), endOfYesterday().getTime()] as const;
|
||||
},
|
||||
本周: () => {
|
||||
return [
|
||||
startOfWeek(nowDate, { weekStartsOn: 1 }).getTime(),
|
||||
endOfWeek(nowDate, { weekStartsOn: 1 }).getTime(),
|
||||
] as const;
|
||||
},
|
||||
本月: () => {
|
||||
return [startOfMonth(nowDate).getTime(), endOfMonth(nowDate).getTime()] as const;
|
||||
},
|
||||
上个月: () => {
|
||||
return [
|
||||
startOfMonth(subMonths(nowDate, 1)).getTime(),
|
||||
endOfMonth(subMonths(nowDate, 1)).getTime(),
|
||||
] as const;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@@ -62,6 +62,7 @@ export function downloadByUrl({
|
||||
// saveAs(imageDataUrl, '附件');
|
||||
canvas.toBlob((blob) => {
|
||||
const link = document.createElement('a');
|
||||
// @ts-ignore
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = getFileName(url);
|
||||
link.click();
|
||||
|
53
web/src/utils/hotgo.ts
Normal file
53
web/src/utils/hotgo.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Ref, UnwrapRef } from '@vue/reactivity';
|
||||
|
||||
export interface Option {
|
||||
label: string;
|
||||
value: string;
|
||||
key: string;
|
||||
type: string;
|
||||
listClass: 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning';
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
[name: string]: Option[];
|
||||
}
|
||||
|
||||
// 获取选项名称
|
||||
export function getOptionLabel(options: Option[], value) {
|
||||
if (options === undefined || options?.length === 0) {
|
||||
return `unknown`;
|
||||
}
|
||||
for (const item of options) {
|
||||
if (item.value == value) {
|
||||
return item.label;
|
||||
}
|
||||
}
|
||||
|
||||
return `unknown`;
|
||||
}
|
||||
|
||||
// 获取选项标签
|
||||
export function getOptionTag(options: Option[], value) {
|
||||
if (options === undefined || options?.length === 0) {
|
||||
return 'default';
|
||||
}
|
||||
for (const item of options) {
|
||||
if (item.value == value) {
|
||||
return item.listClass;
|
||||
}
|
||||
}
|
||||
|
||||
return 'default';
|
||||
}
|
||||
|
||||
// 自适应模板宽度
|
||||
export function adaModalWidth(dialogWidth: Ref<UnwrapRef<string>>) {
|
||||
const val = document.body.clientWidth;
|
||||
const def = 840; // 默认宽度
|
||||
if (val <= def) {
|
||||
dialogWidth.value = '100%';
|
||||
} else {
|
||||
dialogWidth.value = def + 'px';
|
||||
}
|
||||
return dialogWidth.value;
|
||||
}
|
@@ -1,10 +1,8 @@
|
||||
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios';
|
||||
|
||||
import axios from 'axios';
|
||||
import { AxiosCanceler } from './axiosCancel';
|
||||
import { isFunction } from '@/utils/is';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types';
|
||||
import { ContentTypeEnum } from '@/enums/httpEnum';
|
||||
|
||||
|
@@ -18,6 +18,7 @@ import { CreateAxiosOptions, RequestOptions, Result } from './types';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import router from '@/router';
|
||||
import { storage } from '@/utils/Storage';
|
||||
import { encodeParams } from '@/utils/urlUtils';
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
@@ -60,7 +61,7 @@ const transform: AxiosTransform = {
|
||||
}
|
||||
|
||||
// 这里 code,result,message为 后台统一的字段,需要修改为项目自己的接口返回格式
|
||||
let { code, data, message } = response;
|
||||
const { code, data, message } = response;
|
||||
|
||||
// 请求成功
|
||||
const hasSuccess = response && Reflect.has(response, 'code') && code === ResultEnum.SUCCESS;
|
||||
@@ -118,8 +119,11 @@ const transform: AxiosTransform = {
|
||||
onNegativeClick: () => {},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.log('unknown status code:' + code);
|
||||
$message.error(errorMsg);
|
||||
}
|
||||
$message.error(errorMsg);
|
||||
|
||||
throw new Error(errorMsg);
|
||||
},
|
||||
|
||||
@@ -277,6 +281,15 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
|
||||
|
||||
export const http = createAxios();
|
||||
|
||||
// 导出
|
||||
export const jumpExport = function (url, params) {
|
||||
window.location.href =
|
||||
urlPrefix +
|
||||
url +
|
||||
'?' +
|
||||
encodeParams({ ...params, ...{ authorization: useUserStoreWidthOut().token } });
|
||||
};
|
||||
|
||||
// 项目,多个不同 api 地址,直接在这里导出多个
|
||||
// src/api ts 里面接口,就可以单独使用这个请求,
|
||||
// import { httpTwo } from '@/utils/http/axios'
|
||||
|
@@ -158,6 +158,7 @@ export function filterRouter(routerMap: Array<any>) {
|
||||
export const withInstall = <T>(component: T, alias?: string) => {
|
||||
const comp = component as any;
|
||||
comp.install = (app: App) => {
|
||||
// @ts-ignore
|
||||
app.component(comp.name || comp.displayName, component);
|
||||
if (alias) {
|
||||
app.config.globalProperties[alias] = component;
|
||||
@@ -203,6 +204,7 @@ export function getTreeAll(data: any[]): any[] {
|
||||
export function getDynamicProps<T, U>(props: T): Partial<U> {
|
||||
const ret: Recordable = {};
|
||||
|
||||
// @ts-ignore
|
||||
Object.keys(props).map((key) => {
|
||||
ret[key] = unref((props as Recordable)[key]);
|
||||
});
|
||||
|
@@ -80,6 +80,23 @@ export function isArray(val: any): val is Array<any> {
|
||||
return val && Array.isArray(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否为转为string的json
|
||||
*/
|
||||
export function isJsonString(value: any) {
|
||||
try {
|
||||
const toObj = JSON.parse(value);
|
||||
if (toObj && typeof toObj === 'object') {
|
||||
return true;
|
||||
}
|
||||
} catch {}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isNullObject(value: object) {
|
||||
return isNullOrUnDef(value) || JSON.stringify(value) === '{}' || JSON.stringify(value) === '[]';
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否客户端
|
||||
*/
|
||||
@@ -116,3 +133,8 @@ export function isNullAndUnDef(val: unknown): val is null | undefined {
|
||||
export function isNullOrUnDef(val: unknown): val is null | undefined {
|
||||
return isUnDef(val) || isNull(val);
|
||||
}
|
||||
|
||||
// 判断字串符是否以字母开头
|
||||
export function isLetterBegin(str) {
|
||||
return /^[A-z]/.test(str);
|
||||
}
|
||||
|
@@ -22,3 +22,22 @@ export function setObjToUrlParams(baseUrl: string, obj: object): string {
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export function encodeParams(obj) {
|
||||
const arr = [];
|
||||
for (const p in obj) {
|
||||
// @ts-ignore
|
||||
arr.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
|
||||
}
|
||||
return arr.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件后缀
|
||||
*/
|
||||
export function getFileExt(fileName: string) {
|
||||
if (fileName === undefined || fileName === '') {
|
||||
return ``;
|
||||
}
|
||||
return fileName.substring(fileName.lastIndexOf('.') + 1);
|
||||
}
|
||||
|
212
web/src/utils/validateUtil.ts
Normal file
212
web/src/utils/validateUtil.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { FormItemRule } from 'naive-ui';
|
||||
/**
|
||||
* @description 表单验证封装
|
||||
*/
|
||||
export const validate = {
|
||||
ip(rule: FormItemRule, value: any, callback: Function) {
|
||||
// 支持通配符的ipv4正则
|
||||
const ipv4Regex =
|
||||
/^(?:[1-9]?[0-9]|1[0-9]{2}|2(?:[0-4][0-9]|5[0-5]))(?!.*?\.\*\.[*\d])(?:\.(?:(?:[1-9]?[0-9]|1[0-9]{2}|2(?:[0-4][0-9]|5[0-5]))|\*)){1,3}$/;
|
||||
// Ipv6:
|
||||
const ipv6Regex =
|
||||
/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
|
||||
if (!value && !rule.required) {
|
||||
callback();
|
||||
}
|
||||
if (!value) {
|
||||
callback(new Error('请输入IP'));
|
||||
} else if (!ipv4Regex.test(value) && !ipv6Regex.test(value)) {
|
||||
callback(new Error('请输入正确的IP'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
//0-100百分比验证
|
||||
percentage(rule: FormItemRule, value: any, callback: Function) {
|
||||
const reg = /^([1-9]{1,2}$)|(^[0-9]{1,2}\.[0-9]{1,2}$)|100$/;
|
||||
if (!value && !rule.required) {
|
||||
callback(new Error('请输入比例'));
|
||||
} else if (!reg.test(value)) {
|
||||
callback(new Error('请输入0-100的数字'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
// 手机号 (eg:138********,159********)
|
||||
phone(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regPhone = /^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/;
|
||||
if (!value && !rule.required) {
|
||||
callback();
|
||||
} else if (!value) {
|
||||
callback(new Error('请输入手机号码'));
|
||||
} else if (!regPhone.test(value)) {
|
||||
callback(new Error('手机号格式错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
// 用户名 (eg:a123456)
|
||||
userName(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regUserName = /^[0-9a-zA-Z]{6,16}$/;
|
||||
if (!value && !rule.required) {
|
||||
callback(new Error('请输入登录账号'));
|
||||
} else if (!regUserName.test(value)) {
|
||||
callback(new Error('请输入6-16位由字母和数字组成的登录账号'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
// 账号
|
||||
account(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regex = /^[\w_\d]{6,16}$/;
|
||||
if (!value && !rule.required) {
|
||||
callback();
|
||||
} else if (!value) {
|
||||
callback(new Error('请输入账号'));
|
||||
} else if (!regex.test(value)) {
|
||||
callback(new Error('请输入6-16位由字母、数字或下划线组成的账号'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
// 密码
|
||||
password(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regPassword = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,18}$/;
|
||||
if (!value && !rule.required) {
|
||||
callback(new Error('请输入密码'));
|
||||
} else if (!regPassword.test(value)) {
|
||||
callback(new Error('密码格式错误!必须包含6-18为字母和数字'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
// 邮箱
|
||||
email(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regEmails = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
|
||||
// console.log('isRequired is: ', JSON.stringify(isRequired))
|
||||
if (!value && !rule.required) {
|
||||
callback();
|
||||
} else if (!value) {
|
||||
callback(new Error('请输入邮箱'));
|
||||
} else if (!regEmails.test(value)) {
|
||||
callback(new Error('邮箱格式错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
// 金额验证
|
||||
amount(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regAmount = /(^[0-9]{1,10}$)|(^[0-9]{1,10}[\.]{1}[0-9]{1,2}$)/;
|
||||
if (!value && !rule.required) {
|
||||
callback();
|
||||
} else if (!value) {
|
||||
callback(new Error('请输入金额'));
|
||||
} else if (!regAmount.test(value)) {
|
||||
callback(new Error('金额格式错误,最多允许输入10位整数及2位小数'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
// 身份证验证
|
||||
idCard(rule: FormItemRule, value: any, callback: Function, isEnabled = true) {
|
||||
const regIdCard =
|
||||
/^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$|^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X|x)$/;
|
||||
if (!isEnabled) {
|
||||
callback();
|
||||
} else if (!value && !rule.required) {
|
||||
callback();
|
||||
} else if (!value) {
|
||||
callback(new Error('请输入身份证号'));
|
||||
} else if (!regIdCard.test(value)) {
|
||||
callback(new Error('身份证号码格式错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
// 银行卡验证
|
||||
bank(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regBank = /^([1-9]{1})(\d{15}|\d{16}|\d{18})$/;
|
||||
if (!value && !rule.required) {
|
||||
callback();
|
||||
} else if (!value) {
|
||||
callback();
|
||||
} else if (!regBank.test(value)) {
|
||||
callback(new Error('银行卡号码格式错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
// 非零正整数验证
|
||||
num(rule: FormItemRule, value: any, callback: Function) {
|
||||
const reg = /^\+?[1-9][0-9]*$/;
|
||||
if (!value && !rule.required) {
|
||||
callback(new Error('请填写非零正整数'));
|
||||
} else {
|
||||
if (!reg.test(value)) {
|
||||
callback(new Error('请输入非零的正整数'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
// 银行卡
|
||||
bankCard(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regBankCard = /^(\d{16}|\d{19})$/;
|
||||
if (value == '' && !rule.required) {
|
||||
callback(new Error('请输入银行卡号'));
|
||||
} else {
|
||||
if (!regBankCard.test(value)) {
|
||||
callback(new Error('银行卡号格式错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
// 固话格式
|
||||
tel(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regTel = /^(0\d{2,3}-?)?\d{7,8}$/;
|
||||
if (value == '' && !rule.required) {
|
||||
callback(new Error('请输入座机号码'));
|
||||
} else {
|
||||
if (!regTel.test(value)) {
|
||||
callback(new Error('座机号码格式错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
// QQ号码
|
||||
qq(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regex = /^[1-9][0-9]{4,}$/;
|
||||
if (!value && !rule.required) {
|
||||
callback();
|
||||
} else if (!value) {
|
||||
callback(new Error('请输入QQ号码'));
|
||||
} else {
|
||||
if (!regex.test(value)) {
|
||||
callback(new Error('QQ号码格式错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
// weibo号
|
||||
weibo(rule: FormItemRule, value: any, callback: Function) {
|
||||
const regex = /^[0-9a-zA-Z\u4e00-\u9fa5_-]*$/;
|
||||
if (!value && !rule.required) {
|
||||
callback();
|
||||
} else if (!value) {
|
||||
callback(new Error('请输入微博账号'));
|
||||
} else {
|
||||
if (!regex.test(value)) {
|
||||
callback(new Error('微博号码格式错误'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
// 不验证
|
||||
none(_rule: FormItemRule, _value: any, callback: Function) {
|
||||
callback();
|
||||
},
|
||||
};
|
@@ -7,7 +7,6 @@ let socket: WebSocket;
|
||||
let isActive: boolean;
|
||||
|
||||
export function getSocket(): WebSocket {
|
||||
console.log('socket:', socket);
|
||||
if (socket === undefined) {
|
||||
location.reload();
|
||||
}
|
||||
@@ -15,7 +14,6 @@ export function getSocket(): WebSocket {
|
||||
}
|
||||
|
||||
export function getActive(): boolean {
|
||||
console.log('isActive:', isActive);
|
||||
return isActive;
|
||||
}
|
||||
|
||||
@@ -85,7 +83,6 @@ export default (onMessage: Function) => {
|
||||
event: SocketEnum.EventPing,
|
||||
})
|
||||
);
|
||||
console.log('ping');
|
||||
self.serverTimeoutObj = setTimeout(function () {
|
||||
console.log('关闭服务');
|
||||
socket.close();
|
||||
@@ -133,7 +130,7 @@ export default (onMessage: Function) => {
|
||||
|
||||
socket.onmessage = function (event) {
|
||||
isActive = true;
|
||||
console.log('WebSocket:收到一条消息', event.data);
|
||||
// console.log('WebSocket:收到一条消息', event.data);
|
||||
|
||||
let isHeart = false;
|
||||
const message = JSON.parse(event.data);
|
||||
|
@@ -15,7 +15,7 @@
|
||||
>
|
||||
<n-descriptions bordered label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="版本">
|
||||
<n-tag type="info"> 2.0.3 </n-tag>
|
||||
<n-tag type="info"> {{ config?.version }}</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="最后编译时间">
|
||||
<n-tag type="info"> {{ lastBuildTime }} </n-tag>
|
||||
@@ -29,9 +29,7 @@
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="预览地址">
|
||||
<div class="flex items-center">
|
||||
<a href="https://hotgo.facms.cn/admin" class="py-2" target="_blank"
|
||||
>查看预览地址</a
|
||||
>
|
||||
<a href="https://hotgo.facms.cn/admin" class="py-2" target="_blank">查看预览地址</a>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="Github">
|
||||
@@ -82,6 +80,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
const config = ref(useUserStore.config);
|
||||
export interface schemaItem {
|
||||
field: string;
|
||||
label: string;
|
||||
|
@@ -1,5 +1,55 @@
|
||||
import { h } from 'vue';
|
||||
import { NAvatar, NTag } from 'naive-ui';
|
||||
import { h, ref } from 'vue';
|
||||
import { NAvatar, NImage, NTag } from 'naive-ui';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { isNullOrUnDef } from '@/utils/is';
|
||||
export const options = ref<Options>({
|
||||
sys_normal_disable: [],
|
||||
config_upload_drive: [],
|
||||
});
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'member_id',
|
||||
component: 'NInput',
|
||||
label: '用户ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入用户ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入用户ID', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'drive',
|
||||
component: 'NSelect',
|
||||
label: '选择驱动',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择驱动',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
@@ -11,7 +61,7 @@ export const columns = [
|
||||
key: 'appId',
|
||||
},
|
||||
{
|
||||
title: '会员ID',
|
||||
title: '用户ID',
|
||||
key: 'memberId',
|
||||
},
|
||||
{
|
||||
@@ -45,16 +95,40 @@ export const columns = [
|
||||
key: 'fileUrl',
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(NAvatar, {
|
||||
size: 40,
|
||||
if (row.fileUrl === '') {
|
||||
return ``;
|
||||
}
|
||||
if (row.kind !== 'images') {
|
||||
return h(
|
||||
NAvatar,
|
||||
{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
},
|
||||
{
|
||||
default: () => getFileExt(row.fileUrl),
|
||||
}
|
||||
);
|
||||
}
|
||||
return h(NImage, {
|
||||
width: 40,
|
||||
height: 40,
|
||||
src: row.fileUrl,
|
||||
style: {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '本地路径',
|
||||
key: 'path',
|
||||
},
|
||||
// {
|
||||
// title: '本地路径',
|
||||
// key: 'path',
|
||||
// },
|
||||
{
|
||||
title: '扩展名',
|
||||
key: 'ext',
|
||||
@@ -67,24 +141,44 @@ export const columns = [
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
if (isNullOrUnDef(row.status)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 1 ? 'success' : 'warning',
|
||||
type: getOptionTag(options.value.sys_normal_disable, row.status),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.status == 1 ? '正常' : '隐藏'),
|
||||
default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '上传时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
];
|
||||
|
||||
async function loadOptions() {
|
||||
options.value = await Dicts({
|
||||
types: ['sys_normal_disable', 'config_upload_drive'],
|
||||
});
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.sys_normal_disable;
|
||||
break;
|
||||
case 'drive':
|
||||
item.componentProps.options = options.value.config_upload_drive;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await loadOptions();
|
||||
|
@@ -14,6 +14,7 @@
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
@@ -29,7 +30,16 @@
|
||||
<UploadOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
上传附件
|
||||
上传图片
|
||||
</n-button>
|
||||
|
||||
<n-button type="primary" @click="addFileTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<UploadOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
上传文件
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
@@ -48,12 +58,12 @@
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
style="width: 60%"
|
||||
title="上传附件"
|
||||
title="上传图片"
|
||||
>
|
||||
<n-upload
|
||||
multiple
|
||||
directory-dnd
|
||||
:action="`${uploadUrl}/admin/upload/image`"
|
||||
:action="`${uploadUrl}${urlPrefix}/upload/image`"
|
||||
:headers="uploadHeaders"
|
||||
:data="{ type: 0 }"
|
||||
@before-upload="beforeUpload"
|
||||
@@ -66,7 +76,39 @@
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<n-icon size="48" :depth="3">
|
||||
<archive-icon />
|
||||
<CloudUploadOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
<n-text style="font-size: 16px"> 点击或者拖动图片到该区域来上传</n-text>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0"> 单次最多允许20个图片</n-p>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</n-modal>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showFileModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
style="width: 60%"
|
||||
title="上传文件"
|
||||
>
|
||||
<n-upload
|
||||
multiple
|
||||
directory-dnd
|
||||
:action="`${uploadUrl}${urlPrefix}/upload/file`"
|
||||
:headers="uploadHeaders"
|
||||
:data="{ type: 0 }"
|
||||
@before-upload="beforeUpload"
|
||||
@finish="finish"
|
||||
name="file"
|
||||
:max="20"
|
||||
:default-file-list="fileList"
|
||||
list-type="image"
|
||||
>
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<n-icon size="48" :depth="3">
|
||||
<FileAddOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
<n-text style="font-size: 16px"> 点击或者拖动文件到该区域来上传</n-text>
|
||||
@@ -82,125 +124,38 @@
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { UploadFileInfo, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { Delete, Edit, List, Status } from '@/api/apply/attachment';
|
||||
import { columns } from './columns';
|
||||
import { DeleteOutlined, UploadOutlined } from '@vicons/antd';
|
||||
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { Delete, List } from '@/api/apply/attachment';
|
||||
import { columns, schemas } from './columns';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
UploadOutlined,
|
||||
FileAddOutlined,
|
||||
CloudUploadOutlined,
|
||||
} from '@vicons/antd';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import componentSetting from '@/settings/componentSetting';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
|
||||
const { uploadUrl } = globSetting;
|
||||
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
const uploadHeaders = reactive({
|
||||
Authorization: useUserStore.token,
|
||||
});
|
||||
|
||||
const fileList = ref<UploadFileInfo[]>([
|
||||
// {
|
||||
// id: 'c',
|
||||
// name: '图片.png',
|
||||
// status: 'finished',
|
||||
// url: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
// },
|
||||
]);
|
||||
|
||||
const driveOptions = [
|
||||
{
|
||||
value: 'local',
|
||||
label: '本地',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
content: '',
|
||||
status: null,
|
||||
});
|
||||
|
||||
const rules = {
|
||||
title: {
|
||||
// required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入标题',
|
||||
},
|
||||
};
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'member_id',
|
||||
component: 'NInput',
|
||||
label: '用户ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入用户ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入用户ID', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'drive',
|
||||
component: 'NSelect',
|
||||
label: '驱动',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择驱动',
|
||||
options: driveOptions,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: statusOptions,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const fileList = ref<UploadFileInfo[]>([]);
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const showFileModal = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const searchFormRef = ref({});
|
||||
const formRef = ref({});
|
||||
const searchFormRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const resetFormParams = {
|
||||
basicLogo: '',
|
||||
id: 0,
|
||||
title: '',
|
||||
name: '',
|
||||
type: 1,
|
||||
receiver: '',
|
||||
remark: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
};
|
||||
let formParams = ref(resetFormParams);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
@@ -211,18 +166,14 @@
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
label: '下载',
|
||||
onClick: handleDown.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
dropDownActions: statusActions,
|
||||
select: (key) => {
|
||||
updateStatus(record.id, key);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -234,22 +185,23 @@
|
||||
});
|
||||
|
||||
function addTable() {
|
||||
showFileModal.value = false;
|
||||
showModal.value = true;
|
||||
fileList.value = [];
|
||||
}
|
||||
|
||||
function addFileTable() {
|
||||
showModal.value = false;
|
||||
showFileModal.value = true;
|
||||
fileList.value = [];
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
|
||||
return await List({ ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
batchDeleteDisabled.value = true;
|
||||
}
|
||||
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
@@ -257,58 +209,25 @@
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams:' + JSON.stringify(formParams.value));
|
||||
Edit(formParams.value)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
showModal.value = true;
|
||||
formParams.value = record;
|
||||
function handleDown(record: Recordable) {
|
||||
window.open(record.fileUrl);
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete(record).then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -318,51 +237,29 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
params.value = values;
|
||||
function handleSubmit(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
params.value = values;
|
||||
function handleReset(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function updateStatus(id, status) {
|
||||
Status({ id: id, status: status })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable({});
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
}
|
||||
|
||||
//上传之前
|
||||
function beforeUpload({ file }) {
|
||||
function beforeUpload({ _file }) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,7 @@
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
@@ -133,7 +134,7 @@
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
const params = ref({
|
||||
const params = ref<any>({
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
content: '',
|
||||
@@ -193,8 +194,8 @@
|
||||
const dialog = useDialog();
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const searchFormRef = ref({});
|
||||
const formRef = ref({});
|
||||
const searchFormRef = ref<any>({});
|
||||
const formRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
|
||||
@@ -210,7 +211,7 @@
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
};
|
||||
let formParams = ref(resetFormParams);
|
||||
let formParams = ref<any>(resetFormParams);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
@@ -250,11 +251,10 @@
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
|
||||
return await List({ ...params.value, ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
@@ -273,20 +273,14 @@
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams:' + JSON.stringify(formParams.value));
|
||||
Edit(formParams.value)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
Edit(formParams.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
@@ -295,31 +289,24 @@
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
showModal.value = true;
|
||||
formParams.value = record;
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -329,26 +316,20 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
@@ -359,17 +340,12 @@
|
||||
}
|
||||
|
||||
function updateStatus(id, status) {
|
||||
Status({ id: id, status: status })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable({});
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
Status({ id: id, status: status }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { h } from 'vue';
|
||||
import { NAvatar, NTag } from 'naive-ui';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
|
@@ -14,6 +14,7 @@
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
@@ -133,7 +134,7 @@
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
const params = ref({
|
||||
const params = ref<any>({
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
content: '',
|
||||
@@ -193,8 +194,8 @@
|
||||
const dialog = useDialog();
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const searchFormRef = ref({});
|
||||
const formRef = ref({});
|
||||
const searchFormRef = ref<any>({});
|
||||
const formRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
|
||||
@@ -210,7 +211,7 @@
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
};
|
||||
let formParams = ref(resetFormParams);
|
||||
let formParams = ref<any>(resetFormParams);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
@@ -250,11 +251,10 @@
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
|
||||
return await List({ ...params.value, ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
@@ -273,10 +273,8 @@
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams:' + JSON.stringify(formParams.value));
|
||||
Edit(formParams.value)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
@@ -295,31 +293,24 @@
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
showModal.value = true;
|
||||
formParams.value = record;
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -329,20 +320,15 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -359,17 +345,12 @@
|
||||
}
|
||||
|
||||
function updateStatus(id, status) {
|
||||
Status({ id: id, status: status })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable({});
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
Status({ id: id, status: status }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
147
web/src/views/curdDemo/edit.vue
Normal file
147
web/src/views/curdDemo/edit.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="isShowModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="params?.id > 0 ? '编辑 #' + params?.id : '新建'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-form
|
||||
:model="params"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="分类ID" path="categoryId">
|
||||
<n-input-number placeholder="请输入分类ID" v-model:value="params.categoryId" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input placeholder="请输入标题" v-model:value="params.title" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input type="textarea" placeholder="描述" v-model:value="params.description" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="内容" path="content">
|
||||
<Editor style="height: 450px" v-model:value="params.content" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="单图" path="image">
|
||||
<UploadImage :maxNumber="1" v-model:value="params.image" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="附件" path="attachfile">
|
||||
<UploadFile :maxNumber="1" v-model:value="params.attachfile" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="显示开关" path="switch">
|
||||
<n-switch v-model:value="params.switch" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number placeholder="请输入排序" v-model:value="params.sort" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-select v-model:value="params.status" :options="options.sys_normal_disable" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { Edit, MaxSort } from '@/api/curdDemo';
|
||||
import Editor from '@/components/Editor/editor.vue';
|
||||
import UploadImage from '@/components/Upload/uploadImage.vue';
|
||||
import UploadFile from '@/components/Upload/uploadFile.vue';
|
||||
import { rules, options, State, newState } from './model';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
const emit = defineEmits(['reloadTable', 'updateShowModal']);
|
||||
|
||||
interface Props {
|
||||
showModal: boolean;
|
||||
formParams?: State;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showModal: false,
|
||||
formParams: () => {
|
||||
return newState(null);
|
||||
},
|
||||
});
|
||||
|
||||
const isShowModal = computed({
|
||||
get: () => {
|
||||
return props.showModal;
|
||||
},
|
||||
set: (value) => {
|
||||
emit('updateShowModal', value);
|
||||
},
|
||||
});
|
||||
|
||||
const params = computed(() => {
|
||||
return props.formParams;
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(params.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
isShowModal.value = false;
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => params.value,
|
||||
(value) => {
|
||||
if (value.id === 0) {
|
||||
MaxSort().then((res) => {
|
||||
params.value.sort = res.sort;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
249
web/src/views/curdDemo/index.vue
Normal file
249
web/src/views/curdDemo/index.vue
Normal file
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="生成演示列表">
|
||||
<!-- 这里有系统自动生成的CURD表格 -->
|
||||
</n-card>
|
||||
</div>
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="addTable"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/curdDemo/edit'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
</n-button>
|
||||
<n-button
|
||||
type="error"
|
||||
@click="handleBatchDelete"
|
||||
:disabled="batchDeleteDisabled"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/curdDemo/delete'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="handleExport"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/demoVar/export'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Edit
|
||||
@reloadTable="reloadTable"
|
||||
@updateShowModal="updateShowModal"
|
||||
:showModal="showModal"
|
||||
:formParams="formParams"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { Delete, List, Status, Export } from '@/api/curdDemo';
|
||||
import { State, columns, schemas, options, newState } from './model';
|
||||
import { DeleteOutlined, PlusOutlined, ExportOutlined } from '@vicons/antd';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getOptionLabel } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
const { hasPermission } = usePermission();
|
||||
const router = useRouter();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const searchFormRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const showModal = ref(false);
|
||||
const formParams = ref<State>();
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 300,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['/curdDemo/edit'],
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
onClick: handleStatus.bind(null, record, 2),
|
||||
ifShow: () => {
|
||||
return record.status === 1;
|
||||
},
|
||||
auth: ['/curdDemo/status'],
|
||||
},
|
||||
{
|
||||
label: '启用',
|
||||
onClick: handleStatus.bind(null, record, 1),
|
||||
ifShow: () => {
|
||||
return record.status === 2;
|
||||
},
|
||||
auth: ['/curdDemo/status'],
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/curdDemo/delete'],
|
||||
},
|
||||
],
|
||||
dropDownActions: [
|
||||
{
|
||||
label: '查看详情',
|
||||
key: 'view',
|
||||
auth: ['/curdDemo/view'],
|
||||
},
|
||||
],
|
||||
select: (key) => {
|
||||
if (key === 'view') {
|
||||
return handleView(record);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...searchFormRef.value?.formModel, ...res });
|
||||
};
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(null);
|
||||
}
|
||||
|
||||
function updateShowModal(value) {
|
||||
showModal.value = value;
|
||||
}
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleView(record: Recordable) {
|
||||
router.push({ name: 'curdDemoView', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(record as State);
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleBatchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
|
||||
function handleStatus(record: Recordable, status: number) {
|
||||
Status({ id: record.id, status: status }).then((_res) => {
|
||||
message.success('设为' + getOptionLabel(options.value.sys_normal_disable, status) + '成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
242
web/src/views/curdDemo/model.ts
Normal file
242
web/src/views/curdDemo/model.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NAvatar, NImage, NTag, NSwitch, NRate } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { Switch } from '@/api/curdDemo';
|
||||
import { isArray, isNullObject } from '@/utils/is';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
const { hasPermission } = usePermission();
|
||||
const $message = window['$message'];
|
||||
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
categoryId: number;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
image: string;
|
||||
attachfile: string;
|
||||
switch: number;
|
||||
sort: number;
|
||||
status: number;
|
||||
createdBy: number;
|
||||
updatedBy: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string;
|
||||
}
|
||||
|
||||
export const defaultState = {
|
||||
id: 0,
|
||||
categoryId: 0,
|
||||
title: '',
|
||||
description: '',
|
||||
content: '',
|
||||
image: '',
|
||||
attachfile: '',
|
||||
switch: 1,
|
||||
sort: 0,
|
||||
status: 1,
|
||||
createdBy: 0,
|
||||
updatedBy: 0,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
deletedAt: '',
|
||||
};
|
||||
|
||||
export function newState(state: State | null): State {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(defaultState);
|
||||
}
|
||||
|
||||
export const options = ref<Options>({
|
||||
sys_normal_disable: [],
|
||||
});
|
||||
|
||||
export const rules = {
|
||||
};
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'id',
|
||||
component: 'NInputNumber',
|
||||
label: 'ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'testCategoryName',
|
||||
component: 'NInput',
|
||||
label: '分类名称',
|
||||
componentProps: {
|
||||
placeholder: '请输入分类名称',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '分类ID',
|
||||
key: 'categoryId',
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '单图',
|
||||
key: 'image',
|
||||
render(row) {
|
||||
return h(NImage, {
|
||||
width: 32,
|
||||
height: 32,
|
||||
src: row.image,
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '附件',
|
||||
key: 'attachfile',
|
||||
render(row) {
|
||||
if (row.attachfile === '') {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NAvatar,
|
||||
{
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
default: () => getFileExt(row.attachfile),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '显示开关',
|
||||
key: 'switch',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.switch === 1,
|
||||
checked: '开启',
|
||||
unchecked: '关闭',
|
||||
disabled: !hasPermission(['/curdDemo/switch']),
|
||||
onUpdateValue: function (e) {
|
||||
console.log('onUpdateValue e:' + JSON.stringify(e));
|
||||
row.switch = e ? 1 : 2;
|
||||
Switch({ id: row.id, key: 'switch', value: row.switch }).then((_res) => {
|
||||
$message.success('操作成功');
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sort',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
if (isNullObject(row.status)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.sys_normal_disable, row.status),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
{
|
||||
title: '分类名称',
|
||||
key: 'testCategoryName',
|
||||
},
|
||||
];
|
||||
|
||||
async function loadOptions() {
|
||||
options.value = await Dicts({
|
||||
types: [
|
||||
'sys_normal_disable',
|
||||
],
|
||||
});
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.sys_normal_disable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await loadOptions();
|
108
web/src/views/curdDemo/view.vue
Normal file
108
web/src/views/curdDemo/view.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="生成演示详情"> <!-- CURD详情页--> </n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard mt-4" size="small" :segmented="{ content: true }">
|
||||
<n-descriptions label-placement="left" class="py-2" column="4">
|
||||
<n-descriptions-item>
|
||||
<template #label>分类ID</template>
|
||||
{{ formValue.categoryId }}
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
<template #label>标题</template>
|
||||
{{ formValue.title }}
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
<template #label>描述</template>
|
||||
<span v-html="formValue.description"></span></n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
<template #label>内容</template>
|
||||
<span v-html="formValue.content"></span></n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
<template #label>单图</template>
|
||||
<n-image style="margin-left: 10px; height: 100px; width: 100px" :src="formValue.image"
|
||||
/></n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
<template #label>附件</template>
|
||||
<div
|
||||
class="upload-card"
|
||||
v-show="formValue.attachfile !== ''"
|
||||
@click="download(formValue.attachfile)"
|
||||
>
|
||||
<div class="upload-card-item" style="height: 100px; width: 100px">
|
||||
<div class="upload-card-item-info">
|
||||
<div class="img-box">
|
||||
<n-avatar :style="fileAvatarCSS">{{ getFileExt(formValue.attachfile) }}</n-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item label="显示开关">
|
||||
<n-switch v-model:value="formValue.switch" :unchecked-value="2" :checked-value="1" :disabled="true"
|
||||
/></n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
<template #label>排序</template>
|
||||
{{ formValue.sort }}
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item label="状态">
|
||||
<template v-for="(item, key) in formValue?.status" :key="key">
|
||||
<n-tag
|
||||
:type="getOptionTag(options.sys_normal_disable, item)"
|
||||
size="small"
|
||||
class="min-left-space"
|
||||
>{{ getOptionLabel(options.sys_normal_disable, item) }}</n-tag
|
||||
>
|
||||
</template>
|
||||
</n-descriptions-item>
|
||||
|
||||
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { View } from '@/api/curdDemo';
|
||||
import { newState, options } from './model';
|
||||
import { getOptionLabel, getOptionTag } from '@/utils/hotgo';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const id = Number(router.currentRoute.value.params.id);
|
||||
const formValue = ref(newState(null));
|
||||
const fileAvatarCSS = computed(() => {
|
||||
return {
|
||||
'--n-merged-size': `var(--n-avatar-size-override, 80px)`,
|
||||
'--n-font-size': `18px`,
|
||||
};
|
||||
});
|
||||
|
||||
//下载
|
||||
function download(url: string) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (id < 1) {
|
||||
message.error('ID不正确,请检查!');
|
||||
return;
|
||||
}
|
||||
formValue.value = await View({ id: id });
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
509
web/src/views/develop/code/components/BaseInfo.vue
Normal file
509
web/src/views/develop/code/components/BaseInfo.vue
Normal file
@@ -0,0 +1,509 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card
|
||||
:bordered="true"
|
||||
title="基本设置"
|
||||
class="proCard mt-2"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
>
|
||||
<n-form ref="formRef" :model="formValue">
|
||||
<n-row :gutter="24">
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item label="生成类型" path="title">
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="selectList.genType"
|
||||
v-model:value="formValue.genType"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item label="实体命名" path="varName">
|
||||
<n-input placeholder="请输入" v-model:value="formValue.varName" />
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item
|
||||
label="数据库"
|
||||
path="dbName"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="selectList.db"
|
||||
v-model:value="formValue.dbName"
|
||||
@update:value="handleDbUpdateValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item
|
||||
label="数据库表"
|
||||
path="tableName"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="tablesLoading"
|
||||
placeholder="请选择"
|
||||
:options="tablesOption"
|
||||
v-model:value="formValue.tableName"
|
||||
@update:value="handleTableUpdateValue"
|
||||
:disabled="formValue.dbName === ''"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="18">
|
||||
<n-form-item
|
||||
label="表格头部按钮组"
|
||||
path="tableName"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-checkbox-group v-model:value="formValue.options.headOps">
|
||||
<n-space item-style="display: flex;">
|
||||
<n-checkbox value="add" label="新增表单按钮" />
|
||||
<n-checkbox value="batchDel" label="批量删除按钮" />
|
||||
<n-checkbox value="export" label="导出按钮" />
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="24">
|
||||
<n-form-item
|
||||
label="表格列操作"
|
||||
path="columnOps"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-checkbox-group v-model:value="formValue.options.columnOps">
|
||||
<n-space item-style="display: flex;">
|
||||
<n-checkbox value="edit" label="编辑" />
|
||||
<n-checkbox value="status" label="状态修改" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>主表中存在`status`字段时才会生效</span>
|
||||
</n-popover>
|
||||
<n-checkbox value="del" label="删除" />
|
||||
<n-checkbox value="view" label="详情页" />
|
||||
<n-checkbox value="check" label="开启勾选列" />
|
||||
<n-checkbox value="switch" label="操作开关" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>主表中存在`switch`字段时才会生效</span>
|
||||
</n-popover>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="24">
|
||||
<n-form-item
|
||||
label="自动化操作"
|
||||
path="autoOps"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-checkbox-group v-model:value="formValue.options.autoOps">
|
||||
<n-space item-style="display: flex;">
|
||||
<n-checkbox value="genMenuPermissions" label="生成菜单权限" />
|
||||
<n-checkbox value="runDao" label="生成前运行 [gf gen dao]" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>请确保运行环境已安装gf命令</span>
|
||||
</n-popover>
|
||||
<n-checkbox value="runService" label="生成后运行 [gf gen service]" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>请确保运行环境已安装gf命令</span>
|
||||
</n-popover>
|
||||
<n-checkbox value="forcedCover" label="强制覆盖" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>只会强制覆盖需要生成的文件,但不包含SQL文件</span>
|
||||
</n-popover>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col
|
||||
:span="6"
|
||||
style="min-width: 200px"
|
||||
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
|
||||
>
|
||||
<n-form-item label="上级菜单" path="pid">
|
||||
<n-tree-select
|
||||
:options="optionMenuTree"
|
||||
:value="formValue.options.menu.pid"
|
||||
@update:value="handleUpdateMenuPid"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col
|
||||
:span="6"
|
||||
style="min-width: 200px"
|
||||
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
|
||||
>
|
||||
<n-form-item label="菜单名称" path="tableComment">
|
||||
<n-input placeholder="请输入" v-model:value="formValue.tableComment" />
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col
|
||||
:span="6"
|
||||
style="min-width: 200px"
|
||||
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
|
||||
>
|
||||
<n-form-item label="菜单图标" path="menuIcon">
|
||||
<IconSelector style="width: 100%" v-model:value="formValue.options.menu.icon" />
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col
|
||||
:span="6"
|
||||
style="min-width: 200px"
|
||||
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
|
||||
>
|
||||
<n-form-item label="菜单排序" path="menuIcon">
|
||||
<n-input-number
|
||||
style="width: 100%"
|
||||
placeholder="请输入"
|
||||
v-model:value="formValue.options.menu.sort"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="true"
|
||||
title="关联表设置"
|
||||
class="proCard mt-2"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-space>
|
||||
<n-button type="warning" @click="addJoin" :disabled="formValue.options?.join?.length >= 3"
|
||||
>新增关联表</n-button
|
||||
>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<n-form ref="formRef" :model="formValue">
|
||||
<n-alert :show-icon="false">关联表数量建议在三个以下</n-alert>
|
||||
|
||||
<n-row :gutter="6" v-for="(join, index) in formValue.options.join" :key="index">
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item label="关联表" path="join.linkTable">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="tablesLoading"
|
||||
placeholder="请选择"
|
||||
:options="linkTablesOption"
|
||||
v-model:value="join.linkTable"
|
||||
@update:value="handleLinkTableUpdateValue(join)"
|
||||
:disabled="formValue.dbName === ''"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="3" style="min-width: 100px">
|
||||
<n-form-item
|
||||
label="别名"
|
||||
path="join.alias"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-input
|
||||
placeholder="请输入"
|
||||
v-model:value="join.alias"
|
||||
@update:value="updateJoinAlias"
|
||||
/>
|
||||
|
||||
<template #feedback> {{ joinAliasFeedback }}</template>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="3" style="min-width: 100px">
|
||||
<n-form-item label="关联方式" path="join.linkMode">
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="selectList.linkMode"
|
||||
v-model:value="join.linkMode"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="5" style="min-width: 180px">
|
||||
<n-form-item label="关联字段" path="join.field">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="linkColumnsLoading"
|
||||
placeholder="请选择"
|
||||
:options="linkColumnsOption[join.uuid]"
|
||||
v-model:value="join.field"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="5" style="min-width: 180px">
|
||||
<n-form-item label="主表关联字段" path="join.masterField">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="columnsLoading"
|
||||
placeholder="请选择"
|
||||
:options="columnsOption"
|
||||
v-model:value="join.masterField"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="2" style="min-width: 50px">
|
||||
<n-space>
|
||||
<n-form-item label="操作" path="title">
|
||||
<n-button @click="delJoin(join, index)" size="small" strong secondary type="error"
|
||||
>移除</n-button
|
||||
>
|
||||
</n-form-item>
|
||||
</n-space>
|
||||
</n-col>
|
||||
</n-row>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { FormInst } from 'naive-ui';
|
||||
import { newState, selectListObj } from './model';
|
||||
import { TableSelect, ColumnSelect } from '@/api/develop/code';
|
||||
import { getRandomString } from '@/utils/charset';
|
||||
import IconSelector from '@/components/IconSelector/index.vue';
|
||||
import { QuestionCircleOutlined } from '@vicons/antd';
|
||||
import { getMenuList } from '@/api/system/menu';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { isLetterBegin } from '@/utils/is';
|
||||
|
||||
const formRef = ref<FormInst | null>(null);
|
||||
const tablesLoading = ref(false);
|
||||
const columnsLoading = ref(false);
|
||||
const linkColumnsLoading = ref(false);
|
||||
const tablesOption = ref<any>([]); // 数据库表选项
|
||||
const columnsOption = ref<any>([]); // 主表字段选项
|
||||
const linkTablesOption = ref<any>([]); // 关联表选项
|
||||
const linkColumnsOption = ref<any>([]); // 关联表字段选项
|
||||
|
||||
const optionMenuTree = ref([
|
||||
{
|
||||
id: 0,
|
||||
key: 0,
|
||||
label: '根目录',
|
||||
pid: 0,
|
||||
title: '根目录',
|
||||
type: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
interface Props {
|
||||
value?: any;
|
||||
selectList: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: newState(null),
|
||||
selectList: selectListObj,
|
||||
});
|
||||
|
||||
watch(props, async (newVal, oldVal) => {
|
||||
if (newVal.value.dbName != oldVal.value.dbName) {
|
||||
await instLoad();
|
||||
}
|
||||
});
|
||||
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:value', value);
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(async function () {
|
||||
await instLoad();
|
||||
// 切换tab时会导致选项被清空,这里重新进行加载
|
||||
await loadLinkColumnsOption();
|
||||
await loadMenuTreeOption();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
const loadMenuTreeOption = async () => {
|
||||
const options = await getMenuList();
|
||||
optionMenuTree.value = [
|
||||
{
|
||||
id: 0,
|
||||
key: 0,
|
||||
label: '根目录',
|
||||
pid: 0,
|
||||
title: '根目录',
|
||||
type: 1,
|
||||
},
|
||||
];
|
||||
optionMenuTree.value = optionMenuTree.value.concat(options.list);
|
||||
};
|
||||
|
||||
const loadSelect = async () => {
|
||||
columnsOption.value = await loadColumnSelect(formValue.value.tableName);
|
||||
};
|
||||
|
||||
async function instLoad() {
|
||||
columnsLoading.value = true;
|
||||
tablesLoading.value = true;
|
||||
await loadSelect();
|
||||
await loadTableSelect(formValue.value.dbName);
|
||||
tablesLoading.value = false;
|
||||
columnsLoading.value = false;
|
||||
}
|
||||
|
||||
async function loadLinkColumnsOption() {
|
||||
if (formValue.value.options.join === undefined) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < formValue.value.options.join.length; i++) {
|
||||
linkColumnsLoading.value = true;
|
||||
linkColumnsOption.value[formValue.value.options.join[i].uuid] = await loadColumnSelect(
|
||||
formValue.value.options.join[i].linkTable
|
||||
);
|
||||
linkColumnsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理选项更新
|
||||
async function handleDbUpdateValue(value, _option) {
|
||||
tablesLoading.value = true;
|
||||
await loadTableSelect(value);
|
||||
tablesLoading.value = false;
|
||||
}
|
||||
|
||||
async function loadTableSelect(value) {
|
||||
const options = await TableSelect({ name: value });
|
||||
tablesOption.value = cloneDeep(options);
|
||||
linkTablesOption.value = cloneDeep(options);
|
||||
}
|
||||
|
||||
async function loadColumnSelect(value) {
|
||||
return await ColumnSelect({ name: formValue.value.dbName, table: value });
|
||||
}
|
||||
|
||||
function handleTableUpdateValue(value, option) {
|
||||
formValue.value.varName = option?.defVarName as string;
|
||||
formValue.value.daoName = option?.daoName as string;
|
||||
formValue.value.tableComment = option?.defTableComment as string;
|
||||
}
|
||||
|
||||
function addJoin() {
|
||||
if (formValue.value.options.join === undefined) {
|
||||
formValue.value.options.join = [];
|
||||
}
|
||||
let uuid = getRandomString(16, true);
|
||||
formValue.value.options.join.push({
|
||||
uuid: uuid,
|
||||
linkTable: '',
|
||||
alias: '',
|
||||
linkMode: 1,
|
||||
field: '',
|
||||
masterField: '',
|
||||
daoName: '',
|
||||
columns: [],
|
||||
});
|
||||
linkColumnsOption.value[uuid] = [];
|
||||
}
|
||||
|
||||
function delJoin(join, index) {
|
||||
formValue.value.options.join.splice(index, 1);
|
||||
delete linkColumnsOption.value[join.uuid];
|
||||
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
|
||||
if (i > -1) {
|
||||
linkTablesOption.value[i].disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLinkTableUpdateValue(join) {
|
||||
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
|
||||
if (i > -1) {
|
||||
join.alias = linkTablesOption.value[i].defAlias;
|
||||
join.daoName = linkTablesOption.value[i].daoName;
|
||||
linkTablesOption.value[i].disabled = true;
|
||||
}
|
||||
|
||||
linkColumnsLoading.value = true;
|
||||
linkColumnsOption.value[join.uuid] = await loadColumnSelect(join.linkTable);
|
||||
// 清空更新前的字段
|
||||
join.field = '';
|
||||
linkColumnsLoading.value = false;
|
||||
}
|
||||
|
||||
const joinAliasFeedback = ref('');
|
||||
function updateJoinAlias(value: string) {
|
||||
if (value.length < 3) {
|
||||
joinAliasFeedback.value = '别名不能小于3位';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLetterBegin(value)) {
|
||||
joinAliasFeedback.value = '别名必须以字母开头';
|
||||
return;
|
||||
}
|
||||
joinAliasFeedback.value = '';
|
||||
}
|
||||
|
||||
function handleUpdateMenuPid(value: string | number | Array<string | number> | null) {
|
||||
formValue.value.options.menu.pid = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
::v-deep(.default_text_value) {
|
||||
color: var(--n-tab-text-color-active);
|
||||
}
|
||||
::v-deep(.tips-help-icon) {
|
||||
margin-left: -16px;
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
357
web/src/views/develop/code/components/EditMasterCell.vue
Normal file
357
web/src/views/develop/code/components/EditMasterCell.vue
Normal file
@@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<n-spin :show="show" description="加载中...">
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
:single-line="false"
|
||||
size="small"
|
||||
:striped="true"
|
||||
:resizable="true"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:openChecked="false"
|
||||
:showTopRight="false"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:canResize="true"
|
||||
:resizeHeightOffset="-20000"
|
||||
:pagination="false"
|
||||
:scroll-x="1090"
|
||||
:scrollbar-props="{ trigger: 'none' }"
|
||||
/>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, ref } from 'vue';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
|
||||
import { ColumnList } from '@/api/develop/code';
|
||||
import { NButton, NCheckbox, NInput, NSelect, NTooltip, NTreeSelect } from 'naive-ui';
|
||||
import { HelpCircleOutline } from '@vicons/ionicons5';
|
||||
import { renderIcon } from '@/utils';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const renderTooltip = (trigger, content) => {
|
||||
return h(NTooltip, null, {
|
||||
trigger: () => trigger,
|
||||
default: () => content,
|
||||
});
|
||||
};
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
interface Props {
|
||||
value?: any;
|
||||
selectList: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: genInfoObj,
|
||||
selectList: selectListObj,
|
||||
});
|
||||
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:value', value);
|
||||
},
|
||||
});
|
||||
|
||||
const actionRef = ref();
|
||||
const columns = ref<any>([]);
|
||||
const show = ref(false);
|
||||
const dataSource = ref(formValue.value.masterColumns);
|
||||
onMounted(async () => {
|
||||
show.value = true;
|
||||
if (formValue.value.masterColumns.length === 0) {
|
||||
formValue.value.masterColumns = await ColumnList({
|
||||
name: formValue.value.dbName,
|
||||
table: formValue.value.tableName,
|
||||
});
|
||||
dataSource.value = formValue.value.masterColumns;
|
||||
}
|
||||
|
||||
columns.value = [
|
||||
{
|
||||
title: '位置',
|
||||
key: 'id',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
ghost: true,
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
|
||||
);
|
||||
},
|
||||
key: 'field',
|
||||
align: 'center',
|
||||
width: 800,
|
||||
children: [
|
||||
{
|
||||
title: '字段列名',
|
||||
key: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '物理类型',
|
||||
key: 'sqlType',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'Go属性',
|
||||
key: 'goName',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
title: 'Go类型',
|
||||
key: 'goType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Ts属性',
|
||||
key: 'tsName',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
title: 'Ts类型',
|
||||
key: 'tsType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '字段描述',
|
||||
key: 'dc',
|
||||
width: 150,
|
||||
render(row) {
|
||||
return h(NInput, {
|
||||
value: row.dc,
|
||||
onUpdateValue: function (e) {
|
||||
row.dc = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
width: 800,
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
ghost: true,
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '新增/编辑表单', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'勾选编辑以后会在新增、编辑表单中显示该字段;当同时勾选列表查询时,会优先使用配置的表单组件'
|
||||
);
|
||||
},
|
||||
key: 'edit',
|
||||
align: 'center',
|
||||
children: [
|
||||
{
|
||||
align: 'center',
|
||||
title: '编辑',
|
||||
key: 'isEdit',
|
||||
width: 50,
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isEdit,
|
||||
disabled: row.name === 'id',
|
||||
onUpdateChecked: function (e) {
|
||||
row.isEdit = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '必填',
|
||||
key: 'required',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.required,
|
||||
disabled: row.name === 'id',
|
||||
onUpdateChecked: function (e) {
|
||||
row.required = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '唯一',
|
||||
key: 'unique',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.unique,
|
||||
disabled: row.name === 'id',
|
||||
onUpdateChecked: function (e) {
|
||||
row.unique = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '表单组件',
|
||||
key: 'formMode',
|
||||
width: 200,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
value: row.formMode,
|
||||
options: getFormModeOptions(row.tsType),
|
||||
// render: function (row) {
|
||||
// return props.selectList?.formMode ?? [];
|
||||
// },
|
||||
// onFocus: function (e) {
|
||||
// console.log('表单组件 onFocus row:', e);
|
||||
// },
|
||||
onUpdateValue: function (e) {
|
||||
row.formMode = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '表单验证',
|
||||
key: 'formRole',
|
||||
width: 200,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
value: row.formRole,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.formRole ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.formRole = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
key: 'dictType',
|
||||
width: 300,
|
||||
render(row) {
|
||||
return h(NTreeSelect, {
|
||||
value: row.dictType,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.dictMode ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.dictType = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
width: 800,
|
||||
title: '列表',
|
||||
key: 'list',
|
||||
align: 'center',
|
||||
children: [
|
||||
{
|
||||
title: '列表',
|
||||
key: 'isList',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isList,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isList = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '导出',
|
||||
key: 'isExport',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isExport,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isExport = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '查询',
|
||||
key: 'isQuery',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isQuery,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isQuery = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '查询条件',
|
||||
key: 'queryWhere',
|
||||
width: 300,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
value: row.queryWhere,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.whereMode ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.queryWhere = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
show.value = false;
|
||||
});
|
||||
|
||||
function getFormModeOptions(type: string) {
|
||||
const options = cloneDeep(props.selectList?.formMode ?? []);
|
||||
if (options.length === 0) {
|
||||
return [];
|
||||
}
|
||||
switch (type) {
|
||||
case 'number':
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const allows = ['InputNumber', 'Radio', 'Select', 'Switch', 'Rate'];
|
||||
if (!allows.includes(options[i].value)) {
|
||||
options[i].disabled = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return options;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
243
web/src/views/develop/code/components/EditSlaveCell.vue
Normal file
243
web/src/views/develop/code/components/EditSlaveCell.vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<n-spin :show="show" description="加载中...">
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
:single-line="false"
|
||||
size="small"
|
||||
:striped="true"
|
||||
:resizable="true"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:openChecked="false"
|
||||
:showTopRight="false"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:canResize="true"
|
||||
:resizeHeightOffset="-20000"
|
||||
:pagination="false"
|
||||
:scroll-x="1090"
|
||||
:scrollbar-props="{ trigger: 'none' }"
|
||||
/>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Component, computed, h, onMounted, ref } from 'vue';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
|
||||
import { ColumnList } from '@/api/develop/code';
|
||||
import { NButton, NCheckbox, NIcon, NInput, NSelect, NTooltip } from 'naive-ui';
|
||||
import { HelpCircleOutline } from '@vicons/ionicons5';
|
||||
|
||||
const renderTooltip = (trigger, content) => {
|
||||
return h(NTooltip, null, {
|
||||
trigger: () => trigger,
|
||||
default: () => content,
|
||||
});
|
||||
};
|
||||
function renderIcon(icon: Component) {
|
||||
return () => h(NIcon, null, { default: () => h(icon) });
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
interface Props {
|
||||
value?: any;
|
||||
selectList: any;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: genInfoObj,
|
||||
selectList: selectListObj,
|
||||
uuid: '',
|
||||
});
|
||||
|
||||
const columns = ref<any>([]);
|
||||
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:value', value);
|
||||
},
|
||||
});
|
||||
|
||||
function getIndex() {
|
||||
if (formValue.value.options.join.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
for (let i = 0; i < formValue.value.options.join.length; i++) {
|
||||
if (formValue.value.options.join[i].uuid === props.uuid) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const show = ref(false);
|
||||
const dataSource = ref([]);
|
||||
onMounted(async () => {
|
||||
show.value = true;
|
||||
setTimeout(async () => {
|
||||
const index = getIndex();
|
||||
if (formValue.value.options.join[index].columns.length === 0) {
|
||||
formValue.value.options.join[index].columns = await ColumnList({
|
||||
name: formValue.value.dbName,
|
||||
table: formValue.value.options.join[index].linkTable,
|
||||
isLink: 1,
|
||||
alias: formValue.value.options.join[index].alias,
|
||||
});
|
||||
}
|
||||
|
||||
dataSource.value = formValue.value.options.join[index].columns;
|
||||
|
||||
columns.value = [
|
||||
{
|
||||
title: '位置',
|
||||
key: 'id',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
ghost: true,
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
|
||||
);
|
||||
},
|
||||
key: 'field',
|
||||
align: 'center',
|
||||
width: 800,
|
||||
children: [
|
||||
{
|
||||
title: '字段列名',
|
||||
key: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '物理类型',
|
||||
key: 'sqlType',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'Go属性',
|
||||
key: 'goName',
|
||||
width: 260,
|
||||
},
|
||||
{
|
||||
title: 'Go类型',
|
||||
key: 'goType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Ts属性',
|
||||
key: 'tsName',
|
||||
width: 260,
|
||||
},
|
||||
{
|
||||
title: 'Ts类型',
|
||||
key: 'tsType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '字段描述',
|
||||
key: 'dc',
|
||||
width: 150,
|
||||
render(row) {
|
||||
return h(NInput, {
|
||||
value: row.dc,
|
||||
onUpdateValue: function (e) {
|
||||
row.dc = e;
|
||||
// await saveProductCustom(row.id, 'frontShow', e);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
width: 800,
|
||||
title: '列表',
|
||||
key: 'list',
|
||||
align: 'center',
|
||||
children: [
|
||||
{
|
||||
title: '列表',
|
||||
key: 'isList',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isList,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isList = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '导出',
|
||||
key: 'isExport',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isExport,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isExport = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '查询',
|
||||
key: 'isQuery',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isQuery,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isQuery = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '查询条件',
|
||||
key: 'queryWhere',
|
||||
width: 300,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
value: row.queryWhere,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.whereMode ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.queryWhere = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
show.value = false;
|
||||
}, 50);
|
||||
});
|
||||
|
||||
const actionRef = ref();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
84
web/src/views/develop/code/components/PreviewTab.vue
Normal file
84
web/src/views/develop/code/components/PreviewTab.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane v-for="(view, index) in views" :key="index" :name="view.name" :tab="view.name">
|
||||
<n-tag :type="view.tag.type" class="tag-margin">
|
||||
{{ view.tag.label }}
|
||||
<template #icon>
|
||||
<n-icon :component="view.tag.icon" />
|
||||
</template>
|
||||
{{ view.path }}
|
||||
</n-tag>
|
||||
<n-scrollbar class="code-scrollbar" trigger="none">
|
||||
<n-code :code="view.content" />
|
||||
</n-scrollbar>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import {
|
||||
CheckmarkCircle,
|
||||
CheckmarkDoneCircle,
|
||||
CloseCircleOutline,
|
||||
HelpCircleOutline,
|
||||
RemoveCircleOutline,
|
||||
} from '@vicons/ionicons5';
|
||||
|
||||
interface Props {
|
||||
previewModel: any;
|
||||
showModal: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
previewModel: cloneDeep({ views: {} }),
|
||||
showModal: false,
|
||||
});
|
||||
|
||||
const views = computed(() => {
|
||||
let tmpViews: any = [];
|
||||
let i = 0;
|
||||
for (const [k, v] of Object.entries(props.previewModel.views)) {
|
||||
let item = v as any;
|
||||
item.name = k;
|
||||
switch (item.meth) {
|
||||
case 1:
|
||||
item.tag = { type: 'success', label: '创建文件', icon: CheckmarkCircle };
|
||||
break;
|
||||
case 2:
|
||||
item.tag = { type: 'warning', label: '覆盖文件', icon: CheckmarkDoneCircle };
|
||||
break;
|
||||
case 3:
|
||||
item.tag = { type: 'info', label: '已存在跳过', icon: CloseCircleOutline };
|
||||
break;
|
||||
case 4:
|
||||
item.tag = { type: 'error', label: '不生成', icon: RemoveCircleOutline };
|
||||
break;
|
||||
default:
|
||||
item.tag = { type: 'error', label: '未知状态', icon: HelpCircleOutline };
|
||||
}
|
||||
tmpViews[i] = item;
|
||||
i++;
|
||||
}
|
||||
return tmpViews;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
::v-deep(.alert-margin) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
::v-deep(.tag-margin) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
::v-deep(.code-scrollbar) {
|
||||
height: calc(100vh - 300px);
|
||||
background: #282b2e;
|
||||
color: #e0e2e4;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
62
web/src/views/develop/code/components/model.ts
Normal file
62
web/src/views/develop/code/components/model.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export const genFileObj = {
|
||||
meth: 1,
|
||||
content: '',
|
||||
path: '',
|
||||
required: true,
|
||||
};
|
||||
|
||||
export interface joinAttr {
|
||||
uuid: string;
|
||||
linkTable: string;
|
||||
alias: string;
|
||||
linkMode: number;
|
||||
field: string;
|
||||
masterField: string;
|
||||
columns: any;
|
||||
}
|
||||
|
||||
export const genInfoObj = {
|
||||
id: 0,
|
||||
genType: 10,
|
||||
varName: '',
|
||||
options: {
|
||||
headOps: ['add', 'batchDel', 'export'],
|
||||
columnOps: ['edit', 'del', 'view', 'status', 'switch', 'check'],
|
||||
autoOps: ['genMenuPermissions', 'runDao', 'runService'],
|
||||
join: [],
|
||||
menu: {
|
||||
pid: 0,
|
||||
icon: 'MenuOutlined',
|
||||
sort: 0,
|
||||
},
|
||||
},
|
||||
dbName: '',
|
||||
tableName: '',
|
||||
tableComment: '',
|
||||
daoName: '',
|
||||
masterColumns: [],
|
||||
status: 2,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
};
|
||||
|
||||
export const selectListObj = {
|
||||
db: [],
|
||||
genType: [],
|
||||
status: [],
|
||||
tables: [],
|
||||
formMode: [],
|
||||
formRole: [],
|
||||
dictMode: [],
|
||||
whereMode: [],
|
||||
buildMeth: [],
|
||||
};
|
||||
|
||||
export function newState(state) {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(genInfoObj);
|
||||
}
|
264
web/src/views/develop/code/deploy.vue
Normal file
264
web/src/views/develop/code/deploy.vue
Normal file
@@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="show" description="正在生成配置信息...">
|
||||
<n-card>
|
||||
<n-tabs
|
||||
type="card"
|
||||
class="card-tabs"
|
||||
:default-value="value"
|
||||
animated
|
||||
tab-style="min-width: 80px;"
|
||||
style="margin: 0 -4px"
|
||||
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;"
|
||||
:on-update:value="updateTabs"
|
||||
ref="tabsRef"
|
||||
@close="handleClose"
|
||||
@add="handleAdd"
|
||||
>
|
||||
<n-tab-pane v-for="panel in panels" :key="panel" :name="panel">
|
||||
<template v-if="panel === '基本信息'">
|
||||
<BaseInfo v-model:value="genInfo" :selectList="selectList" />
|
||||
</template>
|
||||
|
||||
<template v-if="panel === '主表字段'">
|
||||
<EditMasterCell v-model:value="genInfo" :selectList="selectList" />
|
||||
</template>
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane
|
||||
v-for="panel in slavePanels"
|
||||
:key="panel"
|
||||
:name="panel"
|
||||
v-show="slavePanels.length > 0 && slavePanels !== []"
|
||||
>
|
||||
<EditSlaveCell
|
||||
v-model:value="genInfo"
|
||||
:uuid="slaveMap[panel]"
|
||||
:selectList="selectList"
|
||||
/>
|
||||
</n-tab-pane>
|
||||
|
||||
<template #suffix>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="preview">预览代码</n-button>
|
||||
<n-button type="success" :loading="formBtnLoading" @click="submitBuild"
|
||||
>提交生成</n-button
|
||||
>
|
||||
<n-button type="info" dashed :loading="formBtnLoading" @click="submitSave"
|
||||
>仅保存配置</n-button
|
||||
>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-tabs>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:block-scroll="false"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="card"
|
||||
title="预览代码"
|
||||
style="width: 95%"
|
||||
>
|
||||
<PreviewTab :previewModel="previewModel" />
|
||||
<template #action>
|
||||
<n-space justify="end">
|
||||
<n-button @click="() => (showModal = false)">关闭</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="submitBuild"
|
||||
>提交生成</n-button
|
||||
>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card-tabs .n-tabs-nav--bar-type {
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import BaseInfo from './components/BaseInfo.vue';
|
||||
import EditMasterCell from './components/EditMasterCell.vue';
|
||||
import EditSlaveCell from './components/EditSlaveCell.vue';
|
||||
import { Selects, View, Preview, Build, Edit } from '@/api/develop/code';
|
||||
import { selectListObj, newState } from '@/views/develop/code/components/model';
|
||||
import PreviewTab from '@/views/develop/code/components/PreviewTab.vue';
|
||||
import { isJsonString } from '@/utils/is';
|
||||
|
||||
interface Props {
|
||||
genId?: number;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), { genId: 0 });
|
||||
const router = useRouter();
|
||||
const genId = Number(router.currentRoute.value.params.id ?? props.genId);
|
||||
const show = ref(false);
|
||||
const message = useMessage();
|
||||
const selectList = ref<any>(selectListObj);
|
||||
const genInfo = ref(newState(null));
|
||||
const tabsRef = ref();
|
||||
const value = ref('基本信息');
|
||||
const panels = ref(['基本信息', '主表字段']);
|
||||
const slaveMap = ref<any>([]);
|
||||
const slavePanels = ref<any>([]);
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const previewModel = ref<any>();
|
||||
const dialog = useDialog();
|
||||
|
||||
onMounted(async () => {
|
||||
if (genId < 1 && props.genId < 1) {
|
||||
message.error('生成ID不正确,请检查!');
|
||||
return;
|
||||
}
|
||||
await getGenInfo();
|
||||
await loadSelect();
|
||||
});
|
||||
|
||||
async function getGenInfo() {
|
||||
let tmp = await View({ id: genId });
|
||||
if (isJsonString(tmp.options)) {
|
||||
tmp.options = JSON.parse(tmp.options);
|
||||
}
|
||||
|
||||
if (tmp.masterColumns === undefined || tmp.masterColumns.length === 0) {
|
||||
tmp.masterColumns = [];
|
||||
}
|
||||
if (isJsonString(tmp.masterColumns)) {
|
||||
tmp.masterColumns = JSON.parse(tmp.masterColumns);
|
||||
}
|
||||
|
||||
genInfo.value = tmp;
|
||||
}
|
||||
|
||||
watch(
|
||||
genInfo,
|
||||
(newVal, _oldVal) => {
|
||||
if (newVal.genType >= 10 && newVal.genType < 20) {
|
||||
handleAdd('主表字段');
|
||||
} else {
|
||||
handleClose('主表字段');
|
||||
}
|
||||
|
||||
if (newVal.options.join !== undefined) {
|
||||
slavePanels.value = [];
|
||||
for (let i = 0; i <= newVal.options.join.length; i++) {
|
||||
if (newVal.options.join[i]?.alias !== undefined && newVal.options.join[i]?.alias !== '') {
|
||||
handleSlaveAdd(
|
||||
'关联表[ ' + newVal.options.join[i]?.alias + ' ]',
|
||||
newVal.options.join[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true, // 是否深度监听
|
||||
}
|
||||
);
|
||||
|
||||
function updateTabs(value: string | number) {
|
||||
console.log('value:' + value);
|
||||
}
|
||||
|
||||
function handleAdd(name: string) {
|
||||
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
|
||||
if (!~nameIndex) {
|
||||
panels.value.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSlaveAdd(name: string, join) {
|
||||
const nameIndex = slavePanels.value.findIndex((panelName) => panelName === name);
|
||||
if (!~nameIndex) {
|
||||
slavePanels.value.push(name);
|
||||
slaveMap.value[name] = join.uuid;
|
||||
}
|
||||
}
|
||||
|
||||
function _handleSlaveClose(name: string) {
|
||||
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
|
||||
if (!~nameIndex) return;
|
||||
panels.value.splice(nameIndex, 1);
|
||||
if (name === value.value) {
|
||||
value.value = panels.value[Math.min(nameIndex, panels.value.length - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose(name: string) {
|
||||
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
|
||||
if (!~nameIndex) return;
|
||||
panels.value.splice(nameIndex, 1);
|
||||
if (name === value.value) {
|
||||
value.value = panels.value[Math.min(nameIndex, panels.value.length - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
const loadSelect = async () => {
|
||||
selectList.value = await Selects({});
|
||||
};
|
||||
|
||||
async function preview() {
|
||||
previewModel.value = await Preview(genInfo.value);
|
||||
showModal.value = true;
|
||||
}
|
||||
|
||||
function submitBuild() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要提交生成吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Build(genInfo.value).then((_res) => {
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 1500);
|
||||
message.success('生成提交成功,即将刷新页面..');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function submitSave() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要保存生成配置吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Edit(genInfo.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
::v-deep(.alert-margin) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
::v-deep(.tag-margin) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
::v-deep(.code-scrollbar) {
|
||||
height: calc(100vh - 300px);
|
||||
background: #282b2e;
|
||||
color: #e0e2e4;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
@@ -1,39 +1,452 @@
|
||||
<template>
|
||||
<div class="flex flex-col justify-center page-container">
|
||||
<div class="text-center">
|
||||
<h1 class="text-base">代码生成,敬请期待</h1>
|
||||
<n-button type="info" @click="goHome">回到首页</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-card :bordered="false" title="代码生成"> 你可以在这里查看到平台所有的短信发送记录。 </n-card>
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" ref="searchFormRef">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
生成
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="生成"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<!-- <n-alert :show-icon="false" type="info">-->
|
||||
<!-- 注意:!-->
|
||||
<!-- </n-alert>-->
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="生成类型" path="genType">
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="selectList.genType"
|
||||
v-model:value="formParams.genType"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item
|
||||
label="数据库"
|
||||
path="dbName"
|
||||
v-if="formParams.genType >= 10 && formParams.genType < 20"
|
||||
>
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="selectList.db"
|
||||
v-model:value="formParams.dbName"
|
||||
@update:value="handleDbUpdateValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item
|
||||
label="数据库表"
|
||||
path="tableName"
|
||||
v-if="formParams.genType >= 10 && formParams.genType < 20"
|
||||
>
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="tablesLoading"
|
||||
placeholder="请选择"
|
||||
:options="selectList.tables"
|
||||
v-model:value="formParams.tableName"
|
||||
@update:value="handleTableUpdateValue"
|
||||
:disabled="formParams.dbName === ''"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item
|
||||
label="菜单名称"
|
||||
path="tableComment"
|
||||
v-show="formParams.genType >= 10 && formParams.genType < 20"
|
||||
>
|
||||
<n-input placeholder="请输入" v-model:value="formParams.tableComment" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="实体命名" path="varName">
|
||||
<n-input placeholder="请输入" v-model:value="formParams.varName" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showModal = false)">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">生成配置</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, onBeforeMount, reactive, ref } from 'vue';
|
||||
import { NTag, TreeSelectOption, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { List, Delete, Edit, Selects, TableSelect } from '@/api/develop/code';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { PlusOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import { newState } from '@/views/develop/code/components/model';
|
||||
import { getOptionLabel } from '@/utils/hotgo';
|
||||
|
||||
const selectList = ref({
|
||||
db: [],
|
||||
genType: [],
|
||||
status: [],
|
||||
tables: [],
|
||||
formMode: [],
|
||||
formRole: [],
|
||||
dictMode: [],
|
||||
whereMode: [],
|
||||
});
|
||||
const columns = [
|
||||
{
|
||||
title: '生成ID',
|
||||
key: 'id',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '生成类型',
|
||||
key: 'genType',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'info',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(selectList.value.genType, row.genType),
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '实体命名',
|
||||
key: 'varName',
|
||||
render(row) {
|
||||
return row.varName;
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '数据库',
|
||||
key: 'dbName',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '数据表',
|
||||
key: 'tableName',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '菜单名称',
|
||||
key: 'tableComment',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '生成状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 1 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(selectList.value.status, row.status),
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
key: 'updatedAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
const dialog = useDialog();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const searchFormRef = ref<any>();
|
||||
|
||||
const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'genType',
|
||||
component: 'NSelect',
|
||||
label: '生成类型',
|
||||
componentProps: {
|
||||
placeholder: '请选择生成类型',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'varName',
|
||||
component: 'NInput',
|
||||
label: '实体命名',
|
||||
componentProps: {
|
||||
placeholder: '请输入实体命名',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '生成状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态码',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const router = useRouter();
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref<any>();
|
||||
|
||||
function goHome() {
|
||||
router.push('/');
|
||||
const rules = {
|
||||
varName: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '实体命名不能为空,首字母大写',
|
||||
},
|
||||
};
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '生成配置',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
mapWidth();
|
||||
return await List({ ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
router.push({ name: 'develop_code_deploy', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleSubmit(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(null);
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams:' + JSON.stringify(formParams.value));
|
||||
Edit(formParams.value).then((res) => {
|
||||
message.success('生成成功,正在前往配置');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
router.push({ name: 'develop_code_deploy', params: { id: res.id } });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
const dialogWidth = ref('50%');
|
||||
function mapWidth() {
|
||||
let val = document.body.clientWidth;
|
||||
const def = 840; // 默认宽度
|
||||
if (val < def) {
|
||||
dialogWidth.value = '100%';
|
||||
} else {
|
||||
dialogWidth.value = def + 'px';
|
||||
}
|
||||
|
||||
return dialogWidth.value;
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await loadSelect();
|
||||
});
|
||||
|
||||
const loadSelect = async () => {
|
||||
selectList.value = await Selects({});
|
||||
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = selectList.value.status;
|
||||
break;
|
||||
case 'genType':
|
||||
item.componentProps.options = selectList.value.genType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tablesLoading = ref(false);
|
||||
// 处理选项更新
|
||||
async function handleDbUpdateValue(
|
||||
value: string | number | Array<string | number> | null,
|
||||
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
|
||||
) {
|
||||
tablesLoading.value = true;
|
||||
await loadTableSelect(value);
|
||||
tablesLoading.value = false;
|
||||
}
|
||||
|
||||
async function loadTableSelect(value) {
|
||||
selectList.value.tables = await TableSelect({ name: value });
|
||||
}
|
||||
|
||||
function handleTableUpdateValue(value, option) {
|
||||
formParams.value.varName = option?.defVarName as string;
|
||||
formParams.value.daoName = option?.daoName as string;
|
||||
formParams.value.tableComment = option?.defTableComment as string;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
padding: 50px 0;
|
||||
height: 60vh;
|
||||
|
||||
.text-center {
|
||||
h1 {
|
||||
color: #666;
|
||||
padding: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 350px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -7,6 +7,7 @@
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
@@ -223,7 +224,7 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
@@ -236,7 +237,7 @@
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -246,7 +247,7 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
@@ -259,7 +260,7 @@
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
@@ -223,7 +224,7 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
@@ -236,7 +237,7 @@
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -246,7 +247,7 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
@@ -259,7 +260,7 @@
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
@@ -169,7 +170,6 @@
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
@@ -218,25 +218,19 @@
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -246,20 +240,15 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -273,18 +262,15 @@
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
router.push({ name: 'log_view', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = {};
|
||||
reloadTable();
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
@@ -223,7 +224,7 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
@@ -236,7 +237,7 @@
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -246,7 +247,7 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
@@ -259,7 +260,7 @@
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
@@ -169,7 +170,6 @@
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
@@ -207,7 +207,6 @@
|
||||
});
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
@@ -218,25 +217,19 @@
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -246,11 +239,10 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
@@ -259,7 +251,7 @@
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -273,12 +265,10 @@
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
router.push({ name: 'sms_view', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
@@ -17,7 +17,11 @@
|
||||
:rules="rules"
|
||||
>
|
||||
<n-form-item path="username">
|
||||
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.username"
|
||||
placeholder="请输入用户名"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<PersonOutline />
|
||||
@@ -27,6 +31,7 @@
|
||||
</n-form-item>
|
||||
<n-form-item path="password">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.password"
|
||||
type="password"
|
||||
showPasswordOn="click"
|
||||
@@ -140,8 +145,8 @@
|
||||
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
||||
message.success('登录成功,即将进入系统');
|
||||
if (route.name === LOGIN_NAME) {
|
||||
router.replace('/');
|
||||
} else router.replace(toPath);
|
||||
await router.replace('/');
|
||||
} else await router.replace(toPath);
|
||||
} else {
|
||||
message.info(msg || '登录失败');
|
||||
}
|
||||
|
@@ -25,7 +25,6 @@
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { OnlineList, Offline } from '@/api/monitor/monitor';
|
||||
import { columns } from './columns';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const dialog = useDialog();
|
||||
const schemas: FormSchema[] = [
|
||||
@@ -43,8 +42,6 @@
|
||||
},
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
@@ -62,10 +59,6 @@
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
// {
|
||||
// label: '查看详情',
|
||||
// onClick: handleEdit.bind(null, record),
|
||||
// },
|
||||
{
|
||||
label: '强制退出',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
@@ -82,25 +75,19 @@
|
||||
});
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要强制退出该用户?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Offline(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Offline(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -113,19 +100,12 @@
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
router.push({ name: 'serve_log_view', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
function handleReset(_values: Recordable) {
|
||||
formParams.value = {};
|
||||
reloadTable();
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
@@ -169,7 +170,6 @@
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
@@ -218,25 +218,19 @@
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -246,20 +240,15 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('不确定');
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -273,18 +262,15 @@
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
router.push({ name: 'serve_log_view', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
function handleReset(_values: Recordable) {
|
||||
formParams.value = {};
|
||||
reloadTable();
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user