This commit is contained in:
孟帅
2023-02-23 17:53:04 +08:00
parent 7cf1b8ce8e
commit 61d0988d2c
402 changed files with 18340 additions and 35547 deletions

View File

@@ -1 +1 @@
## HotGo 管理后台前端
## HotGo 管理web前端

24258
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "hotgo",
"version": "2.1.3",
"version": "2.2.10",
"author": {
"name": "MengShuai",
"email": "133814250@qq.com",
@@ -45,6 +45,7 @@
"naive-ui": "^2.34.3",
"node-sass": "^7.0.3",
"pinia": "^2.0.14",
"qrcode.vue": "3.3.3",
"qs": "^6.10.3",
"quill-image-uploader": "^1.2.4",
"quill-magic-url": "^4.2.0",

View File

@@ -0,0 +1,17 @@
import { http } from '@/utils/http/axios';
export function getConfig(params) {
return http.request({
url: '/hgexample/config/get',
method: 'get',
params,
});
}
export function updateConfig(params) {
return http.request({
url: '/hgexample/config/update',
method: 'post',
params,
});
}

View File

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

View File

@@ -0,0 +1,64 @@
import { http } from '@/utils/http/axios';
export function List(params) {
return http.request({
url: '/addons/list',
method: 'get',
params,
});
}
export function View(params) {
return http.request({
url: '/addons/view',
method: 'GET',
params,
});
}
export function Status(params) {
return http.request({
url: '/addons/status',
method: 'POST',
params,
});
}
export function Selects(params) {
return http.request({
url: '/addons/selects',
method: 'get',
params,
});
}
export function Build(params) {
return http.request({
url: '/addons/build',
method: 'post',
params,
});
}
export function Install(params) {
return http.request({
url: '/addons/install',
method: 'post',
params,
});
}
export function Upgrade(params) {
return http.request({
url: '/addons/upgrade',
method: 'post',
params,
});
}
export function UnInstall(params) {
return http.request({
url: '/addons/uninstall',
method: 'post',
params,
});
}

View File

@@ -17,6 +17,7 @@ export function getConfig() {
return http.request({
url: ApiEnum.SiteConfig,
method: 'get',
headers: { hostname: location.hostname },
});
}

View File

@@ -1,13 +1,13 @@
<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 v-bind="$props" :value="formValue" :style="{ width: '70%' }" />
</n-input-group>
</div>
</template>

View File

@@ -89,7 +89,7 @@
import { ResultEnum } from '@/enums/httpEnum';
import componentSetting from '@/settings/componentSetting';
import { useGlobSetting } from '@/hooks/setting';
import { isArray, isJsonString, isNullOrUnDef } from '@/utils/is';
import { isArray, isJsonString, isNullOrUnDef, isUrl } from '@/utils/is';
import { getFileExt } from '@/utils/urlUtils';
import { errorImg } from '@/utils/hotgo';
const globSetting = useGlobSetting();
@@ -217,7 +217,7 @@
//组装完整图片地址
function getImgUrl(url: string): string {
const { imgUrl } = globSetting;
return /(^http|https:\/\/)/g.test(url) ? url : `${imgUrl}${url}`;
return isUrl(url) ? url : `${imgUrl}${url}`;
}
function checkFileType(map: string[], fileType: string) {

View File

@@ -0,0 +1,34 @@
/**
* v-copy
* 复制某个值至剪贴板
* 接收参数string类型/Ref<string>类型/Reactive<string>类型
*/
import type { Directive, DirectiveBinding } from 'vue';
import { useMessage } from 'naive-ui';
interface ElType extends HTMLElement {
copyData: string | number;
__handleClick__: any;
}
const copy: Directive = {
mounted(el: ElType, binding: DirectiveBinding) {
el.copyData = binding.value;
el.addEventListener('click', handleClick);
},
updated(el: ElType, binding: DirectiveBinding) {
el.copyData = binding.value;
},
beforeUnmount(el: ElType) {
el.removeEventListener('click', el.__handleClick__);
},
};
function handleClick(this: any) {
const input = document.createElement('input');
input.value = this.copyData.toLocaleString();
document.body.appendChild(input);
input.select();
document.execCommand('Copy');
document.body.removeChild(input);
console.log('复制成功', this.copyData);
}
export default copy;

View File

@@ -0,0 +1,31 @@
/**
* v-debounce
* 按钮防抖指令可自行扩展至input
* 接收参数function类型
*/
import type { Directive, DirectiveBinding } from "vue";
interface ElType extends HTMLElement {
__handleClick__: () => any;
}
const debounce: Directive = {
mounted(el: ElType, binding: DirectiveBinding) {
if (typeof binding.value !== "function") {
throw "callback must be a function";
}
let timer: NodeJS.Timeout | null = null;
el.__handleClick__ = function () {
if (timer) {
clearInterval(timer);
}
timer = setTimeout(() => {
binding.value();
}, 500);
};
el.addEventListener("click", el.__handleClick__);
},
beforeUnmount(el: ElType) {
el.removeEventListener("click", el.__handleClick__);
}
};
export default debounce;

View File

@@ -0,0 +1,49 @@
/*
需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。
思路:
1、设置需要拖拽的元素为absolute其父元素为relative。
2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
4、鼠标松开(onmouseup)时完成一次拖拽
使用:在 Dom 上加上 v-draggable 即可
<div class="dialog-model" v-draggable></div>
*/
import type { Directive } from "vue";
interface ElType extends HTMLElement {
parentNode: any;
}
const draggable: Directive = {
mounted: function (el: ElType) {
el.style.cursor = "move";
el.style.position = "absolute";
el.onmousedown = function (e) {
let disX = e.pageX - el.offsetLeft;
let disY = e.pageY - el.offsetTop;
document.onmousemove = function (e) {
let x = e.pageX - disX;
let y = e.pageY - disY;
let maxX = el.parentNode.offsetWidth - el.offsetWidth;
let maxY = el.parentNode.offsetHeight - el.offsetHeight;
if (x < 0) {
x = 0;
} else if (x > maxX) {
x = maxX;
}
if (y < 0) {
y = 0;
} else if (y > maxY) {
y = maxY;
}
el.style.left = x + "px";
el.style.top = y + "px";
};
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null;
};
};
}
};
export default draggable;

View File

@@ -0,0 +1,41 @@
/*
需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。
思路:
1、第一次点击立即调用方法并禁用按钮等延迟结束再次激活按钮
2、将需要触发的方法绑定在指令上
使用:给 Dom 加上 v-throttle 及回调函数即可
<button v-throttle="debounceClick">节流提交</button>
*/
import type { Directive, DirectiveBinding } from "vue";
interface ElType extends HTMLElement {
__handleClick__: () => any;
disabled: boolean;
}
const throttle: Directive = {
mounted(el: ElType, binding: DirectiveBinding) {
if (typeof binding.value !== "function") {
throw "callback must be a function";
}
let timer: NodeJS.Timeout | null = null;
el.__handleClick__ = function () {
if (timer) {
clearTimeout(timer);
}
if (!el.disabled) {
el.disabled = true;
binding.value();
timer = setTimeout(() => {
el.disabled = false;
}, 1000);
}
};
el.addEventListener("click", el.__handleClick__);
},
beforeUnmount(el: ElType) {
el.removeEventListener("click", el.__handleClick__);
}
};
export default throttle;

View File

@@ -1,6 +1,10 @@
import { App } from 'vue';
import { permission } from '@/directives/permission';
import copy from '@/directives/copy';
import debounce from '@/directives/debounce';
import throttle from '@/directives/throttle';
import draggable from '@/directives/draggable';
/**
* 注册全局自定义指令
@@ -9,4 +13,12 @@ import { permission } from '@/directives/permission';
export function setupDirectives(app: App) {
// 权限控制指令(演示)
app.directive('permission', permission);
// 复制指令
app.directive('copy', copy);
// 防抖指令
app.directive('debounce', debounce);
// 节流指令
app.directive('throttle', throttle);
// 拖拽指令
app.directive('draggable', draggable);
}

View File

@@ -45,6 +45,7 @@ export const routerGenerator = (routerMap, parent?): any[] => {
// Recursion
currentRouter.children = routerGenerator(item.children, currentRouter);
}
return currentRouter;
});
};

View File

@@ -35,6 +35,12 @@ export interface UserInfoState {
lastLoginIp: string;
}
export interface ConfigState {
domain: string;
version: string;
wsAddr: string;
}
export interface IUserState {
token: string;
username: string;
@@ -42,7 +48,7 @@ export interface IUserState {
avatar: string;
permissions: any[];
info: UserInfoState | null;
config: any;
config: ConfigState | null;
}
export const useUserStore = defineStore({
@@ -75,7 +81,7 @@ export const useUserStore = defineStore({
getUserInfo(): UserInfoState | null {
return this.info;
},
getConfig(): object {
getConfig(): ConfigState | null {
return this.config;
},
},
@@ -98,7 +104,7 @@ export const useUserStore = defineStore({
setUserInfo(info: UserInfoState | null) {
this.info = info;
},
setConfig(config) {
setConfig(config: ConfigState | null) {
this.config = config;
},
// 登录

17
web/src/utils/icons.ts Normal file
View File

@@ -0,0 +1,17 @@
import { Component } from 'vue';
import { renderIcon } from '@/utils/index';
import * as antdIcons from '@vicons/antd';
// import * as x5Icons from '@vicons/ionicons5';
export const Icons = {};
for (const element of Object.keys(antdIcons)) {
Icons[element] = renderIcon(antdIcons[element]);
}
// for (const element of Object.keys(x5Icons)) {
// constantRouterIcon[element] = renderIcon(x5Icons[element]);
// }
export function getIconComponent(name: string): Component {
return Icons[name];
}

View File

@@ -15,8 +15,8 @@ export const renderTooltip = (trigger, content) => {
/**
* render 图标
* */
export function renderIcon(icon) {
return () => h(NIcon, null, { default: () => h(icon) });
export function renderIcon(icon, props = null) {
return () => h(NIcon, props, { default: () => h(icon) });
}
/**

View File

@@ -138,3 +138,7 @@ export function isNullOrUnDef(val: unknown): val is null | undefined {
export function isLetterBegin(str) {
return /^[A-z]/.test(str);
}
export function isUrl(url: string): boolean {
return /(^http|https:\/\/)/g.test(url);
}

View File

@@ -2,7 +2,6 @@ import { SocketEnum } from '@/enums/socketEnum';
import { notificationStoreWidthOut } from '@/store/modules/notification';
import { useUserStoreWidthOut } from '@/store/modules/user';
import { TABS_ROUTES } from '@/store/mutation-types';
import { MessageRow } from '@/enums/systemMessageEnum';
import { isJsonString } from '@/utils/is';
let socket: WebSocket;
@@ -103,7 +102,7 @@ export default (onMessage: Function) => {
if (useUserStore.token === '') {
throw new Error('用户未登录,稍后重试...');
}
socket = new WebSocket(useUserStore.config.wsAddr + '?authorization=' + useUserStore.token);
socket = new WebSocket(useUserStore.config?.wsAddr + '?authorization=' + useUserStore.token);
init();
} catch (e) {
console.log('createSocket err:' + e);

View File

@@ -22,7 +22,7 @@
</n-descriptions-item>
<n-descriptions-item label="文档地址">
<div class="flex items-center">
<a href="https://github.com/bufanyun/hotgo" class="py-2" target="_blank"
<a href="https://github.com/bufanyun/hotgo/tree/v2.0/docs" class="py-2" target="_blank"
>查看文档地址</a
>
</div>

View File

@@ -0,0 +1,74 @@
<template>
<div>
<n-spin :show="show" description="请稍候...">
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="测试参数" path="basicTest">
<n-input v-model:value="formValue.basicTest" placeholder="请输入测试参数" />
<template #feedback>
这是一个测试参数每个插件都可以有独立的配置项可以按需添加</template
>
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-spin>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { useMessage } from 'naive-ui';
import { getConfig, updateConfig } from '@/api/addons/hgexample/config';
const group = ref('basic');
const show = ref(false);
const rules = {
basicTest: {
required: true,
message: '请输入测试参数',
trigger: 'blur',
},
};
const formRef: any = ref(null);
const message = useMessage();
const formValue = ref({
basicTest: 'HotGo',
});
function formSubmit() {
formRef.value.validate((errors) => {
if (!errors) {
updateConfig({ group: group.value, list: formValue.value }).then((_res) => {
message.success('更新成功');
load();
});
} else {
message.error('验证失败,请填写完整信息');
}
});
}
onMounted(() => {
load();
});
function load() {
show.value = true;
new Promise((_resolve, _reject) => {
getConfig({ group: group.value })
.then((res) => {
formValue.value = res.list;
})
.finally(() => {
show.value = false;
});
});
}
</script>

View File

@@ -0,0 +1,82 @@
<template>
<div>
<n-grid cols="24 300:1 600:24" :x-gap="24">
<n-grid-item span="6">
<n-card :bordered="false" size="small" class="proCard">
<n-thing
class="thing-cell"
v-for="item in typeTabList"
:key="item.key"
:class="{ 'thing-cell-on': type === item.key }"
@click="switchType(item)"
>
<template #header>{{ item.name }}</template>
<template #description>{{ item.desc }}</template>
</n-thing>
</n-card>
</n-grid-item>
<n-grid-item span="18">
<n-card :bordered="false" size="small" :title="typeTitle" class="proCard">
<BasicSetting v-if="type === 1" />
</n-card>
</n-grid-item>
</n-grid>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue';
import BasicSetting from './BasicSetting.vue';
const typeTabList = [
{
name: '基本设置',
desc: '系统常规设置',
key: 1,
},
];
export default defineComponent({
components: {
BasicSetting,
},
setup() {
const state = reactive({
type: 1,
typeTitle: '基本设置',
});
function switchType(e) {
state.type = e.key;
state.typeTitle = e.name;
}
return {
...toRefs(state),
switchType,
typeTabList,
};
},
});
</script>
<style lang="less" scoped>
.thing-cell {
margin: 0 -16px 10px;
padding: 5px 16px;
&:hover {
background: #f3f3f3;
cursor: pointer;
}
}
.thing-cell-on {
background: #f0faff;
color: #2d8cf0;
::v-deep(.n-thing-main .n-thing-header .n-thing-header__title) {
color: #2d8cf0;
}
&:hover {
background: #f0faff;
}
}
</style>

View File

@@ -0,0 +1,45 @@
<template>
<n-form class="py-4">
<n-form-item label="测试入口" path="index">
<n-input-group>
<n-input placeholder="请输入" :default-value="url" :disabled="true" />
<n-button v-copy="url" type="primary" @click="copy"> 复制链接 </n-button>
</n-input-group>
</n-form-item>
<n-form-item label="二维码" path="index">
<div class="text-center">
<qrcode-vue :value="url" :size="220" class="canvas" style="margin: 0 auto" />
</div>
</n-form-item>
</n-form>
</template>
<script lang="ts" setup>
import { useUserStoreWidthOut } from '@/store/modules/user';
import QrcodeVue from 'qrcode.vue';
import { useMessage } from 'naive-ui';
const message = useMessage();
interface Props {
path: string;
}
const props = withDefaults(defineProps<Props>(), {
path: '',
});
const copy = () => {
message.success('复制成功');
};
const useUserStore = useUserStoreWidthOut();
const url = useUserStore.config?.domain + props.path;
</script>
<style scoped>
::v-deep(.card-tabs .n-tabs-nav--bar-type) {
padding-left: 4px;
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<div>
<n-card title="应用入口" style="margin-bottom: 16px">
<n-tabs type="line" animated>
<n-tab-pane name="admin" tab="后台API">
<Form path="/admin/hgexample/index/test?name=HotGo" />
</n-tab-pane>
<n-tab-pane name="api" tab="前台API">
<Form path="/api/hgexample/index/test?name=HotGo" />
</n-tab-pane>
<n-tab-pane name="home" tab="前台页面">
<Form path="/home/hgexample/index/test?name=HotGo" />
</n-tab-pane>
<n-tab-pane name="websocket" tab="Websocket">
<Form path="/socket/hgexample/index/test?name=HotGo" />
</n-tab-pane>
</n-tabs>
</n-card>
</div>
</template>
<script lang="ts" setup>
import Form from './form.vue';
</script>
<style scoped>
::v-deep(.card-tabs .n-tabs-nav--bar-type) {
padding-left: 4px;
}
</style>

View File

@@ -152,7 +152,7 @@
<script lang="ts" setup>
import { onMounted, ref, computed, watch } from 'vue';
import { rules, options, State, newState } from './model';
import { Edit, MaxSort } from '@/api/test';
import { Edit, MaxSort } from '@/api/addons/hgexample/table';
import { useMessage } from 'naive-ui';
import { adaModalWidth } from '@/utils/hotgo';
import DatePicker from '@/components/DatePicker/datePicker.vue';

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="普通表格演示">
<n-card :bordered="false" title="表格例子">
这里提供了一些常用的普通表格组件的用法和表单组件的例子你可能会需要
</n-card>
</div>
@@ -77,7 +77,7 @@
import { useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form/index';
import { Delete, List, Status, Export } from '@/api/test';
import { Delete, List, Status, Export } from '@/api/addons/hgexample/table';
import { State, columns, schemas, options, newState } from './model';
import { DeleteOutlined, PlusOutlined, ExportOutlined } from '@vicons/antd';
import { useRouter } from 'vue-router';
@@ -186,7 +186,7 @@
}
function handleView(record: Recordable) {
router.push({ name: 'test_view', params: { id: record.id } });
router.push({ name: 'table_view', params: { id: record.id } });
}
function handleEdit(record: Recordable) {

View File

@@ -3,7 +3,7 @@ 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/test';
import { Switch } from '@/api/addons/hgexample/table';
import { isNullObject } from '@/utils/is';
import { getFileExt } from '@/utils/urlUtils';
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';

View File

@@ -109,7 +109,7 @@
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useMessage } from 'naive-ui';
import { View } from '@/api/test';
import { View } from '@/api/addons/hgexample/table';
import { newState, options } from './model';
import { getOptionLabel, getOptionTag } from '@/utils/hotgo';
import { getFileExt } from '@/utils/urlUtils';

View File

@@ -3,7 +3,6 @@ import { NTag } from 'naive-ui';
import { cloneDeep } from 'lodash-es';
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
import { Dicts } from '@/api/dict/dict';
import { schemas } from '@/views/test/model';
import { isNullObject } from '@/utils/is';
export const listColumns = [
@@ -100,18 +99,3 @@ export function newState(state: State | null): State {
export const options = ref<Options>({
sys_normal_disable: [],
});
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

@@ -87,7 +87,8 @@
<template v-else>
<div class="text-sn"> 总激活卡板 </div>
<div class="text-sn">
<CountTo :startVal="1" :endVal="saleroom.amount" /> <!-- prefix="¥"-->
<CountTo :startVal="1" :endVal="saleroom.amount" />
<!-- prefix="¥"-->
</div>
</template>
</div>
@@ -325,7 +326,6 @@
onMounted(async () => {
const data = await getConsoleInfo();
console.log('data:'+JSON.stringify(data))
visits.value = data.visits;
saleroom.value = data.saleroom;
orderLarge.value = data.orderLarge;

View File

@@ -0,0 +1,23 @@
import { cloneDeep } from 'lodash-es';
export const genInfoObj = {
label: '',
name: '',
group: 1,
version: 'v1.0.0',
brief: '',
description: '',
author: '',
};
export const selectListObj = {
groupType: [],
status: [],
};
export function newState(state) {
if (state !== null) {
return cloneDeep(state);
}
return cloneDeep(genInfoObj);
}

View File

@@ -0,0 +1,502 @@
<template>
<div>
<div class="n-layout-page-header">
<n-card :bordered="false" title="插件管理">
在这里可以管理和创建插件模板开发独立临时性工具类型的功能时推荐使用插件化开发
</n-card>
</div>
<n-card :bordered="false" class="proCard">
<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>
</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="label">
<n-input placeholder="请输入" v-model:value="formParams.label" />
<template #feedback>显示在插件列表中的标识. 不要超过20个字符</template>
</n-form-item>
<n-form-item label="插件包名" path="name">
<n-input placeholder="请输入" v-model:value="formParams.name" />
<template #feedback>
系统按照此名称创建和查找插件定义, 只能英文和下划线组成建议全部小写例如hgexample
</template>
</n-form-item>
<n-form-item label="功能分组" path="group">
<n-select
placeholder="请选择"
:options="selectList.groupType"
v-model:value="formParams.group"
:on-update:value="onUpdateValueGroup"
/>
</n-form-item>
<n-form-item label="版本" path="version">
<n-input placeholder="请输入" v-model:value="formParams.version" />
<template #feedback>此版本号用于插件的版本更新</template>
</n-form-item>
<n-form-item label="简介" path="brief">
<n-input placeholder="请输入" v-model:value="formParams.brief" />
</n-form-item>
<n-form-item label="使用说明" path="description">
<n-input
type="textarea"
placeholder="使用说明"
v-model:value="formParams.description"
/>
<template #feedback>详细介绍插件的功能和使用方法</template>
</n-form-item>
<n-form-item label="作者" path="author">
<n-input placeholder="请输入" v-model:value="formParams.author" />
</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>
</div>
</template>
<script lang="ts" setup>
import { h, onBeforeMount, reactive, ref } from 'vue';
import {
NIcon,
NTag,
NIconWrapper,
useMessage,
NImage,
useDialog,
useNotification,
} from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import { List, Selects, Build, UnInstall, Install, Upgrade } from '@/api/develop/addons';
import { PlusOutlined } from '@vicons/antd';
import { newState } from '@/views/develop/addons/components/model';
import { errorImg } from '@/utils/hotgo';
import { isUrl } from '@/utils/is';
import { getIconComponent } from '@/utils/icons';
const selectList = ref({
groupType: [],
status: [],
});
const columns = [
{
title: '图标',
key: 'logo',
width: 80,
render(row) {
if (isUrl(row.logo)) {
return h(NImage, {
width: 48,
height: 48,
src: row.logo,
onError: errorImg,
style: {
width: '48px',
height: '48px',
'max-width': '100%',
'max-height': '100%',
},
});
} else {
return h(
NIconWrapper,
{
size: 48,
borderRadius: 8,
},
{
default: () =>
h(
NIcon,
{
size: 36,
style: {
marginTop: '-8px',
},
},
{
default: () => h(getIconComponent(row.logo)),
}
),
}
);
}
},
},
{
title: '模块名称',
key: 'name',
width: 120,
render(row) {
return h('div', {
innerHTML:
'<div >' + row.label + '<br><span style="opacity: 0.8;">' + row.name + '</span></div>',
});
},
},
{
title: '作者',
key: 'author',
width: 100,
},
{
title: '分组',
key: 'groupName',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'info',
bordered: false,
},
{
default: () => row.groupName,
}
);
},
width: 120,
},
{
title: '简介',
key: 'brief',
render(row) {
return row.brief;
},
width: 180,
},
{
title: '详细描述',
key: 'description',
width: 300,
render(row) {
return h('p', { id: 'app' }, [
h('div', {
innerHTML: '<div style="white-space: pre-wrap">' + row.description + '</div>',
}),
]);
},
},
{
title: '版本',
key: 'version',
width: 100,
},
];
const checkedIds = ref([]);
const searchFormRef = ref<any>();
const schemas = ref<FormSchema[]>([
{
field: 'name',
component: 'NInput',
label: '模块名称',
componentProps: {
placeholder: '请输入模块名称或标签',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'group',
component: 'NSelect',
label: '分组',
componentProps: {
placeholder: '请选择分组',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '安装状态',
componentProps: {
placeholder: '请选择状态',
options: [],
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
]);
const notification = useNotification();
const dialog = useDialog();
const showModal = ref(false);
const formBtnLoading = ref(false);
const formRef: any = ref(null);
const message = useMessage();
const actionRef = ref();
const formParams = ref<any>();
const rules = {};
const actionColumn = reactive({
width: 220,
title: '操作',
key: 'action',
// fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '安装模块',
onClick: handleInstall.bind(null, record),
ifShow: () => {
return record.installStatus !== 1;
},
},
{
type: 'warning',
label: '更新配置',
onClick: handleUpgrade.bind(null, record),
ifShow: () => {
return record.installStatus === 1;
},
},
{
type: 'error',
label: '卸载模块',
onClick: handleUnInstall.bind(null, record),
ifShow: () => {
return record.installStatus === 1;
},
},
],
});
},
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
schemas,
});
function onCheckedRow(rowKeys) {
checkedIds.value = rowKeys;
}
const loadDataTable = async (res) => {
mapWidth();
return await List({ ...res, ...searchFormRef.value?.formModel });
};
function reloadTable() {
actionRef.value.reload();
}
function handleInstall(record: Recordable) {
dialog.info({
title: '提示',
content: '你确定要安装 【' + record.label + '】 模块?安装成功后需要重启服务才能生效!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Install(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function handleUpgrade(record: Recordable) {
dialog.warning({
title: '提示',
content: '你确定要更新 【' + record.label + '】 模块?更新成功后需要重启服务才能生效!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
Upgrade(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
function handleUnInstall(record: Recordable) {
dialog.error({
title: '提示',
content: '你确定要卸载 【' + record.label + '】 模块?卸载成功后需要重启服务才能生效!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
UnInstall(record).then((_res) => {
message.success('操作成功');
reloadTable();
});
},
onNegativeClick: () => {
// message.error('取消');
},
});
}
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));
Build(formParams.value).then((_res) => {
showModal.value = false;
buildSuccessNotify();
});
} 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 'group':
item.componentProps.options = selectList.value.groupType;
break;
}
}
};
function onUpdateValueGroup(value) {
formParams.value.group = value;
}
function buildSuccessNotify() {
let count = 10;
const n = notification.success({
title: '插件构建成功',
content: `如果你使用的热编译,页面将在 ${count} 秒后自动刷新插件列表即可生效。否则请手动重启服务后刷新页面!`,
duration: 10000,
closable: false,
onAfterEnter: () => {
const minusCount = () => {
count--;
n.content = `如果你使用的热编译,页面将在 ${count} 秒后自动刷新插件列表即可生效。否则请手动重启服务后刷新页面!`;
if (count > 0) {
window.setTimeout(minusCount, 1000);
}
};
window.setTimeout(minusCount, 1000);
},
onAfterLeave: () => {
location.reload();
},
});
}
</script>
<style lang="less" scoped></style>

View File

@@ -1,306 +1,311 @@
<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-spin :show="bodyShow" description="请稍候...">
<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-space>
</n-col>
</n-row>
</n-form>
</n-card>
</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>如果你选择的表已经生成过dao相关代码可以忽略</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>如果是插件模块勾选后也会自动在对应插件下运行service相关代码生成</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>
</n-spin>
</div>
</template>
@@ -316,7 +321,9 @@
import { cloneDeep } from 'lodash-es';
import { isLetterBegin } from '@/utils/is';
const timer = ref();
const formRef = ref<FormInst | null>(null);
const bodyShow = ref(true);
const tablesLoading = ref(false);
const columnsLoading = ref(false);
const linkColumnsLoading = ref(false);
@@ -364,12 +371,16 @@
});
onMounted(() => {
setTimeout(async function () {
await instLoad();
// 切换tab时会导致选项被清空这里重新进行加载
await loadLinkColumnsOption();
await loadMenuTreeOption();
}, 500);
timer.value = setInterval(async () => {
if (props.value.id > 0) {
clearTimeout(timer.value);
await instLoad();
// 切换tab时会导致选项被清空这里重新进行加载
await loadLinkColumnsOption();
await loadMenuTreeOption();
bodyShow.value = false;
}
}, 30);
});
const loadMenuTreeOption = async () => {

View File

@@ -38,6 +38,7 @@ export const genInfoObj = {
tableComment: '',
daoName: '',
masterColumns: [],
addonName: null,
status: 2,
createdAt: '',
updatedAt: '',

View File

@@ -82,7 +82,7 @@
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useDialog, useMessage } from 'naive-ui';
import { useDialog, useMessage, useNotification } from 'naive-ui';
import BaseInfo from './components/BaseInfo.vue';
import EditMasterCell from './components/EditMasterCell.vue';
import EditSlaveCell from './components/EditSlaveCell.vue';
@@ -110,6 +110,7 @@
const formBtnLoading = ref(false);
const previewModel = ref<any>();
const dialog = useDialog();
const notification = useNotification();
onMounted(async () => {
if (genId < 1 && props.genId < 1) {
@@ -181,15 +182,6 @@
}
}
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;
@@ -218,10 +210,7 @@
formBtnLoading.value = true;
Build(genInfo.value)
.then((_res) => {
setTimeout(function () {
location.reload();
}, 1500);
message.success('生成提交成功,即将刷新页面..');
buildSuccessNotify();
})
.finally(() => {
formBtnLoading.value = false;
@@ -249,6 +238,29 @@
},
});
}
function buildSuccessNotify() {
let count = 6;
const n = notification.success({
title: '生成提交成功',
content: `如果你使用的热编译,页面将在 ${count} 秒后自动刷新即可生效。否则请手动重启服务后刷新页面!`,
duration: 6000,
closable: false,
onAfterEnter: () => {
const minusCount = () => {
count--;
n.content = `如果你使用的热编译,页面将在 ${count} 秒后自动刷新即可生效。否则请手动重启服务后刷新页面!`;
if (count > 0) {
window.setTimeout(minusCount, 1000);
}
};
window.setTimeout(minusCount, 1000);
},
onAfterLeave: () => {
location.reload();
},
});
}
</script>
<style lang="less" scoped>
::v-deep(.alert-margin) {

View File

@@ -1,130 +1,145 @@
<template>
<n-card :bordered="false" class="proCard" title="代码生成">
<!-- <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"
<div>
<n-card :bordered="false" class="proCard" title="代码生成">
<BasicForm
@register="register"
@submit="handleSubmit"
@reset="handleReset"
ref="searchFormRef"
>
<n-form-item label="生成类型" path="genType">
<n-select
placeholder="请选择"
:options="selectList.genType"
v-model:value="formParams.genType"
:on-update:value="onUpdateValueGenType"
/>
</n-form-item>
<template #statusSlot="{ model, field }">
<n-input v-model:value="model[field]" />
</template>
</BasicForm>
<n-form-item label="生成模板" path="genTemplate">
<n-select
placeholder="请选择"
:options="genTemplateOptions"
v-model:value="formParams.genTemplate"
:onFocus="onFocusGenTemplate"
/>
</n-form-item>
<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-form-item
label="数据库"
path="dbName"
v-if="formParams.genType >= 10 && formParams.genType < 20"
<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-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="genType">
<n-select
placeholder="请选择"
:options="selectList.genType"
v-model:value="formParams.genType"
:on-update:value="onUpdateValueGenType"
/>
</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="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="varName">
<n-input placeholder="请输入" v-model:value="formParams.varName" />
</n-form-item>
</n-form>
<n-form-item label="生成模板" path="genTemplate">
<n-select
placeholder="请选择"
:options="genTemplateOptions"
v-model:value="formParams.genTemplate"
:onFocus="onFocusGenTemplate"
@update:value="handleGenTemplateUpdateValue"
/>
</n-form-item>
<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>
<n-form-item label="选择插件" path="addonName" v-show="selectAddon">
<n-select
placeholder="请选择"
:options="selectList.addons"
v-model:value="formParams.addonName"
/>
</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>
</div>
</template>
<script lang="ts" setup>
@@ -147,75 +162,57 @@
formRole: [],
dictMode: [],
whereMode: [],
addons: [],
});
const columns = [
{
title: '生成ID',
title: 'ID',
key: 'id',
width: 80,
width: 60,
},
{
title: '生成类型',
key: 'genType',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'info',
bordered: false,
},
{
default: () => getOptionLabel(selectList.value.genType, row.genType),
}
);
},
width: 180,
},
{
title: '生成模板',
title: '模板',
key: 'genTemplate',
render(row) {
return h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'default',
bordered: false,
},
{
default: () => row.genTemplateGroup,
}
);
return h('p', { id: 'app' }, [
h('div', {
innerHTML: '<div><p>' + row.genTemplateGroup + '</p></div>',
}),
h('div', {
innerHTML:
'<div><p>' + getOptionLabel(selectList.value.genType, row.genType) + '</p></div>',
}),
]);
},
width: 120,
},
{
title: '数据库',
key: 'dbName',
width: 280,
render(row) {
return h('p', { id: 'app' }, [
h('div', {
innerHTML: '<div><p>' + row.dbName + '</p></div>',
}),
h('div', {
innerHTML: '<div><p>' + row.tableName + '</p></div>',
}),
]);
},
},
{
title: '实体命名',
key: 'varName',
render(row) {
return row.varName;
},
width: 180,
},
{
title: '数据库',
key: 'dbName',
width: 150,
},
{
title: '数据表',
key: 'tableName',
width: 200,
width: 140,
},
{
title: '菜单名称',
key: 'tableComment',
width: 200,
width: 140,
},
{
title: '生成状态',
@@ -242,11 +239,6 @@
key: 'createdAt',
width: 180,
},
// {
// title: '更新时间',
// key: 'updatedAt',
// width: 180,
// },
];
const dialog = useDialog();
@@ -409,6 +401,7 @@
e.preventDefault();
formBtnLoading.value = true;
formRef.value.validate((errors) => {
console.log('formParams:' + JSON.stringify(formParams.value));
if (!errors) {
console.log('formParams:' + JSON.stringify(formParams.value));
Edit(formParams.value).then((res) => {
@@ -492,6 +485,12 @@
formParams.value.genType = value;
onFocusGenTemplate();
}
const selectAddon = ref(false);
function handleGenTemplateUpdateValue(value, option) {
formParams.value.genTemplate = value;
selectAddon.value = option.isAddon === true;
}
</script>
<style lang="less" scoped></style>

View File

@@ -1,61 +1,48 @@
<template>
<div>
<n-spin :show="show" description="请稍候...">
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
<n-grid-item>
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="支付宝姓名" path="name">
<n-input v-model:value="formValue.name" />
</n-form-item>
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="支付宝姓名" path="name">
<n-input v-model:value="formValue.name" />
</n-form-item>
<n-form-item label="支付宝账号" path="account ">
<n-input v-model:value="formValue.account" />
</n-form-item>
<n-form-item label="支付宝账号" path="account ">
<n-input v-model:value="formValue.account" />
</n-form-item>
<n-form-item label="支付宝收款码" path="payeeCode">
<UploadImage
:maxNumber="1"
:helpText="'请上传清晰有效的收款码图片大小不超过2M'"
v-model:value="formValue.payeeCode"
/>
</n-form-item>
<n-form-item label="支付宝收款码" path="payeeCode">
<UploadImage
:maxNumber="1"
:helpText="'请上传清晰有效的收款码图片大小不超过2M'"
v-model:value="formValue.payeeCode"
/>
</n-form-item>
<n-form-item label="登录密码" path="password">
<n-input
type="password"
v-model:value="formValue.password"
placeholder="请输入登录密码验证身份"
/>
</n-form-item>
<n-form-item label="登录密码" path="password">
<n-input
type="password"
v-model:value="formValue.password"
placeholder="请输入登录密码验证身份"
/>
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-grid-item>
</n-grid>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-spin>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, unref } from 'vue';
import { onMounted, ref } from 'vue';
import { useMessage } from 'naive-ui';
import UploadImage from '@/components/Upload/uploadImage.vue';
import { BasicUpload } from '@/components/Upload';
import { useGlobSetting } from '@/hooks/setting';
import { useUserStoreWidthOut } from '@/store/modules/user';
import { getUserInfo, updateMemberCash } from '@/api/system/user';
const show = ref(false);
const useUserStore = useUserStoreWidthOut();
const globSetting = useGlobSetting();
const { uploadUrl } = globSetting;
const uploadHeaders = reactive({
Authorization: useUserStore.token,
});
const rules = {
password: {
@@ -94,15 +81,6 @@
});
}
function uploadChange(list: string[]) {
// 单图模式,只需要第一个索引
if (list.length > 0) {
formValue.value.payeeCode = unref(list[0]);
} else {
formValue.value.payeeCode = unref('');
}
}
onMounted(() => {
load();
});

View File

@@ -57,13 +57,21 @@
</template>
<template #suffix> </template>
</n-input>
<img
style="width: 100px"
:src="codeBase64"
@click="refreshCode"
loading="lazy"
alt="点击获取"
/>
<n-loading-bar-provider
:to="loadingBarTargetRef"
container-style="position: absolute;"
>
<img
ref="loadingBarTargetRef"
style="width: 100px"
:src="codeBase64"
@click="refreshCode"
loading="lazy"
alt="点击获取"
/>
<loading-bar-trigger />
</n-loading-bar-provider>
</n-input-group>
</n-form-item>
<n-form-item class="default-color">
@@ -112,10 +120,10 @@
</template>
<script lang="ts" setup>
import { ref, unref, onMounted } from 'vue';
import { ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useUserStore } from '@/store/modules/user';
import { useMessage } from 'naive-ui';
import { useMessage, useLoadingBar } from 'naive-ui';
import { ResultEnum } from '@/enums/httpEnum';
import { PersonOutline, LockClosedOutline, LogoGithub, LogoFacebook } from '@vicons/ionicons5';
import { PageEnum } from '@/enums/pageEnum';
@@ -136,6 +144,8 @@
const loading = ref(false);
const autoLogin = ref(true);
const codeBase64 = ref('');
const loadingBar = useLoadingBar();
const loadingBarTargetRef = ref<undefined | HTMLElement>(undefined);
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
const formInline = ref<FormState>({
@@ -191,10 +201,12 @@
};
async function refreshCode() {
loadingBar.start();
const data = await GetCaptcha();
codeBase64.value = data.base64;
formInline.value.cid = data.cid;
formInline.value.code = '';
loadingBar.finish();
}
onMounted(() => {

View File

@@ -1,48 +1,54 @@
<template>
<n-card :content-style="{ padding: '10px' }" :header-style="{ padding: '5px' }" :segmented="true">
<template #header>
<n-skeleton text v-if="loading" width="60%" />
<template v-else>
<div class="flex items-center justify-between">
<span class="text-sm text-bold">{{ dataModel.title }}</span>
<n-icon style="font-size: 26px">
<div v-if="dataModel.iconClass === 'HardwareChip'">
<HardwareChip />
</div>
<div v-else-if="dataModel.iconClass === 'AppsSharp'">
<AppsSharp />
</div>
<div v-else-if="dataModel.iconClass === 'Analytics'">
<Analytics />
</div>
<div v-else-if="dataModel.iconClass === 'PieChart'">
<PieChart />
</div>
<div>
<n-card
:content-style="{ padding: '10px' }"
:header-style="{ padding: '5px' }"
:segmented="true"
>
<template #header>
<n-skeleton text v-if="loading" width="60%" />
<template v-else>
<div class="flex items-center justify-between">
<span class="text-sm text-bold">{{ dataModel.title }}</span>
<n-icon style="font-size: 26px">
<div v-if="dataModel.iconClass === 'HardwareChip'">
<HardwareChip />
</div>
<div v-else-if="dataModel.iconClass === 'AppsSharp'">
<AppsSharp />
</div>
<div v-else-if="dataModel.iconClass === 'Analytics'">
<Analytics />
</div>
<div v-else-if="dataModel.iconClass === 'PieChart'">
<PieChart />
</div>
<div v-else>
<Bookmark />
</div>
</n-icon>
<div v-else>
<Bookmark />
</div>
</n-icon>
</div>
</template>
</template>
<n-skeleton text v-if="loading" :repeat="6" />
<template v-else>
<div style="height: 130px" class="flex flex-col justify-between">
<div class="flex flex-col justify-center">
<span class="text-xxl">{{ dataModel.data }}</span>
</div>
<div class="flex flex-col justify-center flex-1">
<slot name="extra" :extra="dataModel.extra"></slot>
</div>
<div class="divide"></div>
<div class="flex items-center justify-between">
<span class="text-sm text-grey">{{ dataModel.bottomTitle }}</span>
<span class="text-sm text-grey">{{ dataModel.totalSum }}</span>
</div>
</div>
</template>
</template>
<n-skeleton text v-if="loading" :repeat="6" />
<template v-else>
<div style="height: 130px" class="flex flex-col justify-between">
<div class="flex flex-col justify-center">
<span class="text-xxl">{{ dataModel.data }}</span>
</div>
<div class="flex flex-col justify-center flex-1">
<slot name="extra" :extra="dataModel.extra"></slot>
</div>
<div class="divide"></div>
<div class="flex items-center justify-between">
<span class="text-sm text-grey">{{ dataModel.bottomTitle }}</span>
<span class="text-sm text-grey">{{ dataModel.totalSum }}</span>
</div>
</div>
</template>
</n-card>
</n-card>
</div>
</template>
<script lang="ts">

View File

@@ -1,88 +1,90 @@
<template>
<n-card
:content-style="{ padding: '10px' }"
:header-style="{ padding: '10px' }"
:segmented="true"
>
<template #header>
<n-skeleton text style="width: 50%" v-if="loading" />
<template v-else>
<div class="text-sm"> 实时网卡流量全部网卡</div>
<div>
<n-card
:content-style="{ padding: '10px' }"
:header-style="{ padding: '10px' }"
:segmented="true"
>
<template #header>
<n-skeleton text style="width: 50%" v-if="loading" />
<template v-else>
<div class="text-sm"> 实时网卡流量全部网卡</div>
</template>
</template>
</template>
<div class="chart-item-container">
<n-skeleton text v-if="loading" :repeat="10" />
<template v-else>
<n-grid responsive="screen" cols="1 s:2 m:4 l:4 xl:4 2xl:4" x-gap="5" y-gap="5">
<n-grid-item class="item-wrapper">
<n-card
:bordered="false"
:content-style="{ padding: '10px' }"
:header-style="{ padding: '5px' }"
:segmented="true"
>
<div class="text-number">{{ last.up }} KB</div>
<div class="title-text">
<n-badge :value="1" dot color="rgb(58, 104, 255)" />
上行
</div>
</n-card>
</n-grid-item>
<n-grid-item class="item-wrapper">
<n-card
:bordered="false"
:content-style="{ padding: '10px' }"
:header-style="{ padding: '5px' }"
:segmented="true"
>
<div class="text-number"> {{ last.down ?? 0 }} KB</div>
<div class="title-text">
<n-badge :value="1" dot color="rgb(241, 136, 136)" />
下行
</div>
</n-card>
</n-grid-item>
<n-grid-item class="item-wrapper">
<n-card
:bordered="false"
:content-style="{ padding: '10px' }"
:header-style="{ padding: '5px' }"
:segmented="true"
>
<div class="text-number">{{ last.bytesSent ?? 0 }}</div>
<div class="title-text"> 总发送</div>
</n-card>
</n-grid-item>
<n-grid-item class="item-wrapper">
<n-card
:bordered="false"
:content-style="{ padding: '10px' }"
:header-style="{ padding: '5px' }"
:segmented="true"
>
<div class="text-number">{{ last.bytesRecv ?? 0 }}</div>
<div class="title-text"> 总接收</div>
</n-card>
</n-grid-item>
</n-grid>
<div ref="fullYearSalesChart" class="chart-item"></div>
</template>
</div>
</n-card>
<div class="chart-item-container">
<n-skeleton text v-if="loading" :repeat="10" />
<template v-else>
<n-grid responsive="screen" cols="1 s:2 m:4 l:4 xl:4 2xl:4" x-gap="5" y-gap="5">
<n-grid-item class="item-wrapper">
<n-card
:bordered="false"
:content-style="{ padding: '10px' }"
:header-style="{ padding: '5px' }"
:segmented="true"
>
<div class="text-number">{{ last?.up }} KB</div>
<div class="title-text">
<n-badge :value="1" dot color="rgb(58, 104, 255)" />
上行
</div>
</n-card>
</n-grid-item>
<n-grid-item class="item-wrapper">
<n-card
:bordered="false"
:content-style="{ padding: '10px' }"
:header-style="{ padding: '5px' }"
:segmented="true"
>
<div class="text-number"> {{ last?.down ?? 0 }} KB</div>
<div class="title-text">
<n-badge :value="1" dot color="rgb(241, 136, 136)" />
下行
</div>
</n-card>
</n-grid-item>
<n-grid-item class="item-wrapper">
<n-card
:bordered="false"
:content-style="{ padding: '10px' }"
:header-style="{ padding: '5px' }"
:segmented="true"
>
<div class="text-number">{{ last?.bytesSent ?? 0 }}</div>
<div class="title-text"> 总发送</div>
</n-card>
</n-grid-item>
<n-grid-item class="item-wrapper">
<n-card
:bordered="false"
:content-style="{ padding: '10px' }"
:header-style="{ padding: '5px' }"
:segmented="true"
>
<div class="text-number">{{ last?.bytesRecv ?? 0 }}</div>
<div class="title-text"> 总接收</div>
</n-card>
</n-grid-item>
</n-grid>
<div ref="fullYearSalesChart" class="chart-item"></div>
</template>
</div>
</n-card>
</div>
</template>
<script lang="ts">
import useEcharts from '@/hooks/useEcharts';
import { defineComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { defineComponent, nextTick, onBeforeUnmount, ref, watch } from 'vue';
import { dispose, graphic } from 'echarts';
import { object } from 'vue-types';
export default defineComponent({
name: 'FullYearSalesChart',
props: {
dataModel: {
type: Array,
type: Array || object,
default: () => {
// eslint-disable-next-line vue/require-valid-default-prop
return {};
},
},
@@ -94,7 +96,7 @@
},
},
setup(props) {
const last = ref({
const last = ref<any>({
bytesSent: '0B',
bytesRecv: '0B',
down: '0',
@@ -198,7 +200,6 @@
const fullYearSalesChart = ref<HTMLDivElement | null>(null);
watch(props, (_newVal, _oldVal) => {
last.value = _newVal.dataModel[_newVal.dataModel.length - 1];
// console.log('last _newVal:' + JSON.stringify(last.value));
if (months.value.length < 10) {
for (let i = 0; i < _newVal.dataModel?.length; i++) {
@@ -227,7 +228,9 @@
useEcharts(fullYearSalesChart.value as HTMLDivElement).resize();
};
onBeforeUnmount(() => {
dispose(fullYearSalesChart.value as HTMLDivElement);
if (fullYearSalesChart.value !== null) {
dispose(fullYearSalesChart.value as HTMLDivElement);
}
});
return {
fullYearSalesChart,

View File

@@ -1,84 +1,88 @@
<template>
<div class="main-container">
<n-spin :show="loading" description="正在从服务器查询信息...">
<n-grid responsive="screen" cols="1 s:2 m:4 l:4 xl:4 2xl:4" x-gap="5" y-gap="5">
<n-grid-item v-for="(item, index) of dataSource.head" :key="index" class="item-wrapper">
<DataItem :data-model="item" :loading="loading">
<template v-if="index === 0" #extra="{ extra }">
<div class="margin-top-lg">
<div> {{ extra.data }}</div>
<div class="margin-top-sm"> {{ extra.data1 }}</div>
</div>
</template>
<template v-else-if="index === 1" #extra="{ extra }">
<div class="margin-top" style="position: relative">
<div> 已用内存{{ extra.data }}</div>
<div class="margin-top-sm"> 剩余内存{{ extra.data1 }}</div>
<div class="stack-avatar-wrapper"></div>
</div>
</template>
<template v-else-if="index === 2" #extra="{ extra }">
<n-progress type="line" :percentage="extra.data" />
</template>
<template v-else-if="index === 3" #extra>
<LoadChart ref="mLoadChart" :data-model="dataSource.load" />
</template>
</DataItem>
</n-grid-item>
</n-grid>
<div>
<div class="main-container">
<n-spin :show="loading" description="正在从服务器查询信息...">
<n-grid responsive="screen" cols="1 s:2 m:4 l:4 xl:4 2xl:4" x-gap="5" y-gap="5">
<n-grid-item v-for="(item, index) of dataSource.head" :key="index" class="item-wrapper">
<DataItem :data-model="item" :loading="loading">
<template v-if="index === 0" #extra="{ extra }">
<div class="margin-top-lg">
<div> {{ extra.data }}</div>
<div class="margin-top-sm"> {{ extra.data1 }}</div>
</div>
</template>
<template v-else-if="index === 1" #extra="{ extra }">
<div class="margin-top" style="position: relative">
<div> 已用内存{{ extra.data }}</div>
<div class="margin-top-sm"> 剩余内存{{ extra.data1 }}</div>
<div class="stack-avatar-wrapper"></div>
</div>
</template>
<template v-else-if="index === 2" #extra="{ extra }">
<n-progress type="line" :percentage="extra.data" />
</template>
<template v-else-if="index === 3" #extra>
<LoadChart ref="mLoadChart" :data-model="dataSource.load" />
</template>
</DataItem>
</n-grid-item>
</n-grid>
<n-grid class="mt-2">
<n-grid-item :span="24">
<FullYearSalesChart
ref="fullYearSalesChart"
:data-model="dataSource.net"
:loading="loading"
/>
</n-grid-item>
</n-grid>
<n-grid class="mt-2">
<n-grid-item :span="24">
<FullYearSalesChart
ref="fullYearSalesChart"
:data-model="dataSource.net"
:loading="loading"
/>
</n-grid-item>
</n-grid>
<n-space vertical style="padding-top: 10px">
<n-card title="服务器信息">
<n-descriptions
label-placement="top"
bordered
cols="1 s:1 m:2 l:3 xl:4 2xl:4"
:label-style="{ 'font-weight': 'bold', 'font-size': '16px' }"
>
<n-descriptions-item label="服务器名称">
{{ dataRunInfo.hostname }}
</n-descriptions-item>
<n-descriptions-item label="操作系统"> {{ dataRunInfo.os }}</n-descriptions-item>
<n-descriptions-item label="服务器IP">
{{ dataRunInfo.intranet_ip }} /
{{ dataRunInfo.public_ip }}
</n-descriptions-item>
<n-descriptions-item label="系统架构"> {{ dataRunInfo.arch }}</n-descriptions-item>
</n-descriptions>
</n-card>
<n-card title="GO运行信息">
<n-descriptions
label-placement="top"
bordered
cols="1 s:1 m:2 l:3 xl:4 2xl:4"
:label-style="{ 'font-weight': 'bold', 'font-size': '16px' }"
>
<n-descriptions-item label="语言环境"> {{ dataRunInfo.goName }}</n-descriptions-item>
<n-descriptions-item label="版本号"> {{ dataRunInfo.version }}</n-descriptions-item>
<n-descriptions-item label="启动时间"> {{ dataRunInfo.startTime }}</n-descriptions-item>
<n-descriptions-item label="运行时长">
{{ formatBefore(new Date(dataRunInfo.startTime)) }}
</n-descriptions-item>
<n-descriptions-item label="运行路径"> {{ dataRunInfo.pwd }}</n-descriptions-item>
<n-descriptions-item label="goroutine数量">
{{ dataRunInfo.goroutine }}
</n-descriptions-item>
<n-descriptions-item label="运行内存"> {{ dataRunInfo.goMem }}</n-descriptions-item>
<n-descriptions-item label="磁盘占用"> {{ dataRunInfo.goSize }}</n-descriptions-item>
</n-descriptions>
</n-card>
</n-space>
</n-spin>
<n-space vertical style="padding-top: 10px">
<n-card title="服务器信息">
<n-descriptions
label-placement="top"
bordered
cols="1 s:1 m:2 l:3 xl:4 2xl:4"
:label-style="{ 'font-weight': 'bold', 'font-size': '16px' }"
>
<n-descriptions-item label="服务器名称">
{{ dataRunInfo.hostname }}
</n-descriptions-item>
<n-descriptions-item label="操作系统"> {{ dataRunInfo.os }}</n-descriptions-item>
<n-descriptions-item label="服务器IP">
{{ dataRunInfo.intranet_ip }} /
{{ dataRunInfo.public_ip }}
</n-descriptions-item>
<n-descriptions-item label="系统架构"> {{ dataRunInfo.arch }}</n-descriptions-item>
</n-descriptions>
</n-card>
<n-card title="GO运行信息">
<n-descriptions
label-placement="top"
bordered
cols="1 s:1 m:2 l:3 xl:4 2xl:4"
:label-style="{ 'font-weight': 'bold', 'font-size': '16px' }"
>
<n-descriptions-item label="语言环境"> {{ dataRunInfo.goName }}</n-descriptions-item>
<n-descriptions-item label="版本号"> {{ dataRunInfo.version }}</n-descriptions-item>
<n-descriptions-item label="启动时间">
{{ dataRunInfo.startTime }}</n-descriptions-item
>
<n-descriptions-item label="运行时长">
{{ formatBefore(new Date(dataRunInfo.startTime)) }}
</n-descriptions-item>
<n-descriptions-item label="运行路径"> {{ dataRunInfo.pwd }}</n-descriptions-item>
<n-descriptions-item label="goroutine数量">
{{ dataRunInfo.goroutine }}
</n-descriptions-item>
<n-descriptions-item label="运行内存"> {{ dataRunInfo.goMem }}</n-descriptions-item>
<n-descriptions-item label="磁盘占用"> {{ dataRunInfo.goSize }}</n-descriptions-item>
</n-descriptions>
</n-card>
</n-space>
</n-spin>
</div>
</div>
</template>
@@ -90,7 +94,7 @@
import { SocketEnum } from '@/enums/socketEnum';
import { addOnMessage, sendMsg } from '@/utils/websocket';
import { formatBefore } from '@/utils/dateUtil';
import { useMessage } from 'naive-ui';
import { useDialog, useMessage } from 'naive-ui';
export default defineComponent({
name: 'Home',
@@ -164,34 +168,18 @@
load: {},
net: {},
});
const message = useMessage();
const dialog = useDialog();
const loading = ref(true);
const onMessageList = inject('onMessageList');
// const onAdminMonitorTrends = (res) => {
//
// const data = JSON.parse(res.data);
// console.log('onAdminMonitorTrends...,'+ data.event)
// if (data.event !== SocketEnum.EventAdminMonitorTrends) {
// return;
// }
// console.log('onAdminMonitorTrends okokoko,' + JSON.stringify(data))
// loading.value = false;
// if (data.code == SocketEnum.CodeErr) {
// message.success('查询出错');
// return;
// }
// dataSource.value = data.data;
// console.log('dataSource.value.net:'+JSON.stringify(dataSource.value.net))
// };
//
// addOnMessage(onMessageList, onAdminMonitorTrends);
const onAdminMonitor= (res) => {
const onAdminMonitor = (res) => {
const data = JSON.parse(res.data);
if (data.event === SocketEnum.EventAdminMonitorRunInfo) {
loading.value = false;
if (data.code == SocketEnum.CodeErr) {
message.success('查询出错');
message.error('查询出错:' + data.event);
return;
}
@@ -202,19 +190,29 @@
if (data.event === SocketEnum.EventAdminMonitorTrends) {
loading.value = false;
if (data.code == SocketEnum.CodeErr) {
message.success('查询出错');
message.error('查询出错:' + data.event);
return;
}
dataSource.value = data.data;
return;
}
};
addOnMessage(onMessageList, onAdminMonitor);
onMounted(() => {
getInfo();
setTimeout(() => {
if (loading.value) {
loading.value = false;
dialog.error({
title: '错误',
content: '连接超时请刷新重试。如仍未解决请检查websocket连接是否正确',
positiveText: '确定',
});
}
}, 5000);
});
onUpdated(() => {
@@ -244,7 +242,6 @@
const onResize = () => {
setTimeout(() => {
mLoadChart.value?.updateChart();
// fullYearSalesChart.value?.updateChart();
}, 500);
};
const collapse = true;

View File

@@ -87,7 +87,7 @@
<FormOutlined />
</n-icon>
<span>编辑菜单{{ treeItemTitle ? `${treeItemTitle}` : '' }}</span>
<span style="font-size: 14px">{{ treeItemTitle }}</span>
<!-- <span style="font-size: 14px">{{ treeItemTitle }}</span>-->
</n-space>
</template>

View File

@@ -1,89 +1,90 @@
<template>
<div>
<n-spin :show="show" description="正在获取配置...">
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
<n-grid-item>
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="网站名称" path="basicName">
<n-input v-model:value="formValue.basicName" placeholder="请输入网站名称" />
</n-form-item>
<n-spin :show="show" description="请稍候...">
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="网站名称" path="basicName">
<n-input v-model:value="formValue.basicName" placeholder="请输入网站名称" />
</n-form-item>
<n-form-item label="网站logo" path="basicLogo">
<BasicUpload
:action="`${uploadUrl}/admin/upload/image`"
:headers="uploadHeaders"
:data="{ type: 0 }"
name="file"
:width="100"
:height="100"
:maxNumber="1"
@uploadChange="uploadChange"
v-model:value="formValue.basicLogo"
:helpText="
'网站logo适用于客户端使用图片大小不超过' +
componentSetting.upload.maxSize +
'MB'
"
/>
</n-form-item>
<n-form-item label="网站logo" path="basicLogo">
<BasicUpload
:action="`${uploadUrl}/admin/upload/image`"
:headers="uploadHeaders"
:data="{ type: 0 }"
name="file"
:width="100"
:height="100"
:maxNumber="1"
@uploadChange="uploadChange"
v-model:value="formValue.basicLogo"
:helpText="
'网站logo适用于客户端使用图片大小不超过' + componentSetting.upload.maxSize + 'MB'
"
/>
</n-form-item>
<n-form-item label="网站域名" path="basicDomain">
<n-input v-model:value="formValue.basicDomain" placeholder="请输入网站域名" />
</n-form-item>
<n-form-item label="网站域名" path="basicDomain">
<n-input v-model:value="formValue.basicDomain" placeholder="请输入网站域名" />
<template #feedback>
如果客户端通过IP访问则认为是调试模式走实际请求地址否则走该配置
</template>
</n-form-item>
<n-form-item label="用户是否可注册开关" path="basicRegisterSwitch">
<n-radio-group
v-model:value="formValue.basicRegisterSwitch"
name="basicRegisterSwitch"
>
<n-space>
<n-radio :value="1">开启</n-radio>
<n-radio :value="0">关闭</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="websocket地址" path="basicWsAddr">
<n-input v-model:value="formValue.basicWsAddr" placeholder="请输入websocket地址" />
<template #feedback>
如果客户端通过IP访问则认为是调试模式走实际请求地址否则走该配置
</template>
</n-form-item>
<n-form-item label="验证码开关" path="basicCaptchaSwitch">
<n-radio-group v-model:value="formValue.basicCaptchaSwitch" name="basicCaptchaSwitch">
<n-space>
<n-radio :value="1">开启</n-radio>
<n-radio :value="0">关闭</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="用户是否可注册开关" path="basicRegisterSwitch">
<n-radio-group v-model:value="formValue.basicRegisterSwitch" name="basicRegisterSwitch">
<n-space>
<n-radio :value="1">开启</n-radio>
<n-radio :value="0">关闭</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="网站开启访问" path="basicSystemOpen">
<n-switch
size="large"
v-model:value="formValue.basicSystemOpen"
@update:value="systemOpenChange"
/>
</n-form-item>
<n-form-item label="验证码开关" path="basicCaptchaSwitch">
<n-radio-group v-model:value="formValue.basicCaptchaSwitch" name="basicCaptchaSwitch">
<n-space>
<n-radio :value="1">开启</n-radio>
<n-radio :value="0">关闭</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="网站关闭提示" path="basicCloseText">
<n-input
v-model:value="formValue.basicCloseText"
type="textarea"
placeholder="请输入网站关闭提示"
/>
</n-form-item>
<n-form-item label="网站开启访问" path="basicSystemOpen">
<n-switch
size="large"
v-model:value="formValue.basicSystemOpen"
@update:value="systemOpenChange"
/>
</n-form-item>
<n-form-item label="备案编号" path="basicIcpCode">
<n-input placeholder="请输入备案编号" v-model:value="formValue.basicIcpCode" />
</n-form-item>
<n-form-item label="网站关闭提示" path="basicCloseText">
<n-input
v-model:value="formValue.basicCloseText"
type="textarea"
placeholder="请输入网站关闭提示"
/>
</n-form-item>
<n-form-item label="版权所有" path="basicCopyright">
<n-input placeholder="版权所有" v-model:value="formValue.basicCopyright" />
</n-form-item>
<n-form-item label="备案编号" path="basicIcpCode">
<n-input placeholder="请输入备案编号" v-model:value="formValue.basicIcpCode" />
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-grid-item>
</n-grid>
<n-form-item label="版权所有" path="basicCopyright">
<n-input placeholder="版权所有" v-model:value="formValue.basicCopyright" />
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-spin>
</div>
</template>
@@ -127,6 +128,7 @@
basicName: 'HotGo',
basicLogo: '',
basicDomain: 'https://hotgo.facms.cn',
basicWsAddr: 'wss://hotgo.facms.cn/socket',
basicIcpCode: '',
basicLoginCode: 0,
basicRegisterSwitch: 1,

View File

@@ -1,95 +1,87 @@
<template>
<div>
<n-spin :show="show" description="正在获取配置...">
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
<n-grid-item>
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="SMTP服务器" path="smtpHost">
<n-input v-model:value="formValue.smtpHost" placeholder="" />
<template #feedback> 错误的配置发送邮件会导致服务器超时</template>
</n-form-item>
<n-spin :show="show" description="请稍候...">
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="SMTP服务器" path="smtpHost">
<n-input v-model:value="formValue.smtpHost" placeholder="" />
<template #feedback> 错误的配置发送邮件会导致服务器超时</template>
</n-form-item>
<n-form-item label="SMTP端口" path="smtpPort">
<n-input-number
v-model:value="formValue.smtpPort"
placeholder=""
:show-button="false"
/>
<template #feedback> (不加密默认25,SSL默认465,TLS默认587)</template>
</n-form-item>
<n-form-item label="SMTP用户名" path="smtpUser">
<n-input v-model:value="formValue.smtpUser" placeholder="" />
<template #feedback>填写完整用户名</template>
</n-form-item>
<n-form-item label="SMTP端口" path="smtpPort">
<n-input-number v-model:value="formValue.smtpPort" placeholder="" :show-button="false" />
<template #feedback> (不加密默认25,SSL默认465,TLS默认587)</template>
</n-form-item>
<n-form-item label="SMTP用户名" path="smtpUser">
<n-input v-model:value="formValue.smtpUser" placeholder="" />
<template #feedback>填写完整用户名</template>
</n-form-item>
<n-form-item label="SMTP密码" path="smtpPass">
<n-input
v-model:value="formValue.smtpPass"
placeholder=""
type="password"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
<template #feedback>填写您的密码</template>
</n-form-item>
<n-form-item label="SMTP密码" path="smtpPass">
<n-input
v-model:value="formValue.smtpPass"
placeholder=""
type="password"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
<template #feedback>填写您的密码</template>
</n-form-item>
<n-form-item label="发件人名称" path="smtpSendName">
<n-input v-model:value="formValue.smtpSendName" placeholder="" />
</n-form-item>
<n-form-item label="发件人名称" path="smtpSendName">
<n-input v-model:value="formValue.smtpSendName" placeholder="" />
</n-form-item>
<n-form-item label="管理员邮箱" path="smtpAdminMailbox">
<n-input v-model:value="formValue.smtpAdminMailbox" placeholder="" />
</n-form-item>
<n-form-item label="管理员邮箱" path="smtpAdminMailbox">
<n-input v-model:value="formValue.smtpAdminMailbox" placeholder="" />
</n-form-item>
<n-divider title-placement="left">发信限制</n-divider>
<n-form-item label="最小发送间隔" path="smtpMinInterval">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smtpMinInterval"
>
<template #suffix> </template>
</n-input-number>
<template #feedback> 同地址</template>
</n-form-item>
<n-form-item label="IP最大发送次数" path="smtpMaxIpLimit">
<n-input-number v-model:value="formValue.smtpMaxIpLimit" placeholder="" />
<template #feedback> 同IP每天最大允许发送次数 </template>
</n-form-item>
<n-form-item label="验证码有效期" path="smtpCodeExpire">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smtpCodeExpire"
>
<template #suffix> </template>
</n-input-number>
</n-form-item>
<n-divider title-placement="left">发信限制</n-divider>
<n-form-item label="最小发送间隔" path="smtpMinInterval">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smtpMinInterval"
>
<template #suffix> </template>
</n-input-number>
<template #feedback> 同地址</template>
</n-form-item>
<n-form-item label="IP最大发送次数" path="smtpMaxIpLimit">
<n-input-number v-model:value="formValue.smtpMaxIpLimit" placeholder="" />
<template #feedback> 同IP每天最大允许发送次数 </template>
</n-form-item>
<n-form-item label="验证码有效期" path="smtpCodeExpire">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smtpCodeExpire"
>
<template #suffix> </template>
</n-input-number>
</n-form-item>
<n-form-item label="邮件模板" path="smtpTemplate">
<n-dynamic-input
v-model:value="formValue.smtpTemplate"
preset="pair"
key-placeholder="事件KEY"
value-placeholder="模板路径"
/>
</n-form-item>
<n-form-item label="邮件模板" path="smtpTemplate">
<n-dynamic-input
v-model:value="formValue.smtpTemplate"
preset="pair"
key-placeholder="事件KEY"
value-placeholder="模板路径"
/>
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
<n-button type="default" @click="sendTest">发送测试邮件</n-button>
</n-space>
</div>
</n-form>
</n-grid-item>
</n-grid>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
<n-button type="default" @click="sendTest">发送测试邮件</n-button>
</n-space>
</div>
</n-form>
</n-spin>
<n-modal

View File

@@ -1,34 +1,30 @@
<template>
<div>
<n-spin :show="show" description="正在获取配置...">
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
<n-grid-item>
<n-form :label-width="100" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="高德Web服务key" path="geoAmapWebKey">
<n-input
v-model:value="formValue.geoAmapWebKey"
placeholder=""
type="password"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
<template #feedback> 申请地址https://console.amap.com/dev/key/app</template>
</n-form-item>
<n-spin :show="show" description="请稍候...">
<n-form :label-width="100" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="高德Web服务key" path="geoAmapWebKey">
<n-input
v-model:value="formValue.geoAmapWebKey"
placeholder=""
type="password"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
<template #feedback> 申请地址https://console.amap.com/dev/key/app</template>
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-grid-item>
</n-grid>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-spin>
</div>
</template>

View File

@@ -1,90 +1,86 @@
<template>
<div>
<n-spin :show="show" description="正在获取配置...">
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
<n-grid-item>
<n-form :label-width="100" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="默认驱动" path="smsDrive">
<n-select
placeholder="默认发送驱动"
:options="options.config_sms_drive"
v-model:value="formValue.smsDrive"
/>
</n-form-item>
<n-spin :show="show" description="请稍候...">
<n-form :label-width="100" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="默认驱动" path="smsDrive">
<n-select
placeholder="默认发送驱动"
:options="options.config_sms_drive"
v-model:value="formValue.smsDrive"
/>
</n-form-item>
<n-divider title-placement="left">发信限制</n-divider>
<n-form-item label="最小发送间隔" path="smsMinInterval">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smsMinInterval"
>
<template #suffix> </template>
</n-input-number>
<template #feedback> 同号码</template>
</n-form-item>
<n-form-item label="IP最大发送次数" path="smsMaxIpLimit">
<n-input-number v-model:value="formValue.smsMaxIpLimit" placeholder="" />
<template #feedback> 同IP每天最大允许发送次数 </template>
</n-form-item>
<n-form-item label="验证码有效期" path="smsCodeExpire">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smsCodeExpire"
>
<template #suffix> </template>
</n-input-number>
</n-form-item>
<n-divider title-placement="left">发信限制</n-divider>
<n-form-item label="最小发送间隔" path="smsMinInterval">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smsMinInterval"
>
<template #suffix> </template>
</n-input-number>
<template #feedback> 同号码</template>
</n-form-item>
<n-form-item label="IP最大发送次数" path="smsMaxIpLimit">
<n-input-number v-model:value="formValue.smsMaxIpLimit" placeholder="" />
<template #feedback> 同IP每天最大允许发送次数 </template>
</n-form-item>
<n-form-item label="验证码有效期" path="smsCodeExpire">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.smsCodeExpire"
>
<template #suffix> </template>
</n-input-number>
</n-form-item>
<n-divider title-placement="left">阿里云</n-divider>
<n-form-item label="AccessKeyID" path="smsAliyunAccessKeyID">
<n-input v-model:value="formValue.smsAliyunAccessKeyID" placeholder="" />
<template #feedback
>应用key和密钥你可以通过 https://ram.console.aliyun.com/manage/ak 获取</template
>
</n-form-item>
<n-divider title-placement="left">阿里云</n-divider>
<n-form-item label="AccessKeyID" path="smsAliyunAccessKeyID">
<n-input v-model:value="formValue.smsAliyunAccessKeyID" placeholder="" />
<template #feedback
>应用key和密钥你可以通过 https://ram.console.aliyun.com/manage/ak 获取</template
>
</n-form-item>
<n-form-item label="AccessKeySecret" path="smsAliyunAccessKeySecret">
<n-input
type="password"
v-model:value="formValue.smsAliyunAccessKeySecret"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
</n-form-item>
<n-form-item label="AccessKeySecret" path="smsAliyunAccessKeySecret">
<n-input
type="password"
v-model:value="formValue.smsAliyunAccessKeySecret"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
</n-form-item>
<n-form-item label="签名" path="smsAliyunSign">
<n-input v-model:value="formValue.smsAliyunSign" placeholder="" />
<template #feedback
>申请地址https://dysms.console.aliyun.com/domestic/text/sign</template
>
</n-form-item>
<n-form-item label="签名" path="smsAliyunSign">
<n-input v-model:value="formValue.smsAliyunSign" placeholder="" />
<template #feedback
>申请地址https://dysms.console.aliyun.com/domestic/text/sign</template
>
</n-form-item>
<n-form-item label="短信模板" path="smsAliyunTemplate">
<n-dynamic-input
v-model:value="formValue.smsAliyunTemplate"
preset="pair"
key-placeholder="事件KEY"
value-placeholder="模板CODE"
/>
</n-form-item>
<n-form-item label="短信模板" path="smsAliyunTemplate">
<n-dynamic-input
v-model:value="formValue.smsAliyunTemplate"
preset="pair"
key-placeholder="事件KEY"
value-placeholder="模板CODE"
/>
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
<n-button type="default" @click="sendTest">发送测试短信</n-button>
</n-space>
</div>
</n-form>
</n-grid-item>
</n-grid>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
<n-button type="default" @click="sendTest">发送测试短信</n-button>
</n-space>
</div>
</n-form>
</n-spin>
<n-modal

View File

@@ -1,31 +1,27 @@
<template>
<div>
<n-spin :show="show" description="正在获取配置...">
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
<n-grid-item>
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="默认主题" path="themeDarkTheme">
<n-input v-model:value="formValue.themeDarkTheme" placeholder="" />
<template #feedback> 可选'dark' 'light' </template>
</n-form-item>
<n-spin :show="show" description="请稍候...">
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="默认主题" path="themeDarkTheme">
<n-input v-model:value="formValue.themeDarkTheme" placeholder="" />
<template #feedback> 可选'dark' 'light' </template>
</n-form-item>
<n-form-item label="默认系统主题" path="themeAppTheme">
<n-input v-model:value="formValue.themeAppTheme" placeholder="" />
<template #feedback> 默认#2d8cf0 </template>
</n-form-item>
<n-form-item label="默认侧边栏风格" path="themeNavTheme">
<n-input v-model:value="formValue.themeNavTheme" placeholder="" />
<template #feedback>可选'light' 'header-dark'</template>
</n-form-item>
<n-form-item label="默认系统主题" path="themeAppTheme">
<n-input v-model:value="formValue.themeAppTheme" placeholder="" />
<template #feedback> 默认#2d8cf0 </template>
</n-form-item>
<n-form-item label="默认侧边栏风格" path="themeNavTheme">
<n-input v-model:value="formValue.themeNavTheme" placeholder="" />
<template #feedback>可选'light' 'header-dark'</template>
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-grid-item>
</n-grid>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-spin>
</div>
</template>

View File

@@ -1,108 +1,104 @@
<template>
<div>
<n-spin :show="show" description="正在获取配置...">
<n-grid cols="2 s:2 m:2 l:2 xl:2 2xl:2" responsive="screen">
<n-grid-item>
<n-form :label-width="100" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="默认驱动" path="uploadDrive">
<n-select
placeholder="默认驱动"
:options="uploadDriveList"
v-model:value="formValue.uploadDrive"
/>
</n-form-item>
<n-spin :show="show" description="请稍候...">
<n-form :label-width="100" :model="formValue" :rules="rules" ref="formRef">
<n-form-item label="默认驱动" path="uploadDrive">
<n-select
placeholder="默认驱动"
:options="uploadDriveList"
v-model:value="formValue.uploadDrive"
/>
</n-form-item>
<n-divider title-placement="left">上传限制</n-divider>
<n-form-item label="图片大小限制" path="uploadImageSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadImageSize"
>
<template #suffix> MB</template>
</n-input-number>
</n-form-item>
<n-form-item label="图片类型限制" path="uploadImageType">
<n-input v-model:value="formValue.uploadImageType" placeholder="" />
</n-form-item>
<n-divider title-placement="left">上传限制</n-divider>
<n-form-item label="图片大小限制" path="uploadImageSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadImageSize"
>
<template #suffix> MB</template>
</n-input-number>
</n-form-item>
<n-form-item label="图片类型限制" path="uploadImageType">
<n-input v-model:value="formValue.uploadImageType" placeholder="" />
</n-form-item>
<n-form-item label="文件大小限制" path="uploadFileSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadFileSize"
>
<template #suffix> MB</template>
</n-input-number>
</n-form-item>
<n-form-item label="文件类型限制" path="uploadFileType">
<n-input v-model:value="formValue.uploadFileType" placeholder="" />
</n-form-item>
<n-form-item label="文件大小限制" path="uploadFileSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadFileSize"
>
<template #suffix> MB</template>
</n-input-number>
</n-form-item>
<n-form-item label="文件类型限制" path="uploadFileType">
<n-input v-model:value="formValue.uploadFileType" placeholder="" />
</n-form-item>
<n-divider title-placement="left">本地存储</n-divider>
<n-form-item label="本地存储路径" path="uploadLocalPath">
<n-input v-model:value="formValue.uploadLocalPath" placeholder="" />
<template #feedback>填对外访问的相对路径</template>
</n-form-item>
<n-divider title-placement="left">本地存储</n-divider>
<n-form-item label="本地存储路径" path="uploadLocalPath">
<n-input v-model:value="formValue.uploadLocalPath" placeholder="" />
<template #feedback>填对外访问的相对路径</template>
</n-form-item>
<n-divider title-placement="left">UCloud存储</n-divider>
<n-form-item label="公钥" path="uploadUCloudPublicKey">
<n-input
type="password"
v-model:value="formValue.uploadUCloudPublicKey"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
<template #feedback>获取地址https://console.ucloud.cn/ufile/token</template>
</n-form-item>
<n-divider title-placement="left">UCloud存储</n-divider>
<n-form-item label="公钥" path="uploadUCloudPublicKey">
<n-input
type="password"
v-model:value="formValue.uploadUCloudPublicKey"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
<template #feedback>获取地址https://console.ucloud.cn/ufile/token</template>
</n-form-item>
<n-form-item label="私钥" path="uploadUCloudPrivateKey">
<n-input
type="password"
v-model:value="formValue.uploadUCloudPrivateKey"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
</n-form-item>
<n-form-item label="存储路径" path="uploadUCloudPath">
<n-input v-model:value="formValue.uploadUCloudPath" placeholder="" />
<template #feedback>填对对象存储中的相对路径</template>
</n-form-item>
<n-form-item label="地域API" path="uploadUCloudBucketHost">
<n-input v-model:value="formValue.uploadUCloudBucketHost" placeholder="" />
</n-form-item>
<n-form-item label="存储桶名称" path="uploadUCloudBucketName">
<n-input v-model:value="formValue.uploadUCloudBucketName" placeholder="" />
<template #feedback>存储空间名称</template>
</n-form-item>
<n-form-item label="存储桶地域host" path="uploadUCloudFileHost">
<n-input v-model:value="formValue.uploadUCloudFileHost" placeholder="" />
<template #feedback>不需要包含桶名称</template>
</n-form-item>
<n-form-item label="访问域名" path="uploadUCloudEndpoint">
<n-input v-model:value="formValue.uploadUCloudEndpoint" placeholder="" />
<template #feedback>格式http://abc.com 或 https://abc.com不可为空</template>
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-grid-item>
</n-grid>
<n-form-item label="私钥" path="uploadUCloudPrivateKey">
<n-input
type="password"
v-model:value="formValue.uploadUCloudPrivateKey"
show-password-on="click"
>
<template #password-visible-icon>
<n-icon :size="16" :component="GlassesOutline" />
</template>
<template #password-invisible-icon>
<n-icon :size="16" :component="Glasses" />
</template>
</n-input>
</n-form-item>
<n-form-item label="存储路径" path="uploadUCloudPath">
<n-input v-model:value="formValue.uploadUCloudPath" placeholder="" />
<template #feedback>填对对象存储中的相对路径</template>
</n-form-item>
<n-form-item label="地域API" path="uploadUCloudBucketHost">
<n-input v-model:value="formValue.uploadUCloudBucketHost" placeholder="" />
</n-form-item>
<n-form-item label="存储桶名称" path="uploadUCloudBucketName">
<n-input v-model:value="formValue.uploadUCloudBucketName" placeholder="" />
<template #feedback>存储空间名称</template>
</n-form-item>
<n-form-item label="存储桶地域host" path="uploadUCloudFileHost">
<n-input v-model:value="formValue.uploadUCloudFileHost" placeholder="" />
<template #feedback>不需要包含桶名称</template>
</n-form-item>
<n-form-item label="访问域名" path="uploadUCloudEndpoint">
<n-input v-model:value="formValue.uploadUCloudEndpoint" placeholder="" />
<template #feedback>格式http://abc.com 或 https://abc.com不可为空</template>
</n-form-item>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>
</n-space>
</div>
</n-form>
</n-spin>
</div>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<n-grid :x-gap="24">
<n-grid cols="24 300:1 600:24" :x-gap="24">
<n-grid-item span="6">
<n-card :bordered="false" size="small" class="proCard">
<n-thing

File diff suppressed because it is too large Load Diff