更新2.1.2版本,优化部门、角色权限,增加上下级关系;增加登录、系统、短信日志;优化省市区编码
25099
web/package-lock.json
generated
Normal file
@@ -67,6 +67,7 @@
|
||||
"autoprefixer": "^10.4.7",
|
||||
"commitizen": "^4.2.4",
|
||||
"core-js": "^3.22.5",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
@@ -97,7 +98,9 @@
|
||||
"vite-plugin-compression": "^0.3.6",
|
||||
"vite-plugin-html": "^2.1.2",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-require-transform": "^1.0.5",
|
||||
"vite-plugin-style-import": "^1.4.1",
|
||||
"vite-plugin-top-level-await": "^1.2.2",
|
||||
"vue-eslint-parser": "^7.11.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
@@ -39,3 +39,43 @@ export function View(params) {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取最大排序
|
||||
export function MaxSort() {
|
||||
return http.request({
|
||||
url: '/provinces/maxSort',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取省市区关系树选项列表
|
||||
*/
|
||||
export function getProvincesTree() {
|
||||
return http.request({
|
||||
url: '/provinces/tree',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取省市区下级列表
|
||||
*/
|
||||
export function getProvincesChildrenList(params) {
|
||||
return http.request({
|
||||
url: '/provinces/childrenList',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 唯一省市区ID
|
||||
*/
|
||||
export function CheckProvincesUniqueId(params) {
|
||||
return http.request({
|
||||
url: '/provinces/uniqueId',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
9
web/src/api/base/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { http } from '@/utils/http/axios';
|
||||
|
||||
// 获取验证码
|
||||
export function GetCaptcha() {
|
||||
return http.request({
|
||||
url: '/site/captcha',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
@@ -18,8 +18,7 @@ export function Delete(params) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 新建/编辑生成演示
|
||||
// 添加/编辑生成演示
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/curdDemo/edit',
|
||||
@@ -28,7 +27,6 @@ export function Edit(params) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 修改生成演示状态
|
||||
export function Status(params) {
|
||||
return http.request({
|
||||
@@ -38,7 +36,6 @@ export function Status(params) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 操作生成演示开关
|
||||
export function Switch(params) {
|
||||
return http.request({
|
||||
@@ -48,7 +45,6 @@ export function Switch(params) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 获取生成演示指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
@@ -58,7 +54,6 @@ export function View(params) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 获取生成演示最大排序
|
||||
export function MaxSort() {
|
||||
return http.request({
|
||||
@@ -67,8 +62,7 @@ export function MaxSort() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 导出生成演示
|
||||
export function Export(params) {
|
||||
jumpExport('/curdDemo/export', params);
|
||||
}
|
||||
}
|
||||
|
24
web/src/api/log/smslog.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { http } from '@/utils/http/axios';
|
||||
|
||||
export function getLogList(params) {
|
||||
return http.request({
|
||||
url: '/smsLog/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/smsLog/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/smsLog/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
33
web/src/api/loginLog/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { http, jumpExport } from '@/utils/http/axios';
|
||||
|
||||
// 获取登录日志列表
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/loginLog/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除/批量删除登录日志
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/loginLog/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取登录日志指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/loginLog/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 导出登录日志
|
||||
export function Export(params) {
|
||||
jumpExport('/loginLog/export', params);
|
||||
}
|
@@ -34,7 +34,7 @@ export function Delete(params) {
|
||||
|
||||
export function ResetPwd(params) {
|
||||
return http.request({
|
||||
url: '/member/reset_pwd',
|
||||
url: '/member/resetPwd',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
|
33
web/src/api/serveLog/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { http, jumpExport } from '@/utils/http/axios';
|
||||
|
||||
// 获取服务日志列表
|
||||
export function List(params) {
|
||||
return http.request({
|
||||
url: '/serveLog/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除/批量删除服务日志
|
||||
export function Delete(params) {
|
||||
return http.request({
|
||||
url: '/serveLog/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取服务日志指定详情
|
||||
export function View(params) {
|
||||
return http.request({
|
||||
url: '/serveLog/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 导出服务日志
|
||||
export function Export(params) {
|
||||
jumpExport('/serveLog/export', params);
|
||||
}
|
@@ -29,3 +29,11 @@ export function sendTestEmail(params) {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function sendTestSms(params) {
|
||||
return http.request({
|
||||
url: '/sms/sendTest',
|
||||
method: 'post',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ export function View(params) {
|
||||
|
||||
export function GroupList(params) {
|
||||
return http.request({
|
||||
url: '/cron_group/list',
|
||||
url: '/cronGroup/list',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
@@ -50,7 +50,7 @@ export function GroupList(params) {
|
||||
|
||||
export function GroupDelete(params) {
|
||||
return http.request({
|
||||
url: '/cron_group/delete',
|
||||
url: '/cronGroup/delete',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
@@ -58,7 +58,7 @@ export function GroupDelete(params) {
|
||||
|
||||
export function GroupEdit(params) {
|
||||
return http.request({
|
||||
url: '/cron_group/edit',
|
||||
url: '/cronGroup/edit',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
@@ -66,7 +66,7 @@ export function GroupEdit(params) {
|
||||
|
||||
export function GroupStatus(params) {
|
||||
return http.request({
|
||||
url: '/cron_group/status',
|
||||
url: '/cronGroup/status',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
@@ -74,7 +74,7 @@ export function GroupStatus(params) {
|
||||
|
||||
export function GroupView(params) {
|
||||
return http.request({
|
||||
url: '/cron_group/view',
|
||||
url: '/cronGroup/view',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
@@ -82,8 +82,16 @@ export function GroupView(params) {
|
||||
|
||||
export function getSelect(params) {
|
||||
return http.request({
|
||||
url: '/cron_group/select',
|
||||
url: '/cronGroup/select',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function OnlineExec(params) {
|
||||
return http.request({
|
||||
url: '/cron/onlineExec',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ export function Delete(params) {
|
||||
});
|
||||
}
|
||||
|
||||
// 新建/编辑
|
||||
// 添加/编辑
|
||||
export function Edit(params) {
|
||||
return http.request({
|
||||
url: '/test/edit',
|
||||
|
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@@ -93,6 +93,7 @@
|
||||
import { useBattery } from '@/hooks/useBattery';
|
||||
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { aesEcb } from '@/utils/encrypt';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Lockscreen',
|
||||
@@ -140,7 +141,8 @@
|
||||
}
|
||||
const params = {
|
||||
isLock: true,
|
||||
...state.loginParams,
|
||||
username: state.loginParams.username,
|
||||
password: aesEcb.encrypt(state.loginParams.password),
|
||||
};
|
||||
state.loginLoading = true;
|
||||
const { code, message } = await userStore.login(params);
|
||||
|
@@ -89,6 +89,7 @@
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { isJsonString, isNullOrUnDef } from '@/utils/is';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { errorImg } from '@/utils/hotgo';
|
||||
const globSetting = useGlobSetting();
|
||||
|
||||
export default defineComponent({
|
||||
@@ -258,13 +259,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**图片加载失败显示自定义默认图片(缺省图)*/
|
||||
function errorImg(e) {
|
||||
e.srcElement.src = '/onerror.png';
|
||||
//这一句没用,如果默认图片的路径错了还是会一直闪屏,在方法的前面加个.once只让它执行一次也没用
|
||||
e.srcElement.onerror = null; //防止闪图
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
setTimeout(function () {
|
||||
if (props.maxNumber === 1) {
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { Option } from '@/utils/hotgo';
|
||||
|
||||
export const switchOptions = [
|
||||
{
|
||||
value: 1,
|
||||
@@ -85,7 +87,7 @@ export const tagOptions = [
|
||||
value: 'success',
|
||||
},
|
||||
{
|
||||
label: '黄色',
|
||||
label: '橙色',
|
||||
value: 'warning',
|
||||
},
|
||||
{
|
||||
@@ -93,3 +95,19 @@ export const tagOptions = [
|
||||
value: 'error',
|
||||
},
|
||||
];
|
||||
|
||||
// 登录状态
|
||||
export const loginStatusOptions: Option[] = [
|
||||
{
|
||||
value: 1,
|
||||
label: '成功',
|
||||
key: 1,
|
||||
listClass: 'success',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '失败',
|
||||
key: 2,
|
||||
listClass: 'warning',
|
||||
},
|
||||
];
|
||||
|
@@ -182,6 +182,7 @@
|
||||
import { NotificationsOutline as NotificationsIcon } from '@vicons/ionicons5';
|
||||
import PopoverMessage from './PopoverMessage.vue';
|
||||
import { notificationStoreWidthOut } from '@/store/modules/notification';
|
||||
import notificationImg from '@/assets/images/notification.png';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PageHeader',
|
||||
@@ -393,7 +394,7 @@
|
||||
h(NAvatar, {
|
||||
size: 'small',
|
||||
round: true,
|
||||
src: '/notification.png',
|
||||
src: notificationImg,
|
||||
}),
|
||||
onClose: () => {
|
||||
nRef.value = null;
|
||||
|
25
web/src/utils/encrypt.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
const defaultKey = 'f080a463654b2279';
|
||||
|
||||
export const aesEcb = {
|
||||
// 加密
|
||||
encrypt(word: string, keyStr: string = defaultKey): string {
|
||||
const key = CryptoJS.enc.Utf8.parse(keyStr);
|
||||
const src = CryptoJS.enc.Utf8.parse(word);
|
||||
const encrypted = CryptoJS.AES.encrypt(src, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
return encrypted.toString();
|
||||
},
|
||||
// 解密
|
||||
decrypt(word: string, keyStr: string = defaultKey): string {
|
||||
const key = CryptoJS.enc.Utf8.parse(keyStr);
|
||||
const decrypt = CryptoJS.AES.decrypt(word, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
return CryptoJS.enc.Utf8.stringify(decrypt).toString();
|
||||
},
|
||||
};
|
@@ -1,10 +1,11 @@
|
||||
import { Ref, UnwrapRef } from '@vue/reactivity';
|
||||
import onerrorImg from '@/assets/images/onerror.png';
|
||||
|
||||
export interface Option {
|
||||
label: string;
|
||||
value: string;
|
||||
key: string;
|
||||
type: string;
|
||||
value: string | number;
|
||||
key: string | number;
|
||||
// type: string;
|
||||
listClass: 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning';
|
||||
}
|
||||
|
||||
@@ -51,3 +52,9 @@ export function adaModalWidth(dialogWidth: Ref<UnwrapRef<string>>) {
|
||||
}
|
||||
return dialogWidth.value;
|
||||
}
|
||||
|
||||
// 图片加载失败显示自定义默认图片(缺省图)
|
||||
export function errorImg(e: any): void {
|
||||
e.target.src = onerrorImg;
|
||||
e.target.onerror = null;
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import { Dicts } from '@/api/dict/dict';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { isNullOrUnDef } from '@/utils/is';
|
||||
import { errorImg } from '@/utils/hotgo';
|
||||
export const options = ref<Options>({
|
||||
sys_normal_disable: [],
|
||||
config_upload_drive: [],
|
||||
@@ -116,6 +117,7 @@ export const columns = [
|
||||
width: 40,
|
||||
height: 40,
|
||||
src: row.fileUrl,
|
||||
onError: errorImg,
|
||||
style: {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
|
@@ -31,7 +31,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
添加
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
@@ -45,7 +45,7 @@
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="新建">
|
||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="添加">
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
|
@@ -1,71 +0,0 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '地区名称',
|
||||
key: 'title',
|
||||
},
|
||||
{
|
||||
title: '父ID',
|
||||
key: 'pid',
|
||||
render(row) {
|
||||
return row.pid;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '拼音',
|
||||
key: 'pinyin',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'success',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.pinyin,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '经度',
|
||||
key: 'lng',
|
||||
},
|
||||
{
|
||||
title: '维度',
|
||||
key: 'lat',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 1 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.status == 1 ? '正常' : '隐藏'),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
];
|
191
web/src/views/apply/provinces/edit.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="isShowModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="isUpdate ? '编辑 #' + params?.id : '添加'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-form
|
||||
:model="params"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="上级地区" path="pid">
|
||||
<n-tree-select :options="optionTreeData" :default-value="params.pid" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="地区ID" path="id">
|
||||
<n-input-number
|
||||
style="width: 100%"
|
||||
placeholder="请输入地区ID"
|
||||
v-model:value="params.id"
|
||||
:disabled="isUpdate"
|
||||
path="handleChangeId"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="地区名称" path="title">
|
||||
<n-input placeholder="请输入地区名称" v-model:value="params.title" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="拼音" path="pinyin">
|
||||
<n-input placeholder="请输入拼音" v-model:value="params.pinyin" />
|
||||
</n-form-item>
|
||||
|
||||
<n-grid x-gap="24" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="经度" path="lng">
|
||||
<n-input placeholder="经度" v-model:value="params.lng" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="纬度" path="lat">
|
||||
<n-input placeholder="纬度" v-model:value="params.lat" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="params.sort" clearable />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="params.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in options.sys_normal_disable"
|
||||
:key="Number(status.value)"
|
||||
:value="Number(status.value)"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { options, State, newState } from './model';
|
||||
import { Edit, MaxSort, CheckProvincesUniqueId } from '@/api/apply/provinces';
|
||||
import { FormItemRule, useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
const emit = defineEmits(['reloadTable', 'updateShowModal']);
|
||||
|
||||
interface Props {
|
||||
showModal: boolean;
|
||||
formParams?: State;
|
||||
optionTreeData: any;
|
||||
isUpdate: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showModal: false,
|
||||
formParams: () => {
|
||||
return newState(null);
|
||||
},
|
||||
optionTreeData: [],
|
||||
isUpdate: false,
|
||||
});
|
||||
|
||||
const isShowModal = computed({
|
||||
get: () => {
|
||||
return props.showModal;
|
||||
},
|
||||
set: (value) => {
|
||||
emit('updateShowModal', value);
|
||||
},
|
||||
});
|
||||
|
||||
const params = computed(() => {
|
||||
return props.formParams;
|
||||
});
|
||||
|
||||
const rules = {
|
||||
id: {
|
||||
required: true,
|
||||
async validator(rule: FormItemRule, value: string, callback: Function) {
|
||||
if (!value) {
|
||||
callback(new Error('请填写地区ID'));
|
||||
} else if (!/^\d*$/.test(value)) {
|
||||
callback(new Error('地区ID应该为整数'));
|
||||
} else if (!(await isUniqueId(value))) {
|
||||
callback(new Error('地区ID已存在'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
title: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入地区名称',
|
||||
},
|
||||
};
|
||||
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const dialogWidth = ref('75%');
|
||||
const formBtnLoading = ref(false);
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(params.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
isShowModal.value = false;
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => params.value,
|
||||
(value) => {
|
||||
params.value.oldId = Number(value.id);
|
||||
if (value.id === 0 || value.id === null) {
|
||||
MaxSort().then((res) => {
|
||||
params.value.sort = res.sort;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
async function isUniqueId(newId: any) {
|
||||
const res = await CheckProvincesUniqueId({ oldId: params.value.oldId, newId: newId });
|
||||
return res.unique;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
@@ -1,357 +1,231 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard" title="省市区">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
@keyup.enter="handleSubmit"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="省市区"> 中国省市区编码对照表 </n-card>
|
||||
</div>
|
||||
<n-grid class="mt-6" cols="1 s:1 m:1 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12">
|
||||
<n-gi span="1">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small">
|
||||
<template #header>
|
||||
<n-space>
|
||||
<n-button type="info" icon-placement="left" @click="openCreateDrawer">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button
|
||||
type="info"
|
||||
icon-placement="left"
|
||||
@click="openEditDrawer"
|
||||
:disabled="formParams?.id === null || formParams?.id <= 0"
|
||||
>
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<EditOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
编辑
|
||||
</n-button>
|
||||
<n-button type="error" icon-placement="left" @click="handleDel">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
删除
|
||||
</n-button>
|
||||
<n-button type="info" icon-placement="left" @click="packHandle">
|
||||
{{ expandedKeys.length ? '收起' : '展开' }}
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<AlignLeftOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<n-input v-model:value="pattern" placeholder="输入地区名称搜索">
|
||||
<template #suffix>
|
||||
<n-icon size="18" class="cursor-pointer">
|
||||
<SearchOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
<div class="py-3 menu-list">
|
||||
<template v-if="loading">
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<n-spin size="medium" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-tree
|
||||
block-line
|
||||
cascade
|
||||
checkable
|
||||
:virtual-scroll="true"
|
||||
:pattern="pattern"
|
||||
:data="treeData"
|
||||
:expandedKeys="expandedKeys"
|
||||
style="height: 75vh"
|
||||
@update:selected-keys="selectedTree"
|
||||
@update:expanded-keys="onExpandedKeys"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi span="3">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small">
|
||||
<template #header>
|
||||
<n-space>
|
||||
<n-icon size="18">
|
||||
<FormOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<span>编辑省市区{{ treeItemTitle ? `:${treeItemTitle}` : '' }}</span>
|
||||
<span style="font-size: 14px">{{
|
||||
treeItemTitle ? '' : '从列表选择一项后,进行编辑'
|
||||
}}</span>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="新建">
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="公告标题" path="title">
|
||||
<n-input placeholder="请输入公告标题" v-model:value="formParams.title" />
|
||||
</n-form-item>
|
||||
<List :checkedId="checkedId" :optionTreeData="optionTreeData" @reloadTable="loadData" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-form-item label="公告类型" path="type">
|
||||
<n-radio-group v-model:value="formParams.type" name="type">
|
||||
<n-radio-button
|
||||
v-for="type in typeOptions"
|
||||
:key="type.value"
|
||||
:value="type.value"
|
||||
:label="type.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="公告内容" path="content">
|
||||
<n-input type="textarea" placeholder="请输入内容" v-model:value="formParams.content" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="接收人" path="receiver">
|
||||
<n-input
|
||||
type="textarea"
|
||||
placeholder="多个用户ID用,隔开 、不填则全部接收"
|
||||
v-model:value="formParams.receiver"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="formParams.sort" clearable />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input type="textarea" placeholder="请输入备注" v-model:value="formParams.remark" />
|
||||
</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>
|
||||
<Edit
|
||||
@reloadTable="loadData"
|
||||
@updateShowModal="updateShowModal"
|
||||
:showModal="showModal"
|
||||
:formParams="formParams"
|
||||
:optionTreeData="optionTreeData"
|
||||
:isUpdate="isUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { onMounted, ref, unref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { Delete, Edit, List, Status } from '@/api/apply/provinces';
|
||||
import { columns } from './columns';
|
||||
import { DeleteOutlined, PlusOutlined } from '@vicons/antd';
|
||||
import { statusActions, statusOptions } from '@/enums/optionsiEnum';
|
||||
|
||||
const typeOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '通知',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '公告',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
const params = ref<any>({
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
content: '',
|
||||
status: null,
|
||||
});
|
||||
|
||||
const rules = {
|
||||
title: {
|
||||
// required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入标题',
|
||||
},
|
||||
};
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '公告标题',
|
||||
componentProps: {
|
||||
placeholder: '请输入公告标题',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入公告标题', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
component: 'NInput',
|
||||
label: '内容',
|
||||
componentProps: {
|
||||
placeholder: '请输入内容关键词',
|
||||
showButton: false,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: statusOptions,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
import {
|
||||
AlignLeftOutlined,
|
||||
FormOutlined,
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@vicons/antd';
|
||||
import { getTreeItem } from '@/utils';
|
||||
import List from './list.vue';
|
||||
import { getProvincesTree, Delete } from '@/api/apply/provinces';
|
||||
import Edit from './edit.vue';
|
||||
import { newState } from './model';
|
||||
const isUpdate = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const searchFormRef = ref<any>({});
|
||||
const formRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
let treeItemKey = ref([]);
|
||||
let expandedKeys = ref([]);
|
||||
const treeData = ref([]);
|
||||
const loading = ref(true);
|
||||
const treeItemTitle = ref('');
|
||||
const checkedId = ref(0);
|
||||
const pattern = ref('');
|
||||
const optionTreeData = ref<any>([]);
|
||||
const formParams = ref(newState(null));
|
||||
|
||||
const resetFormParams = {
|
||||
id: 0,
|
||||
title: '',
|
||||
name: '',
|
||||
type: 1,
|
||||
receiver: '',
|
||||
remark: '',
|
||||
sort: 0,
|
||||
status: 1,
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
};
|
||||
let formParams = ref<any>(resetFormParams);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
dropDownActions: statusActions,
|
||||
select: (key) => {
|
||||
updateStatus(record.id, key);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function addTable() {
|
||||
function openCreateDrawer() {
|
||||
showModal.value = true;
|
||||
formParams.value = resetFormParams;
|
||||
formParams.value = newState(null);
|
||||
isUpdate.value = false;
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...params.value, ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
function openEditDrawer() {
|
||||
showModal.value = true;
|
||||
isUpdate.value = true;
|
||||
}
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
function selectedTree(keys) {
|
||||
if (keys.length) {
|
||||
const treeItem = getTreeItem(unref(treeData), keys[0]);
|
||||
treeItemKey.value = keys;
|
||||
treeItemTitle.value = treeItem.label;
|
||||
formParams.value = newState(treeItem);
|
||||
checkedId.value = treeItem.id;
|
||||
} else {
|
||||
batchDeleteDisabled.value = true;
|
||||
treeItemKey.value = [];
|
||||
treeItemTitle.value = '';
|
||||
}
|
||||
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(formParams.value)
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = record;
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
function handleDel() {
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: `您确定想删除吗?`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
Delete({ ...formParams.value }).then(async (_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
await loadData();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
message.error('已取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
function packHandle() {
|
||||
if (expandedKeys.value.length) {
|
||||
expandedKeys.value = [];
|
||||
} else {
|
||||
expandedKeys.value = unref(treeData).map((item: any) => item.key as string) as [];
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
const treeMenuList = await getProvincesTree();
|
||||
Object.assign(
|
||||
formParams,
|
||||
treeMenuList.list.map((item) => item.key)
|
||||
);
|
||||
treeData.value = [];
|
||||
optionTreeData.value = [
|
||||
{
|
||||
id: 0,
|
||||
key: 0,
|
||||
label: '顶级地区',
|
||||
pid: 0,
|
||||
title: '顶级地区',
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
];
|
||||
treeData.value = treeMenuList.list;
|
||||
optionTreeData.value = optionTreeData.value.concat(treeMenuList.list);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
function onExpandedKeys(keys) {
|
||||
expandedKeys.value = keys;
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function updateStatus(id, status) {
|
||||
Status({ id: id, status: status }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
});
|
||||
});
|
||||
function updateShowModal(value) {
|
||||
showModal.value = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
217
web/src/views/apply/provinces/list.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-result
|
||||
v-show="checkedId <= 0"
|
||||
status="info"
|
||||
title="提示"
|
||||
description="请选择一个想要编辑的省市区"
|
||||
/>
|
||||
|
||||
<div v-show="checkedId > 0">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:columns="listColumns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加数据
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
|
||||
<Edit
|
||||
@reloadTable="reloadTable"
|
||||
@updateShowModal="updateShowModal"
|
||||
:showModal="showModal"
|
||||
:formParams="formParams"
|
||||
:optionTreeData="optionTreeData"
|
||||
:isUpdate="isUpdate"
|
||||
/>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, watch } from 'vue';
|
||||
import { useMessage, useDialog } from 'naive-ui';
|
||||
import { BasicColumn, BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { listColumns, newState, State } from './model';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { getProvincesChildrenList, Delete } from '@/api/apply/provinces';
|
||||
import Edit from './edit.vue';
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
|
||||
interface Props {
|
||||
checkedId?: number;
|
||||
optionTreeData: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { checkedId: 0, optionTreeData: [] });
|
||||
const searchFormRef = ref<any>({});
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const actionRef = ref();
|
||||
const isUpdate = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formParams = ref<State>(newState(null));
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
pid: props.checkedId,
|
||||
label: '',
|
||||
});
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'id',
|
||||
component: 'NInput',
|
||||
label: '地区ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入地区ID',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
params.value.label = e;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '地区名称',
|
||||
componentProps: {
|
||||
placeholder: '请输入地区名称',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
params.value.label = e;
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const actionColumn = reactive<BasicColumn>({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:1 l:2 xl:2 2xl:2' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(null);
|
||||
formParams.value.pid = props.checkedId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
if (props.checkedId <= 0) {
|
||||
return [];
|
||||
}
|
||||
return await getProvincesChildrenList({
|
||||
...{ pid: props.checkedId },
|
||||
...searchFormRef.value?.formModel,
|
||||
...res,
|
||||
});
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
emit('reloadTable');
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '您确定想删除吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(record as State);
|
||||
isUpdate.value = true;
|
||||
}
|
||||
|
||||
function handleSubmit(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(_values: Recordable) {
|
||||
params.value.label = '';
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
watch(props, (_newVal, _oldVal) => {
|
||||
if (params.value.pid === _newVal.checkedId) {
|
||||
return;
|
||||
}
|
||||
params.value.pid = _newVal.checkedId;
|
||||
formParams.value.pid = Number(_newVal.checkedId);
|
||||
if (_newVal.checkedId > 0) {
|
||||
reloadTable();
|
||||
}
|
||||
});
|
||||
|
||||
function updateShowModal(value) {
|
||||
showModal.value = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
117
web/src/views/apply/provinces/model.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { h, ref } from 'vue';
|
||||
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 = [
|
||||
{
|
||||
title: '地区ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '地区名称',
|
||||
key: 'title',
|
||||
},
|
||||
{
|
||||
title: '拼音',
|
||||
key: 'pinyin',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'success',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.pinyin,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '经度',
|
||||
key: 'lng',
|
||||
},
|
||||
{
|
||||
title: '维度',
|
||||
key: 'lat',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
if (isNullObject(row.status)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.sys_normal_disable, row.status),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(options.value.sys_normal_disable, row.status),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export interface State {
|
||||
id: number | null;
|
||||
title: string;
|
||||
pinyin: string;
|
||||
lng: string;
|
||||
lat: string;
|
||||
pid: number;
|
||||
sort: number;
|
||||
status: number;
|
||||
oldId: number;
|
||||
}
|
||||
|
||||
export const defaultState = {
|
||||
id: null,
|
||||
title: '',
|
||||
pinyin: '',
|
||||
lng: '',
|
||||
lat: '',
|
||||
pid: 0,
|
||||
sort: 0,
|
||||
status: 1,
|
||||
oldId: 0,
|
||||
};
|
||||
|
||||
export function newState(state: State | null): State {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(defaultState);
|
||||
}
|
||||
|
||||
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();
|
@@ -4,7 +4,7 @@
|
||||
v-model:show="isShowModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="params?.id > 0 ? '编辑 #' + params?.id : '新建'"
|
||||
:title="params?.id > 0 ? '编辑 #' + params?.id : '添加'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
|
@@ -42,7 +42,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
添加
|
||||
</n-button>
|
||||
<n-button
|
||||
type="error"
|
||||
@@ -246,4 +246,4 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -9,12 +9,11 @@ import { getFileExt } from '@/utils/urlUtils';
|
||||
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
|
||||
import { errorImg } from '@/utils/hotgo';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
const { hasPermission } = usePermission();
|
||||
const $message = window['$message'];
|
||||
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
categoryId: number;
|
||||
@@ -62,8 +61,7 @@ export const options = ref<Options>({
|
||||
sys_normal_disable: [],
|
||||
});
|
||||
|
||||
export const rules = {
|
||||
};
|
||||
export const rules = {};
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
@@ -141,6 +139,7 @@ export const columns = [
|
||||
width: 32,
|
||||
height: 32,
|
||||
src: row.image,
|
||||
onError: errorImg,
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
@@ -226,17 +225,15 @@ export const columns = [
|
||||
|
||||
async function loadOptions() {
|
||||
options.value = await Dicts({
|
||||
types: [
|
||||
'sys_normal_disable',
|
||||
],
|
||||
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();
|
||||
await loadOptions();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-card :bordered="false" title="代码生成"> 你可以在这里查看到平台所有的短信发送记录。 </n-card>
|
||||
<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]" />
|
||||
@@ -23,7 +23,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
生成
|
||||
立即生成
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
@@ -41,7 +41,7 @@
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="生成"
|
||||
title="立即生成"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
@@ -142,7 +142,7 @@
|
||||
{
|
||||
title: '生成ID',
|
||||
key: 'id',
|
||||
width: 100,
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '生成类型',
|
||||
@@ -162,7 +162,7 @@
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 200,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '实体命名',
|
||||
@@ -175,7 +175,7 @@
|
||||
{
|
||||
title: '数据库',
|
||||
key: 'dbName',
|
||||
width: 200,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '数据表',
|
||||
@@ -212,11 +212,11 @@
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
key: 'updatedAt',
|
||||
width: 180,
|
||||
},
|
||||
// {
|
||||
// title: '更新时间',
|
||||
// key: 'updatedAt',
|
||||
// width: 180,
|
||||
// },
|
||||
];
|
||||
|
||||
const dialog = useDialog();
|
||||
|
@@ -22,7 +22,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
添加
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="新建">
|
||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="添加">
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
@@ -312,7 +312,7 @@
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
message.success('新建成功');
|
||||
message.success('添加成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-card :bordered="false" class="proCard" title="任务日志">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
@@ -26,10 +26,6 @@
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">cron刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</template>
|
||||
|
@@ -20,6 +20,7 @@ export const columns = [
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
@@ -30,18 +31,22 @@ export const columns = [
|
||||
}
|
||||
return row.member_name + '(' + row.memberId + ')';
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '请求方式',
|
||||
key: 'method',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '请求路径',
|
||||
key: 'url',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '访问IP',
|
||||
key: 'ip',
|
||||
width: 150,
|
||||
},
|
||||
// {
|
||||
// title: 'IP地区',
|
||||
@@ -65,16 +70,19 @@ export const columns = [
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'Goroutine耗时',
|
||||
title: '处理耗时',
|
||||
key: 'takeUpTime',
|
||||
render(row) {
|
||||
return row.takeUpTime + ' ms';
|
||||
},
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '访问时间',
|
||||
key: 'createdAt',
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
|
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="访问日志">
|
||||
全局的访问日志,记录了管理后台中人员的操作记录和服务响应情况
|
||||
</n-card>
|
||||
</div>
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
@@ -15,6 +20,7 @@
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
@@ -26,10 +32,6 @@
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</template>
|
||||
|
@@ -41,16 +41,30 @@
|
||||
title="报错信息"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="报错状态码"> {{ data.errorCode }} </n-descriptions-item>
|
||||
<n-descriptions-item label="报错消息">
|
||||
<n-tag type="success"> {{ data.errorMsg }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="报错日志">
|
||||
<n-tag type="success"> {{ data.errorData }} </n-tag>
|
||||
<n-descriptions-item label="错误状态码"> {{ data.errorCode }} </n-descriptions-item>
|
||||
<n-descriptions-item label="错误提示">
|
||||
<n-tag type="error"> {{ data.errorMsg }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="堆栈打印"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.errorData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
@@ -59,7 +73,7 @@
|
||||
title="Header请求头"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.headerData ?? '{}')"
|
||||
:value="data.headerData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
@@ -76,7 +90,7 @@
|
||||
title="GET参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.getData ?? '{}')"
|
||||
:value="data.getData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
@@ -93,7 +107,7 @@
|
||||
title="POST参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.postData ?? '{}')"
|
||||
:value="data.postData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
|
@@ -1,80 +0,0 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '模块',
|
||||
key: 'module',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.module == 'admin' ? 'info' : 'success',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.module,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
key: 'member_name',
|
||||
render(row) {
|
||||
if (row.memberId === 0) {
|
||||
return row.member_name;
|
||||
}
|
||||
return row.member_name + '(' + row.memberId + ')';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求方式',
|
||||
key: 'method',
|
||||
},
|
||||
{
|
||||
title: '请求路径',
|
||||
key: 'url',
|
||||
},
|
||||
{
|
||||
title: '访问IP',
|
||||
key: 'ip',
|
||||
},
|
||||
// {
|
||||
// title: 'IP地区',
|
||||
// key: 'region',
|
||||
// },
|
||||
{
|
||||
title: '状态码',
|
||||
key: 'errorCode',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.errorCode == 0 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.errorMsg + '(' + row.errorCode + ')',
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Goroutine耗时',
|
||||
key: 'takeUpTime',
|
||||
render(row) {
|
||||
return row.takeUpTime + ' ms';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '访问时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
];
|
@@ -1,200 +1,104 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="登录日志"> 在这里会记录管理后台所有的来访登录情况 </n-card>
|
||||
</div>
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">login-log刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="error"
|
||||
@click="handleBatchDelete"
|
||||
:disabled="batchDeleteDisabled"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/loginLog/delete'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="handleExport"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/loginLog/export'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { getLogList, Delete } from '@/api/log/log';
|
||||
import { columns } from './columns';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { List, Export, Delete } from '@/api/loginLog';
|
||||
import { State, columns, schemas } from './model';
|
||||
import { ExportOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { DeleteOutlined } from '@vicons/antd';
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const router = useRouter();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const searchFormRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'member_id',
|
||||
component: 'NInput',
|
||||
label: '操作人员',
|
||||
componentProps: {
|
||||
placeholder: '请输入操作人员ID',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
component: 'NInput',
|
||||
label: '访问路径',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机访问路径',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: '访问IP',
|
||||
componentProps: {
|
||||
placeholder: '请输入IP地址',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'method',
|
||||
component: 'NSelect',
|
||||
label: '请求方式',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求方式',
|
||||
options: [
|
||||
{
|
||||
label: 'GET',
|
||||
value: 'GET',
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 'POST',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
component: 'NDatePicker',
|
||||
label: '访问时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
// defaultValue: [new Date() - 86400000 * 30, new Date()],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'take_up_time',
|
||||
component: 'NSelect',
|
||||
label: '请求耗时',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求耗时',
|
||||
options: [
|
||||
{
|
||||
label: '50ms内',
|
||||
value: '50',
|
||||
},
|
||||
{
|
||||
label: '100ms内',
|
||||
value: '100',
|
||||
},
|
||||
{
|
||||
label: '200ms内',
|
||||
value: '200',
|
||||
},
|
||||
{
|
||||
label: '500ms内',
|
||||
value: '500',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'error_code',
|
||||
component: 'NSelect',
|
||||
label: '状态码',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态码',
|
||||
options: [
|
||||
{
|
||||
label: '0 成功',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: '-1 失败',
|
||||
value: '-1',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
width: 300,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
// fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '查看详情',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
onClick: handleView.bind(null, record),
|
||||
auth: ['/loginLog/view'],
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/loginLog/delete'],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -207,87 +111,62 @@
|
||||
schemas,
|
||||
});
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
batchDeleteDisabled.value = true;
|
||||
}
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...searchFormRef.value?.formModel, ...res });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await getLogList({ ...formParams.value, ...params.value, ...res });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
router.push({ name: 'login_log_view', params: { id: record.id } });
|
||||
function handleView(record: Recordable) {
|
||||
router.push({ name: 'log_view', params: { id: record.sysLogId } });
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = values;
|
||||
reloadTable();
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = {};
|
||||
reloadTable();
|
||||
function handleBatchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
189
web/src/views/log/login-log/model.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
import { loginStatusOptions } from '@/enums/optionsiEnum';
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
reqId: string;
|
||||
memberId: number;
|
||||
username: string;
|
||||
response: any;
|
||||
loginAt: number;
|
||||
errMsg: string;
|
||||
status: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export const defaultState = {
|
||||
id: 0,
|
||||
reqId: '',
|
||||
memberId: 0,
|
||||
username: '',
|
||||
response: null,
|
||||
loginAt: 0,
|
||||
errMsg: '',
|
||||
status: 1,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
};
|
||||
|
||||
export function newState(state: State | null): State {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(defaultState);
|
||||
}
|
||||
|
||||
export const options = ref<Options>({
|
||||
sys_normal_disable: [],
|
||||
});
|
||||
|
||||
export const rules = {};
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'username',
|
||||
component: 'NInput',
|
||||
label: '用户名',
|
||||
componentProps: {
|
||||
placeholder: '请输入用户名',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'sysLogIp',
|
||||
component: 'NInput',
|
||||
label: 'IP地址',
|
||||
componentProps: {
|
||||
placeholder: '请输入IP地址',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
options: loginStatusOptions,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'loginAt',
|
||||
component: 'NDatePicker',
|
||||
label: '登录时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '记录ID',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
key: 'username',
|
||||
width: 120,
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'info',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.username,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '登录IP',
|
||||
key: 'sysLogIp',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: 'IP归属地',
|
||||
key: 'region',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '浏览器',
|
||||
key: 'browser',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '操作系统',
|
||||
key: 'os',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
if (isNullObject(row.status)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(loginStatusOptions, row.status),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(loginStatusOptions, row.status),
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '提示信息',
|
||||
key: 'errMsg',
|
||||
render(row) {
|
||||
if (row.errMsg !== '') {
|
||||
return row.errMsg;
|
||||
}
|
||||
|
||||
if (row.status === 1) {
|
||||
return '登录成功';
|
||||
}
|
||||
return ``;
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '登录时间',
|
||||
key: 'loginAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
@@ -1,80 +0,0 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '模块',
|
||||
key: 'module',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.module == 'admin' ? 'info' : 'success',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.module,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
key: 'member_name',
|
||||
render(row) {
|
||||
if (row.memberId === 0) {
|
||||
return row.member_name;
|
||||
}
|
||||
return row.member_name + '(' + row.memberId + ')';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求方式',
|
||||
key: 'method',
|
||||
},
|
||||
{
|
||||
title: '请求路径',
|
||||
key: 'url',
|
||||
},
|
||||
{
|
||||
title: '访问IP',
|
||||
key: 'ip',
|
||||
},
|
||||
// {
|
||||
// title: 'IP地区',
|
||||
// key: 'region',
|
||||
// },
|
||||
{
|
||||
title: '状态码',
|
||||
key: 'errorCode',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.errorCode == 0 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.errorMsg + '(' + row.errorCode + ')',
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Goroutine耗时',
|
||||
key: 'takeUpTime',
|
||||
render(row) {
|
||||
return row.takeUpTime + ' ms';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '访问时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
];
|
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<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>
|
||||
@@ -26,140 +27,161 @@
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">sms-log刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { NTag, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { getLogList, Delete } from '@/api/log/log';
|
||||
import { columns } from './columns';
|
||||
import { getLogList, Delete } from '@/api/log/smslog';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { DeleteOutlined } from '@vicons/antd';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
|
||||
const options = ref<Options>({
|
||||
config_sms_template: [],
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '事件模板',
|
||||
key: 'event',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.config_sms_template, row.event),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(options.value.config_sms_template, row.event),
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
key: 'mobile',
|
||||
render(row) {
|
||||
return row.mobile;
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '验证码或短信内容',
|
||||
key: 'code',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '验证次数',
|
||||
key: 'times',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '发送者IP',
|
||||
key: 'ip',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '状态码',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 2 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.status == 2 ? '已使用' : '未使用'),
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
key: 'updatedAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
const dialog = useDialog();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const searchFormRef = ref<any>({});
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'member_id',
|
||||
component: 'NInput',
|
||||
label: '操作人员',
|
||||
field: 'event',
|
||||
component: 'NSelect',
|
||||
label: '事件模板',
|
||||
componentProps: {
|
||||
placeholder: '请输入操作人员ID',
|
||||
placeholder: '请选择事件模板',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
component: 'NInput',
|
||||
label: '手机号',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机号',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
component: 'NInput',
|
||||
label: '访问路径',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机访问路径',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: '访问IP',
|
||||
label: '发送者IP',
|
||||
componentProps: {
|
||||
placeholder: '请输入IP地址',
|
||||
placeholder: '请输入IP',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'method',
|
||||
component: 'NSelect',
|
||||
label: '请求方式',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求方式',
|
||||
options: [
|
||||
{
|
||||
label: 'GET',
|
||||
value: 'GET',
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 'POST',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
component: 'NDatePicker',
|
||||
label: '访问时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
// defaultValue: [new Date() - 86400000 * 30, new Date()],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'take_up_time',
|
||||
component: 'NSelect',
|
||||
label: '请求耗时',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求耗时',
|
||||
options: [
|
||||
{
|
||||
label: '50ms内',
|
||||
value: '50',
|
||||
},
|
||||
{
|
||||
label: '100ms内',
|
||||
value: '100',
|
||||
},
|
||||
{
|
||||
label: '200ms内',
|
||||
value: '200',
|
||||
},
|
||||
{
|
||||
label: '500ms内',
|
||||
value: '500',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'error_code',
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态码',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态码',
|
||||
options: [
|
||||
{
|
||||
label: '0 成功',
|
||||
value: '0',
|
||||
label: '未使用',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '-1 失败',
|
||||
value: '-1',
|
||||
label: '已使用',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
@@ -167,30 +189,21 @@
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
const router = useRouter();
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
// fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '查看详情',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
@@ -207,6 +220,7 @@
|
||||
});
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
@@ -217,19 +231,25 @@
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
// message.error('不确定');
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -239,10 +259,11 @@
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
@@ -251,13 +272,14 @@
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
// message.error('不确定');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await getLogList({ ...formParams.value, ...params.value, ...res });
|
||||
await loadOptions();
|
||||
return await getLogList({ ...searchFormRef.value?.formModel, ...res });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
@@ -265,19 +287,32 @@
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
router.push({ name: 'sms_view', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
formParams.value = values;
|
||||
console.log(values);
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = {};
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
async function loadOptions() {
|
||||
options.value = await Dicts({
|
||||
types: ['config_sms_template'],
|
||||
});
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'event':
|
||||
item.componentProps.options = options.value.config_sms_template;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -1,136 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
:title="data.id ? '日志详情 ID:' + data.id : '日志详情'"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="请求方式">{{ data.method }}</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>请求地址</template>
|
||||
{{ data.url }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
|
||||
<n-descriptions-item label="访问IP">{{ data.ip }}</n-descriptions-item>
|
||||
<n-descriptions-item label="IP归属地">河南 郑州</n-descriptions-item>
|
||||
<n-descriptions-item label="链路ID">{{ data.reqId }}</n-descriptions-item>
|
||||
<n-descriptions-item label="响应时间">{{
|
||||
timestampToTime(data.timestamp)
|
||||
}}</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item label="创建时间">{{ data.createdAt }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="访问代理"
|
||||
>
|
||||
{{ data.userAgent }}
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="报错信息"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="报错状态码"> {{ data.errorCode }} </n-descriptions-item>
|
||||
<n-descriptions-item label="报错消息">
|
||||
<n-tag type="success"> {{ data.errorMsg }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="报错日志">
|
||||
<n-tag type="success"> {{ data.errorData }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="Header请求头"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.headerData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="GET参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.getData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="POST参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.postData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import 'vue3-json-viewer/dist/index.css';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { View } from '@/api/log/log';
|
||||
import { timestampToTime } from '@/utils/dateUtil';
|
||||
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const logId = Number(router.currentRoute.value.params.id);
|
||||
|
||||
onMounted(async () => {
|
||||
if (logId === undefined || logId < 1) {
|
||||
message.error('ID不正确,请检查!');
|
||||
return;
|
||||
}
|
||||
|
||||
await getInfo();
|
||||
});
|
||||
|
||||
const data = ref({});
|
||||
|
||||
const getInfo = async () => {
|
||||
data.value = await View({ id: logId });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
@@ -29,12 +29,12 @@
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="password">
|
||||
<n-form-item path="pass">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.password"
|
||||
type="password"
|
||||
showPasswordOn="click"
|
||||
v-model:value="formInline.pass"
|
||||
type="pass"
|
||||
showpassOn="click"
|
||||
placeholder="请输入密码"
|
||||
>
|
||||
<template #prefix>
|
||||
@@ -44,6 +44,28 @@
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="code">
|
||||
<n-input-group>
|
||||
<n-input
|
||||
:style="{ width: '100%' }"
|
||||
placeholder="验证码"
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.code"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695" :component="SafetyCertificateOutlined" />
|
||||
</template>
|
||||
<template #suffix> </template>
|
||||
</n-input>
|
||||
<img
|
||||
style="width: 100px"
|
||||
:src="codeBase64"
|
||||
@click="refreshCode"
|
||||
loading="lazy"
|
||||
alt="点击获取"
|
||||
/>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
<n-form-item class="default-color">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex-initial">
|
||||
@@ -90,16 +112,22 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { ref, unref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import { PersonOutline, LockClosedOutline, LogoGithub, LogoFacebook } from '@vicons/ionicons5';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
import { SafetyCertificateOutlined } from '@vicons/antd';
|
||||
import { GetCaptcha } from '@/api/base';
|
||||
import { aesEcb } from '@/utils/encrypt';
|
||||
|
||||
interface FormState {
|
||||
username: string;
|
||||
pass: string;
|
||||
cid: string;
|
||||
code: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
@@ -107,17 +135,21 @@
|
||||
const message = useMessage();
|
||||
const loading = ref(false);
|
||||
const autoLogin = ref(true);
|
||||
const codeBase64 = ref('');
|
||||
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
|
||||
|
||||
const formInline = reactive({
|
||||
const formInline = ref<FormState>({
|
||||
username: '',
|
||||
pass: '',
|
||||
cid: '',
|
||||
code: '',
|
||||
password: '',
|
||||
isCaptcha: true,
|
||||
});
|
||||
|
||||
const rules = {
|
||||
username: { required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
password: { required: true, message: '请输入密码', trigger: 'blur' },
|
||||
pass: { required: true, message: '请输入密码', trigger: 'blur' },
|
||||
code: { required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
@@ -129,17 +161,15 @@
|
||||
e.preventDefault();
|
||||
formRef.value.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
const { username, password } = formInline;
|
||||
message.loading('登录中...');
|
||||
loading.value = true;
|
||||
|
||||
const params: FormState = {
|
||||
username,
|
||||
password,
|
||||
};
|
||||
|
||||
try {
|
||||
const { code, message: msg } = await userStore.login(params);
|
||||
const { code, message: msg } = await userStore.login({
|
||||
username: formInline.value.username,
|
||||
password: aesEcb.encrypt(formInline.value.pass),
|
||||
cid: formInline.value.cid,
|
||||
code: formInline.value.code,
|
||||
});
|
||||
message.destroyAll();
|
||||
if (code == ResultEnum.SUCCESS) {
|
||||
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
||||
@@ -149,6 +179,7 @@
|
||||
} else await router.replace(toPath);
|
||||
} else {
|
||||
message.info(msg || '登录失败');
|
||||
await refreshCode();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -158,6 +189,19 @@
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function refreshCode() {
|
||||
const data = await GetCaptcha();
|
||||
codeBase64.value = data.base64;
|
||||
formInline.value.cid = data.cid;
|
||||
formInline.value.code = '';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(function () {
|
||||
refreshCode();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@@ -6,7 +6,7 @@ export const columns = [
|
||||
{
|
||||
title: '会话编号',
|
||||
key: 'id',
|
||||
width: 240,
|
||||
width: 280,
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
|
@@ -12,7 +12,6 @@
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
/>
|
||||
</n-card>
|
||||
@@ -40,21 +39,29 @@
|
||||
},
|
||||
rules: [{ trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'addr',
|
||||
component: 'NInput',
|
||||
label: '登录地址',
|
||||
componentProps: {
|
||||
placeholder: '请输入登录地址',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ trigger: ['blur'] }],
|
||||
},
|
||||
];
|
||||
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
// fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
@@ -93,7 +100,7 @@
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await OnlineList({ ...formParams.value, ...params.value, ...res });
|
||||
return await OnlineList({ ...formParams.value, ...res });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
|
@@ -1,80 +0,0 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '模块',
|
||||
key: 'module',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.module == 'admin' ? 'info' : 'success',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.module,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
key: 'member_name',
|
||||
render(row) {
|
||||
if (row.memberId === 0) {
|
||||
return row.member_name;
|
||||
}
|
||||
return row.member_name + '(' + row.memberId + ')';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求方式',
|
||||
key: 'method',
|
||||
},
|
||||
{
|
||||
title: '请求路径',
|
||||
key: 'url',
|
||||
},
|
||||
{
|
||||
title: '访问IP',
|
||||
key: 'ip',
|
||||
},
|
||||
// {
|
||||
// title: 'IP地区',
|
||||
// key: 'region',
|
||||
// },
|
||||
{
|
||||
title: '状态码',
|
||||
key: 'errorCode',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.errorCode == 0 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.errorMsg + '(' + row.errorCode + ')',
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Goroutine耗时',
|
||||
key: 'takeUpTime',
|
||||
render(row) {
|
||||
return row.takeUpTime + ' ms';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '访问时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
];
|
@@ -1,199 +1,149 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="服务日志">
|
||||
在这里开发者可以快速定位服务端在运行时产生的重要日志,方便排查系统异常和日常运维
|
||||
</n-card>
|
||||
</div>
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="error"
|
||||
@click="handleBatchDelete"
|
||||
:disabled="batchDeleteDisabled"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/serveLog/delete'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="handleExport"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/serveLog/delete'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">系统刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" style="width: 920px">
|
||||
<n-card
|
||||
:bordered="false"
|
||||
title="日志内容"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
>
|
||||
<n-alert type="error" :show-icon="false">
|
||||
{{ preview?.content }}
|
||||
</n-alert>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="堆栈打印"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(preview?.stack)"
|
||||
:expand-depth="10"
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showModal = false)">关闭</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { getLogList, Delete } from '@/api/log/log';
|
||||
import { columns } from './columns';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { List, Export, Delete } from '@/api/serveLog';
|
||||
import { State, columns, schemas } from './model';
|
||||
import { ExportOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { DeleteOutlined } from '@vicons/antd';
|
||||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import 'vue3-json-viewer/dist/index.css';
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const router = useRouter();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const searchFormRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'member_id',
|
||||
component: 'NInput',
|
||||
label: '操作人员',
|
||||
componentProps: {
|
||||
placeholder: '请输入操作人员ID',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
component: 'NInput',
|
||||
label: '访问路径',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机访问路径',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: '访问IP',
|
||||
componentProps: {
|
||||
placeholder: '请输入IP地址',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'method',
|
||||
component: 'NSelect',
|
||||
label: '请求方式',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求方式',
|
||||
options: [
|
||||
{
|
||||
label: 'GET',
|
||||
value: 'GET',
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 'POST',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
component: 'NDatePicker',
|
||||
label: '访问时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
// defaultValue: [new Date() - 86400000 * 30, new Date()],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'take_up_time',
|
||||
component: 'NSelect',
|
||||
label: '请求耗时',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求耗时',
|
||||
options: [
|
||||
{
|
||||
label: '50ms内',
|
||||
value: '50',
|
||||
},
|
||||
{
|
||||
label: '100ms内',
|
||||
value: '100',
|
||||
},
|
||||
{
|
||||
label: '200ms内',
|
||||
value: '200',
|
||||
},
|
||||
{
|
||||
label: '500ms内',
|
||||
value: '500',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'error_code',
|
||||
component: 'NSelect',
|
||||
label: '状态码',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态码',
|
||||
options: [
|
||||
{
|
||||
label: '0 成功',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: '-1 失败',
|
||||
value: '-1',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
});
|
||||
const showModal = ref(false);
|
||||
const formParams = ref<State>();
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
width: 300,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
// fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '查看详情',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
label: '详细报错',
|
||||
onClick: handleStack.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '访问日志',
|
||||
onClick: handleView.bind(null, record),
|
||||
ifShow: record.sysLogId > 0,
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/serveLog/delete'],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -206,17 +156,30 @@
|
||||
schemas,
|
||||
});
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
batchDeleteDisabled.value = true;
|
||||
}
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...searchFormRef.value?.formModel, ...res });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
const preview = ref<Recordable>();
|
||||
function handleStack(record: Recordable) {
|
||||
console.log('handleStack record:' + JSON.stringify(record));
|
||||
showModal.value = true;
|
||||
preview.value = record;
|
||||
}
|
||||
|
||||
function handleView(record: Recordable) {
|
||||
router.push({ name: 'log_view', params: { id: record.sysLogId } });
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
@@ -225,7 +188,7 @@
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
@@ -235,15 +198,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
function handleBatchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
@@ -253,26 +216,9 @@
|
||||
});
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await getLogList({ ...formParams.value, ...params.value, ...res });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
router.push({ name: 'serve_log_view', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
formParams.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(_values: Recordable) {
|
||||
formParams.value = {};
|
||||
reloadTable();
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
189
web/src/views/monitor/serve-log/model.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NAvatar, NImage, NTag, NSwitch, NRate } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
|
||||
import { isArray, isNullObject } from '@/utils/is';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
|
||||
import { format } from 'date-fns';
|
||||
import { getOptionLabel, getOptionTag, Options, errorImg } from '@/utils/hotgo';
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
env: string;
|
||||
traceid: string;
|
||||
levelFormat: string;
|
||||
content: string;
|
||||
stack: any;
|
||||
line: string;
|
||||
triggerNs: number;
|
||||
status: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export const defaultState = {
|
||||
id: 0,
|
||||
env: '',
|
||||
traceid: '',
|
||||
levelFormat: '',
|
||||
content: '',
|
||||
stack: null,
|
||||
line: '',
|
||||
triggerNs: 0,
|
||||
status: 1,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
};
|
||||
|
||||
export function newState(state: State | null): State {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(defaultState);
|
||||
}
|
||||
|
||||
export const options = ref<Options>({
|
||||
sys_normal_disable: [],
|
||||
sys_log_type: [],
|
||||
});
|
||||
|
||||
export const rules = {};
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'traceId',
|
||||
component: 'NInput',
|
||||
label: '链路ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入链路ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'levelFormat',
|
||||
component: 'NSelect',
|
||||
label: '日志级别',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择日志级别',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '日志ID',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '链路ID',
|
||||
key: 'traceId',
|
||||
width: 280,
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'default',
|
||||
bordered: false,
|
||||
checkable: true,
|
||||
},
|
||||
{
|
||||
default: () => row.traceId,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '日志级别',
|
||||
key: 'levelFormat',
|
||||
render(row) {
|
||||
if (isNullObject(row.levelFormat)) {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.sys_log_type, row.levelFormat),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => getOptionLabel(options.value.sys_log_type, row.levelFormat),
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '日志内容',
|
||||
key: 'content',
|
||||
width: 320,
|
||||
},
|
||||
{
|
||||
title: '调用行',
|
||||
key: 'line',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '触发时间',
|
||||
key: 'triggerNs',
|
||||
width: 200,
|
||||
render(row) {
|
||||
if (row.triggerNs <= 0) {
|
||||
return '-';
|
||||
}
|
||||
return format(new Date(row.triggerNs / 1000000), 'yyyy-MM-dd HH:mm:ss.SSS');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '记录时间',
|
||||
key: 'createdAt',
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
|
||||
async function loadOptions() {
|
||||
options.value = await Dicts({
|
||||
types: ['sys_normal_disable', 'sys_log_type'],
|
||||
});
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.sys_normal_disable;
|
||||
break;
|
||||
case 'levelFormat':
|
||||
item.componentProps.options = options.value.sys_log_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await loadOptions();
|
@@ -1,136 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
:title="data.id ? '日志详情 ID:' + data.id : '日志详情'"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="请求方式">{{ data.method }}</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>请求地址</template>
|
||||
{{ data.url }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
|
||||
<n-descriptions-item label="访问IP">{{ data.ip }}</n-descriptions-item>
|
||||
<n-descriptions-item label="IP归属地">河南 郑州</n-descriptions-item>
|
||||
<n-descriptions-item label="链路ID">{{ data.reqId }}</n-descriptions-item>
|
||||
<n-descriptions-item label="响应时间">{{
|
||||
timestampToTime(data.timestamp)
|
||||
}}</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item label="创建时间">{{ data.createdAt }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="访问代理"
|
||||
>
|
||||
{{ data.userAgent }}
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="报错信息"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="报错状态码"> {{ data.errorCode }} </n-descriptions-item>
|
||||
<n-descriptions-item label="报错消息">
|
||||
<n-tag type="success"> {{ data.errorMsg }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="报错日志">
|
||||
<n-tag type="success"> {{ data.errorData }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="Header请求头"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.headerData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="GET参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.getData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="POST参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.postData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import 'vue3-json-viewer/dist/index.css';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { View } from '@/api/log/log';
|
||||
import { timestampToTime } from '@/utils/dateUtil';
|
||||
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const logId = Number(router.currentRoute.value.params.id);
|
||||
|
||||
onMounted(async () => {
|
||||
if (logId === undefined || logId < 1) {
|
||||
message.error('ID不正确,请检查!');
|
||||
return;
|
||||
}
|
||||
|
||||
await getInfo();
|
||||
});
|
||||
|
||||
const data = ref({});
|
||||
|
||||
const getInfo = async () => {
|
||||
data.value = await View({ id: logId });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
@@ -20,7 +20,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建部门
|
||||
添加部门
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
:data="data"
|
||||
:row-key="rowKey"
|
||||
:loading="loading"
|
||||
:resizeHeightOffset="-20000"
|
||||
default-expand-all
|
||||
/>
|
||||
</n-space>
|
||||
@@ -37,7 +38,7 @@
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="formParams?.id > 0 ? '编辑部门 #' + formParams?.id : '新建部门'"
|
||||
:title="formParams?.id > 0 ? '编辑部门 #' + formParams?.id : '添加部门'"
|
||||
>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
@@ -73,9 +74,9 @@
|
||||
<n-input placeholder="请输入邮箱" v-model:value="formParams.email" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="formParams.sort" clearable />
|
||||
</n-form-item>
|
||||
<!-- <n-form-item label="排序" path="sort">-->
|
||||
<!-- <n-input-number v-model:value="formParams.sort" clearable />-->
|
||||
<!-- </n-form-item>-->
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
@@ -198,18 +199,26 @@
|
||||
const data = ref([]);
|
||||
const columns: DataTableColumns<RowData> = [
|
||||
{
|
||||
type: 'selection',
|
||||
},
|
||||
{
|
||||
title: '部门名称',
|
||||
title: '部门',
|
||||
key: 'name',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: 'info',
|
||||
},
|
||||
{
|
||||
default: () => row.name,
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '部门ID',
|
||||
key: 'index',
|
||||
width: 100,
|
||||
},
|
||||
// {
|
||||
// title: '部门ID',
|
||||
// key: 'index',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: '部门编码',
|
||||
key: 'code',
|
||||
@@ -250,15 +259,15 @@
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sort',
|
||||
width: 80,
|
||||
},
|
||||
// {
|
||||
// title: '排序',
|
||||
// key: 'sort',
|
||||
// width: 80,
|
||||
// },
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 200,
|
||||
width: 150,
|
||||
render: (rows, _) => {
|
||||
return rows.createdAt; //timestampToTime();
|
||||
},
|
||||
@@ -300,7 +309,7 @@
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = record;
|
||||
formParams.value = cloneDeep(record);
|
||||
formParams.value.children = null;
|
||||
optionsDefaultValue.value = formParams.value.pid;
|
||||
}
|
||||
|
@@ -5,17 +5,23 @@ export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 100,
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '岗位名称',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '岗位名称',
|
||||
title: '岗位',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: 'info',
|
||||
},
|
||||
{
|
||||
default: () => row.name,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '岗位编码',
|
||||
@@ -42,15 +48,15 @@ export const columns = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sort',
|
||||
width: 100,
|
||||
},
|
||||
// {
|
||||
// title: '排序',
|
||||
// key: 'sort',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 100,
|
||||
width: 150,
|
||||
render: (rows, _) => {
|
||||
return rows.createdAt;
|
||||
},
|
||||
|
@@ -30,7 +30,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建岗位
|
||||
添加岗位
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
@@ -48,7 +48,7 @@
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="formParams?.id > 0 ? '编辑岗位 #' + formParams?.id : '新建岗位'"
|
||||
:title="formParams?.id > 0 ? '编辑岗位 #' + formParams?.id : '添加岗位'"
|
||||
>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
@@ -65,9 +65,9 @@
|
||||
<n-input placeholder="请输入岗位编码" v-model:value="formParams.code" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="formParams.sort" clearable />
|
||||
</n-form-item>
|
||||
<!-- <n-form-item label="排序" path="sort">-->
|
||||
<!-- <n-input-number v-model:value="formParams.sort" clearable />-->
|
||||
<!-- </n-form-item>-->
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
@@ -244,18 +244,14 @@
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(formParams.value)
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
Edit(formParams.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
@@ -293,15 +289,11 @@
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
console.log('_res:' + JSON.stringify(_res));
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
|
@@ -30,7 +30,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建用户
|
||||
添加用户
|
||||
</n-button>
|
||||
<n-button
|
||||
type="error"
|
||||
@@ -52,7 +52,7 @@
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="formParams?.id > 0 ? '编辑用户 #' + formParams?.id : '新建用户'"
|
||||
:title="formParams?.id > 0 ? '编辑用户 #' + formParams?.id : '添加用户'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
|
@@ -355,7 +355,6 @@
|
||||
import CreateDrawer from './CreateDrawer.vue';
|
||||
import IconSelector from '@/components/IconSelector/index.vue';
|
||||
import { State, newState } from '@/views/permission/menu/model';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
|
||||
const menuTypes = [
|
||||
{
|
||||
|
@@ -2,12 +2,12 @@ import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
// {
|
||||
// title: '角色ID',
|
||||
// key: 'id',
|
||||
// },
|
||||
{
|
||||
title: '角色ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '角色名称',
|
||||
title: '角色',
|
||||
key: 'name',
|
||||
render(row) {
|
||||
return h(
|
||||
@@ -20,13 +20,19 @@ export const columns = [
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '上级角色',
|
||||
key: 'pid',
|
||||
title: '角色编码',
|
||||
key: 'key',
|
||||
width: 150,
|
||||
},
|
||||
// {
|
||||
// title: '上级角色',
|
||||
// key: 'pid',
|
||||
// },
|
||||
{
|
||||
title: '是否默认角色',
|
||||
title: '默认角色',
|
||||
key: 'isDefault',
|
||||
render(row) {
|
||||
return h(
|
||||
@@ -39,17 +45,41 @@ export const columns = [
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sort',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 1 ? 'info' : 'error',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.status == 1 ? '正常' : '已禁用'),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
@@ -11,6 +11,8 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:pagination="false"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
@@ -22,15 +24,11 @@
|
||||
添加角色
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<template #action>
|
||||
<TableAction />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
|
||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" :title="editRoleTitle">
|
||||
<div class="py-3 menu-list">
|
||||
<div class="py-3 menu-list" :style="{ maxHeight: '90vh', height: '70vh' }">
|
||||
<n-tree
|
||||
block-line
|
||||
cascade
|
||||
@@ -58,7 +56,12 @@
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<n-modal v-model:show="showModal2" :show-icon="false" preset="dialog" title="添加角色">
|
||||
<n-modal
|
||||
v-model:show="showModal2"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="formParams.id > 0 ? '编辑角色 #' + formParams.id : '添加角色'"
|
||||
>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
@@ -68,7 +71,13 @@
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="上级角色" path="pid">
|
||||
<n-input placeholder="请输入上级角色ID" v-model:value="formParams.pid" />
|
||||
<n-tree-select
|
||||
:options="optionTreeData"
|
||||
:default-value="formParams.pid"
|
||||
key-field="id"
|
||||
label-field="name"
|
||||
:on-update:value="onUpdateValuePid"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="角色名称" path="name">
|
||||
<n-input placeholder="请输入名称" v-model:value="formParams.name" />
|
||||
@@ -76,9 +85,9 @@
|
||||
<n-form-item label="权限编码" path="key">
|
||||
<n-input placeholder="请输入" v-model:value="formParams.key" />
|
||||
</n-form-item>
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="formParams.sort" clearable />
|
||||
</n-form-item>
|
||||
<!-- <n-form-item label="排序" path="sort">-->
|
||||
<!-- <n-input-number v-model:value="formParams.sort" clearable />-->
|
||||
<!-- </n-form-item>-->
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
@@ -117,11 +126,7 @@
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="数据范围" path="dataScope">
|
||||
<n-select
|
||||
v-model:value="dataForm.dataScope"
|
||||
:options="dataScopeOption"
|
||||
@update:value="handleUpdateDataScopeValue"
|
||||
/>
|
||||
<n-select v-model:value="dataForm.dataScope" :options="dataScopeOption" />
|
||||
</n-form-item>
|
||||
<n-form-item label="自定义权限" path="customDept" v-if="dataForm.dataScope === 4">
|
||||
<n-tree-select
|
||||
@@ -148,8 +153,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, onMounted, reactive, ref } from 'vue';
|
||||
import { TreeSelectOption, useDialog, useMessage, SelectOption } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { TreeSelectOption, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicColumn, BasicTable, TableAction } from '@/components/Table';
|
||||
import {
|
||||
Delete,
|
||||
Edit,
|
||||
@@ -181,6 +186,7 @@
|
||||
const expandedKeys = ref([]);
|
||||
const checkedKeys = ref<any>([]);
|
||||
const updatePermissionsParams = ref<any>({});
|
||||
const optionTreeData = ref<any>([]);
|
||||
|
||||
const rules = {
|
||||
name: {
|
||||
@@ -211,7 +217,7 @@
|
||||
|
||||
let formParams = ref<any>(cloneDeep(defaultState));
|
||||
|
||||
const actionColumn = reactive({
|
||||
const actionColumn = reactive<BasicColumn>({
|
||||
width: 320,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
@@ -254,7 +260,7 @@
|
||||
});
|
||||
|
||||
const loadDataTable = async (res: any) => {
|
||||
return await getRoleList({ ...res });
|
||||
return await getRoleList({ ...res, ...{ pageSize: 100, page: 1 } });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys: any[]) {
|
||||
@@ -263,6 +269,7 @@
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
loadDataList();
|
||||
}
|
||||
|
||||
function confirmForm(e: any) {
|
||||
@@ -332,7 +339,7 @@
|
||||
async function handleMenuAuth(record: Recordable) {
|
||||
editRoleTitle.value = `分配 ${record.name} 的菜单权限`;
|
||||
const data = await GetPermissions({ ...{ id: record.id } });
|
||||
checkedKeys.value = data.menuIds; //record.menu_keys;
|
||||
checkedKeys.value = data.menuIds;
|
||||
updatePermissionsParams.value.id = record.id;
|
||||
showModal.value = true;
|
||||
}
|
||||
@@ -348,8 +355,6 @@
|
||||
showDataModal.value = true;
|
||||
}
|
||||
|
||||
function handleUpdateDataScopeValue(value: string, option: SelectOption) {}
|
||||
|
||||
function handleUpdateDeptValue(
|
||||
value: string | number | Array<string | number> | null,
|
||||
_option: TreeSelectOption | null | Array<TreeSelectOption | null>
|
||||
@@ -362,7 +367,6 @@
|
||||
dataFormBtnLoading.value = true;
|
||||
dataFormRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('dataForm.value:' + JSON.stringify(dataForm.value));
|
||||
DataScopeEdit(dataForm.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
@@ -404,11 +408,26 @@
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadDataList();
|
||||
await loadMenuList();
|
||||
await loadDeptList();
|
||||
await loadDataScopeSelect();
|
||||
});
|
||||
|
||||
async function loadDataList() {
|
||||
const data = await getRoleList({ pageSize: 100, page: 1 });
|
||||
optionTreeData.value = [
|
||||
{
|
||||
id: 0,
|
||||
key: 0,
|
||||
label: '顶级角色',
|
||||
pid: 0,
|
||||
name: '顶级角色',
|
||||
},
|
||||
];
|
||||
optionTreeData.value = optionTreeData.value.concat(data.list);
|
||||
}
|
||||
|
||||
async function loadMenuList() {
|
||||
const treeMenuList = await getMenuList();
|
||||
expandedKeys.value = treeMenuList.list.map((item) => item.key);
|
||||
@@ -426,6 +445,10 @@
|
||||
const option = await DataScopeSelect();
|
||||
dataScopeOption.value = option.list;
|
||||
}
|
||||
|
||||
function onUpdateValuePid(value: string | number) {
|
||||
formParams.value.pid = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -30,7 +30,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
添加策略
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
@@ -48,7 +48,7 @@
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="新建"
|
||||
:title="formParams?.id > 0 ? '编辑策略 #' + formParams.id : '添加策略'"
|
||||
style="width: 720px"
|
||||
>
|
||||
<n-form
|
||||
@@ -59,8 +59,8 @@
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="IP地址" path="ip">
|
||||
<n-input type="textarea" placeholder="请输入IP地址" v-model:value="formParams.ip" />
|
||||
<n-form-item label="IP策略" path="ip">
|
||||
<n-input type="textarea" placeholder="请输入IP策略" v-model:value="formParams.ip" />
|
||||
<template #feedback>
|
||||
<p>支持添加IP:如果添加多个IP请用","隔开</p>
|
||||
<p>支持添加IP段,如:192.168.0.0/24</p>
|
||||
@@ -72,7 +72,7 @@
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
v-for="status in blacklistOptions"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
@@ -107,8 +107,23 @@
|
||||
import { Dict } from '@/api/dict/dict';
|
||||
import { getOptionLabel, getOptionTag } from '@/utils/hotgo';
|
||||
|
||||
const blacklistOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '封禁中',
|
||||
listClass: 'warning',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '已解封',
|
||||
listClass: 'success',
|
||||
},
|
||||
].map((s) => {
|
||||
return s;
|
||||
});
|
||||
|
||||
const options = ref({
|
||||
status: [],
|
||||
status: blacklistOptions,
|
||||
});
|
||||
|
||||
const columns = [
|
||||
@@ -185,7 +200,7 @@
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: [],
|
||||
options: blacklistOptions,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
@@ -350,19 +365,6 @@
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function loadOptions() {
|
||||
options.value.status = await Dict('sys_normal_disable');
|
||||
for (const item of schemas.value) {
|
||||
if (item.field === 'status') {
|
||||
item.componentProps.options = options.value.status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -35,13 +35,10 @@
|
||||
<n-input v-model:value="formValue.smtpAdminMailbox" placeholder="" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item>
|
||||
<n-button size="small" type="default" @click="sendTest">发送测试邮件</n-button>
|
||||
</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>
|
||||
@@ -118,11 +115,9 @@
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formParams:' + JSON.stringify(formParams.value));
|
||||
|
||||
showModal.value = false;
|
||||
sendTestEmail(formParams.value).then((_res) => {
|
||||
message.success('发送成功');
|
||||
showModal.value = false;
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
@@ -139,8 +134,6 @@
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
console.log('formValue.value:' + JSON.stringify(formValue.value));
|
||||
|
||||
updateConfig({ group: group.value, list: formValue.value })
|
||||
.then((res) => {
|
||||
console.log('res:' + JSON.stringify(res));
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<n-form-item label="默认驱动" path="smsDrive">
|
||||
<n-select
|
||||
placeholder="默认发送驱动"
|
||||
:options="driveList"
|
||||
:options="options.config_sms_drive"
|
||||
v-model:value="formValue.smsDrive"
|
||||
/>
|
||||
</n-form-item>
|
||||
@@ -39,11 +39,7 @@
|
||||
|
||||
<n-divider title-placement="left">阿里云</n-divider>
|
||||
<n-form-item label="AccessKeyID" path="smsAliyunAccessKeyID">
|
||||
<n-input
|
||||
v-model:value="formValue.smsAliyunAccessKeyID"
|
||||
placeholder=""
|
||||
type="password"
|
||||
/>
|
||||
<n-input v-model:value="formValue.smsAliyunAccessKeyID" placeholder="" />
|
||||
<template #feedback
|
||||
>应用key和密钥你可以通过 https://ram.console.aliyun.com/manage/ak 获取</template
|
||||
>
|
||||
@@ -53,8 +49,15 @@
|
||||
<n-input
|
||||
type="password"
|
||||
v-model:value="formValue.smsAliyunAccessKeySecret"
|
||||
placeholder=""
|
||||
/>
|
||||
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">
|
||||
@@ -68,7 +71,7 @@
|
||||
<n-dynamic-input
|
||||
v-model:value="formValue.smsAliyunTemplate"
|
||||
preset="pair"
|
||||
key-placeholder="key"
|
||||
key-placeholder="事件KEY"
|
||||
value-placeholder="模板CODE"
|
||||
/>
|
||||
</n-form-item>
|
||||
@@ -76,22 +79,74 @@
|
||||
<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>
|
||||
</n-spin>
|
||||
|
||||
<n-modal
|
||||
:block-scroll="false"
|
||||
:mask-closable="false"
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="发送测试短信"
|
||||
>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formTestRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="事件模板" path="event">
|
||||
<n-select :options="options.config_sms_template" v-model:value="formParams.event" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="手机号" path="mobile">
|
||||
<n-input
|
||||
placeholder="请输入接收手机号"
|
||||
v-model:value="formParams.mobile"
|
||||
:required="true"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="验证码" path="code">
|
||||
<n-input
|
||||
placeholder="请输入要接收的验证码"
|
||||
v-model:value="formParams.code"
|
||||
:required="true"
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { getConfig, updateConfig } from '@/api/sys/config';
|
||||
import { getConfig, sendTestSms, updateConfig } from '@/api/sys/config';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { Options } from '@/utils/hotgo';
|
||||
import { GlassesOutline, Glasses } from '@vicons/ionicons5';
|
||||
|
||||
const group = ref('sms');
|
||||
const show = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const formParams = ref({ mobile: '', event: '', code: '1234' });
|
||||
|
||||
const rules = {
|
||||
smsDrive: {
|
||||
@@ -101,19 +156,15 @@
|
||||
},
|
||||
};
|
||||
|
||||
const driveList = [
|
||||
{
|
||||
label: '阿里云',
|
||||
value: 'aliyun',
|
||||
},
|
||||
{
|
||||
label: '腾讯云',
|
||||
value: 'tencent',
|
||||
},
|
||||
];
|
||||
const formTestRef = ref<any>();
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
|
||||
const options = ref<Options>({
|
||||
config_sms_template: [],
|
||||
config_sms_drive: [],
|
||||
});
|
||||
|
||||
const formValue = ref({
|
||||
smsDrive: 'aliyun',
|
||||
smsAliyunAccessKeyID: '',
|
||||
@@ -125,6 +176,11 @@
|
||||
smsCodeExpire: 600,
|
||||
});
|
||||
|
||||
function sendTest() {
|
||||
showModal.value = true;
|
||||
formBtnLoading.value = false;
|
||||
}
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
@@ -149,8 +205,9 @@
|
||||
load();
|
||||
});
|
||||
|
||||
function load() {
|
||||
async function load() {
|
||||
show.value = true;
|
||||
await loadOptions();
|
||||
new Promise((_resolve, _reject) => {
|
||||
getConfig({ group: group.value })
|
||||
.then((res) => {
|
||||
@@ -164,4 +221,26 @@
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function loadOptions() {
|
||||
options.value = await Dicts({
|
||||
types: ['config_sms_template', 'config_sms_drive'],
|
||||
});
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formTestRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
sendTestSms(formParams.value).then((_res) => {
|
||||
message.success('发送成功');
|
||||
showModal.value = false;
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
@@ -30,7 +30,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
添加任务
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
@@ -57,7 +57,7 @@
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="新建"
|
||||
:title="formParams?.id > 0 ? '编辑任务 #' + formParams.id : '添加任务'"
|
||||
style="width: 720px"
|
||||
>
|
||||
<n-form
|
||||
@@ -100,9 +100,8 @@
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="执行次数" path="count">
|
||||
<n-form-item label="执行次数" path="count" v-if="formParams.policy === 4">
|
||||
<n-input placeholder="请输入执行次数" v-model:value="formParams.count" />
|
||||
<template #feedback> 仅在单次、多次策略时生效</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="定时表达式" path="pattern">
|
||||
@@ -154,7 +153,7 @@
|
||||
import { TreeSelectOption, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { Delete, Edit, getSelect, List, Status } from '@/api/sys/cron';
|
||||
import { Delete, Edit, getSelect, List, Status, OnlineExec } from '@/api/sys/cron';
|
||||
import { columns } from './columns';
|
||||
import { DeleteOutlined, GroupOutlined, PlusOutlined } from '@vicons/antd';
|
||||
import { statusActions } from '@/enums/optionsiEnum';
|
||||
@@ -369,8 +368,21 @@
|
||||
}
|
||||
|
||||
function handleExecute(record: Recordable) {
|
||||
console.log('点击了handleExecute', record);
|
||||
message.error('暂未配置');
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '提交成功后将立即执行一次,你确定要执行吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
OnlineExec(record).then((_res) => {
|
||||
message.success('提交成功,执行结果请登录控制台查看日志!');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
|
@@ -20,7 +20,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建分组
|
||||
添加分组
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
@@ -82,7 +82,7 @@
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { columns } from './columns';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { GroupDelete, GroupEdit, GroupList, getSelect } from '@/api/sys/cron';
|
||||
import { GroupDelete, GroupEdit, GroupList, getSelect } from '@/api/sys/cron';
|
||||
import { statusOptions } from '@/enums/optionsiEnum';
|
||||
|
||||
const optionTreeData = ref([]);
|
||||
@@ -96,7 +96,7 @@
|
||||
remark: '',
|
||||
status: statusValue.value,
|
||||
});
|
||||
const modalTitle = ref('新建分组');
|
||||
const modalTitle = ref('添加分组');
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const rules = {
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
modalTitle.value = '新建分组';
|
||||
modalTitle.value = '添加分组';
|
||||
formParams.value = defaultValueRef();
|
||||
}
|
||||
|
||||
@@ -179,11 +179,10 @@
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
GroupDelete(record)
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
GroupDelete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
|
@@ -58,7 +58,7 @@
|
||||
</n-space>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<n-input type="input" v-model:value="pattern" placeholder="输入菜单名称搜索">
|
||||
<n-input type="input" v-model:value="pattern" placeholder="输入字典名称搜索">
|
||||
<template #suffix>
|
||||
<n-icon size="18" class="cursor-pointer">
|
||||
<SearchOutlined />
|
||||
|
@@ -23,7 +23,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
添加数据
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
@@ -32,7 +32,7 @@
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="formParams?.id > 0 ? '编辑' : '新建'"
|
||||
:title="formParams?.id > 0 ? '编辑数据' : '添加数据'"
|
||||
>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
@@ -53,15 +53,19 @@
|
||||
<n-form-item label="标签" path="label">
|
||||
<n-input placeholder="请输入标签名称" v-model:value="formParams.label" />
|
||||
</n-form-item>
|
||||
<n-form-item label="标签样式" path="listClass">
|
||||
<n-select
|
||||
:render-tag="renderTag"
|
||||
v-model:value="formParams.listClass"
|
||||
:options="labelOptions"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="字典键值" path="value">
|
||||
<n-input placeholder="请输入键值" v-model:value="formParams.value" />
|
||||
</n-form-item>
|
||||
<n-form-item label="键值类型" path="valueType">
|
||||
<n-select v-model:value="formParams.valueType" :options="options" />
|
||||
</n-form-item>
|
||||
<n-form-item label="标签样式" path="listClass">
|
||||
<n-select v-model:value="formParams.listClass" :options="tagOptions" />
|
||||
</n-form-item>
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number placeholder="请输入" v-model:value="formParams.sort" />
|
||||
</n-form-item>
|
||||
@@ -93,13 +97,13 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, watch, onMounted } from 'vue';
|
||||
import { TreeSelectOption, useMessage, useDialog } from 'naive-ui';
|
||||
import { TreeSelectOption, useMessage, useDialog, NTag, SelectRenderTag } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { getDataList, getDictSelect, EditData, DeleteData } from '@/api/dict/dict';
|
||||
import { columns } from './columns';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { statusOptions, tagOptions } from '@/enums/optionsiEnum';
|
||||
import { statusOptions } from '@/enums/optionsiEnum';
|
||||
import { TypeSelect } from '@/api/sys/config';
|
||||
import { Option } from '@/utils/hotgo';
|
||||
const options = ref<Option>();
|
||||
@@ -138,6 +142,49 @@
|
||||
},
|
||||
];
|
||||
|
||||
const renderTag: SelectRenderTag = ({ option }) => {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: option.type as 'success' | 'warning' | 'error' | 'info' | 'primary' | 'default',
|
||||
},
|
||||
{ default: () => option.label }
|
||||
);
|
||||
};
|
||||
|
||||
const labelOptions = ref([
|
||||
{
|
||||
label: '绿色',
|
||||
value: 'success',
|
||||
type: 'success',
|
||||
},
|
||||
{
|
||||
label: '橙色',
|
||||
value: 'warning',
|
||||
type: 'warning',
|
||||
},
|
||||
{
|
||||
label: '红色',
|
||||
value: 'error',
|
||||
type: 'error',
|
||||
},
|
||||
{
|
||||
label: '蓝色',
|
||||
value: 'info',
|
||||
type: 'info',
|
||||
},
|
||||
{
|
||||
label: '灰色',
|
||||
value: 'default',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
label: '主题色',
|
||||
value: 'primary',
|
||||
type: 'primary',
|
||||
},
|
||||
]);
|
||||
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
|
@@ -4,7 +4,7 @@
|
||||
v-model:show="isShowModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="params?.id > 0 ? '编辑 #' + params?.id : '新建'"
|
||||
:title="params?.id > 0 ? '编辑 #' + params?.id : '添加'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
|
@@ -37,7 +37,7 @@
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
新建
|
||||
添加
|
||||
</n-button>
|
||||
<n-button
|
||||
type="error"
|
||||
|
@@ -8,7 +8,7 @@ import { isNullObject } from '@/utils/is';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
import { errorImg, getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
const $message = window['$message'];
|
||||
export interface State {
|
||||
id: number;
|
||||
@@ -298,6 +298,7 @@ export const columns = [
|
||||
width: 32,
|
||||
height: 32,
|
||||
src: row.image,
|
||||
onError: errorImg,
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
@@ -319,6 +320,7 @@ export const columns = [
|
||||
width: 32,
|
||||
height: 32,
|
||||
src: image,
|
||||
onError: errorImg,
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
|