mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-08-28 10:09:54 +08:00
发布v2.15.1版本,更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hotgo",
|
||||
"version": "2.13.1",
|
||||
"version": "2.15.1",
|
||||
"author": {
|
||||
"name": "MengShuai",
|
||||
"email": "133814250@qq.com",
|
||||
@@ -30,18 +30,18 @@
|
||||
"dependencies": {
|
||||
"@vicons/antd": "^0.12.0",
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vue/runtime-core": "^3.4.19",
|
||||
"@vue/runtime-core": "^3.4.21",
|
||||
"@vueup/vue-quill": "^1.2.0",
|
||||
"@vueuse/core": "^10.7.1",
|
||||
"axios": "^0.21.4",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"axios": "^1.6.8",
|
||||
"date-fns": "^2.28.0",
|
||||
"echarts": "^5.3.2",
|
||||
"echarts": "^5.5.0",
|
||||
"element-resize-detector": "^1.2.4",
|
||||
"fingerprintjs2": "^2.1.4",
|
||||
"highlight.js": "^11.8.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"naive-ui": "^2.36.0",
|
||||
"mint-filter": "^4.0.3",
|
||||
"naive-ui": "^2.38.1",
|
||||
"pinia": "^2.1.7",
|
||||
"pinyin-pro": "^3.16.3",
|
||||
"print-js": "^1.6.0",
|
||||
@@ -49,14 +49,14 @@
|
||||
"qs": "^6.10.3",
|
||||
"quill-image-uploader": "^1.3.0",
|
||||
"quill-magic-url": "^4.2.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
"vue": "^3.4.0",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.0",
|
||||
"vue-types": "^5.1.1",
|
||||
"vue-waterfall-plugin-next": "^2.2.3",
|
||||
"vue3-json-viewer": "^2.2.2",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vue-waterfall-plugin-next": "^2.2.3",
|
||||
"spark-md5": "^3.0.2",
|
||||
"weixin-js-sdk": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -88,7 +88,7 @@
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^9.1.0",
|
||||
"lint-staged": "^11.2.6",
|
||||
"postcss": "^8.4.13",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^2.6.2",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rimraf": "^3.0.2",
|
||||
@@ -98,9 +98,9 @@
|
||||
"stylelint-order": "^4.1.0",
|
||||
"stylelint-scss": "^3.21.0",
|
||||
"tailwindcss": "^2.2.19",
|
||||
"typescript": "^4.6.4",
|
||||
"typescript": "^5.3.0",
|
||||
"unplugin-vue-components": "^0.17.21",
|
||||
"vite": "^4.2.7",
|
||||
"vite": "^4.5.3",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^2.1.2",
|
||||
"vite-plugin-require-transform": "^1.0.5",
|
||||
|
@@ -43,6 +43,10 @@
|
||||
primaryColor: appTheme,
|
||||
primaryColorHover: lightenStr,
|
||||
primaryColorPressed: lightenStr,
|
||||
// 纵向滚动条宽
|
||||
scrollbarWidth: '10px',
|
||||
// 横向滚动条高
|
||||
scrollbarHeight: '10px',
|
||||
},
|
||||
LoadingBar: {
|
||||
colorLoading: appTheme,
|
||||
|
42
web/src/api/addons/hgexample/tenantOrder/index.ts
Normal file
42
web/src/api/addons/hgexample/tenantOrder/index.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { http, jumpExport } from '@/utils/http/axios';
|
||||
|
||||
// 获取多租户功能演示列表
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/hgexample/tenantOrder/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除/批量删除多租户功能演示
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/hgexample/tenantOrder/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 添加/编辑多租户功能演示
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/hgexample/tenantOrder/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取多租户功能演示指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/hgexample/tenantOrder/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 导出多租户功能演示
|
||||
export function Export(params) {
|
||||
jumpExport('/hgexample/tenantOrder/export', params);
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import { http, jumpExport } from '@/utils/http/axios';
|
||||
|
||||
// 获取生成演示列表
|
||||
// 获取CURD列表列表
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/list',
|
||||
@@ -9,7 +9,7 @@ export function List(params) {
|
||||
});
|
||||
}
|
||||
|
||||
// 删除/批量删除生成演示
|
||||
// 删除/批量删除CURD列表
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/delete',
|
||||
@@ -18,8 +18,7 @@ export function Delete(params) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 添加/编辑生成演示
|
||||
// 添加/编辑CURD列表
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/edit',
|
||||
@@ -28,18 +27,7 @@ export function Edit(params) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 修改生成演示状态
|
||||
export function Status(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/status',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 操作生成演示开关
|
||||
// 操作CURD列表开关
|
||||
export function Switch(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/switch',
|
||||
@@ -48,8 +36,7 @@ export function Switch(params) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 获取生成演示指定详情
|
||||
// 获取CURD列表指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/view',
|
||||
@@ -58,8 +45,7 @@ export function View(params) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 获取生成演示最大排序
|
||||
// 获取CURD列表最大排序
|
||||
export function MaxSort() {
|
||||
return http.request({
|
||||
url: '/curdDemo/maxSort',
|
||||
@@ -67,8 +53,7 @@ export function MaxSort() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 导出生成演示
|
||||
// 导出CURD列表
|
||||
export function Export(params) {
|
||||
jumpExport('/curdDemo/export', params);
|
||||
}
|
||||
}
|
||||
|
53
web/src/api/normalTreeDemo/index.ts
Normal file
53
web/src/api/normalTreeDemo/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { http, jumpExport } from '@/utils/http/axios';
|
||||
|
||||
// 获取普通树表列表
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/normalTreeDemo/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除/批量删除普通树表
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/normalTreeDemo/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 添加/编辑普通树表
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/normalTreeDemo/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取普通树表指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/normalTreeDemo/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取普通树表最大排序
|
||||
export function MaxSort() {
|
||||
return http.request({
|
||||
url: '/normalTreeDemo/maxSort',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
// 获取普通树表关系树选项
|
||||
export function TreeOption() {
|
||||
return http.request({
|
||||
url: '/normalTreeDemo/treeOption',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
53
web/src/api/optionTreeDemo/index.ts
Normal file
53
web/src/api/optionTreeDemo/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { http, jumpExport } from '@/utils/http/axios';
|
||||
|
||||
// 获取选项树表列表
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/optionTreeDemo/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除/批量删除选项树表
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/optionTreeDemo/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 添加/编辑选项树表
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/optionTreeDemo/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取选项树表指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/optionTreeDemo/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取选项树表最大排序
|
||||
export function MaxSort() {
|
||||
return http.request({
|
||||
url: '/optionTreeDemo/maxSort',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
// 获取选项树表关系树选项
|
||||
export function TreeOption() {
|
||||
return http.request({
|
||||
url: '/optionTreeDemo/treeOption',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
@@ -16,6 +16,7 @@ export function Edit(params) {
|
||||
});
|
||||
}
|
||||
|
||||
// 部门状态
|
||||
export function Status(params) {
|
||||
return http.request({
|
||||
url: '/dept/status',
|
||||
@@ -24,6 +25,23 @@ export function Status(params) {
|
||||
});
|
||||
}
|
||||
|
||||
// 获取管理员_部门指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/dept/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取管理员_部门最大排序
|
||||
export function MaxSort() {
|
||||
return http.request({
|
||||
url: '/dept/maxSort',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/dept/delete',
|
||||
@@ -40,3 +58,11 @@ export function getDeptOption() {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 部门关系树选项
|
||||
export function TreeOption() {
|
||||
return http.request({
|
||||
url: '/dept/treeOption',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
@@ -20,14 +20,6 @@ export function Edit(params) {
|
||||
});
|
||||
}
|
||||
|
||||
export function Status(params) {
|
||||
return http.request({
|
||||
url: '/post/status',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/post/delete',
|
||||
|
54
web/src/api/testCategory/index.ts
Normal file
54
web/src/api/testCategory/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { http, jumpExport } from '@/utils/http/axios';
|
||||
|
||||
// 获取测试分类列表
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/testCategory/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除/批量删除测试分类
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/testCategory/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 添加/编辑测试分类
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/testCategory/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 修改测试分类状态
|
||||
export function Status(params) {
|
||||
return http.request({
|
||||
url: '/testCategory/status',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取测试分类指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/testCategory/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取测试分类最大排序
|
||||
export function MaxSort() {
|
||||
return http.request({
|
||||
url: '/testCategory/maxSort',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
@@ -1,93 +1,99 @@
|
||||
<template>
|
||||
<n-date-picker v-bind="$props" v-model:value="modelValue" :shortcuts="shortcuts" :clearable="true"/>
|
||||
<n-date-picker
|
||||
v-bind="$props"
|
||||
v-model:value="modelValue"
|
||||
:shortcuts="showShortcuts ? shortcuts : undefined"
|
||||
:clearable="true"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</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';
|
||||
import { computed, defineComponent, onMounted, ref } from 'vue';
|
||||
import {
|
||||
dateToTimestamp,
|
||||
defRangeShortcuts,
|
||||
defShortcuts,
|
||||
formatToDate,
|
||||
formatToDateTime,
|
||||
timestampToTime,
|
||||
} 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>({});
|
||||
export default defineComponent({
|
||||
name: 'DatePicker',
|
||||
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();
|
||||
function getTimestamp(value) {
|
||||
let t = dateToTimestamp(value);
|
||||
console.log('getTimestamp t:' + t);
|
||||
if (t === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
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()) {
|
||||
const value = getTimestamp(props.formValue);
|
||||
if (props.formValue == ""){
|
||||
emit('update:formValue', setTimestamp(value));
|
||||
}
|
||||
return value;
|
||||
function setTimestamp(value) {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isTimeType()) {
|
||||
return formatToDate(new Date(Number(value)).toDateString());
|
||||
} else {
|
||||
const value = [getTimestamp(props.startValue), getTimestamp(props.endValue)];
|
||||
if (props.startValue == "" && props.endValue == ""){
|
||||
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 {
|
||||
const value = [getTimestamp(props.startValue), getTimestamp(props.endValue)];
|
||||
if (!value[0] && !value[1]) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
},
|
||||
set(value) {
|
||||
if (!isRangeType()) {
|
||||
emit('update:formValue', setTimestamp(value));
|
||||
} else {
|
||||
emit('update:startValue', setTimestamp(value[0]));
|
||||
emit('update:endValue', setTimestamp(value[1]));
|
||||
}
|
||||
return value
|
||||
}
|
||||
},
|
||||
set(value) {
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (!isRangeType()) {
|
||||
emit('update:formValue', setTimestamp(value));
|
||||
shortcuts.value = defShortcuts();
|
||||
} else {
|
||||
emit('update:startValue', setTimestamp(value[0]));
|
||||
emit('update:endValue', setTimestamp(value[1]));
|
||||
shortcuts.value = defRangeShortcuts();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (!isRangeType()) {
|
||||
shortcuts.value = defShortcuts();
|
||||
} else {
|
||||
shortcuts.value = defRangeShortcuts();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
modelValue,
|
||||
shortcuts,
|
||||
};
|
||||
},
|
||||
});
|
||||
return {
|
||||
modelValue,
|
||||
shortcuts,
|
||||
showShortcuts: props.showShortcuts,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
|
@@ -15,4 +15,8 @@ export const basicProps = {
|
||||
type: String as PropType<string> | undefined | Date,
|
||||
default: () => '',
|
||||
},
|
||||
showShortcuts: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: () => true,
|
||||
},
|
||||
};
|
||||
|
@@ -126,12 +126,14 @@
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
const fileUploadRef = ref();
|
||||
const dialogWidth = ref('85%');
|
||||
const dialog = useDialog();
|
||||
const showModal = ref(false);
|
||||
const chooserRef = ref();
|
||||
const previewRef = ref();
|
||||
const fileList = ref<string[]>([]);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(1080);
|
||||
});
|
||||
|
||||
const getCSSProperties = computed(() => {
|
||||
return {
|
||||
@@ -246,7 +248,6 @@
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth, 1080);
|
||||
loadImage();
|
||||
});
|
||||
</script>
|
||||
|
@@ -59,6 +59,13 @@
|
||||
v-bind="getComponentProps(schema)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="schema.component === 'NCascader'">
|
||||
<n-cascader
|
||||
:class="{ isFull: schema.isFull !== false && getProps.isFull }"
|
||||
v-model:value="formModel[schema.field]"
|
||||
v-bind="getComponentProps(schema)"
|
||||
/>
|
||||
</template>
|
||||
<!--动态渲染表单组件-->
|
||||
<component
|
||||
v-else
|
||||
@@ -127,7 +134,16 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, computed, unref, onMounted, watch } from 'vue';
|
||||
import {
|
||||
defineComponent,
|
||||
reactive,
|
||||
ref,
|
||||
computed,
|
||||
unref,
|
||||
onMounted,
|
||||
watch,
|
||||
defineExpose,
|
||||
} from 'vue';
|
||||
import { createPlaceholderMessage } from './helper';
|
||||
import { useFormEvents } from './hooks/useFormEvents';
|
||||
import { useFormValues } from './hooks/useFormValues';
|
||||
@@ -139,8 +155,10 @@
|
||||
import type { GridProps } from 'naive-ui/lib/grid';
|
||||
import type { FormSchema, FormProps, FormActionType } from './types/form';
|
||||
|
||||
import { isArray } from '@/utils/is';
|
||||
import {isArray, isBoolean, isFunction} from '@/utils/is';
|
||||
import { deepMerge } from '@/utils';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import {ActionItem} from "@/components/Table";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicForm',
|
||||
@@ -158,6 +176,7 @@
|
||||
const gridCollapsed = ref(true);
|
||||
const loadingSub = ref(false);
|
||||
const isUpdateDefaultRef = ref(false);
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const getSubmitBtnOptions = computed(() => {
|
||||
return Object.assign(
|
||||
@@ -222,7 +241,12 @@
|
||||
);
|
||||
|
||||
const getSchema = computed((): FormSchema[] => {
|
||||
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
|
||||
const rawSchemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
|
||||
|
||||
const schemas = rawSchemas.filter((schema) => {
|
||||
return hasPermission(schema.auth as string[]) && isIfShow(schema);
|
||||
});
|
||||
|
||||
for (const schema of schemas) {
|
||||
const { defaultValue } = schema;
|
||||
// handle date type
|
||||
@@ -240,6 +264,20 @@
|
||||
formModel,
|
||||
});
|
||||
|
||||
function isIfShow(action: ActionItem): boolean {
|
||||
const ifShow = action.ifShow;
|
||||
|
||||
let isIfShow = true;
|
||||
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(action);
|
||||
}
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
const { handleSubmit, validate, resetFields, getFieldsValue, clearValidate, setFieldsValue } =
|
||||
useFormEvents({
|
||||
emit,
|
||||
@@ -314,6 +352,7 @@
|
||||
isInline,
|
||||
getComponentProps,
|
||||
unfoldToggle,
|
||||
setFieldsValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -2,6 +2,8 @@ import { ComponentType } from './index';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import type { GridProps, GridItemProps } from 'naive-ui/lib/grid';
|
||||
import type { ButtonProps } from 'naive-ui/lib/button';
|
||||
import {PermissionsEnum} from "@/enums/permissionsEnum";
|
||||
import { ActionItem } from '@/components/Table';
|
||||
|
||||
export interface FormSchema {
|
||||
field: string;
|
||||
@@ -16,6 +18,8 @@ export interface FormSchema {
|
||||
giProps?: GridItemProps;
|
||||
isFull?: boolean;
|
||||
suffix?: string;
|
||||
auth?: PermissionsEnum | PermissionsEnum[] | string | string[];
|
||||
ifShow?: boolean | ((action: ActionItem) => boolean);
|
||||
}
|
||||
|
||||
export interface FormProps {
|
||||
|
@@ -7,12 +7,7 @@
|
||||
<template v-else>
|
||||
<AntdSelector v-model:value="formValue" />
|
||||
</template>
|
||||
<n-input
|
||||
v-bind="$props"
|
||||
:value="formValue"
|
||||
:style="{ width: '70%' }"
|
||||
placeholder="请选择图标"
|
||||
/>
|
||||
<n-input v-bind="$props" :value="formValue" placeholder="请选择图标" />
|
||||
</n-input-group>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -94,6 +94,7 @@
|
||||
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { aesEcb } from '@/utils/encrypt';
|
||||
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Lockscreen',
|
||||
@@ -149,6 +150,7 @@
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
onLockLogin(false);
|
||||
useLockscreen.setLock(false);
|
||||
window.location.reload();
|
||||
} else {
|
||||
state.errorMsg = message;
|
||||
state.isLoginError = true;
|
||||
@@ -160,11 +162,18 @@
|
||||
const goLogin = () => {
|
||||
onLockLogin(false);
|
||||
useLockscreen.setLock(false);
|
||||
router.replace({
|
||||
path: '/login',
|
||||
query: {
|
||||
redirect: route.fullPath,
|
||||
},
|
||||
|
||||
userStore.logout().then(() => {
|
||||
// 移除标签页
|
||||
localStorage.removeItem(TABS_ROUTES);
|
||||
router
|
||||
.replace({
|
||||
name: 'Login',
|
||||
query: {
|
||||
redirect: route.fullPath,
|
||||
},
|
||||
})
|
||||
.finally(() => location.reload());
|
||||
});
|
||||
};
|
||||
|
||||
|
@@ -4,7 +4,6 @@
|
||||
* 接收参数:string类型/Ref<string>类型/Reactive<string>类型
|
||||
*/
|
||||
import type { Directive, DirectiveBinding } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
interface ElType extends HTMLElement {
|
||||
copyData: string | number;
|
||||
__handleClick__: any;
|
||||
|
14
web/src/enums/deptEnum.ts
Normal file
14
web/src/enums/deptEnum.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// 部门类型
|
||||
export enum DeptTypeEnum {
|
||||
// 公司
|
||||
Company = 'company',
|
||||
|
||||
// 租户
|
||||
Tenant = 'tenant',
|
||||
|
||||
// 商户
|
||||
Merchant = 'merchant',
|
||||
|
||||
// 用户
|
||||
User = 'user',
|
||||
}
|
@@ -1,35 +1,3 @@
|
||||
import { Option } from '@/utils/hotgo';
|
||||
|
||||
export const switchOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '已开启',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '已关闭',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
|
||||
export const sexOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '男',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '女',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '未知',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
|
||||
export const statusOptions = [
|
||||
{
|
||||
value: 1,
|
||||
@@ -39,22 +7,7 @@ export const statusOptions = [
|
||||
value: 2,
|
||||
label: '停用',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
|
||||
export const hiddenOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '是',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '否',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
];
|
||||
|
||||
// 操作类
|
||||
export const statusActions = [
|
||||
@@ -67,47 +20,3 @@ 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',
|
||||
},
|
||||
];
|
||||
|
||||
// 登录状态
|
||||
export const loginStatusOptions: Option[] = [
|
||||
{
|
||||
value: 1,
|
||||
label: '成功',
|
||||
key: 1,
|
||||
listClass: 'success',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '失败',
|
||||
key: 2,
|
||||
listClass: 'warning',
|
||||
},
|
||||
];
|
||||
|
@@ -1,7 +0,0 @@
|
||||
export enum RoleEnum {
|
||||
// 管理员
|
||||
ADMIN = 'admin',
|
||||
|
||||
// 普通用户
|
||||
NORMAL = 'normal',
|
||||
}
|
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<div class="logo" v-if="navMode === 'horizontal'">
|
||||
<img src="~@/assets/images/logo.png" alt="" />
|
||||
<h2 v-show="!collapsed" class="title">HotGo</h2>
|
||||
<h2 v-show="!collapsed" class="title">{{ projectName }}</h2>
|
||||
</div>
|
||||
<AsideMenu
|
||||
@update:collapsed="updateMenu"
|
||||
@@ -135,7 +135,7 @@
|
||||
</div>
|
||||
<!-- 个人中心 -->
|
||||
<div class="layout-header-trigger layout-header-trigger-min">
|
||||
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
||||
<n-dropdown trigger="click" @select="avatarSelect" :options="avatarOptions" show-arrow>
|
||||
<div class="avatar">
|
||||
<n-avatar v-if="userStore.avatar" round :size="30" :src="userStore.avatar" />
|
||||
<n-avatar v-else round :size="30">{{ userStore.realName }}</n-avatar>
|
||||
@@ -187,6 +187,7 @@
|
||||
useNotification,
|
||||
NotificationReactive,
|
||||
NButton,
|
||||
NText,
|
||||
} from 'naive-ui';
|
||||
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
@@ -233,6 +234,7 @@
|
||||
|
||||
// const { username, avatar } = userStore?.info || {};
|
||||
const drawerSetting = ref();
|
||||
const projectName = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||
|
||||
const state = reactive({
|
||||
// username: username || '',
|
||||
@@ -373,7 +375,36 @@
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function renderCustomHeader() {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
style: 'display: flex; align-items: center; padding: 8px 12px;',
|
||||
},
|
||||
[
|
||||
h('div', null, [
|
||||
h('div', null, [
|
||||
h(NText, { depth: 2 }, { default: () => userStore?.info?.username }),
|
||||
]),
|
||||
h('div', { style: 'font-size: 12px;' }, [
|
||||
h(NText, { depth: 3 }, { default: () => userStore?.info?.roleName }),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
const avatarOptions = [
|
||||
{
|
||||
key: 'header',
|
||||
type: 'render',
|
||||
render: renderCustomHeader,
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
key: 'd1',
|
||||
},
|
||||
{
|
||||
label: '个人设置',
|
||||
key: 1,
|
||||
@@ -507,6 +538,7 @@
|
||||
getIsMobile,
|
||||
userStore,
|
||||
updateMenu,
|
||||
projectName,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -537,6 +569,7 @@
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding-left: 10px;
|
||||
min-width: 200px;
|
||||
|
||||
img {
|
||||
width: auto;
|
||||
@@ -546,6 +579,7 @@
|
||||
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
min-width: 132px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -172,10 +172,9 @@
|
||||
|
||||
const isMixMenuNoneSub = computed(() => {
|
||||
const mixMenu = settingStore.menuSetting.mixMenu;
|
||||
const currentRoute = useRoute();
|
||||
const navMode = unref(getNavMode);
|
||||
if (unref(navMode) != 'horizontal-mix') return true;
|
||||
return !(unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot);
|
||||
return !(unref(navMode) === 'horizontal-mix' && mixMenu && route.meta.isRoot);
|
||||
});
|
||||
|
||||
//动态组装样式 菜单缩进
|
||||
|
@@ -87,6 +87,7 @@
|
||||
getMultiTabsSetting,
|
||||
} = useProjectSetting();
|
||||
|
||||
const route = useRoute();
|
||||
const settingStore = useProjectSettingStore();
|
||||
|
||||
const navMode = getNavMode;
|
||||
@@ -105,11 +106,12 @@
|
||||
return fixed ? 'absolute' : 'static';
|
||||
});
|
||||
|
||||
|
||||
const isMixMenuNoneSub = computed(() => {
|
||||
const mixMenu = settingStore.menuSetting.mixMenu;
|
||||
const currentRoute = useRoute();
|
||||
// const currentRoute = useRoute();
|
||||
if (unref(navMode) != 'horizontal-mix') return true;
|
||||
if (unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot) {
|
||||
if (unref(navMode) === 'horizontal-mix' && mixMenu && route.meta.isRoot) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@@ -18,11 +18,13 @@ import {
|
||||
mobileLogin,
|
||||
} from '@/api/system/user';
|
||||
import { isWechatBrowser } from '@/utils/is';
|
||||
import { DeptTypeEnum } from '@/enums/deptEnum';
|
||||
const Storage = createStorage({ storage: localStorage });
|
||||
|
||||
export interface UserInfoState {
|
||||
id: number;
|
||||
deptName: string;
|
||||
deptType: string;
|
||||
roleName: string;
|
||||
cityLabel: string;
|
||||
permissions: string[];
|
||||
@@ -55,7 +57,7 @@ export interface ConfigState {
|
||||
domain: string;
|
||||
version: string;
|
||||
wsAddr: string;
|
||||
mode:string;
|
||||
mode: string;
|
||||
}
|
||||
|
||||
export interface LoginConfigState {
|
||||
@@ -114,6 +116,18 @@ export const useUserStore = defineStore({
|
||||
getLoginConfig(): LoginConfigState | null {
|
||||
return this.loginConfig;
|
||||
},
|
||||
isCompanyDept(): boolean {
|
||||
return this.info?.deptType == DeptTypeEnum.Company;
|
||||
},
|
||||
isTenantDept(): boolean {
|
||||
return this.info?.deptType == DeptTypeEnum.Tenant;
|
||||
},
|
||||
isMerchantDept(): boolean {
|
||||
return this.info?.deptType == DeptTypeEnum.Merchant;
|
||||
},
|
||||
isUserDept(): boolean {
|
||||
return this.info?.deptType == DeptTypeEnum.User;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setToken(token: string) {
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { Ref, UnwrapRef } from '@vue/reactivity';
|
||||
import onerrorImg from '@/assets/images/onerror.png';
|
||||
import { NTag, SelectRenderTag } from 'naive-ui';
|
||||
import { h } from 'vue';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { ActionItem } from '@/components/Table';
|
||||
import { isBoolean, isFunction } from '@/utils/is';
|
||||
import { PermissionsEnum } from '@/enums/permissionsEnum';
|
||||
|
||||
export interface Option {
|
||||
label: string;
|
||||
@@ -44,15 +45,50 @@ export function getOptionTag(options: Option[], value) {
|
||||
}
|
||||
|
||||
// 自适应模板宽度
|
||||
export function adaModalWidth(dialogWidth: Ref<UnwrapRef<string>>, def = 840) {
|
||||
export function adaModalWidth(def = 840) {
|
||||
const val = document.body.clientWidth;
|
||||
|
||||
if (val <= def) {
|
||||
dialogWidth.value = '100%';
|
||||
return '100%';
|
||||
} else {
|
||||
dialogWidth.value = def + 'px';
|
||||
return def + 'px';
|
||||
}
|
||||
return dialogWidth.value;
|
||||
}
|
||||
|
||||
interface TableColumn {
|
||||
width?: number | string;
|
||||
auth?: PermissionsEnum | PermissionsEnum[] | string | string[];
|
||||
ifShow?: boolean | ((action: ActionItem) => boolean);
|
||||
}
|
||||
|
||||
// 自适应表格组件横向滑动可见宽度
|
||||
export function adaTableScrollX(columns: TableColumn[] = [], actionWidth: number) {
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
let x = 50; // 勾选列宽度
|
||||
columns = columns.filter((column) => {
|
||||
return hasPermission(column.auth as string[]) && isIfShow(column);
|
||||
});
|
||||
for (const column of columns) {
|
||||
if (column.width && Number(column.width) >= 1) {
|
||||
x += Number(column.width);
|
||||
} else {
|
||||
x += 100; // 默认列宽度
|
||||
}
|
||||
}
|
||||
x += actionWidth;
|
||||
return x;
|
||||
}
|
||||
|
||||
export function isIfShow(action: ActionItem): boolean {
|
||||
let isIfShow = true;
|
||||
const ifShow = action.ifShow;
|
||||
if (isBoolean(ifShow)) {
|
||||
isIfShow = ifShow;
|
||||
}
|
||||
if (isFunction(ifShow)) {
|
||||
isIfShow = ifShow(action);
|
||||
}
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
// 图片加载失败显示自定义默认图片(缺省图)
|
||||
@@ -61,16 +97,6 @@ export function errorImg(e: any): void {
|
||||
e.target.onerror = null;
|
||||
}
|
||||
|
||||
export const renderTag: SelectRenderTag = ({ option }) => {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: option.listClass as 'success' | 'warning' | 'error' | 'info' | 'primary' | 'default',
|
||||
},
|
||||
{ default: () => option.label }
|
||||
);
|
||||
};
|
||||
|
||||
export function timeFix() {
|
||||
const time = new Date();
|
||||
const hour = time.getHours();
|
||||
@@ -98,3 +124,32 @@ export function rdmLightRgbColor(): string {
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
// 将列表数据转为树形数据
|
||||
export function convertListToTree(list: any[], idField = 'id', pidField = 'pid') {
|
||||
const min = list.reduce((prev, current) => (prev[pidField] < current[pidField] ? prev : current));
|
||||
|
||||
const map = list.reduce((acc, item) => {
|
||||
acc[item[idField]] = { ...item, children: [] };
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
list.forEach((item) => {
|
||||
if (item[pidField] !== min[pidField]) {
|
||||
map[item[pidField]].children.push(map[item[idField]]);
|
||||
}
|
||||
});
|
||||
return list.filter((item) => item[pidField] === min[pidField]).map((item) => map[item[idField]]);
|
||||
}
|
||||
|
||||
// 从树选项中获取所有key
|
||||
export function getTreeKeys(data: any[], idField = 'id') {
|
||||
const keys = [];
|
||||
data.map((item) => {
|
||||
keys.push(item[idField]);
|
||||
if (item.children && item.children.length) {
|
||||
keys.push(...getTreeKeys(item.children));
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ export function checkStatus(status: number, msg: string): void {
|
||||
$message.error('网络请求超时');
|
||||
break;
|
||||
case 500:
|
||||
$message.error('服务器错误,请联系管理员!');
|
||||
$message.error('服务器错误,请稍候重试!');
|
||||
break;
|
||||
case 501:
|
||||
$message.error('网络未实现');
|
||||
|
@@ -1,9 +1,21 @@
|
||||
import { h, unref } from 'vue';
|
||||
import type { App, Plugin } from 'vue';
|
||||
import { NIcon, NTag, NTooltip } from 'naive-ui';
|
||||
import {
|
||||
NAvatar,
|
||||
NBadge,
|
||||
NButton,
|
||||
NIcon,
|
||||
NPopover,
|
||||
NTable,
|
||||
NTag,
|
||||
NTooltip,
|
||||
SelectRenderTag,
|
||||
} from 'naive-ui';
|
||||
import { EllipsisHorizontalCircleOutline } from '@vicons/ionicons5';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
import { isObject } from './is/index';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { VNode } from '@vue/runtime-core';
|
||||
|
||||
export const renderTooltip = (trigger, content) => {
|
||||
return h(NTooltip, null, {
|
||||
@@ -38,6 +50,100 @@ export function renderNew(type = 'warning', text = 'New', color: object = newTag
|
||||
);
|
||||
}
|
||||
|
||||
// render 标记
|
||||
export function renderBadge(node: VNode) {
|
||||
return h(
|
||||
NBadge,
|
||||
{
|
||||
dot: true,
|
||||
type: 'info',
|
||||
},
|
||||
{ default: () => node }
|
||||
);
|
||||
}
|
||||
|
||||
// render 标签
|
||||
export const renderTag: SelectRenderTag = ({ option }) => {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: option.listClass as 'success' | 'warning' | 'error' | 'info' | 'primary' | 'default',
|
||||
},
|
||||
{ default: () => option.label }
|
||||
);
|
||||
};
|
||||
|
||||
export interface MemberSumma {
|
||||
id: number; // 用户ID
|
||||
realName: string; // 真实姓名
|
||||
username: string; // 用户名
|
||||
avatar: string; // 头像
|
||||
}
|
||||
|
||||
// render 操作人摘要
|
||||
export const renderPopoverMemberSumma = (member?: MemberSumma) => {
|
||||
if (!member) {
|
||||
return '';
|
||||
}
|
||||
return h(
|
||||
NPopover,
|
||||
{ trigger: 'hover' },
|
||||
{
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => member.realName, icon: renderIcon(EllipsisHorizontalCircleOutline) }
|
||||
),
|
||||
default: () =>
|
||||
h(
|
||||
NTable,
|
||||
{
|
||||
props: {
|
||||
bordered: false,
|
||||
'single-line': false,
|
||||
size: 'small',
|
||||
},
|
||||
},
|
||||
[
|
||||
h('thead', [
|
||||
h('tr', { align: 'center' }, [
|
||||
h('th', '用户ID'),
|
||||
h('th', '头像'),
|
||||
h('th', '姓名'),
|
||||
h('th', '用户名'),
|
||||
]),
|
||||
]),
|
||||
h('tbody', [
|
||||
h('tr', { align: 'center' }, [
|
||||
h('td', member.id),
|
||||
h('td', h(NAvatar, { src: member.avatar, round: true, size: 'small' })),
|
||||
h('td', member.realName),
|
||||
h('td', member.username),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// render html
|
||||
export function renderHtmlTooltip(content: string) {
|
||||
content = content.replace(/\n/g, '<br>');
|
||||
const html = h('p', { id: 'app' }, [
|
||||
h('div', {
|
||||
innerHTML: content,
|
||||
}),
|
||||
]);
|
||||
return renderTooltip(html, html);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归组装菜单格式
|
||||
*/
|
||||
@@ -174,35 +280,6 @@ export const withInstall = <T>(component: T, alias?: string) => {
|
||||
return component as T & Plugin;
|
||||
};
|
||||
|
||||
/**
|
||||
* 找到对应的节点
|
||||
* */
|
||||
let result = null;
|
||||
|
||||
export function getTreeItem(data: any[], key?: string | number): any {
|
||||
data.map((item) => {
|
||||
if (item.key === key) {
|
||||
result = item;
|
||||
} else {
|
||||
if (item.children && item.children.length) {
|
||||
getTreeItem(item.children, key);
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getTreeAll(data: any[]): any[] {
|
||||
const treeAll: any[] = [];
|
||||
data.map((item) => {
|
||||
treeAll.push(item.key);
|
||||
if (item.children && item.children.length) {
|
||||
treeAll.push(...getTreeAll(item.children));
|
||||
}
|
||||
});
|
||||
return treeAll;
|
||||
}
|
||||
|
||||
// dynamic use hook props
|
||||
export function getDynamicProps<T, U>(props: T): Partial<U> {
|
||||
const ret: Recordable = {};
|
||||
@@ -269,15 +346,18 @@ export function getAllExpandKeys(treeData: any): any[] {
|
||||
return expandedKeys;
|
||||
}
|
||||
|
||||
// 从树中查找指定ID
|
||||
export function findTreeDataById(data: any[], id: number | string) {
|
||||
// 从树中查找指定节点
|
||||
export function findTreeNode(data: any[], key?: string | number, keyField = 'key'): any {
|
||||
for (const item of data) {
|
||||
if (item.id === id) {
|
||||
if (item[keyField] == key) {
|
||||
return item;
|
||||
}
|
||||
if (item.children) {
|
||||
const found = findTreeDataById(item.children, id);
|
||||
if (found) return found;
|
||||
} else {
|
||||
if (item.children && item.children.length) {
|
||||
const foundItem = findTreeNode(item.children, key);
|
||||
if (foundItem) {
|
||||
return foundItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@@ -150,7 +150,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { rules, options, State, newState } from './model';
|
||||
import { Edit, MaxSort } from '@/api/addons/hgexample/table';
|
||||
import { useMessage } from 'naive-ui';
|
||||
@@ -190,8 +190,10 @@
|
||||
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -230,10 +232,6 @@
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
|
@@ -27,7 +27,7 @@
|
||||
:actionColumn="actionColumn"
|
||||
:checked-row-keys="checkedIds"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
@update:sorter="handleUpdateSorter"
|
||||
@@ -75,7 +75,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
@@ -84,7 +84,7 @@
|
||||
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 { adaTableScrollX, getOptionLabel } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -102,7 +102,7 @@
|
||||
width: 300,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
@@ -161,6 +161,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
|
76
web/src/views/addons/hgexample/tenantOrder/alert.vue
Normal file
76
web/src/views/addons/hgexample/tenantOrder/alert.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<n-alert :show-icon="false" title="说明">
|
||||
<n-p
|
||||
>这里主要演示多租户业务下,不同用户身份如何在同一页面下展示不同的表格功能和字段数据,以及添加/编辑购买订单时服务端如何自动维护多租户关系</n-p
|
||||
>
|
||||
<n-p style="font-weight: 600">不同身份的测试账号</n-p>
|
||||
<n-table :bordered="false" :single-line="false" size="small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-center">身份</th>
|
||||
<th class="table-center">ID</th>
|
||||
<th class="table-center">账号</th>
|
||||
<th class="table-center">密码</th>
|
||||
<th>身份描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="account in accounts" :key="account.id">
|
||||
<td class="table-center">{{ account.type }}</td>
|
||||
<td class="table-center">{{ account.id }}</td>
|
||||
<td class="table-center">{{ account.username }}</td>
|
||||
<td class="table-center">{{ account.password }}</td>
|
||||
<td>{{ account.dc }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
</n-alert>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Account {
|
||||
type: string;
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
dc: string;
|
||||
}
|
||||
|
||||
const accounts: Account[] = [
|
||||
{
|
||||
type: '公司',
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
dc: '可见全部数据。管理整个平台,包括商户和用户账户',
|
||||
},
|
||||
{
|
||||
type: '租户',
|
||||
id: 8,
|
||||
username: 'ameng',
|
||||
password: '123456',
|
||||
dc: '可见自己下面的商户和用户数据。多租户系统中顶层实体,有自己的多个商户、用户、产品、订单等',
|
||||
},
|
||||
{
|
||||
type: '商户',
|
||||
id: 11,
|
||||
username: 'abai',
|
||||
password: '123456',
|
||||
dc: '可见自己下面的用户数据。受租户的监管和管理,可以独立经营的实体,提供产品或服务,管理自己的业务,包括库存管理、订单处理、结算等',
|
||||
},
|
||||
{
|
||||
type: '用户',
|
||||
id: 12,
|
||||
username: 'asong',
|
||||
password: '123456',
|
||||
dc: '只能看到自己数据。真正购买产品或享受服务的人,与商户互动,管理个人信息等个性化功能',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.table-center {
|
||||
text-align: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
</style>
|
163
web/src/views/addons/hgexample/tenantOrder/edit.vue
Normal file
163
web/src/views/addons/hgexample/tenantOrder/edit.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
:title="formValue.id > 0 ? '编辑购买订单 #' + formValue.id : '添加购买订单'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
:label-placement="settingStore.isMobile ? 'top' : 'left'"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-grid cols="1 s:1 m:1 l:1 xl:1 2xl:1" responsive="screen">
|
||||
<n-gi span="1" v-if="userStore.isCompanyDept">
|
||||
<n-form-item label="租户ID" path="tenantId">
|
||||
<n-input placeholder="请输入租户ID" v-model:value="formValue.tenantId" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1" v-if="userStore.isCompanyDept || userStore.isTenantDept">
|
||||
<n-form-item label="商户ID" path="merchantId">
|
||||
<n-input placeholder="请输入商户ID" v-model:value="formValue.merchantId" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi
|
||||
span="1"
|
||||
v-if="userStore.isCompanyDept || userStore.isTenantDept || userStore.isMerchantDept"
|
||||
>
|
||||
<n-form-item label="用户ID" path="userId">
|
||||
<n-input placeholder="请输入用户ID" v-model:value="formValue.userId" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="购买产品" path="productName">
|
||||
<n-input placeholder="请输入购买产品" v-model:value="formValue.productName" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="关联订单号" path="orderSn">
|
||||
<n-input placeholder="请输入关联订单号" v-model:value="formValue.orderSn" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="充值金额" path="money">
|
||||
<n-input-group>
|
||||
<n-input-number
|
||||
:min="1"
|
||||
:show-button="false"
|
||||
style="width: 100%"
|
||||
placeholder="请输入充值金额"
|
||||
v-model:value="formValue.money"
|
||||
/>
|
||||
<n-input-group-label>元</n-input-group-label>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input placeholder="请输入备注" v-model:value="formValue.remark" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="支付状态" path="status">
|
||||
<n-select v-model:value="formValue.status" :options="options.payStatus" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-spin>
|
||||
</n-scrollbar>
|
||||
<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 { ref, computed } from 'vue';
|
||||
import { Edit, View } from '@/api/addons/hgexample/tenantOrder';
|
||||
import { options, State, newState, rules } from './model';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const message = useMessage();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const userStore = useUserStore();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref<State>(newState(null));
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(840);
|
||||
});
|
||||
|
||||
function openModal(state: State) {
|
||||
showModal.value = true;
|
||||
|
||||
// 新增
|
||||
if (!state || state.id < 1) {
|
||||
formValue.value = newState(state);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(formValue.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
closeForm();
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
146
web/src/views/addons/hgexample/tenantOrder/index.vue
Normal file
146
web/src/views/addons/hgexample/tenantOrder/index.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="多租户功能演示">
|
||||
<Alert />
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
ref="searchFormRef"
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
<BasicTable
|
||||
ref="actionRef"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="addTable"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/hgexample/tenantOrder/edit'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加购买订单
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Edit ref="editRef" @reloadTable="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, computed, onMounted } 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 { List, Delete } from '@/api/addons/hgexample/tenantOrder';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { columns, schemas, loadOptions } from './model';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
import Alert from './alert.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const searchFormRef = ref<any>({});
|
||||
const editRef = ref();
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 144,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['/hgexample/tenantOrder/edit'],
|
||||
},
|
||||
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/hgexample/tenantOrder/delete'],
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
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 reloadTable() {
|
||||
actionRef.value?.reload();
|
||||
}
|
||||
|
||||
// 添加数据
|
||||
function addTable() {
|
||||
editRef.value.openModal(null);
|
||||
}
|
||||
|
||||
// 编辑数据
|
||||
function handleEdit(record: Recordable) {
|
||||
editRef.value.openModal(record);
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
243
web/src/views/addons/hgexample/tenantOrder/model.ts
Normal file
243
web/src/views/addons/hgexample/tenantOrder/model.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { Option, getOptionLabel, getOptionTag } from '@/utils/hotgo';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
export class State {
|
||||
public id = 0; // 主键
|
||||
public tenantId = null; // 租户ID
|
||||
public merchantId = null; // 商户ID
|
||||
public userId = null; // 用户ID
|
||||
public productName = ''; // 购买产品
|
||||
public orderSn = ''; // 关联订单号
|
||||
public money = null; // 充值金额
|
||||
public remark = ''; // 备注
|
||||
public status = 1; // 订单状态
|
||||
public createdAt = ''; // 创建时间
|
||||
public updatedAt = ''; // 修改时间
|
||||
|
||||
constructor(state?: Partial<State>) {
|
||||
if (state) {
|
||||
Object.assign(this, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function newState(state: State | Record<string, any> | null): State {
|
||||
if (state !== null) {
|
||||
if (state instanceof State) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return new State(state);
|
||||
}
|
||||
return new State();
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const rules = {
|
||||
money: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'number',
|
||||
message: '请输入充值金额',
|
||||
},
|
||||
};
|
||||
|
||||
// 表格搜索表单
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'tenantId',
|
||||
component: 'NInput',
|
||||
label: '租户ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入租户ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'merchantId',
|
||||
component: 'NInput',
|
||||
label: '商户ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入商户ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept || userStore.isTenantDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'userId',
|
||||
component: 'NInput',
|
||||
label: '用户ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入用户ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept || userStore.isTenantDept || userStore.isMerchantDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'orderSn',
|
||||
component: 'NInput',
|
||||
label: '订单号',
|
||||
componentProps: {
|
||||
placeholder: '请输入订单号',
|
||||
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);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 表格列
|
||||
export const columns = [
|
||||
{
|
||||
title: '订单ID',
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '租户ID',
|
||||
key: 'tenantId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '商户ID',
|
||||
key: 'merchantId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept || userStore.isTenantDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept || userStore.isTenantDept || userStore.isMerchantDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '购买产品',
|
||||
key: 'productName',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '订单号',
|
||||
key: 'orderSn',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '充值金额',
|
||||
key: 'money',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return row.money + ' 元';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '订单状态',
|
||||
key: 'status',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (isNullObject(row.status)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.payStatus, row.status),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(options.value.payStatus, row.status),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
// 字典数据选项
|
||||
export const options = ref({
|
||||
payStatus: [] as Option[],
|
||||
});
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
Dicts({
|
||||
types: ['payStatus'],
|
||||
}).then((res) => {
|
||||
options.value = res;
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.payStatus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@@ -200,8 +200,10 @@
|
||||
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -240,10 +242,6 @@
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
|
@@ -130,7 +130,6 @@
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@vicons/antd';
|
||||
import { getTreeItem } from '@/utils';
|
||||
import List from './list.vue';
|
||||
import { Delete, Select } from '@/api/addons/hgexample/treeTable';
|
||||
import Edit from './edit.vue';
|
||||
@@ -158,9 +157,9 @@
|
||||
showModal.value = true;
|
||||
}
|
||||
|
||||
function selectedTree(keys) {
|
||||
function selectedTree(keys, opts) {
|
||||
if (keys.length) {
|
||||
const treeItem = getTreeItem(unref(treeData), keys[0]);
|
||||
const treeItem = opts[0];
|
||||
treeItemKey.value = keys;
|
||||
treeItemTitle.value = treeItem.label;
|
||||
formParams.value = newState(treeItem);
|
||||
|
@@ -30,7 +30,7 @@
|
||||
:actionColumn="actionColumn"
|
||||
:checked-row-keys="checkedIds"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
@update:sorter="handleUpdateSorter"
|
||||
@@ -81,7 +81,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, watch } from 'vue';
|
||||
import { computed, h, reactive, ref, watch } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
@@ -89,7 +89,7 @@
|
||||
import { Delete, List, Status, Export } from '@/api/addons/hgexample/treeTable';
|
||||
import { State, columns, schemas, options, newState } from './model';
|
||||
import { DeleteOutlined, PlusOutlined, ExportOutlined } from '@vicons/antd';
|
||||
import { getOptionLabel } from '@/utils/hotgo';
|
||||
import { adaTableScrollX, getOptionLabel } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
|
||||
interface Props {
|
||||
@@ -111,10 +111,10 @@
|
||||
const pid = ref(0);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 300,
|
||||
width: 200,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
@@ -146,6 +146,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
|
@@ -195,7 +195,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, onMounted, reactive, ref } from 'vue';
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
@@ -218,12 +218,12 @@
|
||||
renderLabel,
|
||||
renderMultipleSelectTag,
|
||||
} from '@/enums/systemMessageEnum';
|
||||
import { adaModalWidth, getOptionLabel, renderTag } from '@/utils/hotgo';
|
||||
import { adaModalWidth, getOptionLabel } from '@/utils/hotgo';
|
||||
import { renderTag } from '@/utils';
|
||||
import Editor from '@/components/Editor/editor.vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { GetMemberOption } from '@/api/org/user';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const rules = {
|
||||
title: {
|
||||
@@ -286,6 +286,7 @@
|
||||
},
|
||||
];
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
@@ -295,8 +296,10 @@
|
||||
const formRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const dialogWidth = ref('75%');
|
||||
const options = ref<personOption[]>();
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
const resetFormParams = {
|
||||
id: 0,
|
||||
@@ -312,7 +315,7 @@
|
||||
let formParams = ref<any>(cloneDeep(resetFormParams));
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 180,
|
||||
width: 200,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
@@ -440,9 +443,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -458,9 +458,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -486,7 +483,6 @@
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
await getMemberOption();
|
||||
});
|
||||
</script>
|
||||
|
@@ -79,7 +79,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { options, State, newState } from './model';
|
||||
import { Edit, MaxSort, CheckProvincesUniqueId } from '@/api/apply/provinces';
|
||||
import { FormItemRule, useMessage } from 'naive-ui';
|
||||
@@ -140,8 +140,10 @@
|
||||
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -182,10 +184,6 @@
|
||||
const res = await CheckProvincesUniqueId({ oldId: params.value.oldId, newId: newId });
|
||||
return res.unique;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
|
@@ -127,11 +127,11 @@
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@vicons/antd';
|
||||
import { getTreeItem } from '@/utils';
|
||||
import List from './list.vue';
|
||||
import { getProvincesTree, Delete } from '@/api/apply/provinces';
|
||||
import Edit from './edit.vue';
|
||||
import { newState } from './model';
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const showModal = ref(false);
|
||||
const message = useMessage();
|
||||
@@ -157,9 +157,9 @@
|
||||
isUpdate.value = true;
|
||||
}
|
||||
|
||||
function selectedTree(keys) {
|
||||
function selectedTree(keys, opts) {
|
||||
if (keys.length) {
|
||||
const treeItem = getTreeItem(unref(treeData), keys[0]);
|
||||
const treeItem = opts[0];
|
||||
treeItemKey.value = keys;
|
||||
treeItemTitle.value = treeItem.label;
|
||||
formParams.value = newState(treeItem);
|
||||
|
@@ -26,7 +26,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
@@ -54,7 +54,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, watch } from 'vue';
|
||||
import { h, reactive, ref, watch, computed } from 'vue';
|
||||
import { useMessage, useDialog } from 'naive-ui';
|
||||
import { BasicColumn, BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
@@ -62,6 +62,8 @@
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { getProvincesChildrenList, Delete } from '@/api/apply/provinces';
|
||||
import Edit from './edit.vue';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
|
||||
interface Props {
|
||||
@@ -111,10 +113,10 @@
|
||||
];
|
||||
|
||||
const actionColumn = reactive<BasicColumn>({
|
||||
width: 220,
|
||||
width: 150,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
@@ -132,6 +134,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(listColumns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:1 l:2 xl:2 2xl:2' },
|
||||
labelWidth: 80,
|
||||
|
@@ -19,7 +19,8 @@
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable" class="min-left-space">
|
||||
@@ -56,7 +57,6 @@
|
||||
</n-alert>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
@@ -150,7 +150,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
@@ -162,6 +162,7 @@
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getUserInfo } from '@/api/system/user';
|
||||
import { getCashConfig } from '@/api/sys/config';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
interface Props {
|
||||
type?: string;
|
||||
@@ -170,8 +171,8 @@
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: '',
|
||||
});
|
||||
const router = useRouter();
|
||||
|
||||
const router = useRouter();
|
||||
const params = ref<any>({
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
@@ -179,8 +180,6 @@
|
||||
status: null,
|
||||
});
|
||||
|
||||
const rules = {};
|
||||
|
||||
const estimated = ref(
|
||||
'本次提现预计将在 ' +
|
||||
timestampToTime(new Date().setTime(new Date().getTime() + 86400 * 4 * 1000) / 1000) +
|
||||
@@ -276,6 +275,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
function setCash() {
|
||||
router.push({
|
||||
name: 'home_account',
|
||||
@@ -334,9 +337,6 @@
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
})
|
||||
.catch((_e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
@@ -366,9 +366,6 @@
|
||||
reloadTable();
|
||||
PaymentRef.value = ref(resetPaymentParams);
|
||||
});
|
||||
})
|
||||
.catch((_e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
|
@@ -19,7 +19,7 @@
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
@@ -44,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
@@ -52,6 +52,7 @@
|
||||
import { List, Export } from '@/api/creditsLog';
|
||||
import { columns, schemas } from './model';
|
||||
import { ExportOutlined } from '@vicons/antd';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
interface Props {
|
||||
type?: string;
|
||||
@@ -61,6 +62,10 @@
|
||||
type: '',
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, 0);
|
||||
});
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const message = useMessage();
|
||||
|
@@ -63,7 +63,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { rules, State, newState, options } from './model';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
@@ -95,8 +95,10 @@
|
||||
const params = ref<State>(props.formParams);
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -117,10 +119,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
@@ -46,18 +46,19 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { rules, State, newState } from './model';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { ApplyRefund, View } from '@/api/order';
|
||||
const emit = defineEmits(['reloadTable', 'updateShowModal']);
|
||||
|
||||
interface Props {
|
||||
showModal: boolean;
|
||||
formParams?: State;
|
||||
}
|
||||
|
||||
const emit = defineEmits(['reloadTable', 'updateShowModal']);
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showModal: false,
|
||||
formParams: () => {
|
||||
@@ -78,8 +79,10 @@
|
||||
const params = ref<State>(props.formParams);
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -100,10 +103,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
@@ -74,7 +74,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
@@ -84,6 +84,8 @@
|
||||
import { ExportOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import ApplyRefund from './applyRefund.vue';
|
||||
import AcceptRefund from './acceptRefund.vue';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
interface Props {
|
||||
type?: string;
|
||||
}
|
||||
@@ -142,6 +144,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
|
@@ -1,89 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
:title="formValue.id > 0 ? '编辑 #' + formValue.id : '添加'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
:title="formValue.id > 0 ? '编辑CURD列表 #' + formValue.id : '添加CURD列表'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
:label-placement="settingStore.isMobile ? 'top' : 'left'"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="分类ID" path="categoryId">
|
||||
<n-input-number placeholder="请输入分类ID" v-model:value="formValue.categoryId" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input placeholder="请输入标题" v-model:value="formValue.title" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input type="textarea" placeholder="描述" v-model:value="formValue.description" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="内容" path="content">
|
||||
<Editor style="height: 450px" id="content" v-model:value="formValue.content" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="单图" path="image">
|
||||
<UploadImage :maxNumber="1" v-model:value="formValue.image" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="附件" path="attachfile">
|
||||
<UploadFile :maxNumber="1" v-model:value="formValue.attachfile" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="所在城市" path="cityId">
|
||||
<CitySelector v-model:value="formValue.cityId" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="显示开关" path="switch">
|
||||
<n-switch :unchecked-value="2" :checked-value="1" v-model:value="formValue.switch"
|
||||
<n-grid cols="1 s:1 m:2 l:2 xl:2 2xl:2" responsive="screen">
|
||||
<n-gi span="1">
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input placeholder="请输入标题" v-model:value="formValue.title" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input type="textarea" placeholder="描述" v-model:value="formValue.description" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="内容" path="content">
|
||||
<Editor style="height: 450px" id="content" v-model:value="formValue.content" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="单图" path="image">
|
||||
<UploadImage :maxNumber="1" v-model:value="formValue.image" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="附件" path="attachfile">
|
||||
<UploadFile :maxNumber="1" v-model:value="formValue.attachfile" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="所在城市" path="cityId">
|
||||
<CitySelector v-model:value="formValue.cityId" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number placeholder="请输入排序" v-model:value="formValue.sort" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="显示开关" path="switch">
|
||||
<n-switch :unchecked-value="2" :checked-value="1" v-model:value="formValue.switch"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number placeholder="请输入排序" v-model:value="formValue.sort" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-select v-model:value="formValue.status" :options="options.sys_normal_disable" />
|
||||
</n-form-item>
|
||||
|
||||
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-select v-model:value="formValue.status" :options="options.sys_normal_disable" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="测试分类" path="categoryId">
|
||||
<n-select v-model:value="formValue.categoryId" :options="options.testCategoryOption" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-scrollbar>
|
||||
<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>
|
||||
</n-spin>
|
||||
</n-spin>
|
||||
</n-scrollbar>
|
||||
<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 { ref } from 'vue';
|
||||
import { Edit, MaxSort, View } from '@/api/curdDemo';
|
||||
import { ref, computed } from 'vue';
|
||||
import { Edit, View, MaxSort } from '@/api/curdDemo';
|
||||
import { options, State, newState, rules } from './model';
|
||||
import Editor from '@/components/Editor/editor.vue';
|
||||
import UploadImage from '@/components/Upload/uploadImage.vue';
|
||||
import UploadFile from '@/components/Upload/uploadFile.vue';
|
||||
import CitySelector from '@/components/CitySelector/citySelector.vue';
|
||||
import { rules, options, State, newState } from './model';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
@@ -91,12 +106,43 @@
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const message = useMessage();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const dialogWidth = ref('75%');
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref<State>(newState(null));
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(840);
|
||||
});
|
||||
|
||||
function openModal(state: State) {
|
||||
showModal.value = true;
|
||||
|
||||
// 新增
|
||||
if (!state || state.id < 1) {
|
||||
formValue.value = newState(state);
|
||||
|
||||
loading.value = true;
|
||||
MaxSort()
|
||||
.then((res) => {
|
||||
formValue.value.sort = res.sort;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -106,7 +152,7 @@
|
||||
Edit(formValue.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
closeForm();
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
@@ -122,37 +168,9 @@
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function openModal(state: State) {
|
||||
adaModalWidth(dialogWidth);
|
||||
showModal.value = true;
|
||||
loading.value = true;
|
||||
|
||||
// 新增
|
||||
if (!state || state.id < 1) {
|
||||
formValue.value = newState(state);
|
||||
MaxSort()
|
||||
.then((res) => {
|
||||
formValue.value.sort = res.sort;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
<style lang="less"></style>
|
||||
|
@@ -1,44 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="生成演示">
|
||||
<n-card :bordered="false" title="CURD列表">
|
||||
<!-- 这是由系统生成的CURD表格,你可以将此行注释改为表格的描述 -->
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<BasicForm ref="searchFormRef" @register="register" @submit="reloadTable" @reset="reloadTable" @keyup.enter="reloadTable">
|
||||
<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"
|
||||
:checked-row-keys="checkedIds"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
<BasicTable ref="actionRef" openChecked :columns="columns" :request="loadDataTable" :row-key="(row) => row.id" :actionColumn="actionColumn" :scroll-x="scrollX" :resizeHeightOffset="-10000" :checked-row-keys="checkedIds" @update:checked-row-keys="handleOnCheckedRow">
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="addTable"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/curdDemo/edit'])"
|
||||
>
|
||||
<n-button type="primary" @click="addTable" class="min-left-space" v-if="hasPermission(['/curdDemo/edit'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
@@ -46,13 +21,7 @@
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button
|
||||
type="error"
|
||||
@click="handleBatchDelete"
|
||||
:disabled="batchDeleteDisabled"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/curdDemo/delete'])"
|
||||
>
|
||||
<n-button type="error" @click="handleBatchDelete" class="min-left-space" v-if="hasPermission(['/curdDemo/delete'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
@@ -60,12 +29,7 @@
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="handleExport"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/curdDemo/export'])"
|
||||
>
|
||||
<n-button type="primary" @click="handleExport" class="min-left-space" v-if="hasPermission(['/curdDemo/export'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
@@ -76,39 +40,36 @@
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Edit @reloadTable="reloadTable" ref="editRef" />
|
||||
<View ref="viewRef" />
|
||||
<Edit ref="editRef" @reloadTable="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { h, reactive, ref, computed, onMounted } 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 { List, Export, Delete, Status } from '@/api/curdDemo';
|
||||
import { columns, schemas, options } from './model';
|
||||
import { List, Export, Delete } from '@/api/curdDemo';
|
||||
import { PlusOutlined, ExportOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import { getOptionLabel } from '@/utils/hotgo';
|
||||
import { columns, schemas, loadOptions } from './model';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
import View from './view.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const searchFormRef = ref<any>({});
|
||||
const viewRef = ref();
|
||||
const editRef = ref();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 300,
|
||||
width: 144,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
@@ -118,75 +79,54 @@
|
||||
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 scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
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 onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
// 更新选中的行
|
||||
function handleOnCheckedRow(rowKeys) {
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
// 重新加载表格数据
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
actionRef.value?.reload();
|
||||
}
|
||||
|
||||
// 添加数据
|
||||
function addTable() {
|
||||
editRef.value.openModal(null);
|
||||
}
|
||||
|
||||
// 编辑数据
|
||||
function handleEdit(record: Recordable) {
|
||||
editRef.value.openModal(record);
|
||||
}
|
||||
|
||||
function handleView(record: Recordable) {
|
||||
viewRef.value.openModal(record);
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
@@ -202,7 +142,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
function handleBatchDelete() {
|
||||
if (checkedIds.value.length < 1){
|
||||
message.error('请至少选择一项要删除的数据');
|
||||
return;
|
||||
}
|
||||
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
@@ -210,7 +156,6 @@
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
batchDeleteDisabled.value = true;
|
||||
checkedIds.value = [];
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
@@ -219,19 +164,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 导出
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -1,42 +1,44 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NAvatar, NImage, NTag, NSwitch, NRate } from 'naive-ui';
|
||||
import { NImage, NAvatar, NSwitch, NTag } 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 { isNullObject } from '@/utils/is';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
import { getOptionLabel, getOptionTag, Option, Options, errorImg } from '@/utils/hotgo';
|
||||
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { Option, errorImg, getOptionLabel, getOptionTag } from '@/utils/hotgo';
|
||||
import { renderPopoverMemberSumma, MemberSumma } from '@/utils';
|
||||
import { Switch } from '@/api/curdDemo';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
const { hasPermission } = usePermission();
|
||||
const $message = window['$message'];
|
||||
|
||||
export class State {
|
||||
public id = 0; // ID
|
||||
public categoryId = 0; // 分类ID
|
||||
public title = ''; // 标题
|
||||
public description = ''; // 描述
|
||||
public content = ''; // 内容
|
||||
public image = ''; // 单图
|
||||
public attachfile = ''; // 附件
|
||||
public cityId = 0; // 所在城市
|
||||
public switch = 2; // 显示开关
|
||||
public cityId = null; // 所在城市
|
||||
public sort = 0; // 排序
|
||||
public switch = 2; // 显示开关
|
||||
public status = 1; // 状态
|
||||
public createdBy = 0; // 创建者
|
||||
public updatedBy = 0; // 更新者
|
||||
public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
|
||||
public createdAt = ''; // 创建时间
|
||||
public updatedBy = 0; // 更新者
|
||||
public updatedBySumma?: null | MemberSumma = null; // 更新者摘要信息
|
||||
public updatedAt = ''; // 修改时间
|
||||
public deletedAt = ''; // 删除时间
|
||||
public categoryId = null; // 测试分类
|
||||
|
||||
constructor(state?: Partial<State>) {
|
||||
if (state) {
|
||||
Object.assign(this, state);
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
export function newState(state: State | Record<string, any> | null): State {
|
||||
if (state !== null) {
|
||||
@@ -48,21 +50,8 @@ export function newState(state: State | Record<string, any> | null): State {
|
||||
return new State();
|
||||
}
|
||||
|
||||
export interface IOptions extends Options {
|
||||
sys_normal_disable: Option[];
|
||||
};
|
||||
|
||||
export const options = ref<IOptions>({
|
||||
sys_normal_disable: [],
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
export const rules = {
|
||||
categoryId: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'number',
|
||||
message: '请输入分类ID',
|
||||
},
|
||||
title: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
@@ -87,12 +76,19 @@ export const rules = {
|
||||
type: 'number',
|
||||
message: '请输入排序',
|
||||
},
|
||||
categoryId: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'number',
|
||||
message: '请输入测试分类',
|
||||
},
|
||||
};
|
||||
|
||||
// 表格搜索表单
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'id',
|
||||
component: 'NInputNumber',
|
||||
component: 'NInput',
|
||||
label: 'ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入ID',
|
||||
@@ -101,6 +97,28 @@ export const schemas = ref<FormSchema[]>([
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '标题',
|
||||
componentProps: {
|
||||
placeholder: '请输入标题',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
component: 'NInput',
|
||||
label: '描述',
|
||||
componentProps: {
|
||||
placeholder: '请输入描述',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
@@ -114,6 +132,17 @@ export const schemas = ref<FormSchema[]>([
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdBy',
|
||||
component: 'NInput',
|
||||
label: '创建者',
|
||||
componentProps: {
|
||||
placeholder: '请输入ID|用户名|姓名|手机号',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
@@ -127,12 +156,25 @@ export const schemas = ref<FormSchema[]>([
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'categoryId',
|
||||
component: 'NSelect',
|
||||
label: '测试分类',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择测试分类',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'testCategoryName',
|
||||
component: 'NInput',
|
||||
label: '分类名称',
|
||||
label: '关联分类',
|
||||
componentProps: {
|
||||
placeholder: '请输入分类名称',
|
||||
placeholder: '请输入关联分类',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
@@ -140,31 +182,37 @@ export const schemas = ref<FormSchema[]>([
|
||||
},
|
||||
]);
|
||||
|
||||
// 表格列
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '分类ID',
|
||||
key: 'categoryId',
|
||||
align: 'left',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description',
|
||||
align: 'left',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: '单图',
|
||||
key: 'image',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NImage, {
|
||||
width: 32,
|
||||
height: 32,
|
||||
src: row.image,
|
||||
fallbackSrc: errorImg,
|
||||
onError: errorImg,
|
||||
style: {
|
||||
width: '32px',
|
||||
@@ -178,6 +226,8 @@ export const columns = [
|
||||
{
|
||||
title: '附件',
|
||||
key: 'attachfile',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (row.attachfile === '') {
|
||||
return ``;
|
||||
@@ -194,13 +244,16 @@ export const columns = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '所在城市',
|
||||
key: 'cityId',
|
||||
title: '排序',
|
||||
key: 'sort',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '显示开关',
|
||||
key: 'switch',
|
||||
width: 100,
|
||||
align: 'left',
|
||||
width: 150,
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.switch === 1,
|
||||
@@ -217,13 +270,11 @@ export const columns = [
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sort',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (isNullObject(row.status)) {
|
||||
return ``;
|
||||
@@ -246,38 +297,86 @@ export const columns = [
|
||||
{
|
||||
title: '创建者',
|
||||
key: 'createdBy',
|
||||
},
|
||||
{
|
||||
title: '更新者',
|
||||
key: 'updatedBy',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
render(row) {
|
||||
return renderPopoverMemberSumma(row.createdBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '更新者',
|
||||
key: 'updatedBy',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
render(row) {
|
||||
return renderPopoverMemberSumma(row.updatedBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
key: 'updatedAt',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '分类名称',
|
||||
title: '测试分类',
|
||||
key: 'categoryId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (isNullObject(row.categoryId)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.testCategoryOption, row.categoryId),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(options.value.testCategoryOption, row.categoryId),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '关联分类',
|
||||
key: 'testCategoryName',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 字典数据选项
|
||||
export const options = ref({
|
||||
sys_normal_disable: [] as Option[],
|
||||
testCategoryOption: [] as Option[],
|
||||
});
|
||||
|
||||
await loadOptions();
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
Dicts({
|
||||
types: ['sys_normal_disable', 'testCategoryOption'],
|
||||
}).then((res) => {
|
||||
options.value = res;
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.sys_normal_disable;
|
||||
break;
|
||||
case 'categoryId':
|
||||
item.componentProps.options = options.value.testCategoryOption;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@@ -1,81 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-drawer v-model:show="showModal" :width="dialogWidth">
|
||||
<n-drawer-content>
|
||||
<template #header> 生成演示详情 </template>
|
||||
<template #footer>
|
||||
<n-button @click="showModal = false"> 关闭 </n-button>
|
||||
</template>
|
||||
<n-drawer v-model:show="showModal" :width="dialogWidth">
|
||||
<n-drawer-content title="CURD列表详情" closable>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-descriptions label-placement="left" class="py-2" column="1">
|
||||
<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>
|
||||
<n-descriptions-item label="测试分类">
|
||||
<n-tag :type="getOptionTag(options.testCategoryOption, formValue?.categoryId)" size="small" class="min-left-space">
|
||||
{{ getOptionLabel(options.testCategoryOption, formValue?.categoryId) }}
|
||||
</n-tag>
|
||||
</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>
|
||||
{{ formValue.cityId }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
排序
|
||||
</template>
|
||||
{{ formValue.sort }}
|
||||
</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 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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item>
|
||||
<template #label>所在城市</template>
|
||||
{{ formValue.cityId }}
|
||||
</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="状态">
|
||||
<n-tag
|
||||
:type="getOptionTag(options.sys_normal_disable, formValue?.status)"
|
||||
size="small"
|
||||
class="min-left-space"
|
||||
>{{ getOptionLabel(options.sys_normal_disable, formValue?.status) }}</n-tag
|
||||
>
|
||||
</n-descriptions-item>
|
||||
|
||||
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="状态">
|
||||
<n-tag :type="getOptionTag(options.sys_normal_disable, formValue?.status)" size="small" class="min-left-space">
|
||||
{{ getOptionLabel(options.sys_normal_disable, formValue?.status) }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</n-spin>
|
||||
</n-spin>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -88,10 +85,12 @@
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
|
||||
const message = useMessage();
|
||||
const dialogWidth = ref('75%');
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref(newState(null));
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(580);
|
||||
});
|
||||
const fileAvatarCSS = computed(() => {
|
||||
return {
|
||||
'--n-merged-size': `var(--n-avatar-size-override, 80px)`,
|
||||
@@ -105,7 +104,6 @@
|
||||
}
|
||||
|
||||
function openModal(state: State) {
|
||||
adaModalWidth(dialogWidth, 580);
|
||||
showModal.value = true;
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
@@ -122,4 +120,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -1,339 +1,24 @@
|
||||
<template>
|
||||
<div class="console">
|
||||
<!--数据卡片-->
|
||||
<n-grid cols="1 s:2 m:3 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12" :y-gap="8">
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="卡板量"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="success">日</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo v-else :startVal="1" :endVal="visits.dayVisits" class="text-3xl" />
|
||||
</div>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
日同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="visits.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
周同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="visits.decline" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" text :repeat="2" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总卡板量: </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" :endVal="visits.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="激活卡板"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="info">周</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo
|
||||
v-else
|
||||
prefix="¥"
|
||||
:startVal="1"
|
||||
:endVal="saleroom.weekSaleroom"
|
||||
class="text-3xl"
|
||||
/>
|
||||
</div>
|
||||
<div class="py-2 px-2 flex justify-between">
|
||||
<div class="text-sn flex-1">
|
||||
<n-progress
|
||||
type="line"
|
||||
:percentage="saleroom.degree"
|
||||
:indicator-placement="'inside'"
|
||||
processing
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总激活卡板: </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" :endVal="saleroom.amount" />
|
||||
<!-- prefix="¥"-->
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="代理商"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="warning">周</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo v-else :startVal="1" :endVal="orderLarge.weekLarge" class="text-3xl" />
|
||||
</div>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
日同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
周同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总代理商量: </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="提现佣金"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="error">月</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo v-else prefix="¥" :startVal="1" :endVal="volume.weekLarge" class="text-3xl" />
|
||||
</div>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
月同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="volume.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
月同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="volume.decline" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总提现额: </div>
|
||||
<div class="text-sn">
|
||||
<CountTo prefix="¥" :startVal="1" :endVal="volume.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
|
||||
<!--导航卡片-->
|
||||
<div class="mt-4">
|
||||
<n-grid cols="1 s:2 m:3 l:8 xl:8 2xl:8" responsive="screen" :x-gap="16" :y-gap="8">
|
||||
<n-grid-item v-for="(item, index) in iconList" :key="index" @click="item.eventObject || {}">
|
||||
<NCard content-style="padding-top: 0;" size="small" :bordered="false">
|
||||
<template #footer>
|
||||
<n-skeleton v-if="loading" size="medium" />
|
||||
<div class="cursor-pointer" v-else>
|
||||
<p class="flex justify-center">
|
||||
<span>
|
||||
<n-icon :size="item.size" class="flex-1" :color="item.color">
|
||||
<component :is="item.icon" v-on="item.eventObject || {}" />
|
||||
</n-icon>
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex justify-center"
|
||||
><span>{{ item.title }}</span></p
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
|
||||
<!--访问量 | 流量趋势-->
|
||||
<VisiTab />
|
||||
<div>
|
||||
<Company v-if="userStore.isCompanyDept" />
|
||||
<Tenant v-else-if="userStore.isTenantDept" />
|
||||
<Merchant v-else-if="userStore.isMerchantDept" />
|
||||
<User v-else-if="userStore.isUserDept" />
|
||||
<template v-else>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="默认首页">
|
||||
部门类型未配置首页:{{ userStore.info?.deptType }}
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getConsoleInfo } from '@/api/dashboard/console';
|
||||
import VisiTab from './components/VisiTab.vue';
|
||||
import { CountTo } from '@/components/CountTo/index';
|
||||
import {
|
||||
CaretUpOutlined,
|
||||
CaretDownOutlined,
|
||||
UsergroupAddOutlined,
|
||||
BarChartOutlined,
|
||||
ShoppingCartOutlined,
|
||||
AccountBookOutlined,
|
||||
CreditCardOutlined,
|
||||
MailOutlined,
|
||||
TagsOutlined,
|
||||
SettingOutlined,
|
||||
} from '@vicons/antd';
|
||||
import Company from './console_company.vue';
|
||||
import Merchant from './console_merchant.vue';
|
||||
import User from './console_user.vue';
|
||||
import Tenant from './console_tenant.vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const loading = ref(true);
|
||||
const visits = ref<any>({});
|
||||
const saleroom = ref<any>({});
|
||||
const orderLarge = ref<any>({});
|
||||
const volume = ref({});
|
||||
const router = useRouter();
|
||||
|
||||
// 图标列表
|
||||
const iconList = [
|
||||
{
|
||||
icon: UsergroupAddOutlined,
|
||||
size: '32',
|
||||
title: '用户',
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => router.push({ name: 'user' }),
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: BarChartOutlined,
|
||||
size: '32',
|
||||
title: '分析',
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: ShoppingCartOutlined,
|
||||
size: '32',
|
||||
title: '商品',
|
||||
color: '#ff9c6e',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: AccountBookOutlined,
|
||||
size: '32',
|
||||
title: '订单',
|
||||
color: '#b37feb',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: CreditCardOutlined,
|
||||
size: '32',
|
||||
title: '票据',
|
||||
color: '#ffd666',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: MailOutlined,
|
||||
size: '32',
|
||||
title: '消息',
|
||||
color: '#5cdbd3',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: TagsOutlined,
|
||||
size: '32',
|
||||
title: '标签',
|
||||
color: '#ff85c0',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: SettingOutlined,
|
||||
size: '32',
|
||||
title: '配置',
|
||||
color: '#ffc069',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await getConsoleInfo();
|
||||
visits.value = data.visits;
|
||||
saleroom.value = data.saleroom;
|
||||
orderLarge.value = data.orderLarge;
|
||||
volume.value = data.volume;
|
||||
loading.value = false;
|
||||
});
|
||||
const userStore = useUserStore();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
339
web/src/views/dashboard/console/console_company.vue
Normal file
339
web/src/views/dashboard/console/console_company.vue
Normal file
@@ -0,0 +1,339 @@
|
||||
<template>
|
||||
<div class="console">
|
||||
<!--数据卡片-->
|
||||
<n-grid cols="1 s:2 m:3 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12" :y-gap="8">
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="卡板量"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="success">日</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo v-else :startVal="1" :endVal="visits.dayVisits" class="text-3xl" />
|
||||
</div>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
日同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="visits.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
周同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="visits.decline" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" text :repeat="2" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总卡板量: </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" :endVal="visits.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="激活卡板"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="info">周</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo
|
||||
v-else
|
||||
prefix="¥"
|
||||
:startVal="1"
|
||||
:endVal="saleroom.weekSaleroom"
|
||||
class="text-3xl"
|
||||
/>
|
||||
</div>
|
||||
<div class="py-2 px-2 flex justify-between">
|
||||
<div class="text-sn flex-1">
|
||||
<n-progress
|
||||
type="line"
|
||||
:percentage="saleroom.degree"
|
||||
:indicator-placement="'inside'"
|
||||
processing
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总激活卡板: </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" :endVal="saleroom.amount" />
|
||||
<!-- prefix="¥"-->
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="代理商"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="warning">周</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo v-else :startVal="1" :endVal="orderLarge.weekLarge" class="text-3xl" />
|
||||
</div>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
日同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
周同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总代理商量: </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="提现佣金"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="error">月</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo v-else prefix="¥" :startVal="1" :endVal="volume.weekLarge" class="text-3xl" />
|
||||
</div>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
月同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="volume.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
月同比
|
||||
<CountTo :startVal="1" suffix="%" :endVal="volume.decline" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总提现额: </div>
|
||||
<div class="text-sn">
|
||||
<CountTo prefix="¥" :startVal="1" :endVal="volume.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
|
||||
<!--导航卡片-->
|
||||
<div class="mt-4">
|
||||
<n-grid cols="1 s:2 m:3 l:8 xl:8 2xl:8" responsive="screen" :x-gap="16" :y-gap="8">
|
||||
<n-grid-item v-for="(item, index) in iconList" :key="index" @click="item.eventObject || {}">
|
||||
<NCard content-style="padding-top: 0;" size="small" :bordered="false">
|
||||
<template #footer>
|
||||
<n-skeleton v-if="loading" size="medium" />
|
||||
<div class="cursor-pointer" v-else>
|
||||
<p class="flex justify-center">
|
||||
<span>
|
||||
<n-icon :size="item.size" class="flex-1" :color="item.color">
|
||||
<component :is="item.icon" v-on="item.eventObject || {}" />
|
||||
</n-icon>
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex justify-center"
|
||||
><span>{{ item.title }}</span></p
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
|
||||
<!--访问量 | 流量趋势-->
|
||||
<VisiTab />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getConsoleInfo } from '@/api/dashboard/console';
|
||||
import VisiTab from './components/VisiTab.vue';
|
||||
import { CountTo } from '@/components/CountTo';
|
||||
import {
|
||||
CaretUpOutlined,
|
||||
CaretDownOutlined,
|
||||
UsergroupAddOutlined,
|
||||
BarChartOutlined,
|
||||
ShoppingCartOutlined,
|
||||
AccountBookOutlined,
|
||||
CreditCardOutlined,
|
||||
MailOutlined,
|
||||
TagsOutlined,
|
||||
SettingOutlined,
|
||||
} from '@vicons/antd';
|
||||
|
||||
const loading = ref(true);
|
||||
const visits = ref<any>({});
|
||||
const saleroom = ref<any>({});
|
||||
const orderLarge = ref<any>({});
|
||||
const volume = ref({});
|
||||
const router = useRouter();
|
||||
|
||||
// 图标列表
|
||||
const iconList = [
|
||||
{
|
||||
icon: UsergroupAddOutlined,
|
||||
size: '32',
|
||||
title: '用户',
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => router.push({ name: 'user' }),
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: BarChartOutlined,
|
||||
size: '32',
|
||||
title: '分析',
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: ShoppingCartOutlined,
|
||||
size: '32',
|
||||
title: '商品',
|
||||
color: '#ff9c6e',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: AccountBookOutlined,
|
||||
size: '32',
|
||||
title: '订单',
|
||||
color: '#b37feb',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: CreditCardOutlined,
|
||||
size: '32',
|
||||
title: '票据',
|
||||
color: '#ffd666',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: MailOutlined,
|
||||
size: '32',
|
||||
title: '消息',
|
||||
color: '#5cdbd3',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: TagsOutlined,
|
||||
size: '32',
|
||||
title: '标签',
|
||||
color: '#ff85c0',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: SettingOutlined,
|
||||
size: '32',
|
||||
title: '配置',
|
||||
color: '#ffc069',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await getConsoleInfo();
|
||||
visits.value = data.visits;
|
||||
saleroom.value = data.saleroom;
|
||||
orderLarge.value = data.orderLarge;
|
||||
volume.value = data.volume;
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
11
web/src/views/dashboard/console/console_merchant.vue
Normal file
11
web/src/views/dashboard/console/console_merchant.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="商户首页"> 这是商户的首页,如果需要,你可以定制TA </n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
11
web/src/views/dashboard/console/console_tenant.vue
Normal file
11
web/src/views/dashboard/console/console_tenant.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="租户首页"> 这是租户的首页,如果需要,你可以定制TA </n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
13
web/src/views/dashboard/console/console_user.vue
Normal file
13
web/src/views/dashboard/console/console_user.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="用户首页">
|
||||
这是普通用户的首页,如果需要,你可以定制TA
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
@@ -41,6 +41,7 @@
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="创建新插件"
|
||||
@@ -127,12 +128,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { NIcon, useMessage, useDialog, useNotification } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { List, Build, UnInstall, Install, Upgrade } from '@/api/develop/addons';
|
||||
import { PlusOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { newState, schemas, columns, options } from './model';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
@@ -144,15 +145,17 @@
|
||||
const formRef: any = ref(null);
|
||||
const actionRef = ref();
|
||||
const formParams = ref<any>();
|
||||
const dialogWidth = ref('50%');
|
||||
const checkedIds = ref([]);
|
||||
const searchFormRef = ref<any>();
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
@@ -196,7 +199,6 @@
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
adaModalWidth(dialogWidth);
|
||||
return await List({ ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
|
@@ -60,6 +60,54 @@
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<!-- 树表-->
|
||||
<template v-if="formValue.genType == 11">
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item path="title">
|
||||
<template #label>
|
||||
<div class="flex flex-row items-end"
|
||||
>树名称字段
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button strong text>
|
||||
<template #icon>
|
||||
<n-icon size="15" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
树节点的显示名称字段名, 如:`title`
|
||||
</n-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="columnsLoading"
|
||||
placeholder="请选择"
|
||||
:options="columnsOption"
|
||||
v-model:value="formValue.options.tree.titleColumn"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item label="树表格样式" path="styleType">
|
||||
<n-radio-group v-model:value="formValue.options.tree.styleType" name="styleType">
|
||||
<n-radio
|
||||
v-for="status in selectList.treeStyleType"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
>{{ status.label }}</n-radio
|
||||
>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</template>
|
||||
|
||||
<!-- 树表-->
|
||||
|
||||
<n-col :span="18">
|
||||
<n-form-item
|
||||
label="表格头部按钮组"
|
||||
@@ -97,15 +145,6 @@
|
||||
<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>表单组件中存在`开关`类型才会生效</span>
|
||||
</n-popover>
|
||||
<n-checkbox value="notFilterAuth" label="不过滤权限" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
@@ -122,7 +161,7 @@
|
||||
|
||||
<n-col :span="24">
|
||||
<n-form-item
|
||||
label="自动化操作"
|
||||
label="高级设置"
|
||||
path="autoOps"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
@@ -136,7 +175,7 @@
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>如果你选择的表已经生成过dao相关代码,可以忽略</span>
|
||||
<span>如果你选择的表已经生成过dao相关代码,取消勾选可减少生成时间</span>
|
||||
</n-popover>
|
||||
<n-checkbox value="runService" label="生成后运行 [gf gen service]" />
|
||||
<n-popover trigger="hover">
|
||||
@@ -147,6 +186,19 @@
|
||||
</template>
|
||||
<span>如果是插件模块,勾选后也会自动在对应插件下运行service相关代码生成</span>
|
||||
</n-popover>
|
||||
<n-checkbox
|
||||
value="genFuncDict"
|
||||
label="生成字典选项"
|
||||
@click="handleCheckboxGenFuncDict"
|
||||
/>
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>将表数据生成为数据选项,并注册到内置的方法字典</span>
|
||||
</n-popover>
|
||||
<n-checkbox value="forcedCover" label="强制覆盖" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
@@ -166,7 +218,13 @@
|
||||
style="min-width: 200px"
|
||||
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
|
||||
>
|
||||
<n-form-item label="上级菜单" path="pid">
|
||||
<n-form-item path="pid">
|
||||
<template #label>
|
||||
<span>上级菜单</span>
|
||||
<n-button class="ml-2" text type="primary" strong @click="handleAddMenu"
|
||||
>菜单管理</n-button
|
||||
>
|
||||
</template>
|
||||
<n-tree-select
|
||||
:options="optionMenuTree"
|
||||
:value="formValue.options.menu.pid"
|
||||
@@ -224,16 +282,16 @@
|
||||
<template #header-extra>
|
||||
<n-space>
|
||||
<n-button
|
||||
type="warning"
|
||||
type="primary"
|
||||
@click="addJoin"
|
||||
:disabled="formValue.options?.join?.length >= 3"
|
||||
:disabled="formValue.options?.join?.length >= 20"
|
||||
>新增关联表</n-button
|
||||
>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<n-form ref="formRef" :model="formValue">
|
||||
<n-alert :show-icon="false">关联表数量建议在三个以下</n-alert>
|
||||
<n-alert type="warning" :show-icon="false" v-if="formValue.options?.join?.length > 3">关联表数量建议在三个以下</n-alert>
|
||||
<div class="mt-4"></div>
|
||||
<n-row :gutter="6" v-for="(join, index) in formValue.options.join" :key="index">
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
@@ -315,12 +373,19 @@
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
|
||||
<MenuModal ref="menuModalRef" @reloadTable="loadMenuTreeOption" />
|
||||
<SetFuncDict
|
||||
ref="setFuncDictRef"
|
||||
@update="handleUpdateFuncDict"
|
||||
:columnsOption="columnsOption"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { FormInst } from 'naive-ui';
|
||||
import { FormInst, useDialog, useMessage } from 'naive-ui';
|
||||
import { newState, selectListObj } from './model';
|
||||
import { TableSelect, ColumnSelect } from '@/api/develop/code';
|
||||
import { getRandomString } from '@/utils/charset';
|
||||
@@ -329,7 +394,11 @@
|
||||
import { getMenuList } from '@/api/system/menu';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { isLetterBegin } from '@/utils/is';
|
||||
import MenuModal from '@/views/permission/menu/menuModal.vue';
|
||||
import SetFuncDict from './SetFuncDict.vue';
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const timer = ref();
|
||||
const formRef = ref<FormInst | null>(null);
|
||||
const bodyShow = ref(true);
|
||||
@@ -340,6 +409,8 @@
|
||||
const columnsOption = ref<any>([]); // 主表字段选项
|
||||
const linkTablesOption = ref<any>([]); // 关联表选项
|
||||
const linkColumnsOption = ref<any>([]); // 关联表字段选项
|
||||
const menuModalRef = ref();
|
||||
const setFuncDictRef = ref();
|
||||
|
||||
const optionMenuTree = ref([
|
||||
{
|
||||
@@ -515,6 +586,20 @@
|
||||
function handleUpdateMenuPid(value: string | number | Array<string | number> | null) {
|
||||
formValue.value.options.menu.pid = value;
|
||||
}
|
||||
|
||||
function handleAddMenu() {
|
||||
menuModalRef.value.openModal();
|
||||
}
|
||||
|
||||
function handleCheckboxGenFuncDict() {
|
||||
if (formValue.value.options.autoOps.includes('genFuncDict')) {
|
||||
setFuncDictRef.value.openModal(formValue.value.options.funcDict);
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdateFuncDict(value) {
|
||||
formValue.value.options.funcDict = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@@ -1,58 +1,85 @@
|
||||
<template>
|
||||
<n-spin :show="show" description="加载中...">
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-spin :show="show" description="加载中...">
|
||||
<BasicTable
|
||||
:single-line="false"
|
||||
size="small"
|
||||
:striped="true"
|
||||
:resizable="true"
|
||||
striped
|
||||
resizable
|
||||
canResize
|
||||
virtual-scroll
|
||||
:single-line="false"
|
||||
:showTopRight="false"
|
||||
:pagination="false"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:openChecked="false"
|
||||
:showTopRight="false"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:canResize="true"
|
||||
:pagination="false"
|
||||
:scroll-x="3000"
|
||||
:resizeHeightOffset="-20000"
|
||||
:scroll-x="columnCollapse ? 1400 : 2400"
|
||||
:scroll-y="720"
|
||||
:scrollbar-props="{ trigger: 'none' }"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-tooltip placement="top-start" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button type="primary" @click="reloadFields(true)" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Reload />
|
||||
</n-icon>
|
||||
</template>
|
||||
重置字段
|
||||
</n-button>
|
||||
</template>
|
||||
主要用于重置字段设置或数据库表字段发生变化时重新载入
|
||||
</n-tooltip>
|
||||
<n-space>
|
||||
<n-popconfirm @positive-click="reloadColumns">
|
||||
<template #trigger>
|
||||
<n-button type="primary" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Reload />
|
||||
</n-icon>
|
||||
</template>
|
||||
重置字段
|
||||
</n-button>
|
||||
</template>
|
||||
重置后将从数据库重新加载表,不保留当前字段配置,确定要重置吗?
|
||||
</n-popconfirm>
|
||||
|
||||
<n-popconfirm @positive-click="syncColumns">
|
||||
<template #trigger>
|
||||
<n-button type="primary" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Sync />
|
||||
</n-icon>
|
||||
</template>
|
||||
同步字段
|
||||
</n-button>
|
||||
</template>
|
||||
同步是从数据库重新加载表,保留当前有效的字段配置,确定要同步吗?
|
||||
</n-popconfirm>
|
||||
|
||||
<n-button type="default" class="min-left-space" @click="handleMove">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<MoveOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
移动字段
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
</n-spin>
|
||||
</n-card>
|
||||
<Move ref="moveRef" v-model:columns="dataSource" />
|
||||
</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 {
|
||||
formatColumns,
|
||||
formGridColsOptions,
|
||||
formGridSpanOptions,
|
||||
genInfoObj,
|
||||
selectListObj,
|
||||
} from '@/views/develop/code/components/model';
|
||||
import { ColumnList } from '@/api/develop/code';
|
||||
import { NButton, NCheckbox, NInput, NSelect, NTooltip, NTreeSelect,NCascader } from 'naive-ui';
|
||||
import { HelpCircleOutline, Reload } from '@vicons/ionicons5';
|
||||
import { renderIcon } from '@/utils';
|
||||
import { NInputNumber, NSpace, NButton, NCheckbox, NInput, NSelect, NCascader } from 'naive-ui';
|
||||
import { HelpCircleOutline, Reload, Sync, MoveOutline } from '@vicons/ionicons5';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const renderTooltip = (trigger, content) => {
|
||||
return h(NTooltip, null, {
|
||||
trigger: () => trigger,
|
||||
default: () => content,
|
||||
});
|
||||
};
|
||||
import { renderIcon, renderTooltip } from '@/utils';
|
||||
import Move from './Move.vue';
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
@@ -75,93 +102,22 @@
|
||||
},
|
||||
});
|
||||
|
||||
const actionRef = ref();
|
||||
const columns = ref<any>([]);
|
||||
const show = ref(false);
|
||||
const dataSource = ref(formValue.value.masterColumns);
|
||||
|
||||
async function reloadFields(loading = false) {
|
||||
dataSource.value = [];
|
||||
if (loading) {
|
||||
show.value = true;
|
||||
}
|
||||
|
||||
formValue.value.masterColumns = await ColumnList({
|
||||
name: formValue.value.dbName,
|
||||
table: formValue.value.tableName,
|
||||
});
|
||||
dataSource.value = formValue.value.masterColumns;
|
||||
if (loading) {
|
||||
show.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
show.value = true;
|
||||
if (formValue.value.masterColumns.length === 0) {
|
||||
await reloadFields();
|
||||
}
|
||||
|
||||
columns.value = [
|
||||
{
|
||||
title: '位置',
|
||||
key: 'id',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
|
||||
);
|
||||
},
|
||||
key: 'field',
|
||||
align: 'center',
|
||||
width: 800,
|
||||
children: [
|
||||
const dataSource = ref([]);
|
||||
const moveRef = ref();
|
||||
const columnCollapse = ref(true);
|
||||
const columnsCollapseData = computed(() => {
|
||||
return columnCollapse.value
|
||||
? [
|
||||
{
|
||||
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,
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NInput, {
|
||||
value: row.dc,
|
||||
@@ -171,7 +127,98 @@
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: '字段列名',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '物理类型',
|
||||
key: 'sqlType',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'Go属性',
|
||||
key: 'goName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Go类型',
|
||||
key: 'goType',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'Ts属性',
|
||||
key: 'tsName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Ts类型',
|
||||
key: 'tsType',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '字段描述',
|
||||
key: 'dc',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NInput, {
|
||||
value: row.dc,
|
||||
onUpdateValue: function (e) {
|
||||
row.dc = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const columns = computed(() => {
|
||||
return [
|
||||
{
|
||||
title: '',
|
||||
key: 'id',
|
||||
width: 30,
|
||||
render(row, index) {
|
||||
return index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return h('div', null, [
|
||||
renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
type: 'primary',
|
||||
style: { 'margin-left': '20px' },
|
||||
onClick: () => (columnCollapse.value = !columnCollapse.value),
|
||||
},
|
||||
{ default: () => (columnCollapse.value ? '展开 >>' : '折叠 <<') }
|
||||
),
|
||||
]);
|
||||
},
|
||||
key: 'field',
|
||||
align: 'center',
|
||||
width: 800,
|
||||
children: columnsCollapseData.value,
|
||||
},
|
||||
{
|
||||
width: 800,
|
||||
@@ -197,21 +244,26 @@
|
||||
align: 'center',
|
||||
title: '编辑',
|
||||
key: 'isEdit',
|
||||
width: 50,
|
||||
width: 30,
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
const disabled = isEditDisabled(row);
|
||||
const checkbox = h(NCheckbox, {
|
||||
defaultChecked: row.isEdit,
|
||||
disabled: row.name === 'id',
|
||||
disabled: disabled,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isEdit = e;
|
||||
},
|
||||
});
|
||||
if (!disabled) {
|
||||
return checkbox;
|
||||
}
|
||||
return renderTooltip(checkbox, '该字段属性由系统维护,无需单独配置!');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '必填',
|
||||
key: 'required',
|
||||
width: 50,
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
@@ -226,7 +278,7 @@
|
||||
{
|
||||
title: '唯一',
|
||||
key: 'unique',
|
||||
width: 50,
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
@@ -241,17 +293,12 @@
|
||||
{
|
||||
title: '表单组件',
|
||||
key: 'formMode',
|
||||
width: 200,
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
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;
|
||||
},
|
||||
@@ -259,11 +306,35 @@
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '表单验证',
|
||||
title: '绑定字典',
|
||||
key: 'dictType',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (row.dictType == 0) {
|
||||
row.dictType = null;
|
||||
}
|
||||
return h(NCascader, {
|
||||
placeholder: ' ',
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
showPath: false,
|
||||
checkStrategy: 'child',
|
||||
disabled: row.name === 'id',
|
||||
value: row.dictType,
|
||||
options: props.selectList?.dictMode ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.dictType = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '验证规则',
|
||||
key: 'formRole',
|
||||
width: 200,
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.formRole,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.formRole ?? [],
|
||||
@@ -274,22 +345,43 @@
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
key: 'dictType',
|
||||
width: 300,
|
||||
title(_column) {
|
||||
return h(NSpace, { inline: true }, [
|
||||
renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '栅格', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'表单每行摆放组件的个数。响应式栅格,小屏幕自动转为每行摆放一个组件。参考文档:https://www.naiveui.com/zh-CN/os-theme/components/grid#responsive-item.vue'
|
||||
),
|
||||
h(NSelect, {
|
||||
style: { width: '100px' },
|
||||
size: 'small',
|
||||
consistentMenuWidth: false,
|
||||
value: formValue.value.options.presetStep.formGridCols,
|
||||
options: formGridColsOptions,
|
||||
onUpdateValue: function (e) {
|
||||
formValue.value.options.presetStep.formGridCols = e;
|
||||
},
|
||||
}),
|
||||
]);
|
||||
},
|
||||
key: 'formGridSpan',
|
||||
width: 120,
|
||||
render(row) {
|
||||
if (row.dictType == 0){
|
||||
row.dictType = null;
|
||||
}
|
||||
return h(NCascader, {
|
||||
placeholder: '请选择字典类型',
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
disabled: row.name === 'id',
|
||||
value: row.dictType,
|
||||
options: props.selectList?.dictMode ?? [],
|
||||
value: row.formGridSpan,
|
||||
options: getFormGridSpanOptions(formValue.value.options.presetStep.formGridCols),
|
||||
onUpdateValue: function (e) {
|
||||
row.dictType = e;
|
||||
row.formGridSpan = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -305,7 +397,7 @@
|
||||
{
|
||||
title: '列表',
|
||||
key: 'isList',
|
||||
width: 50,
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
@@ -319,7 +411,7 @@
|
||||
{
|
||||
title: '导出',
|
||||
key: 'isExport',
|
||||
width: 50,
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
@@ -333,7 +425,7 @@
|
||||
{
|
||||
title: '查询',
|
||||
key: 'isQuery',
|
||||
width: 50,
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
@@ -347,9 +439,10 @@
|
||||
{
|
||||
title: '查询条件',
|
||||
key: 'queryWhere',
|
||||
width: 300,
|
||||
width: 90,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.queryWhere,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.whereMode ?? [],
|
||||
@@ -359,13 +452,108 @@
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排列方式',
|
||||
key: 'align',
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.align,
|
||||
options: props.selectList?.tableAlign ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.align = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '列宽', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'选填。设定固定值时表格生成自动计算scroll-x,未设定默认每列按100计算'
|
||||
);
|
||||
},
|
||||
key: 'width',
|
||||
width: 50,
|
||||
render(row) {
|
||||
return h(NInputNumber, {
|
||||
value: row.width,
|
||||
placeholder: ' ',
|
||||
min: -1,
|
||||
max: 2000,
|
||||
showButton: false,
|
||||
onUpdateValue: function (e) {
|
||||
row.width = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
show.value = false;
|
||||
});
|
||||
|
||||
// 同步字段
|
||||
function syncColumns() {
|
||||
show.value = true;
|
||||
dataSource.value = [];
|
||||
|
||||
const params = {
|
||||
name: formValue.value.dbName,
|
||||
table: formValue.value.tableName,
|
||||
};
|
||||
|
||||
ColumnList(params)
|
||||
.then((res) => {
|
||||
const columns = formatColumns(res);
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
// 相同字段名称和类型,保留原字段属性
|
||||
const index = formValue.value.masterColumns.findIndex(
|
||||
(item) => item.name == columns[i].name && item.dataType == columns[i].dataType
|
||||
);
|
||||
if (index !== -1) {
|
||||
columns[i] = formValue.value.masterColumns[index];
|
||||
}
|
||||
}
|
||||
|
||||
formValue.value.masterColumns = columns;
|
||||
dataSource.value = formValue.value.masterColumns;
|
||||
})
|
||||
.finally(() => {
|
||||
show.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 重载字段属性
|
||||
function reloadColumns() {
|
||||
show.value = true;
|
||||
dataSource.value = [];
|
||||
|
||||
const params = {
|
||||
name: formValue.value.dbName,
|
||||
table: formValue.value.tableName,
|
||||
};
|
||||
|
||||
ColumnList(params)
|
||||
.then((res) => {
|
||||
formValue.value.masterColumns = formatColumns(res);
|
||||
dataSource.value = formValue.value.masterColumns;
|
||||
})
|
||||
.finally(() => {
|
||||
show.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function getFormModeOptions(type: string) {
|
||||
const options = cloneDeep(props.selectList?.formMode ?? []);
|
||||
if (options.length === 0) {
|
||||
@@ -374,7 +562,16 @@
|
||||
switch (type) {
|
||||
case 'number':
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const allows = ['InputNumber', 'Radio', 'Select', 'Switch', 'Rate'];
|
||||
const allows = [
|
||||
'Input',
|
||||
'InputNumber',
|
||||
'Radio',
|
||||
'Select',
|
||||
'Switch',
|
||||
'Rate',
|
||||
'TreeSelect',
|
||||
'Cascader',
|
||||
];
|
||||
if (!allows.includes(options[i].value)) {
|
||||
options[i].disabled = true;
|
||||
}
|
||||
@@ -382,8 +579,73 @@
|
||||
break;
|
||||
default:
|
||||
}
|
||||
options.sort((a, b) => (a.disabled === b.disabled ? 0 : a.disabled ? 1 : -1));
|
||||
return options;
|
||||
}
|
||||
|
||||
function getFormGridSpanOptions(cols: number) {
|
||||
if (cols < 1) {
|
||||
cols = 1;
|
||||
}
|
||||
if (cols > 4) {
|
||||
cols = 4;
|
||||
}
|
||||
for (let i = 0; i < formValue.value.masterColumns.length; i++) {
|
||||
if (!formValue.value.masterColumns[i].formGridSpan) {
|
||||
formValue.value.masterColumns[i].formGridSpan = 1;
|
||||
}
|
||||
if (formValue.value.masterColumns[i].formGridSpan > cols) {
|
||||
formValue.value.masterColumns[i].formGridSpan = cols;
|
||||
}
|
||||
}
|
||||
return formGridSpanOptions.slice(0, Math.min(cols, formGridSpanOptions.length));
|
||||
}
|
||||
|
||||
// 禁止编辑的字段,由系统维护
|
||||
function isEditDisabled(row) {
|
||||
const disabledNames = [
|
||||
'id',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
if (disabledNames.includes(row.name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (formValue.value.genType == 11) {
|
||||
const disabledTreeNames = ['pid', 'level', 'tree'];
|
||||
if (disabledTreeNames.includes(row.name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleMove() {
|
||||
moveRef.value.openModal();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (formValue.value.masterColumns.length === 0) {
|
||||
reloadColumns();
|
||||
} else {
|
||||
show.value = true;
|
||||
setTimeout(function () {
|
||||
dataSource.value = formValue.value.masterColumns;
|
||||
show.value = false;
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.tree-tips {
|
||||
margin-left: 12px;
|
||||
color: #18a058;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
@@ -2,43 +2,85 @@
|
||||
<n-spin :show="show" description="加载中...">
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
:single-line="false"
|
||||
size="small"
|
||||
:striped="true"
|
||||
:resizable="true"
|
||||
striped
|
||||
resizable
|
||||
canResize
|
||||
:single-line="false"
|
||||
:showTopRight="false"
|
||||
:pagination="false"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:openChecked="false"
|
||||
:showTopRight="false"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:canResize="true"
|
||||
:resizeHeightOffset="-20000"
|
||||
:pagination="false"
|
||||
:scroll-x="1090"
|
||||
:scroll-x="columnCollapse ? 880 : 1880"
|
||||
:scroll-y="720"
|
||||
:scrollbar-props="{ trigger: 'none' }"
|
||||
/>
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-space>
|
||||
<n-popconfirm @positive-click="reloadColumns(getIndex())">
|
||||
<template #trigger>
|
||||
<n-button type="primary" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Reload />
|
||||
</n-icon>
|
||||
</template>
|
||||
重置字段
|
||||
</n-button>
|
||||
</template>
|
||||
重置后将从数据库重新加载该表的默认配置,确定要重置吗?
|
||||
</n-popconfirm>
|
||||
|
||||
<n-popconfirm @positive-click="syncColumns(getIndex())">
|
||||
<template #trigger>
|
||||
<n-button type="primary" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Sync />
|
||||
</n-icon>
|
||||
</template>
|
||||
同步字段
|
||||
</n-button>
|
||||
</template>
|
||||
同步是从数据库重新加载表,保留当前有效的字段配置,确定要同步吗?
|
||||
</n-popconfirm>
|
||||
|
||||
<n-button type="default" class="min-left-space" @click="handleMove">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<MoveOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
移动字段
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Move ref="moveRef" v-model:columns="dataSource" />
|
||||
</n-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Component, computed, h, onMounted, ref } from 'vue';
|
||||
import { computed, h, onMounted, ref } from 'vue';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
|
||||
import { formatColumns, 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) });
|
||||
}
|
||||
import {
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NIcon,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NTooltip,
|
||||
} from 'naive-ui';
|
||||
import { HelpCircleOutline, MoveOutline, Reload, Sync, WarningOutline } from '@vicons/ionicons5';
|
||||
import { renderIcon, renderTooltip } from '@/utils';
|
||||
import Move from './Move.vue';
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
@@ -54,8 +96,6 @@
|
||||
uuid: '',
|
||||
});
|
||||
|
||||
const columns = ref<any>([]);
|
||||
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
@@ -65,6 +105,243 @@
|
||||
},
|
||||
});
|
||||
|
||||
const show = ref(false);
|
||||
const dataSource = ref([]);
|
||||
const moveRef = ref();
|
||||
const columnCollapse = ref(true);
|
||||
const columnsCollapseData = computed(() => {
|
||||
return columnCollapse.value
|
||||
? [
|
||||
{
|
||||
title: '字段列名',
|
||||
key: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
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);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: '字段列名',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '物理类型',
|
||||
key: 'sqlType',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'Go属性',
|
||||
key: 'goName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'Go类型',
|
||||
key: 'goType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Ts属性',
|
||||
key: 'tsName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'Ts类型',
|
||||
key: 'tsType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '字段描述',
|
||||
key: 'dc',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NInput, {
|
||||
value: row.dc,
|
||||
onUpdateValue: function (e) {
|
||||
row.dc = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const columns = computed(() => {
|
||||
return [
|
||||
{
|
||||
title: '',
|
||||
key: 'id',
|
||||
width: 50,
|
||||
render(row, index) {
|
||||
return index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return h('div', null, [
|
||||
renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
type: 'primary',
|
||||
style: { 'margin-left': '20px' },
|
||||
onClick: () => (columnCollapse.value = !columnCollapse.value),
|
||||
},
|
||||
{ default: () => (columnCollapse.value ? '展开 >>' : '折叠 <<') }
|
||||
),
|
||||
]);
|
||||
},
|
||||
key: 'field',
|
||||
align: 'center',
|
||||
width: 800,
|
||||
children: columnsCollapseData.value,
|
||||
},
|
||||
{
|
||||
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: 100,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.queryWhere,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.whereMode ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.queryWhere = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排列方式',
|
||||
key: 'align',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.align,
|
||||
options: props.selectList?.tableAlign ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.align = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '列宽', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'选填。设定固定值时表格生成自动计算scroll-x,未设定默认每列按100计算'
|
||||
);
|
||||
},
|
||||
key: 'width',
|
||||
width: 50,
|
||||
render(row) {
|
||||
return h(NInputNumber, {
|
||||
value: row.width,
|
||||
placeholder: ' ',
|
||||
min: 0,
|
||||
max: 2000,
|
||||
showButton: false,
|
||||
onUpdateValue: function (e) {
|
||||
row.width = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleMove() {
|
||||
moveRef.value.openModal();
|
||||
}
|
||||
function getIndex() {
|
||||
if (formValue.value.options.join.length === 0) {
|
||||
return -1;
|
||||
@@ -77,166 +354,77 @@
|
||||
return -1;
|
||||
}
|
||||
|
||||
const show = ref(false);
|
||||
const dataSource = ref([]);
|
||||
onMounted(async () => {
|
||||
// 同步字段属性
|
||||
function syncColumns(index: number) {
|
||||
show.value = true;
|
||||
setTimeout(async () => {
|
||||
dataSource.value = [];
|
||||
|
||||
const join = formValue.value.options.join[index];
|
||||
const params = {
|
||||
name: formValue.value.dbName,
|
||||
table: join.linkTable,
|
||||
isLink: 1,
|
||||
alias: join.alias,
|
||||
};
|
||||
|
||||
ColumnList(params)
|
||||
.then((res) => {
|
||||
const columns = formatColumns(res);
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
// 相同字段名称和类型,保留原字段属性
|
||||
const index2 = join.columns.findIndex(
|
||||
(item) => item.name == columns[i].name && item.dataType == columns[i].dataType
|
||||
);
|
||||
if (index2 !== -1) {
|
||||
columns[i] = join.columns[index2];
|
||||
}
|
||||
}
|
||||
join.columns = columns;
|
||||
dataSource.value = join.columns;
|
||||
})
|
||||
.finally(() => {
|
||||
show.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 重载字段属性
|
||||
function reloadColumns(index: number) {
|
||||
show.value = true;
|
||||
dataSource.value = [];
|
||||
|
||||
const join = formValue.value.options.join[index];
|
||||
const params = {
|
||||
name: formValue.value.dbName,
|
||||
table: join.linkTable,
|
||||
isLink: 1,
|
||||
alias: join.alias,
|
||||
};
|
||||
|
||||
ColumnList(params)
|
||||
.then((res) => {
|
||||
join.columns = formatColumns(res);
|
||||
dataSource.value = join.columns;
|
||||
})
|
||||
.finally(() => {
|
||||
show.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
show.value = true;
|
||||
setTimeout(() => {
|
||||
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,
|
||||
});
|
||||
|
||||
// 已存在直接加载
|
||||
if (formValue.value.options.join[index].columns.length > 0) {
|
||||
dataSource.value = formValue.value.options.join[index].columns;
|
||||
show.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
dataSource.value = formValue.value.options.join[index].columns;
|
||||
|
||||
columns.value = [
|
||||
{
|
||||
title: '位置',
|
||||
key: 'id',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
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);
|
||||
reloadColumns(index);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
const actionRef = ref();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
82
web/src/views/develop/code/components/Move.vue
Normal file
82
web/src/views/develop/code/components/Move.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
title="移动字段"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-card
|
||||
:bordered="false"
|
||||
:content-style="{ padding: '0px' }"
|
||||
:header-style="{ padding: 'px' }"
|
||||
:segmented="true"
|
||||
>
|
||||
请通过拖拽来移动字段的位置。
|
||||
<div class="mt-8"></div>
|
||||
<Draggable
|
||||
class="draggable-ul"
|
||||
animation="300"
|
||||
:list="columns"
|
||||
group="people"
|
||||
itemKey="name"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="cursor-move draggable-li">
|
||||
<n-tag type="default" size="small" style="font-weight: 800">{{
|
||||
element.name
|
||||
}}</n-tag
|
||||
><span class="ml-2">{{ element.dc }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
</n-card>
|
||||
</n-scrollbar>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
const showModal = ref(false);
|
||||
const columns = defineModel<[]>('columns');
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(360);
|
||||
});
|
||||
|
||||
function openModal() {
|
||||
showModal.value = true;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.draggable-ul {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin-top: -8px;
|
||||
|
||||
.draggable-li {
|
||||
width: 100%;
|
||||
padding: 8px 4px;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #efeff5;
|
||||
}
|
||||
|
||||
.draggable-li:hover {
|
||||
background-color: rgba(229, 231, 235, var(--tw-border-opacity));
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,16 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<textarea id="copy-code" :value="content"></textarea>
|
||||
<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-space justify="space-between">
|
||||
<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-button type="primary" size="small" class="tag-margin" @click="handleCopy(view.content)"
|
||||
>复制本页代码</n-button
|
||||
>
|
||||
</n-space>
|
||||
<n-scrollbar class="code-scrollbar" trigger="none">
|
||||
<n-code :code="view.content" :hljs="hljs" language="goLang" show-line-numbers />
|
||||
<n-code
|
||||
:class="'code-' + getFileExtension(view.path)"
|
||||
:code="view.content"
|
||||
:hljs="hljs"
|
||||
:language="getFileExtension(view.path)"
|
||||
show-line-numbers
|
||||
/>
|
||||
</n-scrollbar>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
@@ -18,9 +30,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import hljs from 'highlight.js/lib/core';
|
||||
import goLang from 'highlight.js/lib/languages/go';
|
||||
import go from 'highlight.js/lib/languages/go';
|
||||
import typescript from 'highlight.js/lib/languages/typescript';
|
||||
import xml from 'highlight.js/lib/languages/xml';
|
||||
import sql from 'highlight.js/lib/languages/sql';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import {
|
||||
CheckmarkCircle,
|
||||
@@ -29,8 +44,12 @@
|
||||
HelpCircleOutline,
|
||||
RemoveCircleOutline,
|
||||
} from '@vicons/ionicons5';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
hljs.registerLanguage('goLang', goLang);
|
||||
hljs.registerLanguage('go', go);
|
||||
hljs.registerLanguage('ts', typescript);
|
||||
hljs.registerLanguage('sql', sql);
|
||||
hljs.registerLanguage('vue', xml);
|
||||
|
||||
interface Props {
|
||||
previewModel: any;
|
||||
@@ -41,7 +60,8 @@
|
||||
previewModel: cloneDeep({ views: {} }),
|
||||
showModal: false,
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
const content = ref('');
|
||||
const views = computed(() => {
|
||||
let tmpViews: any = [];
|
||||
let i = 0;
|
||||
@@ -69,12 +89,31 @@
|
||||
}
|
||||
return tmpViews;
|
||||
});
|
||||
|
||||
function getFileExtension(path: string): string {
|
||||
const parts = path.split('.');
|
||||
if (parts.length > 1) {
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function handleCopy(code: string) {
|
||||
content.value = code;
|
||||
setTimeout(function () {
|
||||
const copyVal = document.getElementById('copy-code');
|
||||
copyVal.select();
|
||||
document.execCommand('copy');
|
||||
message.success('已复制');
|
||||
}, 20);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
::v-deep(.alert-margin) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
::v-deep(.tag-margin) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -85,4 +124,38 @@
|
||||
color: #e0e2e4;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
::v-deep(.code-vue .hljs-tag) {
|
||||
color: rgb(242, 197, 92);
|
||||
}
|
||||
|
||||
::v-deep(.code-vue .hljs-name) {
|
||||
color: rgb(242, 197, 92);
|
||||
}
|
||||
|
||||
::v-deep(.code-vue .hljs-attr) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
::v-deep(.code-go .hljs-params) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
::v-deep(.code-ts .hljs-params) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
::v-deep(.code-ts .hljs-property) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
::v-deep(.code-ts .hljs-function) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
#copy-code {
|
||||
position: fixed;
|
||||
top: -100px;
|
||||
left: -100px;
|
||||
}
|
||||
</style>
|
||||
|
133
web/src/views/develop/code/components/SetFuncDict.vue
Normal file
133
web/src/views/develop/code/components/SetFuncDict.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
title="设置选项字段"
|
||||
v-model:show="showModal"
|
||||
:block-scroll="false"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
>
|
||||
<n-form
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="选项值" path="valueColumn">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
placeholder="请选择"
|
||||
:options="columnsOption"
|
||||
v-model:value="formValue.valueColumn"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="选项名称" path="labelColumn">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
placeholder="请选择"
|
||||
:options="columnsOption"
|
||||
v-model:value="formValue.labelColumn"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">取消</n-button>
|
||||
<n-button type="info" @click="confirmForm">保存</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { rules } from '@/views/addons/stock/itemBrand/model';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { Edit } from '@/api/addons/stock/itemClass';
|
||||
|
||||
interface Props {
|
||||
columnsOption: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
columnsOption: [],
|
||||
});
|
||||
|
||||
const rules = {
|
||||
valueColumn: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
message: '选项值不能为空',
|
||||
},
|
||||
labelColumn: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
message: '选项名称不能为空',
|
||||
},
|
||||
};
|
||||
|
||||
const message = useMessage();
|
||||
const emit = defineEmits(['update']);
|
||||
const showModal = ref(false);
|
||||
const formRef = ref();
|
||||
const formValue = ref({
|
||||
valueColumn: null,
|
||||
labelColumn: null,
|
||||
});
|
||||
|
||||
function openModal(state) {
|
||||
showModal.value = true;
|
||||
if (!state) {
|
||||
state = {
|
||||
valueColumn: null,
|
||||
labelColumn: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (!state.valueColumn) {
|
||||
const item = props.columnsOption.find((item) => item.value === 'id');
|
||||
if (item) {
|
||||
state.valueColumn = item.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.labelColumn) {
|
||||
const item = props.columnsOption.find((item) => item.value === 'title' || item.value === 'name');
|
||||
if (item) {
|
||||
state.labelColumn = item.value;
|
||||
}
|
||||
}
|
||||
formValue.value = cloneDeep(state);
|
||||
emit('update', formValue.value);
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
emit('update', formValue.value);
|
||||
closeForm();
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
@@ -1,4 +1,5 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { isJsonString } from '@/utils/is';
|
||||
|
||||
export const genFileObj = {
|
||||
meth: 1,
|
||||
@@ -24,7 +25,7 @@ export const genInfoObj = {
|
||||
varName: '',
|
||||
options: {
|
||||
headOps: ['add', 'batchDel', 'export'],
|
||||
columnOps: ['edit', 'del', 'view', 'status', 'switch', 'check'],
|
||||
columnOps: ['edit', 'del', 'view', 'status', 'check'],
|
||||
autoOps: ['genMenuPermissions', 'runDao', 'runService'],
|
||||
join: [],
|
||||
menu: {
|
||||
@@ -32,6 +33,17 @@ export const genInfoObj = {
|
||||
icon: 'MenuOutlined',
|
||||
sort: 0,
|
||||
},
|
||||
tree: {
|
||||
titleColumn: null,
|
||||
styleType: 1,
|
||||
},
|
||||
funcDict: {
|
||||
valueColumn: null,
|
||||
labelColumn: null,
|
||||
},
|
||||
presetStep: {
|
||||
formGridCols: 1,
|
||||
},
|
||||
},
|
||||
dbName: '',
|
||||
tableName: '',
|
||||
@@ -54,6 +66,8 @@ export const selectListObj = {
|
||||
dictMode: [],
|
||||
whereMode: [],
|
||||
buildMeth: [],
|
||||
tableAlign: [],
|
||||
treeStyleType: [],
|
||||
};
|
||||
|
||||
export function newState(state) {
|
||||
@@ -62,3 +76,67 @@ export function newState(state) {
|
||||
}
|
||||
return cloneDeep(genInfoObj);
|
||||
}
|
||||
|
||||
export const formGridColsOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '一行一列',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '一行两列',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '一行三列',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '一行四列',
|
||||
},
|
||||
];
|
||||
|
||||
export const formGridSpanOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '占一列位置',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '占两列位置',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '占三列位置',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '占四列位置',
|
||||
},
|
||||
];
|
||||
|
||||
// 格式化列字段
|
||||
export function formatColumns(columns: any) {
|
||||
if (columns === undefined || columns.length === 0) {
|
||||
columns = [];
|
||||
}
|
||||
|
||||
if (isJsonString(columns)) {
|
||||
columns = JSON.parse(columns);
|
||||
}
|
||||
|
||||
if (columns.length > 0) {
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
if (!columns[i].formGridSpan) {
|
||||
columns[i].formGridSpan = 1;
|
||||
}
|
||||
if (!columns[i].align) {
|
||||
columns[i].align = 'left';
|
||||
}
|
||||
if (!columns[i].width || columns[i].width < 1) {
|
||||
columns[i].width = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
@@ -40,7 +40,10 @@
|
||||
|
||||
<template #suffix>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="preview">预览代码</n-button>
|
||||
<n-button type="default" @click="handleBack">返回列表</n-button>
|
||||
<n-button type="primary" :loading="formBtnPreviewLoading" @click="preview"
|
||||
>预览代码</n-button
|
||||
>
|
||||
<n-button type="success" :loading="formBtnLoading" @click="submitBuild"
|
||||
>提交生成</n-button
|
||||
>
|
||||
@@ -87,7 +90,7 @@
|
||||
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 { selectListObj, newState, formatColumns } from '@/views/develop/code/components/model';
|
||||
import PreviewTab from '@/views/develop/code/components/PreviewTab.vue';
|
||||
import { isJsonString } from '@/utils/is';
|
||||
|
||||
@@ -108,6 +111,7 @@
|
||||
const slavePanels = ref<any>([]);
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const formBtnPreviewLoading = ref(false);
|
||||
const previewModel = ref<any>();
|
||||
const dialog = useDialog();
|
||||
const notification = useNotification();
|
||||
@@ -123,15 +127,27 @@
|
||||
|
||||
async function getGenInfo() {
|
||||
let tmp = await View({ id: genId });
|
||||
// 导入主表数据
|
||||
tmp.masterColumns = formatColumns(tmp.masterColumns);
|
||||
|
||||
// 导入生成选项
|
||||
if (isJsonString(tmp.options)) {
|
||||
tmp.options = JSON.parse(tmp.options);
|
||||
}
|
||||
|
||||
if (tmp.masterColumns === undefined || tmp.masterColumns.length === 0) {
|
||||
tmp.masterColumns = [];
|
||||
// 预设流程
|
||||
if (!tmp.options.presetStep) {
|
||||
tmp.options.presetStep = {
|
||||
formGridCols: 1,
|
||||
};
|
||||
}
|
||||
if (isJsonString(tmp.masterColumns)) {
|
||||
tmp.masterColumns = JSON.parse(tmp.masterColumns);
|
||||
|
||||
// 树表
|
||||
if (!tmp.options.tree) {
|
||||
tmp.options.tree = {
|
||||
titleColumn: null,
|
||||
styleType: 1,
|
||||
};
|
||||
}
|
||||
|
||||
genInfo.value = tmp;
|
||||
@@ -146,12 +162,12 @@
|
||||
handleClose('主表字段');
|
||||
}
|
||||
|
||||
if (newVal.options.join !== undefined) {
|
||||
if (newVal && newVal.options && 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 !== '') {
|
||||
for (let i = 0; i < newVal.options.join.length; i++) {
|
||||
if (newVal.options.join[i]?.alias) {
|
||||
handleSlaveAdd(
|
||||
'关联表[ ' + newVal.options.join[i]?.alias + ' ]',
|
||||
'关联表[ ' + newVal.options.join[i].alias + ' ]',
|
||||
newVal.options.join[i]
|
||||
);
|
||||
}
|
||||
@@ -195,14 +211,21 @@
|
||||
selectList.value = await Selects({});
|
||||
};
|
||||
|
||||
async function preview() {
|
||||
previewModel.value = await Preview(genInfo.value);
|
||||
showModal.value = true;
|
||||
function preview() {
|
||||
formBtnPreviewLoading.value = true;
|
||||
Preview(genInfo.value)
|
||||
.then((res) => {
|
||||
previewModel.value = res;
|
||||
showModal.value = true;
|
||||
})
|
||||
.finally(() => {
|
||||
formBtnPreviewLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function submitBuild() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: '你确定要提交生成吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
@@ -216,15 +239,12 @@
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function submitSave() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: '你确定要保存生成配置吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
@@ -233,18 +253,15 @@
|
||||
message.success('操作成功');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildSuccessNotify() {
|
||||
let count = 6;
|
||||
let count = 10;
|
||||
const n = notification.success({
|
||||
title: '生成提交成功',
|
||||
content: `如果你使用的热编译,页面将在 ${count} 秒后自动刷新即可生效。否则请手动重启服务后刷新页面!`,
|
||||
duration: 6000,
|
||||
duration: 10000,
|
||||
closable: false,
|
||||
onAfterEnter: () => {
|
||||
const minusCount = () => {
|
||||
@@ -261,6 +278,18 @@
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: '你确定要返回生成列表?系统不会主动保存更改',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
router.push({ name: 'develop_code' });
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
::v-deep(.alert-margin) {
|
||||
|
@@ -20,6 +20,7 @@
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
@@ -30,8 +31,7 @@
|
||||
</template>
|
||||
立即生成
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="立即生成"
|
||||
@@ -302,10 +303,10 @@
|
||||
};
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
width: 180,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
|
@@ -156,7 +156,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useSendCode } from '@/hooks/common';
|
||||
@@ -173,7 +173,6 @@
|
||||
|
||||
const { sendLabel, isCounting, loading: sendLoading, activateSend } = useSendCode();
|
||||
const userStore = useUserStore();
|
||||
const dialogWidth = ref('75%');
|
||||
const rules = {
|
||||
basicName: {
|
||||
required: true,
|
||||
@@ -191,6 +190,9 @@
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
});
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(580);
|
||||
});
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
@@ -310,8 +312,4 @@
|
||||
function sendEmailCode() {
|
||||
activateSend(SendBindEmail());
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth, 580);
|
||||
});
|
||||
</script>
|
||||
|
@@ -140,7 +140,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useSendCode } from '@/hooks/common';
|
||||
@@ -157,7 +157,6 @@
|
||||
|
||||
const { sendLabel, isCounting, loading: sendLoading, activateSend } = useSendCode();
|
||||
const userStore = useUserStore();
|
||||
const dialogWidth = ref('75%');
|
||||
const rules = {
|
||||
basicName: {
|
||||
required: true,
|
||||
@@ -175,6 +174,9 @@
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
});
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(580);
|
||||
});
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
@@ -212,9 +214,9 @@
|
||||
function openUpdatePassForm() {
|
||||
message.error('未开放');
|
||||
return;
|
||||
showModal.value = true;
|
||||
formValue.value.newPassword = '';
|
||||
formValue.value.oldPassword = '';
|
||||
// showModal.value = true;
|
||||
// formValue.value.newPassword = '';
|
||||
// formValue.value.oldPassword = '';
|
||||
}
|
||||
|
||||
const formMobileBtnLoading = ref(false);
|
||||
@@ -250,9 +252,9 @@
|
||||
function openUpdateMobileForm() {
|
||||
message.error('未开放');
|
||||
return;
|
||||
showMobileModal.value = true;
|
||||
formMobileValue.value.mobile = '';
|
||||
formMobileValue.value.code = '';
|
||||
// showMobileModal.value = true;
|
||||
// formMobileValue.value.mobile = '';
|
||||
// formMobileValue.value.code = '';
|
||||
}
|
||||
|
||||
const formEmailBtnLoading = ref(false);
|
||||
@@ -298,8 +300,4 @@
|
||||
function sendEmailCode() {
|
||||
activateSend(SendBindEmail());
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth, 580);
|
||||
});
|
||||
</script>
|
||||
|
@@ -52,9 +52,9 @@
|
||||
{
|
||||
field: 'member_id',
|
||||
component: 'NInput',
|
||||
label: '操作人员',
|
||||
label: '操作人',
|
||||
componentProps: {
|
||||
placeholder: '请输入操作人员ID',
|
||||
placeholder: '请输入操作人ID',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
|
@@ -20,7 +20,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
@@ -39,7 +39,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
@@ -47,6 +47,7 @@
|
||||
import { columns } from './columns';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { DeleteOutlined } from '@vicons/antd';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const dialog = useDialog();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
@@ -56,9 +57,9 @@
|
||||
{
|
||||
field: 'member_id',
|
||||
component: 'NInput',
|
||||
label: '操作人员',
|
||||
label: '操作人',
|
||||
componentProps: {
|
||||
placeholder: '请输入操作人员ID',
|
||||
placeholder: '请输入操作人ID',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
@@ -183,7 +184,7 @@
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 150,
|
||||
width: 160,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
@@ -204,6 +205,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
|
@@ -1,106 +1,122 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
:title="data.id ? '日志详情 ID:' + data.id : '日志详情'"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="请求方式">{{ data.method }}</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>请求地址</template>
|
||||
{{ data.url }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
|
||||
<n-descriptions-item label="访问IP">{{ data.ip }}</n-descriptions-item>
|
||||
<n-descriptions-item label="IP归属地">{{ data.cityLabel }}</n-descriptions-item>
|
||||
<n-descriptions-item label="链路ID">{{ data.reqId }}</n-descriptions-item>
|
||||
<n-descriptions-item label="响应时间">{{
|
||||
timestampToTime(data.timestamp)
|
||||
}}</n-descriptions-item>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
:title="data.id ? '日志详情 ID:' + data.id : '日志详情'"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="请求方式">{{ data.method }}</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>请求地址</template>
|
||||
{{ data.url }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
|
||||
<n-descriptions-item label="访问IP">{{ data.ip }}</n-descriptions-item>
|
||||
<n-descriptions-item label="IP归属地">{{ data.cityLabel }}</n-descriptions-item>
|
||||
<n-descriptions-item label="链路ID">{{ data.reqId }}</n-descriptions-item>
|
||||
<n-descriptions-item label="响应时间">{{
|
||||
timestampToTime(data.timestamp)
|
||||
}}</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item label="创建时间">{{ data.createdAt }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="访问代理"
|
||||
>
|
||||
{{ data.userAgent }}
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="报错信息"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="错误状态码"> {{ data.errorCode }} </n-descriptions-item>
|
||||
<n-descriptions-item label="错误提示">
|
||||
<n-tag type="error"> {{ data.errorMsg }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-descriptions-item label="创建时间">{{ data.createdAt }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="访问代理"
|
||||
>
|
||||
{{ data.userAgent }}
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="报错信息"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="错误状态码"> {{ data.errorCode }} </n-descriptions-item>
|
||||
<n-descriptions-item label="错误提示">
|
||||
<n-tag type="error"> {{ data.errorMsg }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="堆栈打印"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.errorData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="堆栈打印"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.errorData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="Header请求头"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.headerData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="Header请求头"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.headerData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="GET参数"
|
||||
>
|
||||
<JsonViewer :value="data.getData" :expand-depth="5" copyable boxed sort class="json-width" />
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="GET参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.getData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="POST参数"
|
||||
>
|
||||
<JsonViewer :value="data.postData" :expand-depth="5" copyable boxed sort class="json-width" />
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="POST参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.postData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -116,20 +132,27 @@
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const logId = Number(router.currentRoute.value.params.id);
|
||||
const loading = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
onMounted(() => {
|
||||
if (logId === undefined || logId < 1) {
|
||||
message.error('ID不正确,请检查!');
|
||||
return;
|
||||
}
|
||||
|
||||
await getInfo();
|
||||
getInfo();
|
||||
});
|
||||
|
||||
const data = ref({});
|
||||
|
||||
const getInfo = async () => {
|
||||
data.value = await View({ id: logId });
|
||||
const getInfo = () => {
|
||||
loading.value = true;
|
||||
View({ id: logId })
|
||||
.then((res) => {
|
||||
data.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@@ -24,7 +24,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
@@ -61,16 +61,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="login_log_index">
|
||||
import { h, reactive, ref } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, 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 { List, Export, Delete } from '@/api/loginLog';
|
||||
import { columns, schemas } from './model';
|
||||
import { columns, schemas, loadOptions } from './model';
|
||||
import { ExportOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const router = useRouter();
|
||||
@@ -94,6 +95,9 @@
|
||||
label: '查看详情',
|
||||
onClick: handleView.bind(null, record),
|
||||
auth: ['/loginLog/view'],
|
||||
ifShow: () => {
|
||||
return record.sysLogId > 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
@@ -105,6 +109,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
@@ -140,9 +148,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -158,9 +163,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,6 +170,10 @@
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -4,8 +4,8 @@ import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
import { loginStatusOptions } from '@/enums/optionsiEnum';
|
||||
import { getOptionLabel, getOptionTag, Option } from '@/utils/hotgo';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
@@ -40,10 +40,6 @@ export function newState(state: State | null): State {
|
||||
return cloneDeep(defaultState);
|
||||
}
|
||||
|
||||
export const options = ref<Options>({
|
||||
sys_normal_disable: [],
|
||||
});
|
||||
|
||||
export const rules = {};
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
@@ -59,7 +55,7 @@ export const schemas = ref<FormSchema[]>([
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'sysLogIp',
|
||||
field: 'loginIp',
|
||||
component: 'NInput',
|
||||
label: 'IP地址',
|
||||
componentProps: {
|
||||
@@ -76,7 +72,7 @@ export const schemas = ref<FormSchema[]>([
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
options: loginStatusOptions,
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
@@ -125,7 +121,7 @@ export const columns = [
|
||||
},
|
||||
{
|
||||
title: '登录IP',
|
||||
key: 'sysLogIp',
|
||||
key: 'loginIp',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
@@ -156,11 +152,11 @@ export const columns = [
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(loginStatusOptions, row.status),
|
||||
type: getOptionTag(options.value.sys_login_status, row.status),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(loginStatusOptions, row.status),
|
||||
default: () => getOptionLabel(options.value.sys_login_status, row.status),
|
||||
}
|
||||
);
|
||||
},
|
||||
@@ -187,3 +183,24 @@ export const columns = [
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
// 字典数据选项
|
||||
export const options = ref({
|
||||
sys_login_status: [] as Option[],
|
||||
});
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
Dicts({
|
||||
types: ['sys_login_status'],
|
||||
}).then((res) => {
|
||||
options.value = res;
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.sys_login_status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -1,136 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
:title="data.id ? '日志详情 ID:' + data.id : '日志详情'"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="请求方式">{{ data.method }}</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>请求地址</template>
|
||||
{{ data.url }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
|
||||
<n-descriptions-item label="访问IP">{{ data.ip }}</n-descriptions-item>
|
||||
<n-descriptions-item label="IP归属地">河南 郑州</n-descriptions-item>
|
||||
<n-descriptions-item label="链路ID">{{ data.reqId }}</n-descriptions-item>
|
||||
<n-descriptions-item label="响应时间">{{
|
||||
timestampToTime(data.timestamp)
|
||||
}}</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item label="创建时间">{{ data.createdAt }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="访问代理"
|
||||
>
|
||||
{{ data.userAgent }}
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="报错信息"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="报错状态码"> {{ data.errorCode }} </n-descriptions-item>
|
||||
<n-descriptions-item label="报错消息">
|
||||
<n-tag type="success"> {{ data.errorMsg }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="报错日志">
|
||||
<n-tag type="success"> {{ data.errorData }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="Header请求头"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.headerData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="GET参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.getData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="POST参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.postData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import 'vue3-json-viewer/dist/index.css';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { View } from '@/api/log/log';
|
||||
import { timestampToTime } from '@/utils/dateUtil';
|
||||
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const logId = Number(router.currentRoute.value.params.id);
|
||||
|
||||
onMounted(async () => {
|
||||
if (logId === undefined || logId < 1) {
|
||||
message.error('ID不正确,请检查!');
|
||||
return;
|
||||
}
|
||||
|
||||
await getInfo();
|
||||
});
|
||||
|
||||
const data = ref({});
|
||||
|
||||
const getInfo = async () => {
|
||||
data.value = await View({ id: logId });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
@@ -25,7 +25,8 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
@@ -43,15 +44,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { NTag, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { getLogList, Delete } from '@/api/log/smslog';
|
||||
import { DeleteOutlined } from '@vicons/antd';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
import { defRangeShortcuts } from "@/utils/dateUtil";
|
||||
import { adaTableScrollX, getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
|
||||
const options = ref<Options>({
|
||||
config_sms_template: [],
|
||||
@@ -235,6 +236,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
|
@@ -11,6 +11,9 @@
|
||||
{{ item.label }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
<n-space justify="center" class="mt-2">
|
||||
<n-text depth="3">SaaS系统多租户多应用设计</n-text>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
@@ -23,7 +26,7 @@
|
||||
|
||||
const accounts = [
|
||||
{
|
||||
label: '超级管理员',
|
||||
label: '超管',
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
},
|
||||
@@ -33,10 +36,20 @@
|
||||
password: '123456',
|
||||
},
|
||||
{
|
||||
label: '代理商',
|
||||
label: '租户',
|
||||
username: 'ameng',
|
||||
password: '123456',
|
||||
},
|
||||
{
|
||||
label: '商户',
|
||||
username: 'abai',
|
||||
password: '123456',
|
||||
},
|
||||
{
|
||||
label: '用户',
|
||||
username: 'asong',
|
||||
password: '123456',
|
||||
},
|
||||
];
|
||||
|
||||
function login(username: string, password: string) {
|
||||
|
@@ -292,7 +292,9 @@
|
||||
message.success('登录成功,即将进入系统');
|
||||
if (route.name === LOGIN_NAME) {
|
||||
await router.replace('/');
|
||||
} else await router.replace(toPath);
|
||||
} else {
|
||||
await router.replace(toPath);
|
||||
}
|
||||
} else {
|
||||
message.destroyAll();
|
||||
message.info(msg || '登录失败');
|
||||
|
@@ -140,7 +140,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import '../components/style.less';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
@@ -165,6 +165,7 @@
|
||||
password: string;
|
||||
}
|
||||
|
||||
const emit = defineEmits(['updateActiveModule']);
|
||||
const formRef = ref();
|
||||
const router = useRouter();
|
||||
const message = useMessage();
|
||||
@@ -176,8 +177,9 @@
|
||||
const { sendLabel, isCounting, loading: sendLoading, activateSend } = useSendCode();
|
||||
const agreement = ref(false);
|
||||
const inviteCodeDisabled = ref(false);
|
||||
const dialogWidth = ref('85%');
|
||||
const emit = defineEmits(['updateActiveModule']);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
const formInline = ref<FormState>({
|
||||
username: '',
|
||||
@@ -243,8 +245,6 @@
|
||||
inviteCodeDisabled.value = true;
|
||||
formInline.value.inviteCode = inviteCode;
|
||||
}
|
||||
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
|
||||
function updateActiveModule(key: string) {
|
||||
|
@@ -54,7 +54,7 @@ export const columns = [
|
||||
{
|
||||
title: '登录地址',
|
||||
key: 'addr',
|
||||
width: 120,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
|
@@ -19,7 +19,8 @@
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="info" @click="openGroupModal">
|
||||
@@ -46,7 +47,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
@@ -57,6 +58,7 @@
|
||||
import Edit from '@/views/monitor/netconn/modal/edit.vue';
|
||||
import { newState, options, State } from '@/views/monitor/netconn/modal/model';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
@@ -66,7 +68,7 @@
|
||||
const formParams = ref({});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 150,
|
||||
width: 180,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
@@ -89,6 +91,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
@@ -172,9 +178,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -97,7 +97,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { Edit, View } from '@/api/serveLicense';
|
||||
import DatePicker from '@/components/DatePicker/datePicker.vue';
|
||||
import { rules, options, State, newState } from './model';
|
||||
@@ -131,8 +131,10 @@
|
||||
const params = ref<State>(props.formParams);
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -153,10 +155,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
@@ -22,9 +22,8 @@
|
||||
:actionColumn="actionColumn"
|
||||
:checked-row-keys="checkedIds"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
@@ -118,7 +117,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, onMounted, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage, NTag } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
@@ -126,7 +125,7 @@
|
||||
import { Delete, Export, List, Status, AssignRouter } from '@/api/serveLicense';
|
||||
import { columns, newState, options, schemas, State } from './model';
|
||||
import { DeleteOutlined, ExportOutlined, PlusOutlined } from '@vicons/antd';
|
||||
import { adaModalWidth, getOptionLabel } from '@/utils/hotgo';
|
||||
import { adaModalWidth, adaTableScrollX, getOptionLabel } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
@@ -139,9 +138,11 @@
|
||||
const showModal = ref(false);
|
||||
const formParams = ref<State>();
|
||||
const showRoutesModal = ref(false);
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 300,
|
||||
@@ -188,6 +189,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
@@ -233,9 +238,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -253,9 +255,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -341,10 +340,6 @@
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -46,18 +46,31 @@ export const columns = [
|
||||
key: 'avatar',
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(NAvatar, {
|
||||
size: 32,
|
||||
src: row.avatar,
|
||||
});
|
||||
if (row.avatar !== '') {
|
||||
return h(NAvatar, {
|
||||
circle: true,
|
||||
size: 'small',
|
||||
src: row.avatar,
|
||||
});
|
||||
} else {
|
||||
return h(
|
||||
NAvatar,
|
||||
{
|
||||
circle: true,
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
default: () => row.username.substring(0, 2),
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '登录IP',
|
||||
key: 'ip',
|
||||
width: 120,
|
||||
width: 150,
|
||||
},
|
||||
|
||||
// {
|
||||
// title: 'IP地区',
|
||||
// key: 'region',
|
||||
|
@@ -16,20 +16,22 @@
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { OnlineList, Offline } from '@/api/monitor/monitor';
|
||||
import { columns } from './columns';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
@@ -107,6 +109,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
@@ -125,9 +131,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,7 @@
|
||||
:actionColumn="actionColumn"
|
||||
:checked-row-keys="checkedIds"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
@@ -100,7 +100,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
@@ -111,6 +111,7 @@
|
||||
import { useRouter } from 'vue-router';
|
||||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import 'vue3-json-viewer/dist/index.css';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const router = useRouter();
|
||||
@@ -123,7 +124,7 @@
|
||||
const showModal = ref(false);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 150,
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
@@ -152,6 +153,10 @@
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
|
163
web/src/views/normalTreeDemo/edit.vue
Normal file
163
web/src/views/normalTreeDemo/edit.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
:title="formValue.id > 0 ? '编辑普通树表 #' + formValue.id : '添加普通树表'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
:label-placement="settingStore.isMobile ? 'top' : 'left'"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-grid cols="1 s:1 m:2 l:2 xl:2 2xl:2" responsive="screen">
|
||||
<n-gi span="2">
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input placeholder="请输入标题" v-model:value="formValue.title" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="上级" path="pid">
|
||||
<n-tree-select
|
||||
:options="treeOption"
|
||||
v-model:value="formValue.pid"
|
||||
key-field="id"
|
||||
label-field="title"
|
||||
clearable
|
||||
filterable
|
||||
default-expand-all
|
||||
show-path
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="测试分类" path="categoryId">
|
||||
<n-select v-model:value="formValue.categoryId" :options="options.testCategoryOption" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input placeholder="请输入描述" v-model:value="formValue.description" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number placeholder="请输入排序" v-model:value="formValue.sort" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-select v-model:value="formValue.status" :options="options.sys_normal_disable" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-spin>
|
||||
</n-scrollbar>
|
||||
<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 { ref, computed } from 'vue';
|
||||
import { Edit, View, MaxSort } from '@/api/normalTreeDemo';
|
||||
import { options, State, newState, treeOption, loadTreeOption, rules } from './model';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const message = useMessage();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref<State>(newState(null));
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(840);
|
||||
});
|
||||
|
||||
function openModal(state: State) {
|
||||
showModal.value = true;
|
||||
|
||||
// 加载关系树选项
|
||||
loadTreeOption();
|
||||
|
||||
// 新增
|
||||
if (!state || state.id < 1) {
|
||||
formValue.value = newState(state);
|
||||
|
||||
loading.value = true;
|
||||
MaxSort()
|
||||
.then((res) => {
|
||||
formValue.value.sort = res.sort;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(formValue.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
closeForm();
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
202
web/src/views/normalTreeDemo/index.vue
Normal file
202
web/src/views/normalTreeDemo/index.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="普通树表">
|
||||
<!-- 这是由系统生成的CURD表格,你可以将此行注释改为表格的描述 -->
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm ref="searchFormRef" @register="register" @submit="reloadTable" @reset="reloadTable" @keyup.enter="reloadTable">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
<BasicTable ref="actionRef" openChecked :columns="columns" :request="loadDataTable" :row-key="(row) => row.id" :actionColumn="actionColumn" :scroll-x="1280" :resizeHeightOffset="-10000" :cascade="false" :expanded-row-keys="expandedKeys" @update:expanded-row-keys="updateExpandedKeys" :checked-row-keys="checkedIds" @update:checked-row-keys="handleOnCheckedRow">
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable" class="min-left-space" v-if="hasPermission(['/normalTreeDemo/edit'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button type="error" @click="handleBatchDelete" class="min-left-space" v-if="hasPermission(['/normalTreeDemo/delete'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button type="primary" icon-placement="left" @click="handleAllExpanded" class="min-left-space">
|
||||
全部{{ expandedKeys.length ? '收起' : '展开' }}
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<AlignLeftOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Edit ref="editRef" @reloadTable="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, onMounted } 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 { List, Delete } from '@/api/normalTreeDemo';
|
||||
import { PlusOutlined, DeleteOutlined, AlignLeftOutlined } from '@vicons/antd';
|
||||
import { columns, schemas, loadOptions, newState } from './model';
|
||||
import { convertListToTree } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const searchFormRef = ref<any>({});
|
||||
const viewRef = ref();
|
||||
const editRef = ref();
|
||||
const checkedIds = ref([]);
|
||||
const expandedKeys = ref([]);
|
||||
const allTreeKeys = ref([]);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 216,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['/normalTreeDemo/edit'],
|
||||
},
|
||||
{
|
||||
label: '添加',
|
||||
onClick: handleAdd.bind(null, record),
|
||||
auth: ['/normalTreeDemo/edit'],
|
||||
},
|
||||
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/normalTreeDemo/delete'],
|
||||
},
|
||||
],
|
||||
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
// 加载普通数表数据
|
||||
const loadDataTable = async (res = {}) => {
|
||||
const params = { ...(searchFormRef.value?.formModel ?? {}), ...res, pagination: false };
|
||||
const dataSource = await List(params);
|
||||
allTreeKeys.value = expandedKeys.value = dataSource.list.map((item) => item.id);
|
||||
dataSource.list = convertListToTree(dataSource.list, 'id');
|
||||
return dataSource;
|
||||
};
|
||||
|
||||
// 更新选中的行
|
||||
function handleOnCheckedRow(rowKeys) {
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
// 重新加载表格数据
|
||||
function reloadTable() {
|
||||
actionRef.value?.reload();
|
||||
}
|
||||
|
||||
// 添加数据
|
||||
function addTable() {
|
||||
editRef.value.openModal(null);
|
||||
}
|
||||
|
||||
// 添加树节点下级数据
|
||||
function handleAdd(record: Recordable) {
|
||||
const state = newState(null);
|
||||
state.pid = record.id;
|
||||
editRef.value.openModal(state);
|
||||
}
|
||||
|
||||
// 编辑数据
|
||||
function handleEdit(record: Recordable) {
|
||||
editRef.value.openModal(record);
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
function handleBatchDelete() {
|
||||
if (checkedIds.value.length < 1){
|
||||
message.error('请至少选择一项要删除的数据');
|
||||
return;
|
||||
}
|
||||
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
checkedIds.value = [];
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 收起/展开全部树节点
|
||||
function handleAllExpanded() {
|
||||
if (expandedKeys.value.length) {
|
||||
expandedKeys.value = [];
|
||||
} else {
|
||||
expandedKeys.value = allTreeKeys.value;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新展开的树节点
|
||||
function updateExpandedKeys(openKeys: never[]) {
|
||||
expandedKeys.value = openKeys;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
222
web/src/views/normalTreeDemo/model.ts
Normal file
222
web/src/views/normalTreeDemo/model.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { Option, getOptionLabel, getOptionTag } from '@/utils/hotgo';
|
||||
import { renderPopoverMemberSumma, MemberSumma } from '@/utils';
|
||||
import { TreeOption } from '@/api/normalTreeDemo';
|
||||
|
||||
export class State {
|
||||
public title = ''; // 标题
|
||||
public id = 0; // ID
|
||||
public pid = 0; // 上级
|
||||
public level = 1; // 关系树级别
|
||||
public tree = ''; // 关系树
|
||||
public categoryId = null; // 测试分类
|
||||
public description = ''; // 描述
|
||||
public sort = 0; // 排序
|
||||
public status = 1; // 状态
|
||||
public createdBy = 0; // 创建者
|
||||
public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
|
||||
public updatedBy = 0; // 更新者
|
||||
public createdAt = ''; // 创建时间
|
||||
public updatedAt = ''; // 修改时间
|
||||
public deletedAt = ''; // 删除时间
|
||||
|
||||
constructor(state?: Partial<State>) {
|
||||
if (state) {
|
||||
Object.assign(this, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function newState(state: State | Record<string, any> | null): State {
|
||||
if (state !== null) {
|
||||
if (state instanceof State) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return new State(state);
|
||||
}
|
||||
return new State();
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const rules = {
|
||||
title: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
message: '请输入标题',
|
||||
},
|
||||
};
|
||||
|
||||
// 表格搜索表单
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '标题',
|
||||
componentProps: {
|
||||
placeholder: '请输入标题',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'categoryId',
|
||||
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);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 表格列
|
||||
export const columns = [
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '测试分类',
|
||||
key: 'categoryId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (isNullObject(row.categoryId)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.testCategoryOption, row.categoryId),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(options.value.testCategoryOption, row.categoryId),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description',
|
||||
align: 'left',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
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: 'createdBy',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return renderPopoverMemberSumma(row.createdBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
// 字典数据选项
|
||||
export const options = ref({
|
||||
sys_normal_disable: [] as Option[],
|
||||
testCategoryOption: [] as Option[],
|
||||
});
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
Dicts({
|
||||
types: ['sys_normal_disable', 'testCategoryOption'],
|
||||
}).then((res) => {
|
||||
options.value = res;
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.sys_normal_disable;
|
||||
break;
|
||||
case 'categoryId':
|
||||
item.componentProps.options = options.value.testCategoryOption;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关系树选项
|
||||
export const treeOption = ref([]);
|
||||
|
||||
// 加载关系树选项
|
||||
export function loadTreeOption() {
|
||||
TreeOption().then((res) => {
|
||||
treeOption.value = res;
|
||||
});
|
||||
}
|
92
web/src/views/normalTreeDemo/view.vue
Normal file
92
web/src/views/normalTreeDemo/view.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-drawer v-model:show="showModal" :width="dialogWidth">
|
||||
<n-drawer-content title="普通树表详情" closable>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-descriptions label-placement="left" class="py-2" column="1">
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
标题
|
||||
</template>
|
||||
{{ formValue.title }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
上级
|
||||
</template>
|
||||
{{ formValue.pid }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="测试分类">
|
||||
<n-tag :type="getOptionTag(options.testCategoryOption, formValue?.categoryId)" size="small" class="min-left-space">
|
||||
{{ getOptionLabel(options.testCategoryOption, formValue?.categoryId) }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
描述
|
||||
</template>
|
||||
{{ formValue.description }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
排序
|
||||
</template>
|
||||
{{ formValue.sort }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="状态">
|
||||
<n-tag :type="getOptionTag(options.sys_normal_disable, formValue?.status)" size="small" class="min-left-space">
|
||||
{{ getOptionLabel(options.sys_normal_disable, formValue?.status) }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-spin>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { View } from '@/api/normalTreeDemo';
|
||||
import { State, newState, options } from './model';
|
||||
import { adaModalWidth, getOptionLabel, getOptionTag } from '@/utils/hotgo';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
|
||||
const message = useMessage();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref(newState(null));
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(580);
|
||||
});
|
||||
const fileAvatarCSS = computed(() => {
|
||||
return {
|
||||
'--n-merged-size': `var(--n-avatar-size-override, 80px)`,
|
||||
'--n-font-size': `18px`,
|
||||
};
|
||||
});
|
||||
|
||||
//下载
|
||||
function download(url: string) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
function openModal(state: State) {
|
||||
showModal.value = true;
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
163
web/src/views/optionTreeDemo/edit.vue
Normal file
163
web/src/views/optionTreeDemo/edit.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
:title="formValue.id > 0 ? '编辑选项树表 #' + formValue.id : '添加选项树表'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
:label-placement="settingStore.isMobile ? 'top' : 'left'"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-grid cols="1 s:1 m:2 l:2 xl:2 2xl:2" responsive="screen">
|
||||
<n-gi span="2">
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input placeholder="请输入标题" v-model:value="formValue.title" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="上级" path="pid">
|
||||
<n-tree-select
|
||||
:options="treeOption"
|
||||
v-model:value="formValue.pid"
|
||||
key-field="id"
|
||||
label-field="title"
|
||||
clearable
|
||||
filterable
|
||||
default-expand-all
|
||||
show-path
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="测试分类" path="categoryId">
|
||||
<n-select v-model:value="formValue.categoryId" :options="options.testCategoryOption" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input type="textarea" placeholder="描述" v-model:value="formValue.description" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number placeholder="请输入排序" v-model:value="formValue.sort" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-select v-model:value="formValue.status" :options="options.sys_normal_disable" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-spin>
|
||||
</n-scrollbar>
|
||||
<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 { ref, computed } from 'vue';
|
||||
import { Edit, View, MaxSort } from '@/api/optionTreeDemo';
|
||||
import { options, State, newState, treeOption, loadTreeOption, rules } from './model';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const message = useMessage();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref<State>(newState(null));
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(840);
|
||||
});
|
||||
|
||||
function openModal(state: State) {
|
||||
showModal.value = true;
|
||||
|
||||
// 加载关系树选项
|
||||
loadTreeOption();
|
||||
|
||||
// 新增
|
||||
if (!state || state.id < 1) {
|
||||
formValue.value = newState(state);
|
||||
|
||||
loading.value = true;
|
||||
MaxSort()
|
||||
.then((res) => {
|
||||
formValue.value.sort = res.sort;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(formValue.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
closeForm();
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
314
web/src/views/optionTreeDemo/index.vue
Normal file
314
web/src/views/optionTreeDemo/index.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="选项树表">
|
||||
<!-- 这是由系统生成的CURD表格,你可以将此行注释改为表格的描述 -->
|
||||
</n-card>
|
||||
</div>
|
||||
<n-grid class="mt-4" cols="1 s:1 m:1 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12">
|
||||
<n-gi span="1">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small">
|
||||
<template #header>
|
||||
<n-space>
|
||||
<n-button type="info" icon-placement="left" @click="addTable" v-if="hasPermission(['/optionTreeDemo/edit'])">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button v-if="hasPermission(['/optionTreeDemo/edit'])" type="info" icon-placement="left" @click="handleEdit(selectedState)" :disabled="selectedState.id < 1">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<EditOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
编辑
|
||||
</n-button>
|
||||
<n-button v-if="hasPermission(['/optionTreeDemo/delete'])" type="error" icon-placement="left" @click="handleEdit(selectedState)" :disabled="selectedState.id < 1">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
删除
|
||||
</n-button>
|
||||
<n-button type="info" icon-placement="left" @click="handleAllExpanded">
|
||||
{{ expandedKeys.length ? '收起' : '展开' }}
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<AlignLeftOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<n-input v-model:value="pattern" placeholder="输入名称搜索">
|
||||
<template #suffix>
|
||||
<n-icon size="18" class="cursor-pointer">
|
||||
<SearchOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
<div class="py-3 menu-list">
|
||||
<template v-if="loading">
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<n-spin size="medium" />
|
||||
</div>
|
||||
</template>
|
||||
<n-tree v-else show-line block-line cascade virtual-scroll :pattern="pattern" :data="treeOption" :expandedKeys="expandedKeys" style="height: 75vh" key-field="id" label-field="title" @update:selected-keys="handleSelected" @update:expanded-keys="handleOnExpandedKeys" />
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi span="3">
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<template #header v-if="selectedState.id > 0">
|
||||
<n-space>
|
||||
<n-icon size="18">
|
||||
<FormOutlined />
|
||||
</n-icon>
|
||||
<span>
|
||||
正在编辑 {{ selectedState.title }}
|
||||
</span>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-result v-show="selectedState.id < 1" status="info" title="提示" description="请先从列表选择一项后,进行编辑">
|
||||
<template #footer>
|
||||
<n-button type="info" icon-placement="left" @click="handleAdd(selectedState)" v-if="hasPermission(['/optionTreeDemo/edit'])">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
</template>
|
||||
</n-result>
|
||||
<BasicForm v-if="selectedState.id > 0" ref="searchFormRef" @register="register" @submit="reloadTable" @reset="reloadTable" @keyup.enter="reloadTable">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
<BasicTable v-if="selectedState.id > 0" ref="actionRef" openChecked :columns="columns" :request="loadDataTable" :row-key="(row) => row.id" :actionColumn="actionColumn" :scroll-x="scrollX" :resizeHeightOffset="-10000" :checked-row-keys="checkedIds" @update:checked-row-keys="handleOnCheckedRow">
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="handleAdd(selectedState)" class="min-left-space" v-if="hasPermission(['/optionTreeDemo/edit'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button type="error" @click="handleBatchDelete" class="min-left-space" v-if="hasPermission(['/optionTreeDemo/delete'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<Edit ref="editRef" @reloadTable="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, computed, onMounted, unref } 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 { List, Delete, TreeOption } from '@/api/optionTreeDemo';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, AlignLeftOutlined, FormOutlined, SearchOutlined } from '@vicons/antd';
|
||||
import { columns, schemas, loadOptions, loadTreeOption, treeOption, State, newState } from './model';
|
||||
import { adaTableScrollX, getTreeKeys } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const searchFormRef = ref<any>({});
|
||||
const editRef = ref();
|
||||
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const expandedKeys = ref([]);
|
||||
const pattern = ref('');
|
||||
const selectedState = ref<State>(newState(null));
|
||||
const loading = ref(false);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 144,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['/optionTreeDemo/edit'],
|
||||
},
|
||||
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/optionTreeDemo/delete'],
|
||||
},
|
||||
],
|
||||
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
// 加载选项式树表数据
|
||||
const loadDataTable = async (res = {}) => {
|
||||
if (selectedState.value.id < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 刷新树选项
|
||||
loadTreeOption();
|
||||
|
||||
// 获取选中的下级列表
|
||||
const params = {
|
||||
...(searchFormRef.value?.formModel ?? {}),
|
||||
...res,
|
||||
pid: selectedState.value.id,
|
||||
};
|
||||
return await List(params);
|
||||
};
|
||||
|
||||
// 更新选中的行
|
||||
function handleOnCheckedRow(rowKeys) {
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
// 重新加载表格数据
|
||||
function reloadTable() {
|
||||
actionRef.value?.reload();
|
||||
}
|
||||
|
||||
// 添加数据
|
||||
function addTable() {
|
||||
editRef.value.openModal(null);
|
||||
}
|
||||
|
||||
// 添加树节点下级数据
|
||||
function handleAdd(record: Recordable) {
|
||||
const state = newState(null);
|
||||
state.pid = record.id;
|
||||
editRef.value.openModal(state);
|
||||
}
|
||||
|
||||
// 编辑数据
|
||||
function handleEdit(record: Recordable) {
|
||||
editRef.value.openModal(record);
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
function handleBatchDelete() {
|
||||
if (checkedIds.value.length < 1){
|
||||
message.error('请至少选择一项要删除的数据');
|
||||
return;
|
||||
}
|
||||
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
checkedIds.value = [];
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSelected(keys, option) {
|
||||
if (keys.length) {
|
||||
selectedState.value = newState(option[0]);
|
||||
reloadTable();
|
||||
} else {
|
||||
selectedState.value = newState(null);
|
||||
}
|
||||
}
|
||||
|
||||
function handleOnExpandedKeys(keys) {
|
||||
expandedKeys.value = keys;
|
||||
}
|
||||
|
||||
function handleAllExpanded() {
|
||||
if (expandedKeys.value.length) {
|
||||
expandedKeys.value = [];
|
||||
} else {
|
||||
expandedKeys.value = getTreeKeys(unref(treeOption), 'id');
|
||||
}
|
||||
}
|
||||
|
||||
// 首次加载树选项,默认展开全部
|
||||
function firstLoadTreeOption() {
|
||||
loading.value = true;
|
||||
TreeOption().then((res) => {
|
||||
treeOption.value = res;
|
||||
expandedKeys.value = getTreeKeys(unref(treeOption), 'id');
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
firstLoadTreeOption();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
216
web/src/views/optionTreeDemo/model.ts
Normal file
216
web/src/views/optionTreeDemo/model.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { Option, getOptionLabel, getOptionTag } from '@/utils/hotgo';
|
||||
import { renderPopoverMemberSumma, MemberSumma } from '@/utils';
|
||||
import { TreeOption } from '@/api/optionTreeDemo';
|
||||
|
||||
export class State {
|
||||
public title = ''; // 标题
|
||||
public id = 0; // ID
|
||||
public pid = 0; // 上级
|
||||
public level = 1; // 关系树级别
|
||||
public tree = ''; // 关系树
|
||||
public categoryId = null; // 测试分类
|
||||
public description = ''; // 描述
|
||||
public sort = 0; // 排序
|
||||
public status = 1; // 状态
|
||||
public createdBy = 0; // 创建者
|
||||
public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
|
||||
public updatedBy = 0; // 更新者
|
||||
public createdAt = ''; // 创建时间
|
||||
public updatedAt = ''; // 修改时间
|
||||
public deletedAt = ''; // 删除时间
|
||||
|
||||
constructor(state?: Partial<State>) {
|
||||
if (state) {
|
||||
Object.assign(this, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function newState(state: State | Record<string, any> | null): State {
|
||||
if (state !== null) {
|
||||
if (state instanceof State) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return new State(state);
|
||||
}
|
||||
return new State();
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const rules = {
|
||||
title: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
message: '请输入标题',
|
||||
},
|
||||
};
|
||||
|
||||
// 表格搜索表单
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '标题',
|
||||
componentProps: {
|
||||
placeholder: '请输入标题',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'categoryId',
|
||||
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);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 表格列
|
||||
export const columns = [
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '测试分类',
|
||||
key: 'categoryId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (isNullObject(row.categoryId)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.testCategoryOption, row.categoryId),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(options.value.testCategoryOption, row.categoryId),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
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: 'createdBy',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return renderPopoverMemberSumma(row.createdBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
// 字典数据选项
|
||||
export const options = ref({
|
||||
sys_normal_disable: [] as Option[],
|
||||
testCategoryOption: [] as Option[],
|
||||
});
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
Dicts({
|
||||
types: ['sys_normal_disable', 'testCategoryOption'],
|
||||
}).then((res) => {
|
||||
options.value = res;
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.sys_normal_disable;
|
||||
break;
|
||||
case 'categoryId':
|
||||
item.componentProps.options = options.value.testCategoryOption;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关系树选项
|
||||
export const treeOption = ref([]);
|
||||
|
||||
// 加载关系树选项
|
||||
export function loadTreeOption() {
|
||||
TreeOption().then((res) => {
|
||||
treeOption.value = res;
|
||||
});
|
||||
}
|
92
web/src/views/optionTreeDemo/view.vue
Normal file
92
web/src/views/optionTreeDemo/view.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-drawer v-model:show="showModal" :width="dialogWidth">
|
||||
<n-drawer-content title="选项树表详情" closable>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-descriptions label-placement="left" class="py-2" column="1">
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
标题
|
||||
</template>
|
||||
{{ formValue.title }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
上级
|
||||
</template>
|
||||
{{ formValue.pid }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="测试分类">
|
||||
<n-tag :type="getOptionTag(options.testCategoryOption, formValue?.categoryId)" size="small" class="min-left-space">
|
||||
{{ getOptionLabel(options.testCategoryOption, formValue?.categoryId) }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
描述
|
||||
</template>
|
||||
{{ formValue.description }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
排序
|
||||
</template>
|
||||
{{ formValue.sort }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="状态">
|
||||
<n-tag :type="getOptionTag(options.sys_normal_disable, formValue?.status)" size="small" class="min-left-space">
|
||||
{{ getOptionLabel(options.sys_normal_disable, formValue?.status) }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-spin>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { View } from '@/api/optionTreeDemo';
|
||||
import { State, newState, options } from './model';
|
||||
import { adaModalWidth, getOptionLabel, getOptionTag } from '@/utils/hotgo';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
|
||||
const message = useMessage();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref(newState(null));
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(580);
|
||||
});
|
||||
const fileAvatarCSS = computed(() => {
|
||||
return {
|
||||
'--n-merged-size': `var(--n-avatar-size-override, 80px)`,
|
||||
'--n-font-size': `18px`,
|
||||
};
|
||||
});
|
||||
|
||||
//下载
|
||||
function download(url: string) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
function openModal(state: State) {
|
||||
showModal.value = true;
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
@@ -1,20 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" title="部门管理">
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="部门管理">
|
||||
<!-- 这是由系统生成的CURD表格,你可以将此行注释改为表格的描述 -->
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
ref="searchFormRef"
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
@keyup.enter="handleSubmit"
|
||||
ref="formRef"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
<n-space vertical :size="12">
|
||||
<n-space>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<BasicTable
|
||||
ref="actionRef"
|
||||
openChecked
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="1280"
|
||||
:resizeHeightOffset="-10000"
|
||||
:cascade="false"
|
||||
:expanded-row-keys="expandedKeys"
|
||||
@update:expanded-row-keys="updateExpandedKeys"
|
||||
:checked-row-keys="checkedIds"
|
||||
@update:checked-row-keys="handleOnCheckedRow"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="addTable"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/dept/edit'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
@@ -22,175 +46,91 @@
|
||||
</template>
|
||||
添加部门
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
<n-data-table
|
||||
v-if="data.length > 0 || !loading"
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:row-key="rowKey"
|
||||
:loading="loading"
|
||||
:resizeHeightOffset="-20000"
|
||||
default-expand-all
|
||||
/>
|
||||
</n-space>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="formParams?.id > 0 ? '编辑部门 #' + formParams?.id : '添加部门'"
|
||||
>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="上级部门" path="pid">
|
||||
<n-tree-select
|
||||
key-field="id"
|
||||
:options="options"
|
||||
:default-value="optionsDefaultValue"
|
||||
:default-expand-all="true"
|
||||
@update:value="handleUpdateValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="部门名称" path="name">
|
||||
<n-input placeholder="请输入名称" v-model:value="formParams.name" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="部门编码" path="code">
|
||||
<n-input placeholder="请输入部门编码" v-model:value="formParams.code" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="负责人" path="leader">
|
||||
<n-input placeholder="请输入负责人" v-model:value="formParams.leader" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="联系电话" path="phone">
|
||||
<n-input placeholder="请输入联系电话" v-model:value="formParams.phone" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="邮箱" path="email">
|
||||
<n-input placeholder="请输入邮箱" v-model:value="formParams.email" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="formParams.sort" clearable style="width: 100%" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</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>
|
||||
<n-button
|
||||
type="error"
|
||||
@click="handleBatchDelete"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/dept/delete'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
icon-placement="left"
|
||||
@click="handleAllExpanded"
|
||||
class="min-left-space"
|
||||
>
|
||||
全部{{ expandedKeys.length ? '收起' : '展开' }}
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<AlignLeftOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
</n-modal>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Edit ref="editRef" @reloadTable="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="org_dept">
|
||||
import { h, onMounted, ref } from 'vue';
|
||||
import { DataTableColumns, NButton, NTag, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { TableAction } from '@/components/Table';
|
||||
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
|
||||
import { Delete, Edit, getDeptList, Status } from '@/api/org/dept';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { renderIcon, renderTooltip } from '@/utils';
|
||||
import { HelpCircleOutline } from '@vicons/ionicons5';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, onMounted } 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 { getDeptList, Delete } from '@/api/org/dept';
|
||||
import { PlusOutlined, DeleteOutlined, AlignLeftOutlined } from '@vicons/antd';
|
||||
import { columns, schemas, loadOptions, newState, filterIds } from './model';
|
||||
import { convertListToTree } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
|
||||
type RowData = {
|
||||
createdAt: string;
|
||||
status: number;
|
||||
name: string;
|
||||
id: number;
|
||||
children?: RowData[];
|
||||
};
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const searchFormRef = ref<any>({});
|
||||
const editRef = ref();
|
||||
const checkedIds = ref([]);
|
||||
const expandedKeys = ref([]);
|
||||
const allTreeKeys = ref([]);
|
||||
|
||||
const rules = {
|
||||
name: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入名称',
|
||||
const actionColumn = reactive({
|
||||
width: 160,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['/dept/edit'],
|
||||
},
|
||||
{
|
||||
label: '添加',
|
||||
onClick: handleAdd.bind(null, record),
|
||||
auth: ['/dept/edit'],
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/dept/delete'],
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
code: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入编码',
|
||||
},
|
||||
};
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
component: 'NInput',
|
||||
label: '部门名称',
|
||||
componentProps: {
|
||||
placeholder: '请输入部门名称',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入部门名称', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
component: 'NInput',
|
||||
label: '部门编码',
|
||||
componentProps: {
|
||||
placeholder: '请输入部门编码',
|
||||
showButton: false,
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'leader',
|
||||
component: 'NInput',
|
||||
label: '负责人',
|
||||
componentProps: {
|
||||
placeholder: '请输入负责人',
|
||||
showButton: false,
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
@@ -198,167 +138,45 @@
|
||||
schemas,
|
||||
});
|
||||
|
||||
const options = ref<any>([]);
|
||||
const optionsDefaultValue = ref<any>(null);
|
||||
const loading = ref(false);
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
let formParams = ref<any>();
|
||||
const data = ref<any>([]);
|
||||
const rowKey = (row: RowData) => row.id;
|
||||
|
||||
const defaultState = {
|
||||
id: 0,
|
||||
pid: 0,
|
||||
name: '',
|
||||
code: '',
|
||||
type: '',
|
||||
leader: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
// 加载普通数表数据
|
||||
const loadDataTable = async (res = {}) => {
|
||||
filterIds.value = [];
|
||||
const params = { ...(searchFormRef.value?.formModel ?? {}), ...res, pagination: false };
|
||||
const dataSource = await getDeptList(params);
|
||||
allTreeKeys.value = expandedKeys.value = dataSource.list.map((item) => item.id);
|
||||
dataSource.list = convertListToTree(dataSource.list, 'id');
|
||||
filterIds.value = dataSource.ids;
|
||||
return dataSource;
|
||||
};
|
||||
|
||||
const columns: DataTableColumns<RowData> = [
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '部门', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'支持上下级部门,点击列表中左侧 > 按钮可展开下级部门列表'
|
||||
);
|
||||
},
|
||||
key: 'name',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: 'info',
|
||||
},
|
||||
{
|
||||
default: () => row.name,
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
// {
|
||||
// title: '部门ID',
|
||||
// key: 'index',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: '部门编码',
|
||||
key: 'code',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
key: 'leader',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
key: 'phone',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
key: 'email',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 1 ? 'info' : 'error',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.status == 1 ? '正常' : '已禁用'),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 150,
|
||||
render: (rows, _) => {
|
||||
return rows.createdAt;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 220,
|
||||
fixed: 'right',
|
||||
render(record: any) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '添加',
|
||||
onClick: handleAddSub.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
dropDownActions: statusActions,
|
||||
select: (key) => {
|
||||
updateStatus(record.id, key);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
// 更新选中的行
|
||||
function handleOnCheckedRow(rowKeys) {
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
// 重新加载表格数据
|
||||
function reloadTable() {
|
||||
actionRef.value?.reload();
|
||||
}
|
||||
|
||||
// 添加数据
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = cloneDeep(defaultState);
|
||||
optionsDefaultValue.value = 0;
|
||||
editRef.value.openModal(null);
|
||||
}
|
||||
|
||||
function handleAddSub(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = cloneDeep(defaultState);
|
||||
optionsDefaultValue.value = record.id;
|
||||
// 添加树节点下级数据
|
||||
function handleAdd(record: Recordable) {
|
||||
const state = newState(null);
|
||||
state.pid = record.id;
|
||||
editRef.value.openModal(state);
|
||||
}
|
||||
|
||||
// 编辑数据
|
||||
function handleEdit(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = cloneDeep(record);
|
||||
formParams.value.children = 0;
|
||||
optionsDefaultValue.value = formParams.value.pid;
|
||||
editRef.value.openModal(record);
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
@@ -366,84 +184,53 @@
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
loadDataTable({});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function updateStatus(id: any, status: any) {
|
||||
Status({ id: id, status: status })
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
loadDataTable({});
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
}
|
||||
|
||||
function confirmForm(e: { preventDefault: () => void }) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors: any) => {
|
||||
if (!errors) {
|
||||
Edit(formParams.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
loadDataTable({});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit(values: Recordable) {
|
||||
await loadDataTable(values);
|
||||
}
|
||||
|
||||
function handleReset(_values: Recordable) {}
|
||||
|
||||
const loadDataTable = async (res: Recordable<any>) => {
|
||||
loading.value = true;
|
||||
const tmp = await getDeptList({ ...res, ...formRef.value?.formModel });
|
||||
data.value = tmp?.list;
|
||||
if (data.value === undefined || data.value === null) {
|
||||
data.value = [];
|
||||
// 批量删除
|
||||
function handleBatchDelete() {
|
||||
if (checkedIds.value.length < 1) {
|
||||
message.error('请至少选择一项要删除的数据');
|
||||
return;
|
||||
}
|
||||
|
||||
options.value = [
|
||||
{
|
||||
index: 0,
|
||||
id: 0,
|
||||
label: '顶级部门',
|
||||
children: data.value,
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
checkedIds.value = [];
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
];
|
||||
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await loadDataTable({});
|
||||
});
|
||||
|
||||
function handleUpdateValue(value: any) {
|
||||
formParams.value.pid = value;
|
||||
});
|
||||
}
|
||||
|
||||
// 收起/展开全部树节点
|
||||
function handleAllExpanded() {
|
||||
if (expandedKeys.value.length) {
|
||||
expandedKeys.value = [];
|
||||
} else {
|
||||
expandedKeys.value = allTreeKeys.value;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新展开的树节点
|
||||
function updateExpandedKeys(openKeys: never[]) {
|
||||
expandedKeys.value = openKeys;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
192
web/src/views/org/dept/edit.vue
Normal file
192
web/src/views/org/dept/edit.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
:title="formValue.id > 0 ? '编辑部门 #' + formValue.id : '添加部门'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
:label-placement="settingStore.isMobile ? 'top' : 'left'"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-grid cols="1 s:1 m:1 l:1 xl:1 2xl:1" responsive="screen">
|
||||
<n-gi span="1">
|
||||
<n-form-item label="上级部门" path="pid">
|
||||
<n-tree-select
|
||||
:options="treeOption"
|
||||
v-model:value="formValue.pid"
|
||||
key-field="id"
|
||||
label-field="name"
|
||||
clearable
|
||||
filterable
|
||||
default-expand-all
|
||||
show-path
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="部门名称" path="name">
|
||||
<n-input placeholder="请输入部门名称" v-model:value="formValue.name" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="部门编码" path="code">
|
||||
<n-input placeholder="请输入部门编码" v-model:value="formValue.code" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="部门类型" path="type">
|
||||
<n-radio-group v-model:value="formValue.type" name="type">
|
||||
<n-space>
|
||||
<n-radio v-for="item in options.deptType" :value="item.value">
|
||||
{{ item.label }}
|
||||
</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="负责人" path="leader">
|
||||
<n-input placeholder="请输入负责人" v-model:value="formValue.leader" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="联系电话" path="phone">
|
||||
<n-input placeholder="请输入联系电话" v-model:value="formValue.phone" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="邮箱" path="email">
|
||||
<n-input placeholder="请输入邮箱" v-model:value="formValue.email" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number
|
||||
placeholder="请输入排序"
|
||||
v-model:value="formValue.sort"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formValue.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in options.sys_normal_disable"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-spin>
|
||||
</n-scrollbar>
|
||||
<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 { ref, computed } from 'vue';
|
||||
import { Edit, View, MaxSort } from '@/api/org/dept';
|
||||
import { options, State, newState, treeOption, loadTreeOption, rules } from './model';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const message = useMessage();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref<State>(newState(null));
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(520);
|
||||
});
|
||||
|
||||
function openModal(state: State) {
|
||||
showModal.value = true;
|
||||
|
||||
// 加载关系树选项
|
||||
loadTreeOption();
|
||||
|
||||
// 新增
|
||||
if (!state || state.id < 1) {
|
||||
formValue.value = newState(state);
|
||||
|
||||
loading.value = true;
|
||||
MaxSort()
|
||||
.then((res) => {
|
||||
formValue.value.sort = res.sort;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(formValue.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
closeForm();
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
247
web/src/views/org/dept/model.ts
Normal file
247
web/src/views/org/dept/model.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NTag, NButton } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
import { Option, getOptionLabel, getOptionTag } from '@/utils/hotgo';
|
||||
import { renderTooltip, renderIcon } from '@/utils';
|
||||
import { HelpCircleOutline } from '@vicons/ionicons5';
|
||||
import { TreeOption } from '@/api/org/dept';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
|
||||
export class State {
|
||||
public id = 0; // 部门ID
|
||||
public pid = 0; // 父部门ID
|
||||
public name = ''; // 部门名称
|
||||
public code = ''; // 部门编码
|
||||
public type = 'company'; // 部门类型
|
||||
public leader = ''; // 负责人
|
||||
public phone = ''; // 联系电话
|
||||
public email = ''; // 邮箱
|
||||
public level = 0; // 关系树等级
|
||||
public tree = ''; // 关系树
|
||||
public sort = 0; // 排序
|
||||
public status = 1; // 部门状态
|
||||
public createdAt = ''; // 创建时间
|
||||
public updatedAt = ''; // 更新时间
|
||||
|
||||
constructor(state?: Partial<State>) {
|
||||
if (state) {
|
||||
Object.assign(this, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function newState(state: State | Record<string, any> | null): State {
|
||||
if (state !== null) {
|
||||
if (state instanceof State) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return new State(state);
|
||||
}
|
||||
return new State();
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const rules = {
|
||||
email: {
|
||||
required: false,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
validator: validate.email,
|
||||
},
|
||||
};
|
||||
|
||||
// 表格搜索表单
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'name',
|
||||
component: 'NInput',
|
||||
label: '部门名称',
|
||||
componentProps: {
|
||||
placeholder: '请输入部门名称',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入部门名称', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
component: 'NInput',
|
||||
label: '部门编码',
|
||||
componentProps: {
|
||||
placeholder: '请输入部门编码',
|
||||
showButton: false,
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'leader',
|
||||
component: 'NInput',
|
||||
label: '负责人',
|
||||
componentProps: {
|
||||
placeholder: '请输入负责人',
|
||||
showButton: false,
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const filterIds = ref([]);
|
||||
|
||||
// 表格列
|
||||
export const columns = [
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '部门名称', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'支持上下级部门,点击列表中左侧 > 按钮可展开下级部门列表'
|
||||
);
|
||||
},
|
||||
key: 'name',
|
||||
render(row) {
|
||||
const filter = filterIds.value.includes(row.id as never);
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: 'info',
|
||||
checkable: filter,
|
||||
checked: filter,
|
||||
},
|
||||
{
|
||||
default: () => row.name,
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '部门编码',
|
||||
key: 'code',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '部门类型',
|
||||
key: 'type',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (isNullObject(row.type)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.deptType, row.type),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(options.value.deptType, row.type),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
key: 'leader',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
key: 'phone',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'left',
|
||||
width: 80,
|
||||
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',
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
|
||||
// 字典数据选项
|
||||
export const options = ref({
|
||||
sys_normal_disable: [] as Option[],
|
||||
deptType: [] as Option[],
|
||||
});
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
Dicts({
|
||||
types: ['sys_normal_disable', 'deptType'],
|
||||
}).then((res) => {
|
||||
options.value = res;
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.sys_normal_disable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关系树选项
|
||||
export const treeOption = ref([]);
|
||||
|
||||
// 加载关系树选项
|
||||
export function loadTreeOption() {
|
||||
TreeOption().then((res) => {
|
||||
treeOption.value = res;
|
||||
});
|
||||
}
|
@@ -5,12 +5,12 @@ export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '岗位',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
width: 200,
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
@@ -48,17 +48,14 @@ export const columns = [
|
||||
);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '排序',
|
||||
// key: 'sort',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: '备注',
|
||||
key: 'sort',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 150,
|
||||
render: (rows, _) => {
|
||||
return rows.createdAt;
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
@@ -21,6 +21,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:resizeHeightOffset="-10000"
|
||||
:scroll-x="1090"
|
||||
>
|
||||
<template #tableTitle>
|
||||
@@ -96,16 +97,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="org_post">
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { Delete, Edit, getPostList, Status } from '@/api/org/post';
|
||||
import { Delete, Edit, getPostList } from '@/api/org/post';
|
||||
import { columns } from './columns';
|
||||
import { DeleteOutlined, PlusOutlined } from '@vicons/antd';
|
||||
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
|
||||
import { defRangeShortcuts } from "@/utils/dateUtil";
|
||||
import { statusOptions } from '@/enums/optionsiEnum';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const params = ref<any>({
|
||||
pageSize: 10,
|
||||
@@ -179,25 +181,18 @@
|
||||
|
||||
const resetFormParams = {
|
||||
id: 0,
|
||||
pid: 0,
|
||||
name: '',
|
||||
code: '',
|
||||
type: '',
|
||||
leader: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
sort: 0,
|
||||
name: '',
|
||||
remark: '',
|
||||
sort: null,
|
||||
status: 1,
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
};
|
||||
let formParams = ref<any>(resetFormParams);
|
||||
|
||||
const formParams = ref<any>(resetFormParams);
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
width: 150,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
@@ -211,10 +206,6 @@
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
dropDownActions: statusActions,
|
||||
select: (key) => {
|
||||
updateStatus(record.id, key);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -227,7 +218,7 @@
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = resetFormParams;
|
||||
formParams.value = cloneDeep(resetFormParams);
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
@@ -237,7 +228,6 @@
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
@@ -282,9 +272,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -300,9 +287,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -316,15 +300,6 @@
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function updateStatus(id, status) {
|
||||
Status({ id: id, status: status }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -57,7 +57,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import {
|
||||
addRules as rules,
|
||||
addState as State,
|
||||
@@ -94,8 +94,10 @@
|
||||
const params = ref<State>(props.formParams);
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -116,10 +118,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
@@ -94,8 +94,10 @@
|
||||
const params = ref<State>(props.formParams);
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
@@ -116,10 +118,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { h } from 'vue';
|
||||
import { NAvatar, NTag } from 'naive-ui';
|
||||
import { NAvatar, NTag, NText } from 'naive-ui';
|
||||
import { formatBefore } from '@/utils/dateUtil';
|
||||
|
||||
export const columns = [
|
||||
@@ -17,6 +17,12 @@ export const columns = [
|
||||
title: '姓名',
|
||||
key: 'realName',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (row.realName == '') {
|
||||
return h(NText, { depth: 3 }, { default: () => '未设置' });
|
||||
}
|
||||
return row.realName;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '头像',
|
||||
|
@@ -20,7 +20,8 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1280"
|
||||
:scroll-x="1500"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
@@ -86,54 +87,11 @@
|
||||
class="py-4"
|
||||
>
|
||||
<n-grid x-gap="24" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="姓名" path="realName">
|
||||
<n-input placeholder="请输入姓名" v-model:value="formParams.realName" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="用户名" path="username">
|
||||
<n-input placeholder="请输入登录用户名" v-model:value="formParams.username" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-grid x-gap="24" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="绑定角色" path="roleId">
|
||||
<n-tree-select
|
||||
key-field="id"
|
||||
:options="options.role"
|
||||
:default-value="formParams.roleId"
|
||||
:default-expand-all="true"
|
||||
@update:value="handleUpdateRoleValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="所属部门" path="deptId">
|
||||
<n-tree-select
|
||||
key-field="id"
|
||||
:options="options.dept"
|
||||
:default-value="formParams.deptId"
|
||||
:default-expand-all="true"
|
||||
@update:value="handleUpdateDeptValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-grid x-gap="24" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="绑定岗位" path="postIds">
|
||||
<n-select
|
||||
:default-value="formParams.postIds"
|
||||
multiple
|
||||
:options="options.post"
|
||||
@update:value="handleUpdatePostValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="密码" path="password">
|
||||
<n-input
|
||||
@@ -144,7 +102,57 @@
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-grid x-gap="24" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="所属部门" path="deptId">
|
||||
<n-tree-select
|
||||
key-field="id"
|
||||
:options="options.dept"
|
||||
:default-value="formParams.deptId"
|
||||
@update:value="handleUpdateDeptValue"
|
||||
clearable
|
||||
filterable
|
||||
default-expand-all
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="绑定角色" path="roleId">
|
||||
<n-tree-select
|
||||
key-field="id"
|
||||
:options="options.role"
|
||||
:default-value="formParams.roleId"
|
||||
@update:value="handleUpdateRoleValue"
|
||||
clearable
|
||||
filterable
|
||||
default-expand-all
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-divider title-placement="left">填写更多信息(可选)</n-divider>
|
||||
<n-grid x-gap="24" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="姓名" path="realName">
|
||||
<n-input placeholder="请输入姓名" v-model:value="formParams.realName" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="绑定岗位" path="postIds">
|
||||
<n-select
|
||||
:default-value="formParams.postIds"
|
||||
:options="options.post"
|
||||
@update:value="handleUpdatePostValue"
|
||||
multiple
|
||||
clearable
|
||||
filterable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-grid x-gap="24" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="手机号" path="mobile">
|
||||
@@ -163,7 +171,7 @@
|
||||
<n-form-item label="性别" path="sex">
|
||||
<n-radio-group v-model:value="formParams.sex" name="sex">
|
||||
<n-radio-button
|
||||
v-for="status in sexOptions"
|
||||
v-for="status in options.sys_user_sex"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
@@ -175,7 +183,7 @@
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
v-for="status in options.sys_normal_disable"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
@@ -229,7 +237,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, onMounted } from 'vue';
|
||||
import { h, reactive, ref, onMounted, computed } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { ActionItem, BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm } from '@/components/Form/index';
|
||||
@@ -237,7 +245,6 @@
|
||||
import { columns } from './columns';
|
||||
import { PlusOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import { QrCodeOutline } from '@vicons/ionicons5';
|
||||
import { sexOptions, statusOptions } from '@/enums/optionsiEnum';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { getRandomString } from '@/utils/charset';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
@@ -279,16 +286,18 @@
|
||||
const formRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const dialogWidth = ref('50%');
|
||||
const formParams = ref<any>();
|
||||
const showQrModal = ref(false);
|
||||
const qrParams = ref({
|
||||
name: '',
|
||||
qrUrl: '',
|
||||
});
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 240,
|
||||
width: 280,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
@@ -389,7 +398,6 @@
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
adaModalWidth(dialogWidth);
|
||||
return await List({ ...res, ...searchFormRef.value?.formModel, ...{ roleId: props.type } });
|
||||
};
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user