发布代码生成、更新20+表单组件,优化数据字典,gf版本更新到2.3.1

This commit is contained in:
孟帅
2023-01-18 16:23:39 +08:00
parent 50207ded90
commit 87c27a17a3
386 changed files with 27926 additions and 44297 deletions

View File

@@ -2,10 +2,10 @@
VITE_PORT = 8001
# spa-title
VITE_GLOB_APP_TITLE = HotGo
VITE_GLOB_APP_TITLE = HG后台管理系统
# spa shortname
VITE_GLOB_APP_SHORT_NAME = HotGo
VITE_GLOB_APP_SHORT_NAME = HG
# 生产环境 开启mock
VITE_GLOB_PROD_MOCK = false

View File

@@ -1,5 +1,4 @@
# 只在开发模式中被载入
VITE_PORT = 8001
# 网站根目录
VITE_PUBLIC_PATH = /
@@ -15,7 +14,7 @@ VITE_DROP_CONSOLE = true
# 跨域代理可以配置多个请注意不要换行如果是公网运行请改成公网IP:服务端运行端口
#VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]]
VITE_PROXY=[["/admin","http://42.194.151.158:8000/admin"]]
VITE_PROXY=[["/admin","http://localhost:8000/admin"]]
# API 接口地址
VITE_GLOB_API_URL =

View File

@@ -1,9 +1,11 @@
# 是否开启mock
VITE_USE_MOCK = false
# 只在生产模式中被载入
# 网站根目录
VITE_PUBLIC_PATH = /admin
# 是否开启mock
VITE_USE_MOCK = false
# 网站前缀
VITE_BASE_URL = /

View File

@@ -76,7 +76,7 @@ const menuList = () => {
export default [
{
url: '/admin/menu/list__',
url: '/admin/menu/list',
timeout: 1000,
method: 'get',
response: () => {

View File

@@ -5,6 +5,7 @@ function getMenuKeys() {
const newKeys = [];
doCustomTimes(parseInt(Math.random() * 6), () => {
const key = keys[Math.floor(Math.random() * keys.length)];
// @ts-ignore
newKeys.push(key);
});
return Array.from(new Set(newKeys));
@@ -28,7 +29,7 @@ const roleList = (pageSize) => {
export default [
{
url: '/admin/role/list__',
url: '/admin/role/list',
timeout: 1000,
method: 'get',
response: ({ query }) => {

View File

@@ -1,5 +1,5 @@
import {resultSuccess} from '../_util';
import {ApiEnum} from "@/enums/apiEnum";
import { resultSuccess } from '../_util';
import { ApiEnum } from '@/enums/apiEnum';
const menusList = [
{

View File

@@ -1,6 +1,6 @@
import Mock from 'mockjs';
import {ApiEnum} from '@/enums/apiEnum';
import {resultSuccess} from '../_util';
import { ApiEnum } from '@/enums/apiEnum';
import { resultSuccess } from '../_util';
const Random = Mock.Random;
@@ -44,7 +44,7 @@ export default [
timeout: 1000,
method: 'post',
response: () => {
return resultSuccess({token});
return resultSuccess({ token });
},
},
{

26849
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
{
"name": "hotgo",
"version": "1.8.1",
"version": "2.1.0",
"author": {
"name": "Ahjung",
"email": "735878602@qq.com",
"url": "https://github.com/jekip/naive-ui-admin"
"name": "MengShuai",
"email": "133814250@qq.com",
"url": "https://github.com/bufanyun/hotgo"
},
"private": true,
"scripts": {
@@ -42,7 +42,7 @@
"lodash-es": "^4.17.21",
"mitt": "^2.1.0",
"mockjs": "^1.1.0",
"naive-ui": "^2.28.4",
"naive-ui": "^2.34.3",
"node-sass": "^7.0.3",
"pinia": "^2.0.14",
"qs": "^6.10.3",
@@ -109,6 +109,10 @@
}
},
"keywords": [
"hotgo",
"hg",
"gf",
"goframe",
"vue",
"naive-ui",
"naive-ui-admin",
@@ -120,13 +124,13 @@
],
"repository": {
"type": "git",
"url": "git+https://github.com/jekip/naive-ui-admin.git"
"url": "git+https://github.com/bufanyun/hotgo.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/jekip/naive-ui-admin/issues"
"url": "https://github.com/bufanyun/hotgo/issues"
},
"homepage": "https://github.com/jekip/naive-ui-admin",
"homepage": "https://github.com/bufanyun/hotgo",
"engines": {
"node": "^12 || >=14"
}

7612
web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
web/public/onerror.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -0,0 +1,74 @@
import { http, jumpExport } from '@/utils/http/axios';
// 获取生成演示列表
export function List(params) {
return http.request({
url: '/curdDemo/list',
method: 'get',
params,
});
}
// 删除/批量删除生成演示
export function Delete(params) {
return http.request({
url: '/curdDemo/delete',
method: 'POST',
params,
});
}
// 新建/编辑生成演示
export function Edit(params) {
return http.request({
url: '/curdDemo/edit',
method: 'POST',
params,
});
}
// 修改生成演示状态
export function Status(params) {
return http.request({
url: '/curdDemo/status',
method: 'POST',
params,
});
}
// 操作生成演示开关
export function Switch(params) {
return http.request({
url: '/curdDemo/switch',
method: 'POST',
params,
});
}
// 获取生成演示指定详情
export function View(params) {
return http.request({
url: '/curdDemo/view',
method: 'GET',
params,
});
}
// 获取生成演示最大排序
export function MaxSort() {
return http.request({
url: '/curdDemo/maxSort',
method: 'GET',
});
}
// 导出生成演示
export function Export(params) {
jumpExport('/curdDemo/export', params);
}

View File

@@ -0,0 +1,87 @@
import { http } from '@/utils/http/axios';
export function List(params) {
return http.request({
url: '/genCodes/list',
method: 'get',
params,
});
}
export function Delete(params) {
return http.request({
url: '/genCodes/delete',
method: 'POST',
params,
});
}
export function View(params) {
return http.request({
url: '/genCodes/view',
method: 'GET',
params,
});
}
export function Edit(params) {
return http.request({
url: '/genCodes/edit',
method: 'POST',
params,
});
}
export function Status(params) {
return http.request({
url: '/genCodes/status',
method: 'POST',
params,
});
}
export function Selects(params) {
return http.request({
url: '/genCodes/selects',
method: 'get',
params,
});
}
export function TableSelect(params) {
return http.request({
url: '/genCodes/tableSelect',
method: 'get',
params,
});
}
export function ColumnSelect(params) {
return http.request({
url: '/genCodes/columnSelect',
method: 'get',
params,
});
}
export function ColumnList(params) {
return http.request({
url: '/genCodes/columnList',
method: 'get',
params,
});
}
export function Preview(params) {
return http.request({
url: '/genCodes/preview',
method: 'post',
params,
});
}
export function Build(params) {
return http.request({
url: '/genCodes/build',
method: 'post',
params,
});
}

View File

@@ -6,7 +6,7 @@ import { http } from '@/utils/http/axios';
*/
export function getDictTree(params?) {
return http.request({
url: '/dict_type/tree',
url: '/dictType/tree',
method: 'GET',
params,
});
@@ -19,7 +19,7 @@ export function getDictTree(params?) {
*/
export function EditDict(params?) {
return http.request({
url: '/dict_type/edit',
url: '/dictType/edit',
method: 'POST',
params,
});
@@ -32,7 +32,7 @@ export function EditDict(params?) {
*/
export function DeleteDict(params?) {
return http.request({
url: '/dict_type/delete',
url: '/dictType/delete',
method: 'POST',
params,
});
@@ -44,7 +44,7 @@ export function DeleteDict(params?) {
*/
export function getDictSelect(params?) {
return http.request({
url: '/dict_type/select',
url: '/dictType/select',
method: 'GET',
params,
});
@@ -57,7 +57,7 @@ export function getDictSelect(params?) {
*/
export function EditData(params?) {
return http.request({
url: '/dict_data/edit',
url: '/dictData/edit',
method: 'POST',
params,
});
@@ -70,7 +70,7 @@ export function EditData(params?) {
*/
export function DeleteData(params?) {
return http.request({
url: '/dict_data/delete',
url: '/dictData/delete',
method: 'POST',
params,
});
@@ -82,7 +82,28 @@ export function DeleteData(params?) {
*/
export function getDataList(params?) {
return http.request({
url: '/dict_data/list',
url: '/dictData/list',
method: 'GET',
params,
});
}
/**
* 获取字典数据列表
*/
export function Dict(type) {
return http.request({
url: '/dictData/option/' + type,
method: 'GET',
});
}
/**
* 获取字典数据列表
*/
export function Dicts(params) {
return http.request({
url: '/dictData/options',
method: 'GET',
params,
});

View File

@@ -31,3 +31,11 @@ export function Delete(params) {
params,
});
}
export function ResetPwd(params) {
return http.request({
url: '/member/reset_pwd',
method: 'POST',
params,
});
}

View File

@@ -15,6 +15,13 @@ export function updateConfig(params) {
});
}
export function TypeSelect() {
return http.request({
url: '/config/typeSelect',
method: 'get',
});
}
export function sendTestEmail(params) {
return http.request({
url: '/ems/sendTest',

View File

@@ -3,10 +3,11 @@ import { http } from '@/utils/http/axios';
/**
* @description: 角色列表
*/
export function getRoleList() {
export function getRoleList(params) {
return http.request({
url: '/role/list',
method: 'GET',
params,
});
}
@@ -41,3 +42,18 @@ export function GetPermissions(params) {
params,
});
}
export function DataScopeSelect() {
return http.request({
url: '/role/dataScope/select',
method: 'GET',
});
}
export function DataScopeEdit(params) {
return http.request({
url: '/role/dataScope/edit',
method: 'POST',
params,
});
}

68
web/src/api/test/index.ts Normal file
View File

@@ -0,0 +1,68 @@
import { http, jumpExport } from '@/utils/http/axios';
// 列表
export function List(params) {
return http.request({
url: '/test/list',
method: 'get',
params,
});
}
// 删除/批量删除
export function Delete(params) {
return http.request({
url: '/test/delete',
method: 'POST',
params,
});
}
// 新建/编辑
export function Edit(params) {
return http.request({
url: '/test/edit',
method: 'POST',
params,
});
}
// 修改状态
export function Status(params) {
return http.request({
url: '/test/status',
method: 'POST',
params,
});
}
// 操作开关
export function Switch(params) {
return http.request({
url: '/test/switch',
method: 'POST',
params,
});
}
// 详情
export function View(params) {
return http.request({
url: '/test/view',
method: 'GET',
params,
});
}
// 获取最大排序
export function MaxSort() {
return http.request({
url: '/test/maxSort',
method: 'GET',
});
}
// 导出
export function Export(params) {
jumpExport('/test/export', params);
}

View File

@@ -0,0 +1,84 @@
<template>
<n-date-picker v-bind="$props" v-model:value="modelValue" :shortcuts="shortcuts" />
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, ref } from 'vue';
import {
dateToTimestamp,
formatToDate,
formatToDateTime,
timestampToTime,
defShortcuts,
defRangeShortcuts,
} from '@/utils/dateUtil';
import { basicProps } from './props';
export default defineComponent({
name: 'BasicUpload',
props: {
...basicProps,
},
emits: ['update:formValue', 'update:startValue', 'update:endValue'],
setup(props, { emit }) {
const shortcuts = ref<any>({});
function getTimestamp(value) {
let t = dateToTimestamp(value);
if (t === 0) {
return new Date().getTime();
}
return t;
}
function setTimestamp(value) {
if (!isTimeType()) {
return formatToDate(new Date(Number(value)).toDateString());
} else {
return formatToDateTime(timestampToTime(Number(value / 1000)));
}
}
function isRangeType() {
return props.type.indexOf('range') != -1;
}
function isTimeType() {
return props.type.indexOf('time') != -1;
}
const modelValue = computed({
get() {
if (!isRangeType()) {
return getTimestamp(props.formValue);
} else {
return [getTimestamp(props.startValue), getTimestamp(props.endValue)];
}
},
set(value) {
if (!isRangeType()) {
emit('update:formValue', setTimestamp(value));
} else {
emit('update:startValue', setTimestamp(value[0]));
emit('update:endValue', setTimestamp(value[1]));
}
},
});
onMounted(async () => {
if (!isRangeType()) {
shortcuts.value = defShortcuts();
} else {
shortcuts.value = defRangeShortcuts();
}
});
return {
modelValue,
shortcuts,
};
},
});
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,18 @@
import type { PropType } from 'vue';
import { NDatePicker } from 'naive-ui';
export const basicProps = {
...NDatePicker.props,
formValue: {
type: String as PropType<string> | undefined | Date,
default: () => '',
},
startValue: {
type: String as PropType<string> | undefined | Date,
default: () => '',
},
endValue: {
type: String as PropType<string> | undefined | Date,
default: () => '',
},
};

View File

@@ -0,0 +1,84 @@
<template>
<QuillEditor
ref="quillEditor"
:options="options"
v-model:content="content"
@ready="readyQuill"
class="quillEditor"
:id="quillEditorId"
/>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from 'vue';
import { QuillEditor } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { getRandomString } from '@/utils/charset';
export interface Props {
value: string;
}
const emit = defineEmits(['update:value']);
const quillEditorId = ref('quillEditorId-' + getRandomString(16, true));
const quillEditor = ref();
const content = ref();
const props = withDefaults(defineProps<Props>(), { value: '' });
const options = ref({
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{ header: 1 }, { header: 2 }], // custom button values
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
[{ direction: 'rtl' }], // text direction
[{ size: ['small', false, 'large', 'huge'] }], // custom dropdown
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ font: [] }],
[{ align: [] }],
['clean'],
['image'],
],
},
theme: 'snow',
placeholder: '输入您要编辑的内容!',
});
function readyQuill() {
quillEditor.value.setHTML(props.value);
}
watch(
() => content.value,
(_newValue, _oldValue) => {
if (quillEditor.value !== undefined) {
emit('update:value', quillEditor.value.getHTML());
}
},
{
immediate: true, // 深度监听
}
);
onMounted(async () => {
// 兼容表单分组 n-form-item-blank
let dom = document.getElementById(quillEditorId.value);
if (dom && dom.parentNode) {
const parent = dom.parentNode as Element;
if ('n-form-item-blank' === parent.className) {
parent.setAttribute('style', 'display: block;');
}
}
});
</script>
<style lang="less">
.ql-container {
height: auto;
}
</style>

View File

@@ -10,7 +10,7 @@ export interface FormSchema {
labelMessageStyle?: object | string;
defaultValue?: any;
component?: ComponentType;
componentProps?: object;
componentProps?: object | any;
slot?: string;
rules?: object | object[];
giProps?: GridItemProps;

View File

@@ -0,0 +1,94 @@
<template>
<n-popover trigger="click" placement="bottom" width="400">
<template #trigger>
<n-button>
<template #icon>
<n-icon size="20">
<component :is="formValue !== '' ? formValue : 'AntDesignOutlined'" />
</n-icon>
</template>
</n-button>
</template>
<n-scrollbar class="grid-wrapper">
<n-grid :cols="8" :collapsed="false" responsive="screen" style="height: 300px">
<n-grid-item v-for="(item, index) of icons" :key="index">
<div
class="flex flex-col items-center justify-center p-3 icon-wrapper"
@click="onIconClick(item)"
>
<n-icon size="20">
<component :is="item" />
</n-icon>
</div>
</n-grid-item>
</n-grid>
</n-scrollbar>
<div class="flex justify-end mt-2 mb-2">
<n-pagination
:page="currentPage"
:page-size="pageSize"
:page-slot="8"
:item-count="itemCount"
@update-page="onUpdatePage"
/>
</div>
</n-popover>
</template>
<script lang="ts">
import { computed, defineComponent, ref, shallowReactive } from 'vue';
import * as AntdIcons from '@vicons/antd';
export default defineComponent({
name: 'AntdSelector',
components: AntdIcons,
props: {
value: String,
option: String,
},
emits: ['update:value'],
setup(props, { emit }) {
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
const iconArray = Object.keys(AntdIcons);
const pageSize = 40;
const icons = shallowReactive(iconArray.slice(0, 40));
const currentPage = ref(1);
const itemCount = computed(() => iconArray.length);
function onUpdatePage(page: number) {
currentPage.value = page;
icons.length = 0;
const start = (currentPage.value - 1) * pageSize;
icons.push(...iconArray.slice(start, start + pageSize));
}
function onIconClick(item: any) {
formValue.value = item;
}
return {
icons,
currentPage,
pageSize,
itemCount,
onUpdatePage,
onIconClick,
formValue,
};
},
});
</script>
<style lang="less" scoped>
.grid-wrapper {
.icon-wrapper {
cursor: pointer;
border: 1px solid #f5f5f5;
}
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<n-popover trigger="click" placement="bottom" width="400">
<template #trigger>
<n-button>
<template #icon>
<n-icon size="20">
<component :is="formValue !== '' ? formValue : 'LogoIonic'" />
</n-icon>
</template>
</n-button>
</template>
<n-scrollbar class="grid-wrapper">
<n-grid :cols="8" :collapsed="false" responsive="screen" style="height: 300px">
<n-grid-item v-for="(item, index) of icons" :key="index">
<div
class="flex flex-col items-center justify-center p-3 icon-wrapper"
@click="onIconClick(item)"
>
<n-icon size="20">
<component :is="item" />
</n-icon>
</div>
</n-grid-item>
</n-grid>
</n-scrollbar>
<div class="flex justify-end mt-2 mb-2">
<n-pagination
:page="currentPage"
:page-size="pageSize"
:page-slot="8"
:item-count="itemCount"
@update-page="onUpdatePage"
/>
</div>
</n-popover>
</template>
<script lang="ts">
import { computed, defineComponent, ref, shallowReactive } from 'vue';
import * as Ionicons5Icons from '@vicons/ionicons5';
export default defineComponent({
name: 'Ionicons5Selector',
components: Ionicons5Icons,
props: {
value: String,
option: String,
},
emits: ['update:value'],
setup(props, { emit }) {
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
const iconArray = Object.keys(Ionicons5Icons);
const pageSize = 40;
const icons = shallowReactive(iconArray.slice(0, 40));
const currentPage = ref(1);
const itemCount = computed(() => iconArray.length);
function onUpdatePage(page: number) {
currentPage.value = page;
icons.length = 0;
const start = (currentPage.value - 1) * pageSize;
icons.push(...iconArray.slice(start, start + pageSize));
}
function onIconClick(item: any) {
formValue.value = item;
}
return {
icons,
currentPage,
pageSize,
itemCount,
onUpdatePage,
onIconClick,
formValue,
};
},
});
</script>
<style lang="less" scoped>
.grid-wrapper {
.icon-wrapper {
cursor: pointer;
border: 1px solid #f5f5f5;
}
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<div>
<n-input-group>
<n-input v-bind="$props" :value="formValue" :style="{ width: '70%' }" />
<template v-if="option === 'ionicons5'">
<Ionicons5Selector v-model:value="formValue" />
</template>
<template v-else>
<AntdSelector v-model:value="formValue" />
</template>
</n-input-group>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { basicProps } from '@/components/IconSelector/props';
import Ionicons5Selector from '@/components/IconSelector/Ionicons5Selector.vue';
import AntdSelector from '@/components/IconSelector/AntdSelector.vue';
export default defineComponent({
name: 'BasicUpload',
components: { Ionicons5Selector, AntdSelector },
props: {
...basicProps,
},
emits: ['update:value'],
setup(props, { emit }) {
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
return {
formValue,
};
},
});
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,14 @@
import type { PropType } from 'vue';
import { NInput } from 'naive-ui';
export const basicProps = {
...NInput.props,
option: {
type: String as PropType<string>,
default: 'antd', // ionicons5 | antd
},
value: {
type: String as PropType<string>,
default: () => '',
},
};

View File

@@ -18,7 +18,7 @@
<slot name="tableTitle"></slot>
</div>
<div class="flex items-center table-toolbar-right">
<div class="flex items-center table-toolbar-right" v-show="showTopRight">
<!--顶部右侧区域-->
<slot name="toolbar"></slot>
@@ -65,7 +65,7 @@
</n-tooltip>
<!--表格设置单独抽离成组件-->
<ColumnSetting />
<ColumnSetting :openChecked="openChecked" />
</div>
</div>
<div class="s-table">

View File

@@ -12,14 +12,14 @@
<div class="table-toolbar-inner-popover-title">
<n-space>
<n-checkbox v-model:checked="checkAll" @update:checked="onCheckAll"
>列展示
</n-checkbox>
>列展示</n-checkbox
>
<n-checkbox v-model:checked="selection" @update:checked="onSelection"
>勾选列
</n-checkbox>
>勾选列</n-checkbox
>
<n-button text type="info" size="small" class="mt-1" @click="resetColumns"
>重置
</n-button>
>重置</n-button
>
</n-space>
</div>
</template>
@@ -92,24 +92,22 @@
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, toRaw, toRefs, unref, watchEffect } from 'vue';
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs, watchEffect } from 'vue';
import { useTableContext } from '../../hooks/useTableContext';
import { cloneDeep } from 'lodash-es';
import {
DragOutlined,
SettingOutlined,
VerticalLeftOutlined,
DragOutlined,
VerticalRightOutlined,
VerticalLeftOutlined,
} from '@vicons/antd';
import Draggable from 'vuedraggable/src/vuedraggable';
import Draggable from 'vuedraggable';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
interface Options {
title: string;
key: string;
fixed?: boolean | 'left' | 'right';
}
export default defineComponent({
name: 'ColumnSetting',
components: {
@@ -119,30 +117,32 @@
VerticalRightOutlined,
VerticalLeftOutlined,
},
setup() {
props: {
openChecked: {
type: Boolean as PropType<Boolean>,
default: false,
},
},
setup(props) {
const { getDarkTheme } = useDesignSetting();
const table: any = useTableContext();
const columnsList = ref<Options[]>([]);
const cacheColumnsList = ref<Options[]>([]);
const state = reactive({
selection: true,
selection: false,
checkAll: true,
checkList: [],
defaultCheckList: [],
});
const getSelection = computed(() => {
return state.selection;
});
watchEffect(() => {
const columns = table.getColumns();
if (columns.length) {
init();
}
});
//初始化
function init() {
const columns: any[] = getColumns();
@@ -153,23 +153,27 @@
if (!columnsList.value.length) {
columnsList.value = cloneDeep(newColumns);
cacheColumnsList.value = cloneDeep(newColumns);
// 只有首次加载时需要执行,重复执行会导致生成多个选项
if (props.openChecked) {
state.selection = true;
if (newColumns[0].type != 'selection' && newColumns[0].key != 'selection') {
onSelection(true);
}
}
}
onSelection(true);
}
//切换
function onChange(checkList) {
console.log('checkList:' + JSON.stringify(checkList));
if (state.selection) {
checkList.unshift('selection');
}
setColumns(checkList);
}
//设置
function setColumns(columns) {
table.setColumns(columns);
}
//获取
function getColumns() {
let newRet: any[] = [];
@@ -178,7 +182,6 @@
});
return newRet;
}
//重置
function resetColumns() {
state.checkList = [...state.defaultCheckList];
@@ -193,7 +196,6 @@
setColumns(newColumns);
columnsList.value = newColumns;
}
//全选
function onCheckAll(e) {
let checkList = table.getCacheColumns(true);
@@ -205,33 +207,28 @@
state.checkList = [];
}
}
//拖拽排序
function draggableEnd() {
const newColumns = toRaw(unref(columnsList));
columnsList.value = newColumns;
setColumns(newColumns);
}
//勾选列
function onSelection(e) {
console.log('onSelection:' + JSON.stringify(e));
let checkList = table.getCacheColumns();
if (e) {
if (checkList[0].type === undefined || checkList[0].type !== 'selection') {
checkList.unshift({ type: 'selection', key: 'selection' });
setColumns(checkList);
}
checkList.unshift({ type: 'selection', key: 'selection' });
setColumns(checkList);
} else {
checkList.splice(0, 1);
setColumns(checkList);
}
}
function onMove(e) {
if (e.draggedContext.element.draggable === false) return false;
return true;
}
//固定
function fixedColumn(item, fixed) {
if (!state.checkList.includes(item.key)) return;
@@ -245,7 +242,6 @@
columnsList.value[index].fixed = isFixed;
setColumns(columns);
}
return {
...toRefs(state),
columnsList,
@@ -268,65 +264,54 @@
&-inner-popover-title {
padding: 3px 0;
}
&-right {
&-icon {
margin-left: 12px;
font-size: 16px;
color: var(--text-color);
cursor: pointer;
:hover {
color: #1890ff;
}
}
}
}
.table-toolbar-inner {
&-checkbox {
display: flex;
align-items: center;
padding: 10px 14px;
&:hover {
background: #e6f7ff;
}
.drag-icon {
display: inline-flex;
margin-right: 8px;
cursor: move;
&-hidden {
visibility: hidden;
cursor: default;
}
}
.fixed-item {
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: auto;
}
.ant-checkbox-wrapper {
flex: 1;
&:hover {
color: #1890ff !important;
}
}
}
&-checkbox-dark {
&:hover {
background: hsla(0, 0%, 100%, 0.08);
}
}
}
.toolbar-popover {
.n-popover__content {
padding: 0;

View File

@@ -12,6 +12,14 @@ export const basicProps = {
type: String,
default: null,
},
showTopRight: {
type: Boolean,
default: true,
},
openChecked: {
type: Boolean,
default: false,
},
size: {
type: String,
default: 'medium',

View File

@@ -11,12 +11,24 @@
>
<div class="upload-card-item-info">
<div class="img-box">
<img :src="item" />
<template v-if="fileType === 'image'">
<img :src="item" @error="errorImg($event)" />
</template>
<template v-else>
<n-avatar :style="fileAvatarCSS">{{ getFileExt(item) }}</n-avatar>
</template>
</div>
<div class="img-box-actions">
<n-icon size="18" class="mx-2 action-icon" @click="preview(item)">
<EyeOutlined />
</n-icon>
<template v-if="fileType === 'image'">
<n-icon size="18" class="mx-2 action-icon" @click="preview(item)">
<EyeOutlined />
</n-icon>
</template>
<template v-else>
<n-icon size="18" class="mx-2 action-icon" @click="download(item)">
<CloudDownloadOutlined />
</n-icon>
</template>
<n-icon size="18" class="mx-2 action-icon" @click="remove(index)">
<DeleteOutlined />
</n-icon>
@@ -40,7 +52,7 @@
<n-icon size="18" class="m-auto">
<PlusOutlined />
</n-icon>
<span class="upload-title">上传图片</span>
<span class="upload-title">{{ uploadTitle }}</span>
</div>
</n-upload>
</div>
@@ -68,21 +80,21 @@
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
import { EyeOutlined, DeleteOutlined, PlusOutlined } from '@vicons/antd';
import { defineComponent, toRefs, reactive, computed, watch, onMounted, ref } from 'vue';
import { EyeOutlined, DeleteOutlined, PlusOutlined, CloudDownloadOutlined } from '@vicons/antd';
import { basicProps } from './props';
import { useMessage, useDialog } from 'naive-ui';
import { ResultEnum } from '@/enums/httpEnum';
import componentSetting from '@/settings/componentSetting';
import { useGlobSetting } from '@/hooks/setting';
import { isString } from '@/utils/is';
import { isJsonString, isNullOrUnDef } from '@/utils/is';
import { getFileExt } from '@/utils/urlUtils';
const globSetting = useGlobSetting();
export default defineComponent({
name: 'BasicUpload',
components: { EyeOutlined, DeleteOutlined, PlusOutlined },
components: { EyeOutlined, DeleteOutlined, PlusOutlined, CloudDownloadOutlined },
props: {
...basicProps,
},
@@ -97,6 +109,13 @@
const message = useMessage();
const dialog = useDialog();
const uploadTitle = ref(props.fileType === 'image' ? '上传图片' : '上传附件');
const fileAvatarCSS = computed(() => {
return {
'--n-merged-size': `var(--n-avatar-size-override, ${props.width * 0.8}px)`,
'--n-font-size': `18px`,
};
});
const state = reactive({
showModal: false,
@@ -109,32 +128,55 @@
watch(
() => props.value,
() => {
// console.log('props.value:' + props.value);
// 单图模式
if (typeof props.value === 'string') {
let data: string[] = [];
if (props.value !== '') {
data.push(props.value);
}
state.imgList = data.map((item) => {
return getImgUrl(item);
});
return;
}
// 多图模式
state.imgList = props.value.map((item) => {
return getImgUrl(item);
});
loadValue(props.value);
return;
}
);
watch(
() => props.values,
() => {
loadValue(props.values);
return;
}
);
// 加载默认
function loadValue(value: any) {
if (value === null) {
return;
}
let data: string[] = [];
if (isJsonString(value)) {
value = JSON.parse(value);
}
// 单图模式
if (typeof value === 'string') {
if (value !== '') {
data.push(value);
}
} else {
// 多图模式
data = value;
}
state.imgList = data.map((item) => {
return getImgUrl(item);
});
state.originalImgList = state.imgList;
}
//预览
function preview(url: string) {
state.showModal = true;
state.previewUrl = url;
}
//下载
function download(url: string) {
window.open(url);
}
//删除
function remove(index: number) {
@@ -146,7 +188,11 @@
onPositiveClick: () => {
state.imgList.splice(index, 1);
state.originalImgList.splice(index, 1);
emit('uploadChange', state.originalImgList);
if (props.maxNumber === 1) {
emit('uploadChange', '');
} else {
emit('uploadChange', state.originalImgList);
}
emit('delete', state.originalImgList);
},
onNegativeClick: () => {},
@@ -159,25 +205,29 @@
return /(^http|https:\/\/)/g.test(url) ? url : `${imgUrl}${url}`;
}
function checkFileType(fileType: string) {
return componentSetting.upload.fileType.includes(fileType);
function checkFileType(map: string[], fileType: string) {
if (isNullOrUnDef(map)) {
return true;
}
return map.includes(fileType);
}
//上传之前
function beforeUpload({ file }) {
const fileInfo = file.file;
const { maxSize, accept } = props;
const acceptRef = (isString(accept) && accept.split(',')) || [];
// 设置最大值,则判断
if (maxSize && fileInfo.size / 1024 / 1024 >= maxSize) {
message.error(`上传文件最大值不能超过${maxSize}M`);
if (props.maxSize && fileInfo.size / 1024 / 1024 >= props.maxSize) {
message.error(`上传文件最大值不能超过${props.maxSize}M`);
return false;
}
// 设置类型,则判断
const fileType = componentSetting.upload.fileType;
if (acceptRef.length > 0 && !checkFileType(fileInfo.type)) {
const fileType =
props.fileType === 'image'
? componentSetting.upload.imageType
: componentSetting.upload.fileType;
if (!checkFileType(fileType, fileInfo.type)) {
console.log('checkFileType fileInfo.type:' + fileInfo.type);
message.error(`只能上传文件类型为${fileType.join(',')}`);
return false;
}
@@ -197,20 +247,45 @@
if (code === ResultEnum.SUCCESS) {
let imgUrl: string = getImgUrl(result[imgField]);
state.imgList.push(imgUrl);
state.originalImgList.push(result[imgField]);
emit('uploadChange', state.originalImgList);
state.originalImgList = state.imgList;
if (props.maxNumber === 1) {
emit('uploadChange', imgUrl);
} else {
emit('uploadChange', state.originalImgList);
}
} else {
message.error(msg);
}
}
/**图片加载失败显示自定义默认图片(缺省图)*/
function errorImg(e) {
e.srcElement.src = '/onerror.png';
//这一句没用,如果默认图片的路径错了还是会一直闪屏,在方法的前面加个.once只让它执行一次也没用
e.srcElement.onerror = null; //防止闪图
}
onMounted(async () => {
setTimeout(function () {
if (props.maxNumber === 1) {
loadValue(props.value);
} else {
loadValue(props.values);
}
}, 50);
});
return {
errorImg,
...toRefs(state),
finish,
preview,
download,
remove,
beforeUpload,
getCSSProperties,
uploadTitle,
fileAvatarCSS,
getFileExt,
};
},
});

View File

@@ -3,6 +3,10 @@ import { NUpload } from 'naive-ui';
export const basicProps = {
...NUpload.props,
fileType: {
type: String,
default: 'image',
},
accept: {
type: String,
default: '.jpg,.png,.jpeg,.svg,.gif',
@@ -24,7 +28,7 @@ export const basicProps = {
default: () => '',
},
values: {
type: (Array as PropType<string[]>) || (String as PropType<string>),
type: (Array as PropType<string[]>) || (Object as PropType<object>),
default: () => [],
},
width: {

View File

@@ -0,0 +1,59 @@
<template>
<BasicUpload
:action="`${uploadUrl}${urlPrefix}/upload/file`"
:headers="uploadHeaders"
:data="{ type: 0 }"
name="file"
:width="100"
:height="100"
fileType="file"
:maxNumber="maxNumber"
@uploadChange="uploadChange"
v-model:value="image"
v-model:values="images"
/>
</template>
<script lang="ts" setup>
import { ref, onMounted, unref, reactive } from 'vue';
import { BasicUpload } from '@/components/Upload';
import { useGlobSetting } from '@/hooks/setting';
import { useUserStoreWidthOut } from '@/store/modules/user';
export interface Props {
value: string | string[] | null;
maxNumber: number;
}
const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix || '';
const { uploadUrl } = globSetting;
const useUserStore = useUserStoreWidthOut();
const uploadHeaders = reactive({
Authorization: useUserStore.token,
});
const emit = defineEmits(['update:value']);
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1 });
const image = ref<string>('');
const images = ref<string[] | object>([]);
function uploadChange(list: string | string[]) {
if (props.maxNumber === 1) {
image.value = unref(list as string);
emit('update:value', image.value);
} else {
images.value = unref(list as string[]);
emit('update:value', images.value);
}
}
onMounted(async () => {
if (props.maxNumber === 1) {
image.value = props.value as string;
} else {
images.value = props.value as string[];
}
});
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,58 @@
<template>
<BasicUpload
:action="`${uploadUrl}${urlPrefix}/upload/image`"
:headers="uploadHeaders"
:data="{ type: 0 }"
name="file"
:width="100"
:height="100"
:maxNumber="maxNumber"
@uploadChange="uploadChange"
v-model:value="image"
v-model:values="images"
/>
</template>
<script lang="ts" setup>
import { ref, onMounted, unref, reactive } from 'vue';
import { BasicUpload } from '@/components/Upload';
import { useGlobSetting } from '@/hooks/setting';
import { useUserStoreWidthOut } from '@/store/modules/user';
export interface Props {
value: string | string[] | null;
maxNumber: number;
}
const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix || '';
const { uploadUrl } = globSetting;
const useUserStore = useUserStoreWidthOut();
const uploadHeaders = reactive({
Authorization: useUserStore.token,
});
const emit = defineEmits(['update:value']);
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1 });
const image = ref<string>('');
const images = ref<string[]>([]);
function uploadChange(list: string | string[]) {
if (props.maxNumber === 1) {
image.value = unref(list as string);
emit('update:value', image.value);
} else {
images.value = unref(list as string[]);
emit('update:value', images.value);
}
}
onMounted(async () => {
if (props.maxNumber === 1) {
image.value = props.value as string;
} else {
images.value = props.value as string[];
}
});
</script>
<style lang="less"></style>

View File

@@ -65,3 +65,31 @@ export const statusActions = [
key: 2,
},
];
// 标签
export const tagOptions = [
{
label: '灰色',
value: 'default',
},
{
label: '主色',
value: 'primary',
},
{
label: '蓝色',
value: 'info',
},
{
label: '绿色',
value: 'success',
},
{
label: '黄色',
value: 'warning',
},
{
label: '红色',
value: 'error',
},
];

View File

@@ -29,17 +29,6 @@ export function useTime() {
second.value = date.getSeconds();
};
// 原生时间格式化
// new Intl.DateTimeFormat('zh', {
// year: 'numeric',
// month: '2-digit',
// day: '2-digit',
// hour: '2-digit',
// minute: '2-digit',
// second: '2-digit',
// hour12: false
// }).format(new Date())
updateTime();
onMounted(() => {

View File

@@ -1,15 +1,12 @@
import type { EChartsOption } from 'echarts';
import type { Ref } from 'vue';
import { useTimeoutFn } from '@/hooks/core/useTimeout';
import { tryOnUnmounted } from '@vueuse/core';
import { unref, nextTick, watch, computed, ref } from 'vue';
import { useDebounceFn } from '@vueuse/core';
import { useEventListener } from '@/hooks/event/useEventListener';
import { useBreakpoint } from '@/hooks/event/useBreakpoint';
import echarts from '@/utils/lib/echarts';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
export function useECharts(

View File

@@ -555,13 +555,13 @@
z-index: 11;
}
//::v-deep(.menu-server-link) {
// color: #515a6e;
//
// &:hover {
// color: #1890ff;
// }
//}
::v-deep(.menu-server-link) {
color: #515a6e;
&:hover {
color: #1890ff;
}
}
.action-items-wrapper {
position: relative;
@@ -600,7 +600,7 @@
background-color: transparent !important;
}
/deep/ sup {
:deep(sup) {
top: 1.3em;
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class="logo">
<img src="~@/assets/images/logo.png" alt="" :class="{ 'mr-2': !collapsed }" />
<h2 v-show="!collapsed" class="title">后台管理系统</h2>
<h2 v-show="!collapsed" class="title">HG后台管理系统</h2>
</div>
</template>

View File

@@ -44,7 +44,7 @@ async function bootstrap() {
f.call(null, event);
});
};
app.config.globalProperties.$websocket = Websocket(onMessage);
Websocket(onMessage);
app.mount('#app', true);
}

View File

@@ -1,7 +1,4 @@
export const RedirectName = 'Redirect';
export const ErrorPage = () => import('@/views/exception/404.vue');
export const Layout = () => import('@/layout/index.vue');
export const ParentLayout = () => import('@/layout/parentLayout.vue');

View File

@@ -51,7 +51,6 @@ export const routerGenerator = (routerMap, parent?): any[] => {
/**
* 动态生成菜单
* @returns {Promise<Router>}
*/
export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
return new Promise((resolve, reject) => {

View File

@@ -1,32 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ProjectOutlined } from '@vicons/antd';
import { renderIcon, renderNew } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
path: '/about',
name: 'about',
component: Layout,
meta: {
sort: 10,
isRoot: true,
activeMenu: 'about_index',
icon: renderIcon(ProjectOutlined),
},
children: [
{
path: 'index',
name: `about_index`,
meta: {
title: '关于',
extra: renderNew(),
activeMenu: 'about_index',
},
component: () => import('@/views/about/index.vue'),
},
],
},
];
export default routes;

View File

@@ -1,131 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout, ParentLayout } from '@/router/constant';
import { WalletOutlined } from '@vicons/antd';
import { renderIcon, renderNew } from '@/utils';
const routeName = 'comp';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/comp',
name: routeName,
component: Layout,
redirect: '/comp/table',
meta: {
title: '组件示例',
icon: renderIcon(WalletOutlined),
sort: 8,
},
children: [
{
path: 'table',
name: `${routeName}_table`,
redirect: '/comp/table/basic',
component: ParentLayout,
meta: {
title: '表格',
},
children: [
{
path: 'basic',
name: `${routeName}_table_basic`,
meta: {
title: '基础表格',
},
component: () => import('@/views/comp/table/basic.vue'),
},
{
path: 'editCell',
name: `${routeName}_table_editCell`,
meta: {
title: '单元格编辑',
},
component: () => import('@/views/comp/table/editCell.vue'),
},
{
path: 'editRow',
name: `${routeName}_table_editRow`,
meta: {
title: '整行编辑',
},
component: () => import('@/views/comp/table/editRow.vue'),
},
],
},
{
path: 'form',
name: `${routeName}_form`,
redirect: '/comp/form/basic',
component: ParentLayout,
meta: {
title: '表单',
},
children: [
{
path: 'basic',
name: `${routeName}_form_basic`,
meta: {
title: '基础使用',
},
component: () => import('@/views/comp/form/basic.vue'),
},
{
path: 'useForm',
name: `useForm`,
meta: {
title: 'useForm',
},
component: () => import('@/views/comp/form/useForm.vue'),
},
],
},
{
path: 'upload',
name: `${routeName}_upload`,
meta: {
title: '上传图片',
},
component: () => import('@/views/comp/upload/index.vue'),
},
{
path: 'modal',
name: `${routeName}_modal`,
meta: {
title: '弹窗扩展',
},
component: () => import('@/views/comp/modal/index.vue'),
},
{
path: 'richtext',
name: `richtext`,
meta: {
title: '富文本',
extra: renderNew(),
},
component: () => import('@/views/comp/richtext/vue-quill.vue'),
},
{
path: 'drag',
name: `Drag`,
meta: {
title: '拖拽',
extra: renderNew(),
},
component: () => import('@/views/comp/drag/index.vue'),
},
],
},
];
export default routes;

View File

@@ -1,64 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { DashboardOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
const routeName = 'dashboard';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/dashboard',
name: routeName,
redirect: '/dashboard/console',
component: Layout,
meta: {
title: 'Dashboard',
icon: renderIcon(DashboardOutlined),
permissions: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
sort: 0,
},
children: [
{
path: 'console',
name: `${routeName}_console`,
meta: {
title: '主控台',
permissions: ['dashboard_console'],
affix: true,
},
component: () => import('@/views/dashboard/console/console.vue'),
},
// {
// path: 'monitor',
// name: `${ routeName }_monitor`,
// meta: {
// title: '监控页',
// permissions: ['dashboard_monitor']
// },
// component: () => import('@/views/dashboard/monitor/monitor.vue')
// },
{
path: 'workplace',
name: `${routeName}_workplace`,
meta: {
title: '工作台',
keepAlive: true,
permissions: ['dashboard_workplace'],
},
component: () => import('@/views/dashboard/workplace/workplace.vue'),
},
],
},
];
export default routes;

View File

@@ -1,19 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { DocumentTextOutline } from '@vicons/ionicons5';
import { renderIcon } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
path: '/external',
name: 'https://naive-ui-admin-docs.vercel.app',
component: Layout,
meta: {
title: '项目文档',
icon: renderIcon(DocumentTextOutline),
sort: 9,
},
},
];
export default routes;

View File

@@ -1,57 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ExclamationCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/exception',
name: 'Exception',
redirect: '/exception/403',
component: Layout,
meta: {
title: '异常页面',
icon: renderIcon(ExclamationCircleOutlined),
sort: 3,
},
children: [
{
path: '403',
name: 'exception-403',
meta: {
title: '403',
},
component: () => import('@/views/exception/403.vue'),
},
{
path: '404',
name: 'exception-404',
meta: {
title: '404',
},
component: () => import('@/views/exception/404.vue'),
},
{
path: '500',
name: 'exception-500',
meta: {
title: '500',
},
component: () => import('@/views/exception/500.vue'),
},
],
},
];
export default routes;

View File

@@ -1,57 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ProfileOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/form',
name: 'Form',
redirect: '/form/basic-form',
component: Layout,
meta: {
title: '表单页面',
icon: renderIcon(ProfileOutlined),
sort: 3,
},
children: [
{
path: 'basic-form',
name: 'form-basic-form',
meta: {
title: '基础表单',
},
component: () => import('@/views/form/basicForm/index.vue'),
},
{
path: 'step-form',
name: 'form-step-form',
meta: {
title: '分步表单',
},
component: () => import('@/views/form/stepForm/stepForm.vue'),
},
{
path: 'detail',
name: 'form-detail',
meta: {
title: '表单详情',
},
component: () => import('@/views/form/detail/index.vue'),
},
],
},
];
export default routes;

View File

@@ -1,24 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { DesktopOutline } from '@vicons/ionicons5';
import { renderIcon } from '@/utils/index';
const IFrame = () => import('@/views/iframe/index.vue');
const routes: Array<RouteRecordRaw> = [
{
path: '/frame',
name: 'Frame',
redirect: '/frame/docs',
component: Layout,
meta: {
title: '外部页面',
sort: 8,
icon: renderIcon(DesktopOutline),
},
children: [
],
},
];
export default routes;

View File

@@ -1,51 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { TableOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/list',
name: 'List',
redirect: '/list/basic-list',
component: Layout,
meta: {
title: '列表页面',
icon: renderIcon(TableOutlined),
sort: 2,
},
children: [
{
path: 'basic-list',
name: 'basic-list',
meta: {
title: '基础列表',
},
component: () => import('@/views/list/basicList/index.vue'),
},
{
path: 'basic-info/:id?',
name: 'basic-info',
meta: {
title: '基础详情',
hidden: true,
activeMenu: 'basic-list',
},
component: () => import('@/views/list/basicList/info.vue'),
},
],
},
];
export default routes;

View File

@@ -1,57 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { CheckCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/result',
name: 'Result',
redirect: '/result/success',
component: Layout,
meta: {
title: '结果页面',
icon: renderIcon(CheckCircleOutlined),
sort: 4,
},
children: [
{
path: 'success',
name: 'result-success',
meta: {
title: '成功页',
},
component: () => import('@/views/result/success.vue'),
},
{
path: 'fail',
name: 'result-fail',
meta: {
title: '失败页',
},
component: () => import('@/views/result/fail.vue'),
},
{
path: 'info',
name: 'result-info',
meta: {
title: '信息页',
},
component: () => import('@/views/result/info.vue'),
},
],
},
];
export default routes;

View File

@@ -1,49 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { SettingOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/setting',
name: 'Setting',
redirect: '/setting/account',
component: Layout,
meta: {
title: '设置页面',
icon: renderIcon(SettingOutlined),
sort: 5,
},
children: [
{
path: 'account',
name: 'setting-account',
meta: {
title: '个人设置',
},
component: () => import('@/views/setting/account/account.vue'),
},
{
path: 'system',
name: 'setting-system',
meta: {
title: '系统设置',
},
component: () => import('@/views/setting/system/system.vue'),
},
],
},
];
export default routes;

View File

@@ -1,49 +0,0 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { OptionsSharp } from '@vicons/ionicons5';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/system',
name: 'System',
redirect: '/system/menu',
component: Layout,
meta: {
title: '系统设置',
icon: renderIcon(OptionsSharp),
sort: 1,
},
children: [
{
path: 'menu',
name: 'system_menu',
meta: {
title: '菜单权限管理',
},
component: () => import('@/views/permission/menu/menu.vue'),
},
{
path: 'role',
name: 'system_role',
meta: {
title: '角色权限管理',
},
component: () => import('@/views/permission/role/role.vue'),
},
],
},
];
export default routes;

View File

@@ -8,7 +8,6 @@ import { PageEnum } from '@/enums/pageEnum';
import { ErrorPageRoute } from '@/router/base';
const LOGIN_PATH = PageEnum.BASE_LOGIN;
const whitePathList = [LOGIN_PATH]; // no redirect whitelist
export function createRouterGuards(router: Router) {

View File

@@ -1,53 +1,12 @@
import { renderIcon } from '@/utils/index';
import {
AppstoreOutlined,
CheckCircleOutlined,
DashboardOutlined,
ExclamationCircleOutlined,
ProfileOutlined,
ProjectOutlined,
SettingOutlined,
TableOutlined,
WalletOutlined,
DeleteOutlined,
EditOutlined,
} from '@vicons/antd';
import {
DesktopOutline,
DocumentTextOutline,
OptionsSharp,
SettingsOutline,
LogoBuffer,
KeyOutline,
VolumeLowOutline,
CodeSlashOutline,
EaselOutline,
TimerOutline,
OptionsOutline,
} from '@vicons/ionicons5';
import * as antdIcons from '@vicons/antd';
// import * as x5Icons from '@vicons/ionicons5';
//前端路由图标映射表
export const constantRouterIcon = {
EditOutlined: renderIcon(EditOutlined),
DeleteOutlined: renderIcon(DeleteOutlined),
OptionsOutline: renderIcon(OptionsOutline),
TimerOutline: renderIcon(TimerOutline),
EaselOutline: renderIcon(EaselOutline),
CodeSlashOutline: renderIcon(CodeSlashOutline),
VolumeLowOutline: renderIcon(VolumeLowOutline),
DashboardOutlined: renderIcon(DashboardOutlined),
SettingsOutline: renderIcon(SettingsOutline),
OptionsSharp: renderIcon(OptionsSharp),
TableOutlined: renderIcon(TableOutlined),
ExclamationCircleOutlined: renderIcon(ExclamationCircleOutlined),
ProfileOutlined: renderIcon(ProfileOutlined),
CheckCircleOutlined: renderIcon(CheckCircleOutlined),
SettingOutlined: renderIcon(SettingOutlined),
WalletOutlined: renderIcon(WalletOutlined),
DesktopOutline: renderIcon(DesktopOutline),
DocumentTextOutline: renderIcon(DocumentTextOutline),
ProjectOutlined: renderIcon(ProjectOutlined),
AppstoreOutlined: renderIcon(AppstoreOutlined),
LogoBuffer: renderIcon(LogoBuffer),
KeyOutline: renderIcon(KeyOutline),
};
export const constantRouterIcon = {};
for (const element of Object.keys(antdIcons)) {
constantRouterIcon[element] = renderIcon(antdIcons[element]);
}
// for (const element of Object.keys(x5Icons)) {
// constantRouterIcon[element] = renderIcon(x5Icons[element]);
// }

View File

@@ -6,6 +6,7 @@ export type Component<T extends any = any> =
| (() => Promise<typeof import('*.vue')>)
| (() => Promise<T>);
// @ts-ignore
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
name: string;
meta: RouteMeta;

View File

@@ -13,7 +13,7 @@ export default {
//默认分页数量
defaultPageSize: 10,
//可切换每页数量集合
pageSizes: [10, 20, 30, 40, 50],
pageSizes: [10, 20, 30, 40, 50, 100, 200],
},
upload: {
//考虑接口规范不同
@@ -26,6 +26,29 @@ export default {
//最大上传图片大小
maxSize: 10,
//图片上传类型
fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'],
imageType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'],
//文件上传类型
fileType: [
// 图片
'image/png',
'image/jpg',
'image/jpeg',
'image/gif',
'image/svg+xml',
// 文档
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
// 音频
'audio/mpeg',
'audio/midi',
// 视频
'audio/mp4',
'video/webm',
'video/x-flv',
],
},
};

View File

@@ -2,8 +2,8 @@ import { toRaw, unref } from 'vue';
import { defineStore } from 'pinia';
import { RouteRecordRaw } from 'vue-router';
import { store } from '@/store';
import { asyncRoutes, constantRouter } from '@/router/index';
import { generatorDynamicRouter, removeHiddenMenus } from '@/router/generator-routers';
import { asyncRoutes, constantRouter } from '@/router';
import { generatorDynamicRouter } from '@/router/generator-routers';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
interface TreeHelperConfig {

View File

@@ -6,9 +6,13 @@ Object.keys(allModules).forEach((path) => {
});
// export default modules
// @ts-ignore
import asyncRoute from './async-route';
// @ts-ignore
import user from './user';
// @ts-ignore
import tabsView from './tabs-view';
// @ts-ignore
import lockscreen from './lockscreen';
export default {

View File

@@ -21,7 +21,6 @@ export const notificationStore = defineStore({
},
addMessages(message) {
message = JSON.parse(message);
console.log('message:' + JSON.stringify(message));
if (
message.event !== undefined &&
message.event === 'notice' &&

View File

@@ -55,7 +55,9 @@ export const useTabsViewStore = defineStore({
},
closeOtherTabs(route) {
// 关闭其他
this.tabsList = this.tabsList.filter((item) => item.fullPath == route.fullPath || (item?.meta?.affix ?? false));
this.tabsList = this.tabsList.filter(
(item) => item.fullPath == route.fullPath || (item?.meta?.affix ?? false)
);
},
closeCurrentTab(route) {
// 关闭当前页

View File

@@ -86,6 +86,7 @@ export const useUserStore = defineStore({
// 获取用户信息
GetInfo() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
return new Promise((resolve, reject) => {
getUserInfo()
@@ -108,6 +109,7 @@ export const useUserStore = defineStore({
},
// 获取用户信息
GetConfig() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
return new Promise((resolve, reject) => {
getConfig()

View File

@@ -0,0 +1,3 @@
.min-left-space {
margin-left: 5px;
}

View File

@@ -1,3 +1,4 @@
@import 'transition/index.less';
@import './var.less';
@import './common.less';
@import './hotgo.less';

View File

@@ -121,13 +121,6 @@ export function encodeParams(obj) {
return arr.join('&');
}
/**
* 去重追加
* @param array
* @param son
*/
export function onlyPush(array: any, son: any) {}
/**
* 对象拷贝
* @param obj2

74
web/src/utils/charset.ts Normal file
View File

@@ -0,0 +1,74 @@
/**
* 随机生成字符串
*/
export function getRandomString(len = 12, isSmall = false) {
const _charStr = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789';
const _smallCharStr = 'abacdefghjklmnopqrstuvwxyz0123456789';
const charStr = isSmall ? _smallCharStr : _charStr;
const min = 0;
const max = charStr.length - 1;
let _str = '';
//循环生成字符串
for (let i = 0, index; i < len; i++) {
index = (function (randomIndexFunc, i) {
return randomIndexFunc(min, max, i, randomIndexFunc);
})(function (min, max, i, _self) {
const numStart = _charStr.length - 10;
let indexTemp = Math.floor(Math.random() * (max - min + 1) + min);
if (i == 0 && indexTemp >= numStart) {
indexTemp = _self(min, max, i, _self);
}
return indexTemp;
}, i);
_str += _charStr[index];
}
return _str;
}
/**
* 隐藏中间几位字符
*/
export function structure(array) {
// 将字符串转化成数组
const arrBox = [...array];
const count = arrBox.length;
if (count == 1) {
return '*';
}
let min = 1;
let max = count;
// 两位姓名
if (count == 2) {
min = 0;
max = count;
}
// 三位姓名
if (count == 3) {
min = 0;
max = count - 1;
}
// if (count >= 2 && count <= 8) {
// min = 1;
// }
// 手机号
if (count == 11) {
min = 3;
max = 7;
}
// 身份证号码
if (count >= 15) {
min = 9;
max = count - 4;
}
// 2.将数组中的4-7位变成*
let str = '';
arrBox.map((res, index) => {
if (index > min && index < max) {
str += '*';
} else {
str += res;
}
});
return str;
}

View File

@@ -1,21 +1,38 @@
import { format } from 'date-fns';
import {
endOfMonth,
endOfToday,
endOfWeek,
endOfYesterday,
format,
startOfMonth,
startOfToday,
startOfTomorrow,
startOfWeek,
startOfYesterday,
subMonths,
} from 'date-fns';
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
const DATE_FORMAT = 'YYYY-MM-DD ';
const DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm:ss';
const DATE_FORMAT = 'yyyy-MM-dd';
export function formatToDateTime(date: Date, formatStr = DATE_TIME_FORMAT): string {
const date2 = new Date(date);
return format(date2, formatStr);
export function formatToDateTime(date: string, formatStr = DATE_TIME_FORMAT): string {
if (date === null || date === undefined || date === '') {
return ``;
}
return format(new Date(Date.parse(date)), formatStr);
}
export function formatToDate(date: Date, formatStr = DATE_FORMAT): string {
return format(date, formatStr);
export function formatToDate(date: string, formatStr = DATE_FORMAT): string {
if (date === null || date === undefined || date === '') {
return ``;
}
return format(new Date(Date.parse(date)), formatStr);
}
export function timestampToTime(timestamp) {
const date = new Date(timestamp * 1000);
const Y = date.getFullYear() + '-';
const M = (date.getMonth() + 1 <= 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
const D = (date.getDate() + 1 <= 10 ? '0' + date.getDate() : date.getDate()) + ' ';
const h = (date.getHours() + 1 <= 10 ? '0' + date.getHours() : date.getHours()) + ':';
const m = (date.getMinutes() + 1 <= 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
@@ -26,7 +43,7 @@ export function timestampToTime(timestamp) {
export function timestampToTimeNF(timestamp) {
const date = new Date(timestamp);
const Y = date.getFullYear();
const M = date.getMonth() + 1 <= 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
const M = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
const D = date.getDate() + 1 <= 10 ? '0' + date.getDate() : date.getDate();
const h = date.getHours() + 1 <= 10 ? '0' + date.getHours() : date.getHours();
const m = date.getMinutes() + 1 <= 10 ? '0' + date.getMinutes() : date.getMinutes();
@@ -34,6 +51,13 @@ export function timestampToTimeNF(timestamp) {
return Y.toString() + M.toString() + D.toString() + h.toString() + m.toString() + s.toString();
}
export function dateToTimestamp(date: string) {
if (date === null || date === undefined || date === '') {
return 0;
}
return new Date(date).getTime();
}
export function timestampToDate(timestamp) {
const date = new Date(timestamp);
const Y = date.getFullYear() + '-';
@@ -44,23 +68,16 @@ export function timestampToDate(timestamp) {
export function getTime() {
const myDate = new Date();
const hour = myDate.getHours().toString().padStart(2, '0');
const minutes = myDate.getMinutes().toString().padStart(2, '0');
const seconed = myDate.getSeconds().toString().padStart(2, '0');
return hour + ':' + minutes + ':' + seconed;
}
export function getDate() {
const myDate = new Date();
const month = (myDate.getMonth() + 1).toString().padStart(2, '0');
const day = myDate.getDate().toString().padStart(2, '0');
return myDate.getFullYear() + '-' + month + '-' + day;
}
@@ -129,15 +146,11 @@ export function formatAfter(end): string {
if (end.getTime() - start.getTime() > 0) {
sjc = end.getTime() - start.getTime(); //时间差的毫秒数
}
const days = Math.floor(sjc / (24 * 3600 * 1000)); //计算出相差天数
const leave1 = sjc % (24 * 3600 * 1000); //计算天数后剩余的毫秒数
const hours = Math.floor(leave1 / (3600 * 1000)); //计算出小时数
const leave2 = leave1 % (3600 * 1000); //计算小时数后剩余的毫秒数
const minutes = Math.floor(leave2 / (60 * 1000)); //计算相差分钟数
const leave3 = leave2 % (60 * 1000); //计算分钟数后剩余的毫秒数
const seconds = Math.round(leave3 / 1000); //计算相差秒数
if (days > 0) {
@@ -152,6 +165,38 @@ export function formatAfter(end): string {
if (seconds > 0) {
return seconds + '秒后';
}
return '刚刚';
}
export function defShortcuts() {
return {
今天: startOfToday().getTime(),
昨天: startOfYesterday().getTime(),
明天: startOfTomorrow().getTime(),
};
}
export function defRangeShortcuts() {
const nowDate = new Date();
return {
: [startOfToday().getTime(), endOfToday().getTime()] as const,
: () => {
return [startOfYesterday().getTime(), endOfYesterday().getTime()] as const;
},
: () => {
return [
startOfWeek(nowDate, { weekStartsOn: 1 }).getTime(),
endOfWeek(nowDate, { weekStartsOn: 1 }).getTime(),
] as const;
},
: () => {
return [startOfMonth(nowDate).getTime(), endOfMonth(nowDate).getTime()] as const;
},
: () => {
return [
startOfMonth(subMonths(nowDate, 1)).getTime(),
endOfMonth(subMonths(nowDate, 1)).getTime(),
] as const;
},
};
}

View File

@@ -62,6 +62,7 @@ export function downloadByUrl({
// saveAs(imageDataUrl, '附件');
canvas.toBlob((blob) => {
const link = document.createElement('a');
// @ts-ignore
link.href = window.URL.createObjectURL(blob);
link.download = getFileName(url);
link.click();

53
web/src/utils/hotgo.ts Normal file
View File

@@ -0,0 +1,53 @@
import { Ref, UnwrapRef } from '@vue/reactivity';
export interface Option {
label: string;
value: string;
key: string;
type: string;
listClass: 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning';
}
export interface Options {
[name: string]: Option[];
}
// 获取选项名称
export function getOptionLabel(options: Option[], value) {
if (options === undefined || options?.length === 0) {
return `unknown`;
}
for (const item of options) {
if (item.value == value) {
return item.label;
}
}
return `unknown`;
}
// 获取选项标签
export function getOptionTag(options: Option[], value) {
if (options === undefined || options?.length === 0) {
return 'default';
}
for (const item of options) {
if (item.value == value) {
return item.listClass;
}
}
return 'default';
}
// 自适应模板宽度
export function adaModalWidth(dialogWidth: Ref<UnwrapRef<string>>) {
const val = document.body.clientWidth;
const def = 840; // 默认宽度
if (val <= def) {
dialogWidth.value = '100%';
} else {
dialogWidth.value = def + 'px';
}
return dialogWidth.value;
}

View File

@@ -1,10 +1,8 @@
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios';
import axios from 'axios';
import { AxiosCanceler } from './axiosCancel';
import { isFunction } from '@/utils/is';
import { cloneDeep } from 'lodash-es';
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types';
import { ContentTypeEnum } from '@/enums/httpEnum';

View File

@@ -18,6 +18,7 @@ import { CreateAxiosOptions, RequestOptions, Result } from './types';
import { useUserStoreWidthOut } from '@/store/modules/user';
import router from '@/router';
import { storage } from '@/utils/Storage';
import { encodeParams } from '@/utils/urlUtils';
const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix || '';
@@ -60,7 +61,7 @@ const transform: AxiosTransform = {
}
// 这里 coderesultmessage为 后台统一的字段,需要修改为项目自己的接口返回格式
let { code, data, message } = response;
const { code, data, message } = response;
// 请求成功
const hasSuccess = response && Reflect.has(response, 'code') && code === ResultEnum.SUCCESS;
@@ -118,8 +119,11 @@ const transform: AxiosTransform = {
onNegativeClick: () => {},
});
break;
default:
console.log('unknown status code:' + code);
$message.error(errorMsg);
}
$message.error(errorMsg);
throw new Error(errorMsg);
},
@@ -277,6 +281,15 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
export const http = createAxios();
// 导出
export const jumpExport = function (url, params) {
window.location.href =
urlPrefix +
url +
'?' +
encodeParams({ ...params, ...{ authorization: useUserStoreWidthOut().token } });
};
// 项目,多个不同 api 地址,直接在这里导出多个
// src/api ts 里面接口,就可以单独使用这个请求,
// import { httpTwo } from '@/utils/http/axios'

View File

@@ -158,6 +158,7 @@ export function filterRouter(routerMap: Array<any>) {
export const withInstall = <T>(component: T, alias?: string) => {
const comp = component as any;
comp.install = (app: App) => {
// @ts-ignore
app.component(comp.name || comp.displayName, component);
if (alias) {
app.config.globalProperties[alias] = component;
@@ -203,6 +204,7 @@ export function getTreeAll(data: any[]): any[] {
export function getDynamicProps<T, U>(props: T): Partial<U> {
const ret: Recordable = {};
// @ts-ignore
Object.keys(props).map((key) => {
ret[key] = unref((props as Recordable)[key]);
});

View File

@@ -80,6 +80,23 @@ export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val);
}
/**
* @description: 是否为转为string的json
*/
export function isJsonString(value: any) {
try {
const toObj = JSON.parse(value);
if (toObj && typeof toObj === 'object') {
return true;
}
} catch {}
return false;
}
export function isNullObject(value: object) {
return isNullOrUnDef(value) || JSON.stringify(value) === '{}' || JSON.stringify(value) === '[]';
}
/**
* @description: 是否客户端
*/
@@ -116,3 +133,8 @@ export function isNullAndUnDef(val: unknown): val is null | undefined {
export function isNullOrUnDef(val: unknown): val is null | undefined {
return isUnDef(val) || isNull(val);
}
// 判断字串符是否以字母开头
export function isLetterBegin(str) {
return /^[A-z]/.test(str);
}

View File

@@ -22,3 +22,22 @@ export function setObjToUrlParams(baseUrl: string, obj: object): string {
}
return url;
}
export function encodeParams(obj) {
const arr = [];
for (const p in obj) {
// @ts-ignore
arr.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
}
return arr.join('&');
}
/**
* 获取文件后缀
*/
export function getFileExt(fileName: string) {
if (fileName === undefined || fileName === '') {
return ``;
}
return fileName.substring(fileName.lastIndexOf('.') + 1);
}

View File

@@ -0,0 +1,212 @@
import { FormItemRule } from 'naive-ui';
/**
* @description 表单验证封装
*/
export const validate = {
ip(rule: FormItemRule, value: any, callback: Function) {
// 支持通配符的ipv4正则
const ipv4Regex =
/^(?:[1-9]?[0-9]|1[0-9]{2}|2(?:[0-4][0-9]|5[0-5]))(?!.*?\.\*\.[*\d])(?:\.(?:(?:[1-9]?[0-9]|1[0-9]{2}|2(?:[0-4][0-9]|5[0-5]))|\*)){1,3}$/;
// Ipv6:
const ipv6Regex =
/^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
if (!value && !rule.required) {
callback();
}
if (!value) {
callback(new Error('请输入IP'));
} else if (!ipv4Regex.test(value) && !ipv6Regex.test(value)) {
callback(new Error('请输入正确的IP'));
} else {
callback();
}
},
//0-100百分比验证
percentage(rule: FormItemRule, value: any, callback: Function) {
const reg = /^([1-9]{1,2}$)|(^[0-9]{1,2}\.[0-9]{1,2}$)|100$/;
if (!value && !rule.required) {
callback(new Error('请输入比例'));
} else if (!reg.test(value)) {
callback(new Error('请输入0-100的数字'));
} else {
callback();
}
},
// 手机号 eg:138********,159********
phone(rule: FormItemRule, value: any, callback: Function) {
const regPhone = /^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/;
if (!value && !rule.required) {
callback();
} else if (!value) {
callback(new Error('请输入手机号码'));
} else if (!regPhone.test(value)) {
callback(new Error('手机号格式错误'));
} else {
callback();
}
},
// 用户名 eg:a123456
userName(rule: FormItemRule, value: any, callback: Function) {
const regUserName = /^[0-9a-zA-Z]{6,16}$/;
if (!value && !rule.required) {
callback(new Error('请输入登录账号'));
} else if (!regUserName.test(value)) {
callback(new Error('请输入6-16位由字母和数字组成的登录账号'));
} else {
callback();
}
},
// 账号
account(rule: FormItemRule, value: any, callback: Function) {
const regex = /^[\w_\d]{6,16}$/;
if (!value && !rule.required) {
callback();
} else if (!value) {
callback(new Error('请输入账号'));
} else if (!regex.test(value)) {
callback(new Error('请输入6-16位由字母、数字或下划线组成的账号'));
} else {
callback();
}
},
// 密码
password(rule: FormItemRule, value: any, callback: Function) {
const regPassword = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,18}$/;
if (!value && !rule.required) {
callback(new Error('请输入密码'));
} else if (!regPassword.test(value)) {
callback(new Error('密码格式错误必须包含6-18为字母和数字'));
} else {
callback();
}
},
// 邮箱
email(rule: FormItemRule, value: any, callback: Function) {
const regEmails = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
// console.log('isRequired is: ', JSON.stringify(isRequired))
if (!value && !rule.required) {
callback();
} else if (!value) {
callback(new Error('请输入邮箱'));
} else if (!regEmails.test(value)) {
callback(new Error('邮箱格式错误'));
} else {
callback();
}
},
// 金额验证
amount(rule: FormItemRule, value: any, callback: Function) {
const regAmount = /(^[0-9]{1,10}$)|(^[0-9]{1,10}[\.]{1}[0-9]{1,2}$)/;
if (!value && !rule.required) {
callback();
} else if (!value) {
callback(new Error('请输入金额'));
} else if (!regAmount.test(value)) {
callback(new Error('金额格式错误最多允许输入10位整数及2位小数'));
} else {
callback();
}
},
// 身份证验证
idCard(rule: FormItemRule, value: any, callback: Function, isEnabled = true) {
const regIdCard =
/^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$|^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X|x)$/;
if (!isEnabled) {
callback();
} else if (!value && !rule.required) {
callback();
} else if (!value) {
callback(new Error('请输入身份证号'));
} else if (!regIdCard.test(value)) {
callback(new Error('身份证号码格式错误'));
} else {
callback();
}
},
// 银行卡验证
bank(rule: FormItemRule, value: any, callback: Function) {
const regBank = /^([1-9]{1})(\d{15}|\d{16}|\d{18})$/;
if (!value && !rule.required) {
callback();
} else if (!value) {
callback();
} else if (!regBank.test(value)) {
callback(new Error('银行卡号码格式错误'));
} else {
callback();
}
},
// 非零正整数验证
num(rule: FormItemRule, value: any, callback: Function) {
const reg = /^\+?[1-9][0-9]*$/;
if (!value && !rule.required) {
callback(new Error('请填写非零正整数'));
} else {
if (!reg.test(value)) {
callback(new Error('请输入非零的正整数'));
} else {
callback();
}
}
},
// 银行卡
bankCard(rule: FormItemRule, value: any, callback: Function) {
const regBankCard = /^(\d{16}|\d{19})$/;
if (value == '' && !rule.required) {
callback(new Error('请输入银行卡号'));
} else {
if (!regBankCard.test(value)) {
callback(new Error('银行卡号格式错误'));
} else {
callback();
}
}
},
// 固话格式
tel(rule: FormItemRule, value: any, callback: Function) {
const regTel = /^(0\d{2,3}-?)?\d{7,8}$/;
if (value == '' && !rule.required) {
callback(new Error('请输入座机号码'));
} else {
if (!regTel.test(value)) {
callback(new Error('座机号码格式错误'));
} else {
callback();
}
}
},
// QQ号码
qq(rule: FormItemRule, value: any, callback: Function) {
const regex = /^[1-9][0-9]{4,}$/;
if (!value && !rule.required) {
callback();
} else if (!value) {
callback(new Error('请输入QQ号码'));
} else {
if (!regex.test(value)) {
callback(new Error('QQ号码格式错误'));
} else {
callback();
}
}
},
// weibo号
weibo(rule: FormItemRule, value: any, callback: Function) {
const regex = /^[0-9a-zA-Z\u4e00-\u9fa5_-]*$/;
if (!value && !rule.required) {
callback();
} else if (!value) {
callback(new Error('请输入微博账号'));
} else {
if (!regex.test(value)) {
callback(new Error('微博号码格式错误'));
} else {
callback();
}
}
},
// 不验证
none(_rule: FormItemRule, _value: any, callback: Function) {
callback();
},
};

View File

@@ -7,7 +7,6 @@ let socket: WebSocket;
let isActive: boolean;
export function getSocket(): WebSocket {
console.log('socket:', socket);
if (socket === undefined) {
location.reload();
}
@@ -15,7 +14,6 @@ export function getSocket(): WebSocket {
}
export function getActive(): boolean {
console.log('isActive:', isActive);
return isActive;
}
@@ -85,7 +83,6 @@ export default (onMessage: Function) => {
event: SocketEnum.EventPing,
})
);
console.log('ping');
self.serverTimeoutObj = setTimeout(function () {
console.log('关闭服务');
socket.close();
@@ -133,7 +130,7 @@ export default (onMessage: Function) => {
socket.onmessage = function (event) {
isActive = true;
console.log('WebSocket:收到一条消息', event.data);
// console.log('WebSocket:收到一条消息', event.data);
let isHeart = false;
const message = JSON.parse(event.data);

View File

@@ -15,7 +15,7 @@
>
<n-descriptions bordered label-placement="left" class="py-2">
<n-descriptions-item label="版本">
<n-tag type="info"> 2.0.3 </n-tag>
<n-tag type="info"> {{ config?.version }}</n-tag>
</n-descriptions-item>
<n-descriptions-item label="最后编译时间">
<n-tag type="info"> {{ lastBuildTime }} </n-tag>
@@ -29,9 +29,7 @@
</n-descriptions-item>
<n-descriptions-item label="预览地址">
<div class="flex items-center">
<a href="https://hotgo.facms.cn/admin" class="py-2" target="_blank"
>查看预览地址</a
>
<a href="https://hotgo.facms.cn/admin" class="py-2" target="_blank">查看预览地址</a>
</div>
</n-descriptions-item>
<n-descriptions-item label="Github">
@@ -82,6 +80,11 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useUserStoreWidthOut } from '@/store/modules/user';
const useUserStore = useUserStoreWidthOut();
const config = ref(useUserStore.config);
export interface schemaItem {
field: string;
label: string;

View File

@@ -1,5 +1,55 @@
import { h } from 'vue';
import { NAvatar, NTag } from 'naive-ui';
import { h, ref } from 'vue';
import { NAvatar, NImage, NTag } from 'naive-ui';
import { getFileExt } from '@/utils/urlUtils';
import { Dicts } from '@/api/dict/dict';
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
import { FormSchema } from '@/components/Form';
import { isNullOrUnDef } from '@/utils/is';
export const options = ref<Options>({
sys_normal_disable: [],
config_upload_drive: [],
});
export const schemas = ref<FormSchema[]>([
{
field: 'member_id',
component: 'NInput',
label: '用户ID',
componentProps: {
placeholder: '请输入用户ID',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules: [{ message: '请输入用户ID', trigger: ['blur'] }],
},
{
field: 'drive',
component: 'NSelect',
label: '选择驱动',
defaultValue: null,
componentProps: {
placeholder: '请选择驱动',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '状态',
defaultValue: null,
componentProps: {
placeholder: '请选择类型',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
export const columns = [
{
@@ -11,7 +61,7 @@ export const columns = [
key: 'appId',
},
{
title: '会员ID',
title: '用户ID',
key: 'memberId',
},
{
@@ -45,16 +95,40 @@ export const columns = [
key: 'fileUrl',
width: 80,
render(row) {
return h(NAvatar, {
size: 40,
if (row.fileUrl === '') {
return ``;
}
if (row.kind !== 'images') {
return h(
NAvatar,
{
width: '40px',
height: '40px',
'max-width': '100%',
'max-height': '100%',
},
{
default: () => getFileExt(row.fileUrl),
}
);
}
return h(NImage, {
width: 40,
height: 40,
src: row.fileUrl,
style: {
width: '40px',
height: '40px',
'max-width': '100%',
'max-height': '100%',
},
});
},
},
{
title: '本地路径',
key: 'path',
},
// {
// title: '本地路径',
// key: 'path',
// },
{
title: '扩展名',
key: 'ext',
@@ -67,24 +141,44 @@ export const columns = [
title: '状态',
key: 'status',
render(row) {
if (isNullOrUnDef(row.status)) {
return ``;
}
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: row.status == 1 ? 'success' : 'warning',
type: getOptionTag(options.value.sys_normal_disable, row.status),
bordered: false,
},
{
default: () => (row.status == 1 ? '正常' : '隐藏'),
default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
}
);
},
},
{
title: '上传时间',
key: 'createdAt',
},
];
async function loadOptions() {
options.value = await Dicts({
types: ['sys_normal_disable', 'config_upload_drive'],
});
for (const item of schemas.value) {
switch (item.field) {
case 'status':
item.componentProps.options = options.value.sys_normal_disable;
break;
case 'drive':
item.componentProps.options = options.value.config_upload_drive;
break;
}
}
}
await loadOptions();

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -29,7 +30,16 @@
<UploadOutlined />
</n-icon>
</template>
上传附件
上传图片
</n-button>
&nbsp;
<n-button type="primary" @click="addFileTable">
<template #icon>
<n-icon>
<UploadOutlined />
</n-icon>
</template>
上传文件
</n-button>
&nbsp;
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
@@ -48,12 +58,12 @@
:show-icon="false"
preset="dialog"
style="width: 60%"
title="上传附件"
title="上传图片"
>
<n-upload
multiple
directory-dnd
:action="`${uploadUrl}/admin/upload/image`"
:action="`${uploadUrl}${urlPrefix}/upload/image`"
:headers="uploadHeaders"
:data="{ type: 0 }"
@before-upload="beforeUpload"
@@ -66,7 +76,39 @@
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3">
<archive-icon />
<CloudUploadOutlined />
</n-icon>
</div>
<n-text style="font-size: 16px"> 点击或者拖动图片到该区域来上传</n-text>
<n-p depth="3" style="margin: 8px 0 0 0"> 单次最多允许20个图片</n-p>
</n-upload-dragger>
</n-upload>
</n-modal>
<n-modal
v-model:show="showFileModal"
:show-icon="false"
preset="dialog"
style="width: 60%"
title="上传文件"
>
<n-upload
multiple
directory-dnd
:action="`${uploadUrl}${urlPrefix}/upload/file`"
:headers="uploadHeaders"
:data="{ type: 0 }"
@before-upload="beforeUpload"
@finish="finish"
name="file"
:max="20"
:default-file-list="fileList"
list-type="image"
>
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3">
<FileAddOutlined />
</n-icon>
</div>
<n-text style="font-size: 16px"> 点击或者拖动文件到该区域来上传</n-text>
@@ -82,125 +124,38 @@
import { h, reactive, ref } from 'vue';
import { UploadFileInfo, useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import { Delete, Edit, List, Status } from '@/api/apply/attachment';
import { columns } from './columns';
import { DeleteOutlined, UploadOutlined } from '@vicons/antd';
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
import { BasicForm, useForm } from '@/components/Form/index';
import { Delete, List } from '@/api/apply/attachment';
import { columns, schemas } from './columns';
import {
DeleteOutlined,
UploadOutlined,
FileAddOutlined,
CloudUploadOutlined,
} from '@vicons/antd';
import { useGlobSetting } from '@/hooks/setting';
import { useUserStoreWidthOut } from '@/store/modules/user';
import componentSetting from '@/settings/componentSetting';
import { ResultEnum } from '@/enums/httpEnum';
const useUserStore = useUserStoreWidthOut();
const globSetting = useGlobSetting();
const { uploadUrl } = globSetting;
const urlPrefix = globSetting.urlPrefix || '';
const uploadHeaders = reactive({
Authorization: useUserStore.token,
});
const fileList = ref<UploadFileInfo[]>([
// {
// id: 'c',
// name: '图片.png',
// status: 'finished',
// url: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
// },
]);
const driveOptions = [
{
value: 'local',
label: '本地',
},
].map((s) => {
return s;
});
const params = ref({
pageSize: 10,
title: '',
content: '',
status: null,
});
const rules = {
title: {
// required: true,
trigger: ['blur', 'input'],
message: '请输入标题',
},
};
const schemas: FormSchema[] = [
{
field: 'member_id',
component: 'NInput',
label: '用户ID',
componentProps: {
placeholder: '请输入用户ID',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules: [{ message: '请输入用户ID', trigger: ['blur'] }],
},
{
field: 'drive',
component: 'NSelect',
label: '驱动',
defaultValue: null,
componentProps: {
placeholder: '请选择驱动',
options: driveOptions,
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '状态',
defaultValue: null,
componentProps: {
placeholder: '请选择类型',
options: statusOptions,
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
];
const fileList = ref<UploadFileInfo[]>([]);
const message = useMessage();
const actionRef = ref();
const dialog = useDialog();
const showFileModal = ref(false);
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref({});
const formRef = ref({});
const searchFormRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const resetFormParams = {
basicLogo: '',
id: 0,
title: '',
name: '',
type: 1,
receiver: '',
remark: '',
sort: 0,
status: 1,
created_at: '',
updated_at: '',
};
let formParams = ref(resetFormParams);
const actionColumn = reactive({
width: 220,
title: '操作',
@@ -211,18 +166,14 @@
style: 'button',
actions: [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
label: '下载',
onClick: handleDown.bind(null, record),
},
{
label: '删除',
onClick: handleDelete.bind(null, record),
},
],
dropDownActions: statusActions,
select: (key) => {
updateStatus(record.id, key);
},
});
},
});
@@ -234,22 +185,23 @@
});
function addTable() {
showFileModal.value = false;
showModal.value = true;
fileList.value = [];
}
function addFileTable() {
showModal.value = false;
showFileModal.value = true;
fileList.value = [];
}
const loadDataTable = async (res) => {
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
return await List({ ...res, ...searchFormRef.value?.formModel });
};
function onCheckedRow(rowKeys) {
console.log(rowKeys);
if (rowKeys.length > 0) {
batchDeleteDisabled.value = false;
} else {
batchDeleteDisabled.value = true;
}
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
@@ -257,58 +209,25 @@
actionRef.value.reload();
}
function confirmForm(e) {
e.preventDefault();
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
console.log('formParams:' + JSON.stringify(formParams.value));
Edit(formParams.value)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
formParams.value = ref(resetFormParams);
});
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
showModal.value = true;
formParams.value = record;
function handleDown(record: Recordable) {
window.open(record.fileUrl);
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -318,51 +237,29 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
function handleSubmit(values: Recordable) {
console.log(values);
params.value = values;
function handleSubmit(_values: Recordable) {
reloadTable();
}
function handleReset(values: Recordable) {
params.value = values;
function handleReset(_values: Recordable) {
reloadTable();
}
function updateStatus(id, status) {
Status({ id: id, status: status })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
setTimeout(() => {
reloadTable({});
});
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
}
//上传之前
function beforeUpload({ file }) {
function beforeUpload({ _file }) {
return true;
}

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -133,7 +134,7 @@
].map((s) => {
return s;
});
const params = ref({
const params = ref<any>({
pageSize: 10,
title: '',
content: '',
@@ -193,8 +194,8 @@
const dialog = useDialog();
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref({});
const formRef = ref({});
const searchFormRef = ref<any>({});
const formRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
@@ -210,7 +211,7 @@
created_at: '',
updated_at: '',
};
let formParams = ref(resetFormParams);
let formParams = ref<any>(resetFormParams);
const actionColumn = reactive({
width: 220,
@@ -250,11 +251,10 @@
}
const loadDataTable = async (res) => {
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
return await List({ ...params.value, ...res, ...searchFormRef.value?.formModel });
};
function onCheckedRow(rowKeys) {
console.log(rowKeys);
if (rowKeys.length > 0) {
batchDeleteDisabled.value = false;
} else {
@@ -273,20 +273,14 @@
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
console.log('formParams:' + JSON.stringify(formParams.value));
Edit(formParams.value)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
formParams.value = ref(resetFormParams);
});
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
Edit(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
formParams.value = ref(resetFormParams);
});
});
} else {
message.error('请填写完整信息');
}
@@ -295,31 +289,24 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
showModal.value = true;
formParams.value = record;
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -329,26 +316,20 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
function handleSubmit(values: Recordable) {
console.log(values);
params.value = values;
reloadTable();
}
@@ -359,17 +340,12 @@
}
function updateStatus(id, status) {
Status({ id: id, status: status })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
setTimeout(() => {
reloadTable({});
});
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
Status({ id: id, status: status }).then((_res) => {
message.success('操作成功');
setTimeout(() => {
reloadTable();
});
});
}
</script>

View File

@@ -1,5 +1,5 @@
import { h } from 'vue';
import { NAvatar, NTag } from 'naive-ui';
import { NTag } from 'naive-ui';
export const columns = [
{

View File

@@ -14,6 +14,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -133,7 +134,7 @@
].map((s) => {
return s;
});
const params = ref({
const params = ref<any>({
pageSize: 10,
title: '',
content: '',
@@ -193,8 +194,8 @@
const dialog = useDialog();
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref({});
const formRef = ref({});
const searchFormRef = ref<any>({});
const formRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
@@ -210,7 +211,7 @@
created_at: '',
updated_at: '',
};
let formParams = ref(resetFormParams);
let formParams = ref<any>(resetFormParams);
const actionColumn = reactive({
width: 220,
@@ -250,11 +251,10 @@
}
const loadDataTable = async (res) => {
return await List({ ...params.value, ...res, ...searchFormRef.value.formModel });
return await List({ ...params.value, ...res, ...searchFormRef.value?.formModel });
};
function onCheckedRow(rowKeys) {
console.log(rowKeys);
if (rowKeys.length > 0) {
batchDeleteDisabled.value = false;
} else {
@@ -273,10 +273,8 @@
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
console.log('formParams:' + JSON.stringify(formParams.value));
Edit(formParams.value)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
@@ -295,31 +293,24 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
showModal.value = true;
formParams.value = record;
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -329,20 +320,15 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -359,17 +345,12 @@
}
function updateStatus(id, status) {
Status({ id: id, status: status })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
setTimeout(() => {
reloadTable({});
});
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
Status({ id: id, status: status }).then((_res) => {
message.success('操作成功');
setTimeout(() => {
reloadTable();
});
});
}
</script>

View File

@@ -0,0 +1,147 @@
<template>
<div>
<n-modal
v-model:show="isShowModal"
:show-icon="false"
preset="dialog"
:title="params?.id > 0 ? '编辑 #' + params?.id : '新建'"
:style="{
width: dialogWidth,
}"
>
<n-form
:model="params"
:rules="rules"
ref="formRef"
label-placement="left"
:label-width="80"
class="py-4"
>
<n-form-item label="分类ID" path="categoryId">
<n-input-number placeholder="请输入分类ID" v-model:value="params.categoryId" />
</n-form-item>
<n-form-item label="标题" path="title">
<n-input placeholder="请输入标题" v-model:value="params.title" />
</n-form-item>
<n-form-item label="描述" path="description">
<n-input type="textarea" placeholder="描述" v-model:value="params.description" />
</n-form-item>
<n-form-item label="内容" path="content">
<Editor style="height: 450px" v-model:value="params.content" />
</n-form-item>
<n-form-item label="单图" path="image">
<UploadImage :maxNumber="1" v-model:value="params.image" />
</n-form-item>
<n-form-item label="附件" path="attachfile">
<UploadFile :maxNumber="1" v-model:value="params.attachfile" />
</n-form-item>
<n-form-item label="显示开关" path="switch">
<n-switch v-model:value="params.switch" />
</n-form-item>
<n-form-item label="排序" path="sort">
<n-input-number placeholder="请输入排序" v-model:value="params.sort" />
</n-form-item>
<n-form-item label="状态" path="status">
<n-select v-model:value="params.status" :options="options.sys_normal_disable" />
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="closeForm">取消</n-button>
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, watch } from 'vue';
import { Edit, MaxSort } from '@/api/curdDemo';
import Editor from '@/components/Editor/editor.vue';
import UploadImage from '@/components/Upload/uploadImage.vue';
import UploadFile from '@/components/Upload/uploadFile.vue';
import { rules, options, State, newState } from './model';
import { useMessage } from 'naive-ui';
import { adaModalWidth } from '@/utils/hotgo';
const emit = defineEmits(['reloadTable', 'updateShowModal']);
interface Props {
showModal: boolean;
formParams?: State;
}
const props = withDefaults(defineProps<Props>(), {
showModal: false,
formParams: () => {
return newState(null);
},
});
const isShowModal = computed({
get: () => {
return props.showModal;
},
set: (value) => {
emit('updateShowModal', value);
},
});
const params = computed(() => {
return props.formParams;
});
const message = useMessage();
const formRef = ref<any>({});
const dialogWidth = ref('75%');
const formBtnLoading = ref(false);
function confirmForm(e) {
e.preventDefault();
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
Edit(params.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
isShowModal.value = false;
emit('reloadTable');
});
});
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
onMounted(async () => {
adaModalWidth(dialogWidth);
});
function closeForm() {
isShowModal.value = false;
}
watch(
() => params.value,
(value) => {
if (value.id === 0) {
MaxSort().then((res) => {
params.value.sort = res.sort;
});
}
}
);
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,249 @@
<template>
<div>
<n-card :bordered="false" class="proCard">
<div class="n-layout-page-header">
<n-card :bordered="false" title="生成演示列表">
<!-- 这里有系统自动生成的CURD表格 -->
</n-card>
</div>
<BasicForm
@register="register"
@submit="reloadTable"
@reset="reloadTable"
@keyup.enter="reloadTable"
ref="searchFormRef"
>
<template #statusSlot="{ model, field }">
<n-input v-model:value="model[field]" />
</template>
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
ref="actionRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1090"
:resizeHeightOffset="-10000"
size="small"
>
<template #tableTitle>
<n-button
type="primary"
@click="addTable"
class="min-left-space"
v-if="hasPermission(['/curdDemo/edit'])"
>
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
新建
</n-button>
<n-button
type="error"
@click="handleBatchDelete"
:disabled="batchDeleteDisabled"
class="min-left-space"
v-if="hasPermission(['/curdDemo/delete'])"
>
<template #icon>
<n-icon>
<DeleteOutlined />
</n-icon>
</template>
批量删除
</n-button>
<n-button
type="primary"
@click="handleExport"
class="min-left-space"
v-if="hasPermission(['/demoVar/export'])"
>
<template #icon>
<n-icon>
<ExportOutlined />
</n-icon>
</template>
导出
</n-button>
</template>
</BasicTable>
</n-card>
<Edit
@reloadTable="reloadTable"
@updateShowModal="updateShowModal"
:showModal="showModal"
:formParams="formParams"
/>
</div>
</template>
<script lang="ts" setup>
import { h, reactive, ref } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form/index';
import { usePermission } from '@/hooks/web/usePermission';
import { Delete, List, Status, Export } from '@/api/curdDemo';
import { State, columns, schemas, options, newState } from './model';
import { DeleteOutlined, PlusOutlined, ExportOutlined } from '@vicons/antd';
import { useRouter } from 'vue-router';
import { getOptionLabel } from '@/utils/hotgo';
import Edit from './edit.vue';
const { hasPermission } = usePermission();
const router = useRouter();
const actionRef = ref();
const dialog = useDialog();
const message = useMessage();
const searchFormRef = ref<any>({});
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const showModal = ref(false);
const formParams = ref<State>();
const actionColumn = reactive({
width: 300,
title: '操作',
key: 'action',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: ['/curdDemo/edit'],
},
{
label: '禁用',
onClick: handleStatus.bind(null, record, 2),
ifShow: () => {
return record.status === 1;
},
auth: ['/curdDemo/status'],
},
{
label: '启用',
onClick: handleStatus.bind(null, record, 1),
ifShow: () => {
return record.status === 2;
},
auth: ['/curdDemo/status'],
},
{
label: '删除',
onClick: handleDelete.bind(null, record),
auth: ['/curdDemo/delete'],
},
],
dropDownActions: [
{
label: '查看详情',
key: 'view',
auth: ['/curdDemo/view'],
},
],
select: (key) => {
if (key === 'view') {
return handleView(record);
}
},
});
},
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
const loadDataTable = async (res) => {
return await List({ ...searchFormRef.value?.formModel, ...res });
};
function addTable() {
showModal.value = true;
formParams.value = newState(null);
}
function updateShowModal(value) {
showModal.value = value;
}
function onCheckedRow(rowKeys) {
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
function reloadTable() {
actionRef.value.reload();
}
function handleView(record: Recordable) {
router.push({ name: 'curdDemoView', params: { id: record.id } });
}
function handleEdit(record: Recordable) {
showModal.value = true;
formParams.value = newState(record as State);
}
function handleDelete(record: Recordable) {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record).then((_res) => {
message.success('删除成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function handleBatchDelete() {
dialog.warning({
title: '警告',
content: '你确定要批量删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value }).then((_res) => {
message.success('删除成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function handleExport() {
message.loading('正在导出列表...', { duration: 1200 });
Export(searchFormRef.value?.formModel);
}
function handleStatus(record: Recordable, status: number) {
Status({ id: record.id, status: status }).then((_res) => {
message.success('设为' + getOptionLabel(options.value.sys_normal_disable, status) + '成功');
setTimeout(() => {
reloadTable();
});
});
}
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,242 @@
import { h, ref } from 'vue';
import { NAvatar, NImage, NTag, NSwitch, NRate } from 'naive-ui';
import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { Dicts } from '@/api/dict/dict';
import { Switch } from '@/api/curdDemo';
import { isArray, isNullObject } from '@/utils/is';
import { getFileExt } from '@/utils/urlUtils';
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
import { validate } from '@/utils/validateUtil';
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
import { usePermission } from '@/hooks/web/usePermission';
const { hasPermission } = usePermission();
const $message = window['$message'];
export interface State {
id: number;
categoryId: number;
title: string;
description: string;
content: string;
image: string;
attachfile: string;
switch: number;
sort: number;
status: number;
createdBy: number;
updatedBy: number;
createdAt: string;
updatedAt: string;
deletedAt: string;
}
export const defaultState = {
id: 0,
categoryId: 0,
title: '',
description: '',
content: '',
image: '',
attachfile: '',
switch: 1,
sort: 0,
status: 1,
createdBy: 0,
updatedBy: 0,
createdAt: '',
updatedAt: '',
deletedAt: '',
};
export function newState(state: State | null): State {
if (state !== null) {
return cloneDeep(state);
}
return cloneDeep(defaultState);
}
export const options = ref<Options>({
sys_normal_disable: [],
});
export const rules = {
};
export const schemas = ref<FormSchema[]>([
{
field: 'id',
component: 'NInputNumber',
label: 'ID',
componentProps: {
placeholder: '请输入ID',
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '状态',
defaultValue: null,
componentProps: {
placeholder: '请选择状态',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'createdAt',
component: 'NDatePicker',
label: '创建时间',
componentProps: {
type: 'datetimerange',
clearable: true,
shortcuts: defRangeShortcuts(),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'testCategoryName',
component: 'NInput',
label: '分类名称',
componentProps: {
placeholder: '请输入分类名称',
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
export const columns = [
{
title: 'ID',
key: 'id',
},
{
title: '分类ID',
key: 'categoryId',
},
{
title: '标题',
key: 'title',
},
{
title: '描述',
key: 'description',
},
{
title: '单图',
key: 'image',
render(row) {
return h(NImage, {
width: 32,
height: 32,
src: row.image,
style: {
width: '32px',
height: '32px',
'max-width': '100%',
'max-height': '100%',
},
});
},
},
{
title: '附件',
key: 'attachfile',
render(row) {
if (row.attachfile === '') {
return ``;
}
return h(
NAvatar,
{
size: 'small',
},
{
default: () => getFileExt(row.attachfile),
}
);
},
},
{
title: '显示开关',
key: 'switch',
width: 100,
render(row) {
return h(NSwitch, {
value: row.switch === 1,
checked: '开启',
unchecked: '关闭',
disabled: !hasPermission(['/curdDemo/switch']),
onUpdateValue: function (e) {
console.log('onUpdateValue e:' + JSON.stringify(e));
row.switch = e ? 1 : 2;
Switch({ id: row.id, key: 'switch', value: row.switch }).then((_res) => {
$message.success('操作成功');
});
},
});
},
},
{
title: '排序',
key: 'sort',
},
{
title: '状态',
key: 'status',
render(row) {
if (isNullObject(row.status)) {
return ``;
}
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: getOptionTag(options.value.sys_normal_disable, row.status),
bordered: false,
},
{
default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
}
);
},
},
{
title: '创建时间',
key: 'createdAt',
},
{
title: '分类名称',
key: 'testCategoryName',
},
];
async function loadOptions() {
options.value = await Dicts({
types: [
'sys_normal_disable',
],
});
for (const item of schemas.value) {
switch (item.field) {
case 'status':
item.componentProps.options = options.value.sys_normal_disable;
break;
}
}
}
await loadOptions();

View File

@@ -0,0 +1,108 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="生成演示详情"> <!-- CURD详情页--> </n-card>
</div>
<n-card :bordered="false" class="proCard mt-4" size="small" :segmented="{ content: true }">
<n-descriptions label-placement="left" class="py-2" column="4">
<n-descriptions-item>
<template #label>分类ID</template>
{{ formValue.categoryId }}
</n-descriptions-item>
<n-descriptions-item>
<template #label>标题</template>
{{ formValue.title }}
</n-descriptions-item>
<n-descriptions-item>
<template #label>描述</template>
<span v-html="formValue.description"></span></n-descriptions-item>
<n-descriptions-item>
<template #label>内容</template>
<span v-html="formValue.content"></span></n-descriptions-item>
<n-descriptions-item>
<template #label>单图</template>
<n-image style="margin-left: 10px; height: 100px; width: 100px" :src="formValue.image"
/></n-descriptions-item>
<n-descriptions-item>
<template #label>附件</template>
<div
class="upload-card"
v-show="formValue.attachfile !== ''"
@click="download(formValue.attachfile)"
>
<div class="upload-card-item" style="height: 100px; width: 100px">
<div class="upload-card-item-info">
<div class="img-box">
<n-avatar :style="fileAvatarCSS">{{ getFileExt(formValue.attachfile) }}</n-avatar>
</div>
</div>
</div>
</div>
</n-descriptions-item>
<n-descriptions-item label="显示开关">
<n-switch v-model:value="formValue.switch" :unchecked-value="2" :checked-value="1" :disabled="true"
/></n-descriptions-item>
<n-descriptions-item>
<template #label>排序</template>
{{ formValue.sort }}
</n-descriptions-item>
<n-descriptions-item label="状态">
<template v-for="(item, key) in formValue?.status" :key="key">
<n-tag
:type="getOptionTag(options.sys_normal_disable, item)"
size="small"
class="min-left-space"
>{{ getOptionLabel(options.sys_normal_disable, item) }}</n-tag
>
</template>
</n-descriptions-item>
</n-descriptions>
</n-card>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useMessage } from 'naive-ui';
import { View } from '@/api/curdDemo';
import { newState, options } from './model';
import { getOptionLabel, getOptionTag } from '@/utils/hotgo';
import { getFileExt } from '@/utils/urlUtils';
const message = useMessage();
const router = useRouter();
const id = Number(router.currentRoute.value.params.id);
const formValue = ref(newState(null));
const fileAvatarCSS = computed(() => {
return {
'--n-merged-size': `var(--n-avatar-size-override, 80px)`,
'--n-font-size': `18px`,
};
});
//下载
function download(url: string) {
window.open(url);
}
onMounted(async () => {
if (id < 1) {
message.error('ID不正确请检查');
return;
}
formValue.value = await View({ id: id });
});
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,509 @@
<template>
<div>
<n-card
:bordered="true"
title="基本设置"
class="proCard mt-2"
size="small"
:segmented="{ content: true }"
>
<n-form ref="formRef" :model="formValue">
<n-row :gutter="24">
<n-col :span="6" style="min-width: 200px">
<n-form-item label="生成类型" path="title">
<n-select
placeholder="请选择"
:options="selectList.genType"
v-model:value="formValue.genType"
/>
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item label="实体命名" path="varName">
<n-input placeholder="请输入" v-model:value="formValue.varName" />
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item
label="数据库"
path="dbName"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-select
placeholder="请选择"
:options="selectList.db"
v-model:value="formValue.dbName"
@update:value="handleDbUpdateValue"
/>
</n-form-item>
</n-col>
<n-col :span="6" style="min-width: 200px">
<n-form-item
label="数据库表"
path="tableName"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-select
filterable
tag
:loading="tablesLoading"
placeholder="请选择"
:options="tablesOption"
v-model:value="formValue.tableName"
@update:value="handleTableUpdateValue"
:disabled="formValue.dbName === ''"
/>
</n-form-item>
</n-col>
<n-col :span="18">
<n-form-item
label="表格头部按钮组"
path="tableName"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-checkbox-group v-model:value="formValue.options.headOps">
<n-space item-style="display: flex;">
<n-checkbox value="add" label="新增表单按钮" />
<n-checkbox value="batchDel" label="批量删除按钮" />
<n-checkbox value="export" label="导出按钮" />
</n-space>
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col :span="24">
<n-form-item
label="表格列操作"
path="columnOps"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-checkbox-group v-model:value="formValue.options.columnOps">
<n-space item-style="display: flex;">
<n-checkbox value="edit" label="编辑" />
<n-checkbox value="status" label="状态修改" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>主表中存在`status`字段时才会生效</span>
</n-popover>
<n-checkbox value="del" label="删除" />
<n-checkbox value="view" label="详情页" />
<n-checkbox value="check" label="开启勾选列" />
<n-checkbox value="switch" label="操作开关" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>主表中存在`switch`字段时才会生效</span>
</n-popover>
</n-space>
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col :span="24">
<n-form-item
label="自动化操作"
path="autoOps"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-checkbox-group v-model:value="formValue.options.autoOps">
<n-space item-style="display: flex;">
<n-checkbox value="genMenuPermissions" label="生成菜单权限" />
<n-checkbox value="runDao" label="生成前运行 [gf gen dao]" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>请确保运行环境已安装gf命令</span>
</n-popover>
<n-checkbox value="runService" label="生成后运行 [gf gen service]" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>请确保运行环境已安装gf命令</span>
</n-popover>
<n-checkbox value="forcedCover" label="强制覆盖" />
<n-popover trigger="hover">
<template #trigger>
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
<QuestionCircleOutlined />
</n-icon>
</template>
<span>只会强制覆盖需要生成的文件但不包含SQL文件</span>
</n-popover>
</n-space>
</n-checkbox-group>
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="上级菜单" path="pid">
<n-tree-select
:options="optionMenuTree"
:value="formValue.options.menu.pid"
@update:value="handleUpdateMenuPid"
/>
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="菜单名称" path="tableComment">
<n-input placeholder="请输入" v-model:value="formValue.tableComment" />
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="菜单图标" path="menuIcon">
<IconSelector style="width: 100%" v-model:value="formValue.options.menu.icon" />
</n-form-item>
</n-col>
<n-col
:span="6"
style="min-width: 200px"
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
>
<n-form-item label="菜单排序" path="menuIcon">
<n-input-number
style="width: 100%"
placeholder="请输入"
v-model:value="formValue.options.menu.sort"
clearable
/>
</n-form-item>
</n-col>
</n-row>
</n-form>
</n-card>
<n-card
:bordered="true"
title="关联表设置"
class="proCard mt-2"
size="small"
:segmented="{ content: true }"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<template #header-extra>
<n-space>
<n-button type="warning" @click="addJoin" :disabled="formValue.options?.join?.length >= 3"
>新增关联表</n-button
>
</n-space>
</template>
<n-form ref="formRef" :model="formValue">
<n-alert :show-icon="false">关联表数量建议在三个以下</n-alert>
<n-row :gutter="6" v-for="(join, index) in formValue.options.join" :key="index">
<n-col :span="6" style="min-width: 200px">
<n-form-item label="关联表" path="join.linkTable">
<n-select
filterable
tag
:loading="tablesLoading"
placeholder="请选择"
:options="linkTablesOption"
v-model:value="join.linkTable"
@update:value="handleLinkTableUpdateValue(join)"
:disabled="formValue.dbName === ''"
/>
</n-form-item>
</n-col>
<n-col :span="3" style="min-width: 100px">
<n-form-item
label="别名"
path="join.alias"
v-show="formValue.genType >= 10 && formValue.genType < 20"
>
<n-input
placeholder="请输入"
v-model:value="join.alias"
@update:value="updateJoinAlias"
/>
<template #feedback> {{ joinAliasFeedback }}</template>
</n-form-item>
</n-col>
<n-col :span="3" style="min-width: 100px">
<n-form-item label="关联方式" path="join.linkMode">
<n-select
placeholder="请选择"
:options="selectList.linkMode"
v-model:value="join.linkMode"
/>
</n-form-item>
</n-col>
<n-col :span="5" style="min-width: 180px">
<n-form-item label="关联字段" path="join.field">
<n-select
filterable
tag
:loading="linkColumnsLoading"
placeholder="请选择"
:options="linkColumnsOption[join.uuid]"
v-model:value="join.field"
/>
</n-form-item>
</n-col>
<n-col :span="5" style="min-width: 180px">
<n-form-item label="主表关联字段" path="join.masterField">
<n-select
filterable
tag
:loading="columnsLoading"
placeholder="请选择"
:options="columnsOption"
v-model:value="join.masterField"
/>
</n-form-item>
</n-col>
<n-col :span="2" style="min-width: 50px">
<n-space>
<n-form-item label="操作" path="title">
<n-button @click="delJoin(join, index)" size="small" strong secondary type="error"
>移除</n-button
>
</n-form-item>
</n-space>
</n-col>
</n-row>
</n-form>
</n-card>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, watch } from 'vue';
import { FormInst } from 'naive-ui';
import { newState, selectListObj } from './model';
import { TableSelect, ColumnSelect } from '@/api/develop/code';
import { getRandomString } from '@/utils/charset';
import IconSelector from '@/components/IconSelector/index.vue';
import { QuestionCircleOutlined } from '@vicons/antd';
import { getMenuList } from '@/api/system/menu';
import { cloneDeep } from 'lodash-es';
import { isLetterBegin } from '@/utils/is';
const formRef = ref<FormInst | null>(null);
const tablesLoading = ref(false);
const columnsLoading = ref(false);
const linkColumnsLoading = ref(false);
const tablesOption = ref<any>([]); // 数据库表选项
const columnsOption = ref<any>([]); // 主表字段选项
const linkTablesOption = ref<any>([]); // 关联表选项
const linkColumnsOption = ref<any>([]); // 关联表字段选项
const optionMenuTree = ref([
{
id: 0,
key: 0,
label: '根目录',
pid: 0,
title: '根目录',
type: 1,
},
]);
const emit = defineEmits(['update:value']);
interface Props {
value?: any;
selectList: any;
}
const props = withDefaults(defineProps<Props>(), {
value: newState(null),
selectList: selectListObj,
});
watch(props, async (newVal, oldVal) => {
if (newVal.value.dbName != oldVal.value.dbName) {
await instLoad();
}
});
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
onMounted(() => {
setTimeout(async function () {
await instLoad();
// 切换tab时会导致选项被清空这里重新进行加载
await loadLinkColumnsOption();
await loadMenuTreeOption();
}, 500);
});
const loadMenuTreeOption = async () => {
const options = await getMenuList();
optionMenuTree.value = [
{
id: 0,
key: 0,
label: '根目录',
pid: 0,
title: '根目录',
type: 1,
},
];
optionMenuTree.value = optionMenuTree.value.concat(options.list);
};
const loadSelect = async () => {
columnsOption.value = await loadColumnSelect(formValue.value.tableName);
};
async function instLoad() {
columnsLoading.value = true;
tablesLoading.value = true;
await loadSelect();
await loadTableSelect(formValue.value.dbName);
tablesLoading.value = false;
columnsLoading.value = false;
}
async function loadLinkColumnsOption() {
if (formValue.value.options.join === undefined) {
return;
}
for (let i = 0; i < formValue.value.options.join.length; i++) {
linkColumnsLoading.value = true;
linkColumnsOption.value[formValue.value.options.join[i].uuid] = await loadColumnSelect(
formValue.value.options.join[i].linkTable
);
linkColumnsLoading.value = false;
}
}
// 处理选项更新
async function handleDbUpdateValue(value, _option) {
tablesLoading.value = true;
await loadTableSelect(value);
tablesLoading.value = false;
}
async function loadTableSelect(value) {
const options = await TableSelect({ name: value });
tablesOption.value = cloneDeep(options);
linkTablesOption.value = cloneDeep(options);
}
async function loadColumnSelect(value) {
return await ColumnSelect({ name: formValue.value.dbName, table: value });
}
function handleTableUpdateValue(value, option) {
formValue.value.varName = option?.defVarName as string;
formValue.value.daoName = option?.daoName as string;
formValue.value.tableComment = option?.defTableComment as string;
}
function addJoin() {
if (formValue.value.options.join === undefined) {
formValue.value.options.join = [];
}
let uuid = getRandomString(16, true);
formValue.value.options.join.push({
uuid: uuid,
linkTable: '',
alias: '',
linkMode: 1,
field: '',
masterField: '',
daoName: '',
columns: [],
});
linkColumnsOption.value[uuid] = [];
}
function delJoin(join, index) {
formValue.value.options.join.splice(index, 1);
delete linkColumnsOption.value[join.uuid];
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
if (i > -1) {
linkTablesOption.value[i].disabled = false;
}
}
async function handleLinkTableUpdateValue(join) {
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
if (i > -1) {
join.alias = linkTablesOption.value[i].defAlias;
join.daoName = linkTablesOption.value[i].daoName;
linkTablesOption.value[i].disabled = true;
}
linkColumnsLoading.value = true;
linkColumnsOption.value[join.uuid] = await loadColumnSelect(join.linkTable);
// 清空更新前的字段
join.field = '';
linkColumnsLoading.value = false;
}
const joinAliasFeedback = ref('');
function updateJoinAlias(value: string) {
if (value.length < 3) {
joinAliasFeedback.value = '别名不能小于3位';
return;
}
if (!isLetterBegin(value)) {
joinAliasFeedback.value = '别名必须以字母开头';
return;
}
joinAliasFeedback.value = '';
}
function handleUpdateMenuPid(value: string | number | Array<string | number> | null) {
formValue.value.options.menu.pid = value;
}
</script>
<style lang="less" scoped>
::v-deep(.default_text_value) {
color: var(--n-tab-text-color-active);
}
::v-deep(.tips-help-icon) {
margin-left: -16px;
margin-top: 5px;
display: block;
}
</style>

View File

@@ -0,0 +1,357 @@
<template>
<n-spin :show="show" description="加载中...">
<n-card :bordered="false" class="proCard">
<BasicTable
:single-line="false"
size="small"
:striped="true"
:resizable="true"
:columns="columns"
:dataSource="dataSource"
:openChecked="false"
:showTopRight="false"
:row-key="(row) => row.id"
ref="actionRef"
:canResize="true"
:resizeHeightOffset="-20000"
:pagination="false"
:scroll-x="1090"
:scrollbar-props="{ trigger: 'none' }"
/>
</n-card>
</n-spin>
</template>
<script lang="ts" setup>
import { computed, h, onMounted, ref } from 'vue';
import { BasicTable } from '@/components/Table';
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
import { ColumnList } from '@/api/develop/code';
import { NButton, NCheckbox, NInput, NSelect, NTooltip, NTreeSelect } from 'naive-ui';
import { HelpCircleOutline } from '@vicons/ionicons5';
import { renderIcon } from '@/utils';
import { cloneDeep } from 'lodash-es';
const renderTooltip = (trigger, content) => {
return h(NTooltip, null, {
trigger: () => trigger,
default: () => content,
});
};
const emit = defineEmits(['update:value']);
interface Props {
value?: any;
selectList: any;
}
const props = withDefaults(defineProps<Props>(), {
value: genInfoObj,
selectList: selectListObj,
});
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
const actionRef = ref();
const columns = ref<any>([]);
const show = ref(false);
const dataSource = ref(formValue.value.masterColumns);
onMounted(async () => {
show.value = true;
if (formValue.value.masterColumns.length === 0) {
formValue.value.masterColumns = await ColumnList({
name: formValue.value.dbName,
table: formValue.value.tableName,
});
dataSource.value = formValue.value.masterColumns;
}
columns.value = [
{
title: '位置',
key: 'id',
width: 50,
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
ghost: true,
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
),
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
);
},
key: 'field',
align: 'center',
width: 800,
children: [
{
title: '字段列名',
key: 'name',
width: 150,
},
{
title: '物理类型',
key: 'sqlType',
width: 150,
},
{
title: 'Go属性',
key: 'goName',
width: 130,
},
{
title: 'Go类型',
key: 'goType',
width: 100,
},
{
title: 'Ts属性',
key: 'tsName',
width: 130,
},
{
title: 'Ts类型',
key: 'tsType',
width: 100,
},
{
title: '字段描述',
key: 'dc',
width: 150,
render(row) {
return h(NInput, {
value: row.dc,
onUpdateValue: function (e) {
row.dc = e;
},
});
},
},
],
},
{
width: 800,
title(_column) {
return renderTooltip(
h(
NButton,
{
ghost: true,
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '新增/编辑表单', icon: renderIcon(HelpCircleOutline) }
),
'勾选编辑以后会在新增、编辑表单中显示该字段;当同时勾选列表查询时,会优先使用配置的表单组件'
);
},
key: 'edit',
align: 'center',
children: [
{
align: 'center',
title: '编辑',
key: 'isEdit',
width: 50,
render(row) {
return h(NCheckbox, {
defaultChecked: row.isEdit,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.isEdit = e;
},
});
},
},
{
title: '必填',
key: 'required',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.required,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.required = e;
},
});
},
},
{
title: '唯一',
key: 'unique',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.unique,
disabled: row.name === 'id',
onUpdateChecked: function (e) {
row.unique = e;
},
});
},
},
{
title: '表单组件',
key: 'formMode',
width: 200,
render(row) {
return h(NSelect, {
value: row.formMode,
options: getFormModeOptions(row.tsType),
// render: function (row) {
// return props.selectList?.formMode ?? [];
// },
// onFocus: function (e) {
// console.log('表单组件 onFocus row:', e);
// },
onUpdateValue: function (e) {
row.formMode = e;
},
});
},
},
{
title: '表单验证',
key: 'formRole',
width: 200,
render(row) {
return h(NSelect, {
value: row.formRole,
disabled: row.name === 'id',
options: props.selectList?.formRole ?? [],
onUpdateValue: function (e) {
row.formRole = e;
},
});
},
},
{
title: '字典类型',
key: 'dictType',
width: 300,
render(row) {
return h(NTreeSelect, {
value: row.dictType,
disabled: row.name === 'id',
options: props.selectList?.dictMode ?? [],
onUpdateValue: function (e) {
row.dictType = e;
},
});
},
},
],
},
{
width: 800,
title: '列表',
key: 'list',
align: 'center',
children: [
{
title: '列表',
key: 'isList',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isList,
onUpdateChecked: function (e) {
row.isList = e;
},
});
},
},
{
title: '导出',
key: 'isExport',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isExport,
onUpdateChecked: function (e) {
row.isExport = e;
},
});
},
},
{
title: '查询',
key: 'isQuery',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isQuery,
onUpdateChecked: function (e) {
row.isQuery = e;
},
});
},
},
{
title: '查询条件',
key: 'queryWhere',
width: 300,
render(row) {
return h(NSelect, {
value: row.queryWhere,
disabled: row.name === 'id',
options: props.selectList?.whereMode ?? [],
onUpdateValue: function (e) {
row.queryWhere = e;
},
});
},
},
],
},
];
show.value = false;
});
function getFormModeOptions(type: string) {
const options = cloneDeep(props.selectList?.formMode ?? []);
if (options.length === 0) {
return [];
}
switch (type) {
case 'number':
for (let i = 0; i < options.length; i++) {
const allows = ['InputNumber', 'Radio', 'Select', 'Switch', 'Rate'];
if (!allows.includes(options[i].value)) {
options[i].disabled = true;
}
}
break;
default:
}
return options;
}
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,243 @@
<template>
<n-spin :show="show" description="加载中...">
<n-card :bordered="false" class="proCard">
<BasicTable
:single-line="false"
size="small"
:striped="true"
:resizable="true"
:columns="columns"
:dataSource="dataSource"
:openChecked="false"
:showTopRight="false"
:row-key="(row) => row.id"
ref="actionRef"
:canResize="true"
:resizeHeightOffset="-20000"
:pagination="false"
:scroll-x="1090"
:scrollbar-props="{ trigger: 'none' }"
/>
</n-card>
</n-spin>
</template>
<script lang="ts" setup>
import { Component, computed, h, onMounted, ref } from 'vue';
import { BasicTable } from '@/components/Table';
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
import { ColumnList } from '@/api/develop/code';
import { NButton, NCheckbox, NIcon, NInput, NSelect, NTooltip } from 'naive-ui';
import { HelpCircleOutline } from '@vicons/ionicons5';
const renderTooltip = (trigger, content) => {
return h(NTooltip, null, {
trigger: () => trigger,
default: () => content,
});
};
function renderIcon(icon: Component) {
return () => h(NIcon, null, { default: () => h(icon) });
}
const emit = defineEmits(['update:value']);
interface Props {
value?: any;
selectList: any;
uuid: string;
}
const props = withDefaults(defineProps<Props>(), {
value: genInfoObj,
selectList: selectListObj,
uuid: '',
});
const columns = ref<any>([]);
const formValue = computed({
get() {
return props.value;
},
set(value) {
emit('update:value', value);
},
});
function getIndex() {
if (formValue.value.options.join.length === 0) {
return -1;
}
for (let i = 0; i < formValue.value.options.join.length; i++) {
if (formValue.value.options.join[i].uuid === props.uuid) {
return i;
}
}
return -1;
}
const show = ref(false);
const dataSource = ref([]);
onMounted(async () => {
show.value = true;
setTimeout(async () => {
const index = getIndex();
if (formValue.value.options.join[index].columns.length === 0) {
formValue.value.options.join[index].columns = await ColumnList({
name: formValue.value.dbName,
table: formValue.value.options.join[index].linkTable,
isLink: 1,
alias: formValue.value.options.join[index].alias,
});
}
dataSource.value = formValue.value.options.join[index].columns;
columns.value = [
{
title: '位置',
key: 'id',
width: 50,
},
{
title(_column) {
return renderTooltip(
h(
NButton,
{
ghost: true,
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',
},
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
),
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
);
},
key: 'field',
align: 'center',
width: 800,
children: [
{
title: '字段列名',
key: 'name',
width: 150,
},
{
title: '物理类型',
key: 'sqlType',
width: 150,
},
{
title: 'Go属性',
key: 'goName',
width: 260,
},
{
title: 'Go类型',
key: 'goType',
width: 100,
},
{
title: 'Ts属性',
key: 'tsName',
width: 260,
},
{
title: 'Ts类型',
key: 'tsType',
width: 100,
},
{
title: '字段描述',
key: 'dc',
width: 150,
render(row) {
return h(NInput, {
value: row.dc,
onUpdateValue: function (e) {
row.dc = e;
// await saveProductCustom(row.id, 'frontShow', e);
},
});
},
},
],
},
{
width: 800,
title: '列表',
key: 'list',
align: 'center',
children: [
{
title: '列表',
key: 'isList',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isList,
onUpdateChecked: function (e) {
row.isList = e;
},
});
},
},
{
title: '导出',
key: 'isExport',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isExport,
onUpdateChecked: function (e) {
row.isExport = e;
},
});
},
},
{
title: '查询',
key: 'isQuery',
width: 50,
align: 'center',
render(row) {
return h(NCheckbox, {
defaultChecked: row.isQuery,
onUpdateChecked: function (e) {
row.isQuery = e;
},
});
},
},
{
title: '查询条件',
key: 'queryWhere',
width: 300,
render(row) {
return h(NSelect, {
value: row.queryWhere,
disabled: row.name === 'id',
options: props.selectList?.whereMode ?? [],
onUpdateValue: function (e) {
row.queryWhere = e;
},
});
},
},
],
},
];
show.value = false;
}, 50);
});
const actionRef = ref();
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,84 @@
<template>
<div>
<n-tabs type="line" animated>
<n-tab-pane v-for="(view, index) in views" :key="index" :name="view.name" :tab="view.name">
<n-tag :type="view.tag.type" class="tag-margin">
{{ view.tag.label }}
<template #icon>
<n-icon :component="view.tag.icon" />
</template>
{{ view.path }}
</n-tag>
<n-scrollbar class="code-scrollbar" trigger="none">
<n-code :code="view.content" />
</n-scrollbar>
</n-tab-pane>
</n-tabs>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { cloneDeep } from 'lodash-es';
import {
CheckmarkCircle,
CheckmarkDoneCircle,
CloseCircleOutline,
HelpCircleOutline,
RemoveCircleOutline,
} from '@vicons/ionicons5';
interface Props {
previewModel: any;
showModal: boolean;
}
const props = withDefaults(defineProps<Props>(), {
previewModel: cloneDeep({ views: {} }),
showModal: false,
});
const views = computed(() => {
let tmpViews: any = [];
let i = 0;
for (const [k, v] of Object.entries(props.previewModel.views)) {
let item = v as any;
item.name = k;
switch (item.meth) {
case 1:
item.tag = { type: 'success', label: '创建文件', icon: CheckmarkCircle };
break;
case 2:
item.tag = { type: 'warning', label: '覆盖文件', icon: CheckmarkDoneCircle };
break;
case 3:
item.tag = { type: 'info', label: '已存在跳过', icon: CloseCircleOutline };
break;
case 4:
item.tag = { type: 'error', label: '不生成', icon: RemoveCircleOutline };
break;
default:
item.tag = { type: 'error', label: '未知状态', icon: HelpCircleOutline };
}
tmpViews[i] = item;
i++;
}
return tmpViews;
});
</script>
<style lang="less" scoped>
::v-deep(.alert-margin) {
margin-bottom: 20px;
}
::v-deep(.tag-margin) {
margin-bottom: 10px;
}
::v-deep(.code-scrollbar) {
height: calc(100vh - 300px);
background: #282b2e;
color: #e0e2e4;
padding: 10px;
}
</style>

View File

@@ -0,0 +1,62 @@
import { cloneDeep } from 'lodash-es';
export const genFileObj = {
meth: 1,
content: '',
path: '',
required: true,
};
export interface joinAttr {
uuid: string;
linkTable: string;
alias: string;
linkMode: number;
field: string;
masterField: string;
columns: any;
}
export const genInfoObj = {
id: 0,
genType: 10,
varName: '',
options: {
headOps: ['add', 'batchDel', 'export'],
columnOps: ['edit', 'del', 'view', 'status', 'switch', 'check'],
autoOps: ['genMenuPermissions', 'runDao', 'runService'],
join: [],
menu: {
pid: 0,
icon: 'MenuOutlined',
sort: 0,
},
},
dbName: '',
tableName: '',
tableComment: '',
daoName: '',
masterColumns: [],
status: 2,
createdAt: '',
updatedAt: '',
};
export const selectListObj = {
db: [],
genType: [],
status: [],
tables: [],
formMode: [],
formRole: [],
dictMode: [],
whereMode: [],
buildMeth: [],
};
export function newState(state) {
if (state !== null) {
return cloneDeep(state);
}
return cloneDeep(genInfoObj);
}

View File

@@ -0,0 +1,264 @@
<template>
<div>
<n-spin :show="show" description="正在生成配置信息...">
<n-card>
<n-tabs
type="card"
class="card-tabs"
:default-value="value"
animated
tab-style="min-width: 80px;"
style="margin: 0 -4px"
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;"
:on-update:value="updateTabs"
ref="tabsRef"
@close="handleClose"
@add="handleAdd"
>
<n-tab-pane v-for="panel in panels" :key="panel" :name="panel">
<template v-if="panel === '基本信息'">
<BaseInfo v-model:value="genInfo" :selectList="selectList" />
</template>
<template v-if="panel === '主表字段'">
<EditMasterCell v-model:value="genInfo" :selectList="selectList" />
</template>
</n-tab-pane>
<n-tab-pane
v-for="panel in slavePanels"
:key="panel"
:name="panel"
v-show="slavePanels.length > 0 && slavePanels !== []"
>
<EditSlaveCell
v-model:value="genInfo"
:uuid="slaveMap[panel]"
:selectList="selectList"
/>
</n-tab-pane>
<template #suffix>
<n-space>
<n-button type="primary" @click="preview">预览代码</n-button>
<n-button type="success" :loading="formBtnLoading" @click="submitBuild"
>提交生成</n-button
>
<n-button type="info" dashed :loading="formBtnLoading" @click="submitSave"
>仅保存配置</n-button
>
</n-space>
</template>
</n-tabs>
<n-modal
v-model:show="showModal"
:block-scroll="false"
:mask-closable="false"
:show-icon="false"
preset="card"
title="预览代码"
style="width: 95%"
>
<PreviewTab :previewModel="previewModel" />
<template #action>
<n-space justify="end">
<n-button @click="() => (showModal = false)">关闭</n-button>
<n-button type="info" :loading="formBtnLoading" @click="submitBuild"
>提交生成</n-button
>
</n-space>
</template>
</n-modal>
</n-card>
</n-spin>
</div>
</template>
<style scoped>
.card-tabs .n-tabs-nav--bar-type {
padding-left: 4px;
}
</style>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useDialog, useMessage } from 'naive-ui';
import BaseInfo from './components/BaseInfo.vue';
import EditMasterCell from './components/EditMasterCell.vue';
import EditSlaveCell from './components/EditSlaveCell.vue';
import { Selects, View, Preview, Build, Edit } from '@/api/develop/code';
import { selectListObj, newState } from '@/views/develop/code/components/model';
import PreviewTab from '@/views/develop/code/components/PreviewTab.vue';
import { isJsonString } from '@/utils/is';
interface Props {
genId?: number;
}
const props = withDefaults(defineProps<Props>(), { genId: 0 });
const router = useRouter();
const genId = Number(router.currentRoute.value.params.id ?? props.genId);
const show = ref(false);
const message = useMessage();
const selectList = ref<any>(selectListObj);
const genInfo = ref(newState(null));
const tabsRef = ref();
const value = ref('基本信息');
const panels = ref(['基本信息', '主表字段']);
const slaveMap = ref<any>([]);
const slavePanels = ref<any>([]);
const showModal = ref(false);
const formBtnLoading = ref(false);
const previewModel = ref<any>();
const dialog = useDialog();
onMounted(async () => {
if (genId < 1 && props.genId < 1) {
message.error('生成ID不正确请检查');
return;
}
await getGenInfo();
await loadSelect();
});
async function getGenInfo() {
let tmp = await View({ id: genId });
if (isJsonString(tmp.options)) {
tmp.options = JSON.parse(tmp.options);
}
if (tmp.masterColumns === undefined || tmp.masterColumns.length === 0) {
tmp.masterColumns = [];
}
if (isJsonString(tmp.masterColumns)) {
tmp.masterColumns = JSON.parse(tmp.masterColumns);
}
genInfo.value = tmp;
}
watch(
genInfo,
(newVal, _oldVal) => {
if (newVal.genType >= 10 && newVal.genType < 20) {
handleAdd('主表字段');
} else {
handleClose('主表字段');
}
if (newVal.options.join !== undefined) {
slavePanels.value = [];
for (let i = 0; i <= newVal.options.join.length; i++) {
if (newVal.options.join[i]?.alias !== undefined && newVal.options.join[i]?.alias !== '') {
handleSlaveAdd(
'关联表[ ' + newVal.options.join[i]?.alias + ' ]',
newVal.options.join[i]
);
}
}
}
},
{
deep: true, // 是否深度监听
}
);
function updateTabs(value: string | number) {
console.log('value:' + value);
}
function handleAdd(name: string) {
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) {
panels.value.push(name);
}
}
function handleSlaveAdd(name: string, join) {
const nameIndex = slavePanels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) {
slavePanels.value.push(name);
slaveMap.value[name] = join.uuid;
}
}
function _handleSlaveClose(name: string) {
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) return;
panels.value.splice(nameIndex, 1);
if (name === value.value) {
value.value = panels.value[Math.min(nameIndex, panels.value.length - 1)];
}
}
function handleClose(name: string) {
const nameIndex = panels.value.findIndex((panelName) => panelName === name);
if (!~nameIndex) return;
panels.value.splice(nameIndex, 1);
if (name === value.value) {
value.value = panels.value[Math.min(nameIndex, panels.value.length - 1)];
}
}
const loadSelect = async () => {
selectList.value = await Selects({});
};
async function preview() {
previewModel.value = await Preview(genInfo.value);
showModal.value = true;
}
function submitBuild() {
dialog.warning({
title: '警告',
content: '你确定要提交生成吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Build(genInfo.value).then((_res) => {
setTimeout(function () {
location.reload();
}, 1500);
message.success('生成提交成功,即将刷新页面..');
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function submitSave() {
dialog.warning({
title: '警告',
content: '你确定要保存生成配置吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Edit(genInfo.value).then((_res) => {
message.success('操作成功');
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
</script>
<style lang="less" scoped>
::v-deep(.alert-margin) {
margin-bottom: 20px;
}
::v-deep(.tag-margin) {
margin-bottom: 10px;
}
::v-deep(.code-scrollbar) {
height: calc(100vh - 300px);
background: #282b2e;
color: #e0e2e4;
padding: 10px;
}
</style>

View File

@@ -1,39 +1,452 @@
<template>
<div class="flex flex-col justify-center page-container">
<div class="text-center">
<h1 class="text-base">代码生成敬请期待</h1>
<n-button type="info" @click="goHome">回到首页</n-button>
</div>
</div>
<n-card :bordered="false" class="proCard">
<n-card :bordered="false" title="代码生成"> 你可以在这里查看到平台所有的短信发送记录 </n-card>
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset" ref="searchFormRef">
<template #statusSlot="{ model, field }">
<n-input v-model:value="model[field]" />
</template>
</BasicForm>
<BasicTable
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
ref="actionRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1090"
>
<template #tableTitle>
<n-button type="primary" @click="addTable">
<template #icon>
<n-icon>
<PlusOutlined />
</n-icon>
</template>
生成
</n-button>
&nbsp;
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
<template #icon>
<n-icon>
<DeleteOutlined />
</n-icon>
</template>
批量删除
</n-button>
</template>
</BasicTable>
<n-modal
v-model:show="showModal"
:show-icon="false"
preset="dialog"
title="生成"
:style="{
width: dialogWidth,
}"
>
<!-- <n-alert :show-icon="false" type="info">-->
<!-- 注意:!-->
<!-- </n-alert>-->
<n-form
:model="formParams"
:rules="rules"
ref="formRef"
label-placement="left"
:label-width="80"
class="py-4"
>
<n-form-item label="生成类型" path="genType">
<n-select
placeholder="请选择"
:options="selectList.genType"
v-model:value="formParams.genType"
/>
</n-form-item>
<n-form-item
label="数据库"
path="dbName"
v-if="formParams.genType >= 10 && formParams.genType < 20"
>
<n-select
placeholder="请选择"
:options="selectList.db"
v-model:value="formParams.dbName"
@update:value="handleDbUpdateValue"
/>
</n-form-item>
<n-form-item
label="数据库表"
path="tableName"
v-if="formParams.genType >= 10 && formParams.genType < 20"
>
<n-select
filterable
tag
:loading="tablesLoading"
placeholder="请选择"
:options="selectList.tables"
v-model:value="formParams.tableName"
@update:value="handleTableUpdateValue"
:disabled="formParams.dbName === ''"
/>
</n-form-item>
<n-form-item
label="菜单名称"
path="tableComment"
v-show="formParams.genType >= 10 && formParams.genType < 20"
>
<n-input placeholder="请输入" v-model:value="formParams.tableComment" />
</n-form-item>
<n-form-item label="实体命名" path="varName">
<n-input placeholder="请输入" v-model:value="formParams.varName" />
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="() => (showModal = false)">取消</n-button>
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">生成配置</n-button>
</n-space>
</template>
</n-modal>
</n-card>
</template>
<script lang="ts" setup>
import { h, onBeforeMount, reactive, ref } from 'vue';
import { NTag, TreeSelectOption, useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import { List, Delete, Edit, Selects, TableSelect } from '@/api/develop/code';
import { useRouter } from 'vue-router';
import { PlusOutlined, DeleteOutlined } from '@vicons/antd';
import { newState } from '@/views/develop/code/components/model';
import { getOptionLabel } from '@/utils/hotgo';
const selectList = ref({
db: [],
genType: [],
status: [],
tables: [],
formMode: [],
formRole: [],
dictMode: [],
whereMode: [],
});
const columns = [
{
title: '生成ID',
key: 'id',
width: 100,
},
{
title: '生成类型',
key: 'genType',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'info',
bordered: false,
},
{
default: () => getOptionLabel(selectList.value.genType, row.genType),
}
);
},
width: 200,
},
{
title: '实体命名',
key: 'varName',
render(row) {
return row.varName;
},
width: 180,
},
{
title: '数据库',
key: 'dbName',
width: 200,
},
{
title: '数据表',
key: 'tableName',
width: 200,
},
{
title: '菜单名称',
key: 'tableComment',
width: 200,
},
{
title: '生成状态',
key: 'status',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: row.status == 1 ? 'success' : 'warning',
bordered: false,
},
{
default: () => getOptionLabel(selectList.value.status, row.status),
}
);
},
width: 150,
},
{
title: '创建时间',
key: 'createdAt',
width: 180,
},
{
title: '更新时间',
key: 'updatedAt',
width: 180,
},
];
const dialog = useDialog();
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const searchFormRef = ref<any>();
const schemas = ref<FormSchema[]>([
{
field: 'genType',
component: 'NSelect',
label: '生成类型',
componentProps: {
placeholder: '请选择生成类型',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'varName',
component: 'NInput',
label: '实体命名',
componentProps: {
placeholder: '请输入实体命名',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'status',
component: 'NSelect',
label: '生成状态',
componentProps: {
placeholder: '请选择状态码',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
const router = useRouter();
const showModal = ref(false);
const formBtnLoading = ref(false);
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref<any>();
function goHome() {
router.push('/');
const rules = {
varName: {
required: true,
trigger: ['blur', 'input'],
message: '实体命名不能为空,首字母大写',
},
};
const actionColumn = reactive({
width: 220,
title: '操作',
key: 'action',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '生成配置',
onClick: handleEdit.bind(null, record),
},
{
label: '删除',
onClick: handleDelete.bind(null, record),
},
],
});
},
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
function onCheckedRow(rowKeys) {
batchDeleteDisabled.value = rowKeys.length <= 0;
checkedIds.value = rowKeys;
}
function handleDelete(record: Recordable) {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record).then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function batchDelete() {
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
const loadDataTable = async (res) => {
mapWidth();
return await List({ ...res, ...searchFormRef.value?.formModel });
};
function reloadTable() {
actionRef.value.reload();
}
function handleEdit(record: Recordable) {
router.push({ name: 'develop_code_deploy', params: { id: record.id } });
}
function handleSubmit(_values: Recordable) {
reloadTable();
}
function handleReset(_values: Recordable) {
reloadTable();
}
function addTable() {
showModal.value = true;
formParams.value = newState(null);
}
function confirmForm(e) {
e.preventDefault();
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
console.log('formParams:' + JSON.stringify(formParams.value));
Edit(formParams.value).then((res) => {
message.success('生成成功,正在前往配置');
setTimeout(() => {
showModal.value = false;
router.push({ name: 'develop_code_deploy', params: { id: res.id } });
});
});
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
const dialogWidth = ref('50%');
function mapWidth() {
let val = document.body.clientWidth;
const def = 840; // 默认宽度
if (val < def) {
dialogWidth.value = '100%';
} else {
dialogWidth.value = def + 'px';
}
return dialogWidth.value;
}
onBeforeMount(async () => {
await loadSelect();
});
const loadSelect = async () => {
selectList.value = await Selects({});
for (const item of schemas.value) {
switch (item.field) {
case 'status':
item.componentProps.options = selectList.value.status;
break;
case 'genType':
item.componentProps.options = selectList.value.genType;
break;
}
}
};
const tablesLoading = ref(false);
// 处理选项更新
async function handleDbUpdateValue(
value: string | number | Array<string | number> | null,
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
) {
tablesLoading.value = true;
await loadTableSelect(value);
tablesLoading.value = false;
}
async function loadTableSelect(value) {
selectList.value.tables = await TableSelect({ name: value });
}
function handleTableUpdateValue(value, option) {
formParams.value.varName = option?.defVarName as string;
formParams.value.daoName = option?.daoName as string;
formParams.value.tableComment = option?.defTableComment as string;
}
</script>
<style lang="less" scoped>
.page-container {
width: 100%;
border-radius: 4px;
padding: 50px 0;
height: 60vh;
.text-center {
h1 {
color: #666;
padding: 20px 0;
}
}
img {
width: 350px;
margin: 0 auto;
}
}
</style>
<style lang="less" scoped></style>

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -223,7 +224,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
@@ -236,7 +237,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,7 +247,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
@@ -259,7 +260,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -223,7 +224,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
@@ -236,7 +237,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,7 +247,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
@@ -259,7 +260,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -169,7 +170,6 @@
];
const router = useRouter();
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
@@ -218,25 +218,19 @@
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,20 +240,15 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -273,18 +262,15 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
router.push({ name: 'log_view', params: { id: record.id } });
}
function handleSubmit(values: Recordable) {
console.log(values);
formParams.value = values;
reloadTable();
}
function handleReset(values: Recordable) {
console.log(values);
formParams.value = {};
reloadTable();
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -223,7 +224,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
@@ -236,7 +237,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,7 +247,7 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
@@ -259,7 +260,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -169,7 +170,6 @@
];
const router = useRouter();
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
@@ -207,7 +207,6 @@
});
function onCheckedRow(rowKeys) {
console.log(rowKeys);
if (rowKeys.length > 0) {
batchDeleteDisabled.value = false;
} else {
@@ -218,25 +217,19 @@
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,11 +239,10 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
@@ -259,7 +251,7 @@
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -273,12 +265,10 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
router.push({ name: 'sms_view', params: { id: record.id } });
}
function handleSubmit(values: Recordable) {
console.log(values);
formParams.value = values;
reloadTable();
}

View File

@@ -17,7 +17,11 @@
:rules="rules"
>
<n-form-item path="username">
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
<n-input
@keyup.enter="handleSubmit"
v-model:value="formInline.username"
placeholder="请输入用户名"
>
<template #prefix>
<n-icon size="18" color="#808695">
<PersonOutline />
@@ -27,6 +31,7 @@
</n-form-item>
<n-form-item path="password">
<n-input
@keyup.enter="handleSubmit"
v-model:value="formInline.password"
type="password"
showPasswordOn="click"
@@ -140,8 +145,8 @@
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
message.success('登录成功,即将进入系统');
if (route.name === LOGIN_NAME) {
router.replace('/');
} else router.replace(toPath);
await router.replace('/');
} else await router.replace(toPath);
} else {
message.info(msg || '登录失败');
}

View File

@@ -25,7 +25,6 @@
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import { OnlineList, Offline } from '@/api/monitor/monitor';
import { columns } from './columns';
import { useRouter } from 'vue-router';
const dialog = useDialog();
const schemas: FormSchema[] = [
@@ -43,8 +42,6 @@
},
];
const router = useRouter();
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
@@ -62,10 +59,6 @@
return h(TableAction as any, {
style: 'button',
actions: [
// {
// label: '查看详情',
// onClick: handleEdit.bind(null, record),
// },
{
label: '强制退出',
onClick: handleDelete.bind(null, record),
@@ -82,25 +75,19 @@
});
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要强制退出该用户?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Offline(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Offline(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -113,19 +100,12 @@
actionRef.value.reload();
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
router.push({ name: 'serve_log_view', params: { id: record.id } });
}
function handleSubmit(values: Recordable) {
console.log(values);
formParams.value = values;
reloadTable();
}
function handleReset(values: Recordable) {
console.log(values);
function handleReset(_values: Recordable) {
formParams.value = {};
reloadTable();
}

View File

@@ -7,6 +7,7 @@
</BasicForm>
<BasicTable
:openChecked="true"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
@@ -169,7 +170,6 @@
];
const router = useRouter();
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref({});
@@ -218,25 +218,19 @@
}
function handleDelete(record: Recordable) {
console.log('点击了删除', record);
dialog.warning({
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete(record)
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
// message.error(e.message ?? '操作失败');
});
Delete(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -246,20 +240,15 @@
title: '警告',
content: '你确定要删除?',
positiveText: '确定',
negativeText: '不确定',
negativeText: '取消',
onPositiveClick: () => {
Delete({ id: checkedIds.value })
.then((_res) => {
console.log('_res:' + JSON.stringify(_res));
message.success('操作成功');
reloadTable();
})
.catch((e: Error) => {
message.error(e.message ?? '操作失败');
});
Delete({ id: checkedIds.value }).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('不确定');
// message.error('取消');
},
});
}
@@ -273,18 +262,15 @@
}
function handleEdit(record: Recordable) {
console.log('点击了编辑', record);
router.push({ name: 'serve_log_view', params: { id: record.id } });
}
function handleSubmit(values: Recordable) {
console.log(values);
formParams.value = values;
reloadTable();
}
function handleReset(values: Recordable) {
console.log(values);
function handleReset(_values: Recordable) {
formParams.value = {};
reloadTable();
}

Some files were not shown because too many files have changed in this diff Show More