mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-14 13:13:51 +08:00
发布v2.18.6版本,更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
2
web/.env
2
web/.env
@@ -2,7 +2,7 @@
|
||||
VITE_PORT=8001
|
||||
|
||||
# spa-title
|
||||
VITE_GLOB_APP_TITLE=HotGo管理系统
|
||||
VITE_GLOB_APP_TITLE=加载中
|
||||
|
||||
# spa shortname
|
||||
VITE_GLOB_APP_SHORT_NAME=HG
|
||||
|
||||
79
web/auto-imports.d.ts
vendored
Normal file
79
web/auto-imports.d.ts
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue').EffectScope
|
||||
const computed: typeof import('vue').computed
|
||||
const createApp: typeof import('vue').createApp
|
||||
const customRef: typeof import('vue').customRef
|
||||
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
|
||||
const defineComponent: typeof import('vue').defineComponent
|
||||
const effectScope: typeof import('vue').effectScope
|
||||
const getCurrentInstance: typeof import('vue').getCurrentInstance
|
||||
const getCurrentScope: typeof import('vue').getCurrentScope
|
||||
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
|
||||
const h: typeof import('vue').h
|
||||
const inject: typeof import('vue').inject
|
||||
const isProxy: typeof import('vue').isProxy
|
||||
const isReactive: typeof import('vue').isReactive
|
||||
const isReadonly: typeof import('vue').isReadonly
|
||||
const isRef: typeof import('vue').isRef
|
||||
const isShallow: typeof import('vue').isShallow
|
||||
const markRaw: typeof import('vue').markRaw
|
||||
const nextTick: typeof import('vue').nextTick
|
||||
const onActivated: typeof import('vue').onActivated
|
||||
const onBeforeMount: typeof import('vue').onBeforeMount
|
||||
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
|
||||
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
|
||||
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
|
||||
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
|
||||
const onDeactivated: typeof import('vue').onDeactivated
|
||||
const onErrorCaptured: typeof import('vue').onErrorCaptured
|
||||
const onMounted: typeof import('vue').onMounted
|
||||
const onRenderTracked: typeof import('vue').onRenderTracked
|
||||
const onRenderTriggered: typeof import('vue').onRenderTriggered
|
||||
const onScopeDispose: typeof import('vue').onScopeDispose
|
||||
const onServerPrefetch: typeof import('vue').onServerPrefetch
|
||||
const onUnmounted: typeof import('vue').onUnmounted
|
||||
const onUpdated: typeof import('vue').onUpdated
|
||||
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
|
||||
const provide: typeof import('vue').provide
|
||||
const reactive: typeof import('vue').reactive
|
||||
const readonly: typeof import('vue').readonly
|
||||
const ref: typeof import('vue').ref
|
||||
const resolveComponent: typeof import('vue').resolveComponent
|
||||
const shallowReactive: typeof import('vue').shallowReactive
|
||||
const shallowReadonly: typeof import('vue').shallowReadonly
|
||||
const shallowRef: typeof import('vue').shallowRef
|
||||
const t: typeof import('@/locale/index').t
|
||||
const toRaw: typeof import('vue').toRaw
|
||||
const toRef: typeof import('vue').toRef
|
||||
const toRefs: typeof import('vue').toRefs
|
||||
const toValue: typeof import('vue').toValue
|
||||
const triggerRef: typeof import('vue').triggerRef
|
||||
const unref: typeof import('vue').unref
|
||||
const useAttrs: typeof import('vue').useAttrs
|
||||
const useCssModule: typeof import('vue').useCssModule
|
||||
const useCssVars: typeof import('vue').useCssVars
|
||||
const useId: typeof import('vue').useId
|
||||
const useLink: typeof import('vue-router').useLink
|
||||
const useModel: typeof import('vue').useModel
|
||||
const useRoute: typeof import('vue-router').useRoute
|
||||
const useRouter: typeof import('vue-router').useRouter
|
||||
const useSlots: typeof import('vue').useSlots
|
||||
const useTemplateRef: typeof import('vue').useTemplateRef
|
||||
const watch: typeof import('vue').watch
|
||||
const watchEffect: typeof import('vue').watchEffect
|
||||
const watchPostEffect: typeof import('vue').watchPostEffect
|
||||
const watchSyncEffect: typeof import('vue').watchSyncEffect
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Plugin } from 'vite';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
import setupExtend from 'vite-plugin-vue-setup-extend';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
@@ -23,6 +24,18 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
|
||||
resolvers: [NaiveUiResolver()],
|
||||
}),
|
||||
|
||||
// 自动引入API
|
||||
AutoImport({
|
||||
imports: [
|
||||
'vue',
|
||||
'vue-router',
|
||||
{
|
||||
'@/locale/index': ['t'],
|
||||
},
|
||||
],
|
||||
dts: 'auto-imports.d.ts',
|
||||
}),
|
||||
|
||||
// 支持顶级wait
|
||||
topLevelAwait({
|
||||
// The export name of top-level await promise for each chunk module
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "hotgo",
|
||||
"type": "module",
|
||||
"version": "2.17.8",
|
||||
"version": "2.18.6",
|
||||
"author": {
|
||||
"name": "MengShuai",
|
||||
"email": "133814250@qq.com",
|
||||
@@ -51,7 +51,7 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"mint-filter": "^4.0.3",
|
||||
"mitt": "^3.0.1",
|
||||
"naive-ui": "^2.42.0",
|
||||
"naive-ui": "^2.43.1",
|
||||
"pinia": "^2.2.2",
|
||||
"pinyin-pro": "^3.24.2",
|
||||
"print-js": "^1.6.0",
|
||||
@@ -63,6 +63,7 @@
|
||||
"throttle-debounce": "^5.0.2",
|
||||
"vfonts": "^0.0.3",
|
||||
"vue": "^3.4.38",
|
||||
"vue-i18n": "^11.1.11",
|
||||
"vue-router": "^4.4.3",
|
||||
"vue-types": "^5.1.3",
|
||||
"vue-waterfall-plugin-next": "^2.6.0",
|
||||
@@ -118,6 +119,7 @@
|
||||
"stylelint-scss": "^6.5.1",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "^5.5.4",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.4.2",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
|
||||
@@ -47,3 +47,12 @@ export function ClearKind(params) {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 链接图片转存
|
||||
export function ImageTransferStorage(params) {
|
||||
return http.request({
|
||||
url: '/upload/imageTransferStorage',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
BellOutlined,
|
||||
} from '@vicons/antd';
|
||||
|
||||
import { Refresh } from '@vicons/ionicons5';
|
||||
import { Refresh, LanguageOutline } from '@vicons/ionicons5';
|
||||
|
||||
export default {
|
||||
SettingOutlined,
|
||||
@@ -33,4 +33,5 @@ export default {
|
||||
CheckOutlined,
|
||||
BellOutlined,
|
||||
Refresh,
|
||||
LanguageOutline,
|
||||
};
|
||||
|
||||
@@ -134,6 +134,30 @@
|
||||
<span>全屏</span>
|
||||
</n-tooltip>
|
||||
</div>
|
||||
|
||||
<!-- 国际化 -->
|
||||
<div
|
||||
class="layout-header-trigger layout-header-trigger-min"
|
||||
v-if="userStore.loginConfig?.i18nSwitch"
|
||||
>
|
||||
<n-dropdown
|
||||
:value="i18nStore.getLocale()"
|
||||
trigger="click"
|
||||
@select="localeSelect"
|
||||
:options="availableLocales"
|
||||
show-arrow
|
||||
>
|
||||
<n-tooltip placement="bottom">
|
||||
<template #trigger>
|
||||
<n-icon size="18">
|
||||
<LanguageOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>切换语言</span>
|
||||
</n-tooltip>
|
||||
</n-dropdown>
|
||||
</div>
|
||||
|
||||
<!-- 个人中心 -->
|
||||
<div class="layout-header-trigger layout-header-trigger-min">
|
||||
<n-dropdown trigger="click" @select="avatarSelect" :options="avatarOptions" show-arrow>
|
||||
@@ -200,7 +224,7 @@
|
||||
import SystemMessage from './SystemMessage.vue';
|
||||
import { notificationStoreWidthOut } from '@/store/modules/notification';
|
||||
import { getIcon } from '@/enums/systemMessageEnum';
|
||||
|
||||
import { availableLocales, useI18nStore } from '@/store/modules/i18n';
|
||||
import Search from './Search.vue';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -222,6 +246,7 @@
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const i18nStore = useI18nStore();
|
||||
const userStore = useUserStore();
|
||||
const notificationStore = notificationStoreWidthOut();
|
||||
const useLockscreen = useLockscreenStore();
|
||||
@@ -238,7 +263,7 @@
|
||||
|
||||
// const { username, avatar } = userStore?.info || {};
|
||||
const drawerSetting = ref();
|
||||
const projectName = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||
const projectName = userStore.loginConfig?.projectName;
|
||||
|
||||
const state = reactive({
|
||||
// username: username || '',
|
||||
@@ -431,6 +456,15 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 多久下拉菜单
|
||||
const localeSelect = (key) => {
|
||||
i18nStore.setLocale(key);
|
||||
message.success('切换成功');
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 800);
|
||||
};
|
||||
|
||||
function openSetting() {
|
||||
const { openDrawer } = drawerSetting.value;
|
||||
openDrawer();
|
||||
@@ -543,6 +577,9 @@
|
||||
userStore,
|
||||
updateMenu,
|
||||
projectName,
|
||||
localeSelect,
|
||||
i18nStore,
|
||||
availableLocales,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
props: {
|
||||
@@ -14,7 +16,8 @@
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const projectName = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||
const userStore = useUserStore();
|
||||
const projectName = userStore.loginConfig?.projectName;
|
||||
return {
|
||||
projectName,
|
||||
};
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
import { useLoadingBar } from 'naive-ui';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useI18nStore } from '@/store/modules/i18n';
|
||||
|
||||
const { getDarkTheme } = useDesignSetting();
|
||||
const {
|
||||
@@ -89,6 +90,7 @@
|
||||
|
||||
const route = useRoute();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const i18nStore = useI18nStore();
|
||||
|
||||
const navMode = getNavMode;
|
||||
|
||||
@@ -183,6 +185,7 @@
|
||||
onMounted(() => {
|
||||
checkMobileMode();
|
||||
window.addEventListener('resize', watchWidth);
|
||||
i18nStore.initLocale();
|
||||
//挂载在 window 方便与在js中使用
|
||||
window['$loading'] = useLoadingBar();
|
||||
window['$loading'].finish();
|
||||
|
||||
28
web/src/locale/en.json
Normal file
28
web/src/locale/en.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"你好,美丽世界": "Hello, Beautiful World",
|
||||
"剩余{num}余额": "Remaining {num} Balance",
|
||||
"卡板量": "Total Card",
|
||||
"日": "Day",
|
||||
"日同比": "Than Yesterday",
|
||||
"周同比": "Than LastWeek",
|
||||
"总卡板量:": "ALL Card:",
|
||||
"激活卡板": "Activate Card",
|
||||
"周": "Week",
|
||||
"总激活卡板:": "Total Activate Card:",
|
||||
"代理商": "Agent",
|
||||
"总代理商量:": "Total Agent:",
|
||||
"提现佣金": "Withdrawal Commission",
|
||||
"月": "Month",
|
||||
"月同比": "Than LastMonth",
|
||||
"总提现额:": "Total Withdrawal Amount:",
|
||||
"用户": "Users",
|
||||
"分析": "Analysis",
|
||||
"商品": "Goods",
|
||||
"订单": "Order",
|
||||
"票据": "Receipt",
|
||||
"消息": "Message",
|
||||
"标签": "Label",
|
||||
"配置": "Configure",
|
||||
"流量消耗趋势": "Trend Of Traffic Consumption",
|
||||
"客户端访问量": "Client Traffic"
|
||||
}
|
||||
37
web/src/locale/index.ts
Normal file
37
web/src/locale/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import type { I18nOptions } from 'vue-i18n';
|
||||
import en from './en.json';
|
||||
import zhHant from './zh-Hant.json';
|
||||
import zhHans from './zh-Hans.json';
|
||||
|
||||
const messages = {
|
||||
en,
|
||||
'zh-TW': zhHant,
|
||||
'zh-CN': zhHans,
|
||||
};
|
||||
|
||||
// 创建 i18n 实例配置
|
||||
const i18nConfig: I18nOptions = {
|
||||
legacy: false, // 使用 Composition API 模式
|
||||
locale: 'zh-CN', // 默认语言
|
||||
fallbackLocale: 'zh-CN', // 回退语言
|
||||
messages, // 语言包
|
||||
missingWarn: false, // 关闭找不到 key 的警告
|
||||
fallbackWarn: false, // 关闭回退警告
|
||||
// 当找不到翻译时,返回 key 本身
|
||||
missing: (_locale, key) => {
|
||||
return key;
|
||||
},
|
||||
};
|
||||
|
||||
const i18n = createI18n(i18nConfig);
|
||||
|
||||
export default i18n;
|
||||
|
||||
// 导出 t 函数,支持多种参数形式
|
||||
export const t = (key: string, named?: Record<string, unknown>, options?: any): string => {
|
||||
if (named !== undefined) {
|
||||
return i18n.global.t(key, named, options) as string;
|
||||
}
|
||||
return i18n.global.t(key) as string;
|
||||
};
|
||||
3
web/src/locale/zh-Hans.json
Normal file
3
web/src/locale/zh-Hans.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"剩余{num}余额": "剩余{num}余额"
|
||||
}
|
||||
28
web/src/locale/zh-Hant.json
Normal file
28
web/src/locale/zh-Hant.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"你好,美丽世界": "你好,美麗世界",
|
||||
"剩余{num}余额": "剩餘{num}餘額",
|
||||
"卡板量": "卡板量",
|
||||
"日": "日",
|
||||
"日同比": "日同比",
|
||||
"周同比": "週同比",
|
||||
"总卡板量:": "總卡板量:",
|
||||
"激活卡板": "激活卡板",
|
||||
"周": "週",
|
||||
"总激活卡板:": "總激活卡板:",
|
||||
"代理商": "代理商",
|
||||
"总代理商量:": "總代理商數:",
|
||||
"提现佣金": "提現傭金",
|
||||
"月": "月",
|
||||
"月同比": "月同比",
|
||||
"总提现额:": "總提現額:",
|
||||
"用户": "使用者",
|
||||
"分析": "分析",
|
||||
"商品": "商品",
|
||||
"订单": "訂單",
|
||||
"票据": "票據",
|
||||
"消息": "訊息",
|
||||
"标签": "標籤",
|
||||
"配置": "設定",
|
||||
"流量消耗趋势": "流量消耗趨勢",
|
||||
"客户端访问量": "用戶端訪問量"
|
||||
}
|
||||
@@ -6,12 +6,17 @@ import { setupStore } from '@/store';
|
||||
import { setupNaive, setupDirectives } from '@/plugins';
|
||||
import { AppProvider } from '@/components/Application';
|
||||
import setupWebsocket from '@/utils/websocket/index';
|
||||
import i18n from '@/locale/index';
|
||||
|
||||
async function bootstrap() {
|
||||
const appProvider = createApp(AppProvider);
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// 国际化
|
||||
app.use(i18n);
|
||||
app.config.globalProperties.t = i18n.global.t;
|
||||
|
||||
// 注册全局常用的 naive-ui 组件
|
||||
setupNaive(app);
|
||||
|
||||
|
||||
68
web/src/store/modules/i18n.ts
Normal file
68
web/src/store/modules/i18n.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { createStorage } from '@/utils/Storage';
|
||||
import { CurrentLocale } from '@/store/mutation-types';
|
||||
import i18n from '@/locale/index';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
export const availableLocales = [
|
||||
{
|
||||
label: '简体中文',
|
||||
key: 'zh-CN',
|
||||
},
|
||||
{
|
||||
label: '繁體中文',
|
||||
key: 'zh-TW',
|
||||
},
|
||||
{
|
||||
label: 'English',
|
||||
key: 'en',
|
||||
},
|
||||
];
|
||||
|
||||
export interface II18nStore {
|
||||
currentLocale: string;
|
||||
}
|
||||
|
||||
const Storage = createStorage({ storage: localStorage });
|
||||
|
||||
export const useI18nStore = defineStore({
|
||||
id: 'I18nStore',
|
||||
state: (): II18nStore => ({
|
||||
currentLocale: Storage.get(CurrentLocale, 'zh-CN'),
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
getLocale(): string {
|
||||
return this.currentLocale;
|
||||
},
|
||||
|
||||
setLocale(locale: string) {
|
||||
if (availableLocales.some((l) => l.key === locale)) {
|
||||
(i18n.global.locale as any).value = locale;
|
||||
this.currentLocale = locale;
|
||||
Storage.set(CurrentLocale, locale);
|
||||
}
|
||||
},
|
||||
|
||||
initLocale(): void {
|
||||
const userStore = useUserStore();
|
||||
const defaultLanguage = userStore.loginConfig?.defaultLanguage || 'zh-CN';
|
||||
|
||||
// 未开启国际化功能,仅设置语言不持久化
|
||||
if (!userStore.loginConfig?.i18nSwitch) {
|
||||
this.setLocale(defaultLanguage);
|
||||
return;
|
||||
}
|
||||
|
||||
// 优先使用本地存储的语言设置
|
||||
const savedLocale = Storage.get(CurrentLocale, defaultLanguage);
|
||||
if (savedLocale && availableLocales.some((l) => l.key === savedLocale)) {
|
||||
this.setLocale(savedLocale);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用系统配置的默认语言
|
||||
this.setLocale(defaultLanguage);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -17,6 +17,8 @@ import tabsView from './tabs-view';
|
||||
import lockscreen from './lockscreen';
|
||||
// @ts-ignore
|
||||
import dict from './dict';
|
||||
// @ts-ignore
|
||||
import i18n from './i18n';
|
||||
|
||||
export default {
|
||||
asyncRoute,
|
||||
@@ -24,4 +26,5 @@ export default {
|
||||
tabsView,
|
||||
lockscreen,
|
||||
dict,
|
||||
i18n,
|
||||
};
|
||||
|
||||
@@ -67,6 +67,9 @@ export interface LoginConfigState {
|
||||
loginAutoOpenId: number;
|
||||
loginProtocol: string;
|
||||
loginPolicy: string;
|
||||
i18nSwitch: boolean;
|
||||
defaultLanguage: string;
|
||||
projectName: string;
|
||||
}
|
||||
|
||||
export interface IUserState {
|
||||
|
||||
@@ -5,3 +5,4 @@ export const CURRENT_LOGIN_CONFIG = 'CURRENT-LOGIN-CONFIG'; // 当前登录配
|
||||
export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏
|
||||
export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页
|
||||
export const CURRENT_DICT = 'CURRENT-DICT'; // 当前用户字典配置
|
||||
export const CurrentLocale = 'Current-Locale'; // 当前语言
|
||||
|
||||
@@ -6,20 +6,17 @@ import { checkStatus } from './checkStatus';
|
||||
import { formatRequestDate, joinTimestamp } from './helper';
|
||||
import { ContentTypeEnum, RequestEnum, ResultEnum } from '@/enums/httpEnum';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
|
||||
import { isString, isUrl } from '@/utils/is/';
|
||||
import { deepMerge } from '@/utils';
|
||||
import { setObjToUrlParams } from '@/utils/urlUtils';
|
||||
|
||||
import { CreateAxiosOptions, RequestOptions, Result } from './types';
|
||||
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import router from '@/router';
|
||||
import { storage } from '@/utils/Storage';
|
||||
import { encodeParams } from '@/utils/urlUtils';
|
||||
import { delNullProperty } from '@/utils/array';
|
||||
import { useI18nStore } from '@/store/modules/i18n';
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
@@ -182,6 +179,7 @@ const transform: AxiosTransform = {
|
||||
*/
|
||||
requestInterceptors: (config, options) => {
|
||||
// 请求之前处理config
|
||||
const i18nStore = useI18nStore();
|
||||
const userStore = useUserStoreWidthOut();
|
||||
const token = userStore.getToken;
|
||||
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
|
||||
@@ -190,6 +188,7 @@ const transform: AxiosTransform = {
|
||||
? `${options.authenticationScheme} ${token}`
|
||||
: token;
|
||||
}
|
||||
config.headers.Locale = i18nStore.getLocale();
|
||||
return config;
|
||||
},
|
||||
|
||||
|
||||
@@ -57,6 +57,14 @@
|
||||
</template>
|
||||
上传文档
|
||||
</n-button>
|
||||
<n-button type="success" @click="showUrlModal" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FileImageOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
链接转图片
|
||||
</n-button>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
@@ -73,6 +81,7 @@
|
||||
<FileUpload ref="imageUploadRef" :finish-call="handleFinishCall" upload-type="image" />
|
||||
<FileUpload ref="docUploadRef" :finish-call="handleFinishCall" upload-type="doc" />
|
||||
<MultipartUpload ref="multipartUploadRef" @on-finish="handleFinishCall" />
|
||||
<UrlModal ref="urlModalRef" @reloadTable="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -93,6 +102,7 @@
|
||||
import FileUpload from '@/components/FileChooser/src/Upload.vue';
|
||||
import MultipartUpload from '@/components/Upload/multipartUpload.vue';
|
||||
import { Attachment } from '@/components/FileChooser/src/model';
|
||||
import UrlModal from './urlModal.vue';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const message = useMessage();
|
||||
@@ -105,6 +115,7 @@
|
||||
const imageUploadRef = ref();
|
||||
const docUploadRef = ref();
|
||||
const multipartUploadRef = ref();
|
||||
const urlModalRef =ref();
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 132,
|
||||
@@ -216,6 +227,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function showUrlModal() {
|
||||
urlModalRef.value?.showModal();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadOptions();
|
||||
});
|
||||
|
||||
90
web/src/views/apply/attachment/urlModal.vue
Normal file
90
web/src/views/apply/attachment/urlModal.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="isShowModal"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="链接转图片"
|
||||
>
|
||||
<n-alert type="info"> 将外部图片链接下载并转存至平台的存储驱动中 </n-alert>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
ref="formPacketRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-form-item label="图片链接" path="url">
|
||||
<n-input v-model:value="formParams.url" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="预览图片" v-if="formParams.url != ''">
|
||||
<n-image width="150" :src="formParams.url" />
|
||||
</n-form-item>
|
||||
</n-spin>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">关闭</n-button>
|
||||
<n-button type="primary" :loading="formBtnLoading" @click="confirmForm">上传 </n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { ImageTransferStorage } from '@/api/apply/attachment';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const loading = ref(false);
|
||||
const isShowModal = ref(false);
|
||||
const dialogWidth = ref(adaModalWidth(640));
|
||||
const formBtnLoading = ref(false);
|
||||
const formPacketRef = ref();
|
||||
const message = useMessage();
|
||||
const formParams = ref({
|
||||
url: '',
|
||||
});
|
||||
|
||||
function reloadTable() {
|
||||
emit('reloadTable');
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
ImageTransferStorage(formParams.value)
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
closeForm();
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
||||
function showModal() {
|
||||
isShowModal.value = true;
|
||||
formParams.value.url = '';
|
||||
}
|
||||
|
||||
defineExpose({ showModal });
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -3,11 +3,17 @@
|
||||
<NRow :gutter="24">
|
||||
<NCol :span="24">
|
||||
<n-card content-style="padding: 0;" :bordered="false">
|
||||
<n-tabs type="line" size="large" :tabs-padding="20" pane-style="padding: 20px;">
|
||||
<n-tab-pane name="流量消耗趋势">
|
||||
<n-tabs
|
||||
type="line"
|
||||
size="large"
|
||||
:tabs-padding="20"
|
||||
pane-style="padding: 20px;"
|
||||
default-value="FluxTrend"
|
||||
>
|
||||
<n-tab-pane name="FluxTrend" :tab="t('流量消耗趋势')">
|
||||
<FluxTrend />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="客户端访问量">
|
||||
<n-tab-pane name="VisitAmount" :tab="t('客户端访问量')">
|
||||
<VisitAmount />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<n-grid cols="1 s:2 m:3 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12" :y-gap="8">
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="卡板量"
|
||||
:title="t('卡板量')"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="success">日</n-tag>
|
||||
<n-tag type="success">{{ t('日') }}</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
日同比
|
||||
{{ t('日同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="visits.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
@@ -30,7 +30,7 @@
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
周同比
|
||||
{{ t('周同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="visits.decline" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
@@ -42,7 +42,7 @@
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" text :repeat="2" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总卡板量: </div>
|
||||
<div class="text-sn"> {{ t('总卡板量:') }} </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" :endVal="visits.amount" />
|
||||
</div>
|
||||
@@ -53,13 +53,13 @@
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="激活卡板"
|
||||
:title="t('激活卡板')"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="info">周</n-tag>
|
||||
<n-tag type="info">{{ t('周') }}</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
@@ -85,10 +85,9 @@
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总激活卡板: </div>
|
||||
<div class="text-sn"> {{ t('总激活卡板:') }} </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" :endVal="saleroom.amount" />
|
||||
<!-- prefix="¥"-->
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -97,13 +96,13 @@
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="代理商"
|
||||
:title="t('代理商')"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="warning">周</n-tag>
|
||||
<n-tag type="warning">{{ t('周') }}</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
@@ -113,7 +112,7 @@
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
日同比
|
||||
{{ t('日同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
@@ -123,7 +122,7 @@
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
周同比
|
||||
{{ t('周同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
@@ -135,7 +134,7 @@
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总代理商量: </div>
|
||||
<div class="text-sn"> {{ t('总代理商量:') }} </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.amount" />
|
||||
</div>
|
||||
@@ -146,13 +145,13 @@
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="提现佣金"
|
||||
:title="t('提现佣金')"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="error">月</n-tag>
|
||||
<n-tag type="error">{{ t('月') }}</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
@@ -162,7 +161,7 @@
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
月同比
|
||||
{{ t('月同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="volume.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
@@ -172,7 +171,7 @@
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
月同比
|
||||
{{ t('月同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="volume.decline" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
@@ -184,7 +183,7 @@
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> 总提现额: </div>
|
||||
<div class="text-sn"> {{ t('总提现额:') }} </div>
|
||||
<div class="text-sn">
|
||||
<CountTo prefix="¥" :startVal="1" :endVal="volume.amount" />
|
||||
</div>
|
||||
@@ -230,8 +229,6 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getConsoleInfo } from '@/api/dashboard/console';
|
||||
import VisiTab from './components/VisiTab.vue';
|
||||
import { CountTo } from '@/components/CountTo';
|
||||
@@ -256,80 +253,82 @@
|
||||
const router = useRouter();
|
||||
|
||||
// 图标列表
|
||||
const iconList = [
|
||||
{
|
||||
icon: UsergroupAddOutlined,
|
||||
size: '32',
|
||||
title: '用户',
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => router.push({ name: 'user' }),
|
||||
const iconList = computed(() => {
|
||||
return [
|
||||
{
|
||||
icon: UsergroupAddOutlined,
|
||||
size: '32',
|
||||
title: t('用户'),
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => router.push({ name: 'user' }),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: BarChartOutlined,
|
||||
size: '32',
|
||||
title: '分析',
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
{
|
||||
icon: BarChartOutlined,
|
||||
size: '32',
|
||||
title: t('分析'),
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: ShoppingCartOutlined,
|
||||
size: '32',
|
||||
title: '商品',
|
||||
color: '#ff9c6e',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
{
|
||||
icon: ShoppingCartOutlined,
|
||||
size: '32',
|
||||
title: t('商品'),
|
||||
color: '#ff9c6e',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: AccountBookOutlined,
|
||||
size: '32',
|
||||
title: '订单',
|
||||
color: '#b37feb',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
{
|
||||
icon: AccountBookOutlined,
|
||||
size: '32',
|
||||
title: t('订单'),
|
||||
color: '#b37feb',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: CreditCardOutlined,
|
||||
size: '32',
|
||||
title: '票据',
|
||||
color: '#ffd666',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
{
|
||||
icon: CreditCardOutlined,
|
||||
size: '32',
|
||||
title: t('票据'),
|
||||
color: '#ffd666',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: MailOutlined,
|
||||
size: '32',
|
||||
title: '消息',
|
||||
color: '#5cdbd3',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
{
|
||||
icon: MailOutlined,
|
||||
size: '32',
|
||||
title: t('消息'),
|
||||
color: '#5cdbd3',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: TagsOutlined,
|
||||
size: '32',
|
||||
title: '标签',
|
||||
color: '#ff85c0',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
{
|
||||
icon: TagsOutlined,
|
||||
size: '32',
|
||||
title: t('标签'),
|
||||
color: '#ff85c0',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: SettingOutlined,
|
||||
size: '32',
|
||||
title: '配置',
|
||||
color: '#ffc069',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
{
|
||||
icon: SettingOutlined,
|
||||
size: '32',
|
||||
title: t('配置'),
|
||||
color: '#ffc069',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await getConsoleInfo();
|
||||
|
||||
@@ -27,15 +27,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import LoginFrom from './login/index.vue';
|
||||
import RegisterFrom from './register/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const projectName = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||
const projectName = computed(() => userStore.loginConfig?.projectName);
|
||||
|
||||
interface LoginModule {
|
||||
key: string;
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<n-button @click="closeForm">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">
|
||||
<n-button type="info" :loading="formBtnLoading" :disabled="!isFormValid" @click="confirmForm">
|
||||
确定
|
||||
</n-button>
|
||||
</n-space>
|
||||
@@ -100,6 +100,7 @@
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(840);
|
||||
});
|
||||
const isFormValid = ref(true);
|
||||
|
||||
// 提交表单
|
||||
function confirmForm(e) {
|
||||
|
||||
@@ -9,22 +9,22 @@ import { useDictStore } from '@/store/modules/dict';
|
||||
const dict = useDictStore();
|
||||
|
||||
export class State {
|
||||
public title = ''; // 标题
|
||||
public id = 0; // ID
|
||||
public pid = 0; // 上级
|
||||
public level = 1; // 关系树级别
|
||||
public tree = null; // 关系树
|
||||
public categoryId = null; // 测试分类
|
||||
public description = ''; // 描述
|
||||
public sort = 0; // 排序
|
||||
public status = 1; // 状态
|
||||
public createdBy = 0; // 创建者
|
||||
public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
|
||||
public updatedBy = 0; // 更新者
|
||||
public createdAt = ''; // 创建时间
|
||||
public updatedAt = ''; // 修改时间
|
||||
public deletedAt = ''; // 删除时间
|
||||
|
||||
public title = '';//标题
|
||||
public id = 0;//ID
|
||||
public pid = 0;//上级
|
||||
public level = 1;//关系树级别
|
||||
public tree = null;//关系树
|
||||
public categoryId = null;//测试分类
|
||||
public description = '';//描述
|
||||
public sort = 0;//排序
|
||||
public status = 1;//状态
|
||||
public createdBy = 0;//创建者
|
||||
public createdBySumma?: null | MemberSumma = null;//创建者摘要信息
|
||||
public updatedBy = 0;//更新者
|
||||
public createdAt = '';//创建时间
|
||||
public updatedAt = '';//修改时间
|
||||
public deletedAt = '';//删除时间
|
||||
constructor(state?: Partial<State>) {
|
||||
if (state) {
|
||||
Object.assign(this, state);
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
const allTreeKeys = ref([]);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 160,
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
@@ -114,7 +114,7 @@
|
||||
auth: ['/dept/edit'],
|
||||
},
|
||||
{
|
||||
label: '添加',
|
||||
label: '添加子部门',
|
||||
onClick: handleAdd.bind(null, record),
|
||||
auth: ['/dept/edit'],
|
||||
},
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</n-space>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<n-input type="text" v-model:value="pattern" placeholder="输入菜单名称搜索">
|
||||
<n-input type="text" v-model:value="pattern" placeholder="输入菜单名称或权限路径搜索">
|
||||
<template #suffix>
|
||||
<n-icon size="18" class="cursor-pointer">
|
||||
<SearchOutlined />
|
||||
@@ -72,6 +72,7 @@
|
||||
checkable
|
||||
:virtual-scroll="true"
|
||||
:pattern="pattern"
|
||||
:filter="filterTreeNode"
|
||||
:data="treeOption"
|
||||
:expandedKeys="expandedKeys"
|
||||
style="max-height: 650px; overflow: hidden"
|
||||
@@ -165,6 +166,26 @@
|
||||
expandedKeys.value = keys;
|
||||
}
|
||||
|
||||
// 按名称和权限搜索
|
||||
function filterTreeNode(pattern: string, node: any) {
|
||||
if (!pattern) return true;
|
||||
const searchText = pattern.toLowerCase();
|
||||
|
||||
const label = (node.label || node.title || '').toLowerCase();
|
||||
if (label.includes(searchText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const permissions = node.permissions || '';
|
||||
if (permissions) {
|
||||
const permissionsLower = permissions.toLowerCase();
|
||||
if (permissionsLower.includes(searchText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 加载菜单选项树
|
||||
function loadTreeOption() {
|
||||
const needLoading = treeOption.value.length == 0;
|
||||
|
||||
@@ -1,82 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
:mask-closable="false"
|
||||
preset="dialog"
|
||||
:title="'分配 ' + formValue.name + ' 的菜单权限'"
|
||||
>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<div class="py-3 menu-list" :style="{ maxHeight: '90vh', height: '70vh' }">
|
||||
<n-input size="small" v-model:value="pattern" placeholder="输入菜单名称搜索" class="mb-2">
|
||||
<template #suffix>
|
||||
<n-icon size="18" class="cursor-pointer">
|
||||
<SearchOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
<n-tree
|
||||
block-line
|
||||
checkable
|
||||
check-on-click
|
||||
default-expand-all
|
||||
virtual-scroll
|
||||
:data="treeData"
|
||||
:pattern="pattern"
|
||||
:expandedKeys="expandedKeys"
|
||||
:checked-keys="checkedKeys"
|
||||
style="max-height: 950px; overflow: hidden"
|
||||
@update:checked-keys="checkedTree"
|
||||
@update:expanded-keys="onExpandedKeys"
|
||||
/>
|
||||
</div>
|
||||
</n-spin>
|
||||
<template #action>
|
||||
<n-space class="mt-6" v-if="showImportSelect">
|
||||
<n-input-group>
|
||||
<n-tree-select
|
||||
size="small"
|
||||
placeholder="请选择一个要导入的角色"
|
||||
:consistent-menu-width="false"
|
||||
clearable
|
||||
filterable
|
||||
<n-drawer v-model:show="showModal" :width="dialogWidth" :show-icon="false" preset="dialog">
|
||||
<n-drawer-content closable :title="`分配 ${formValue.name} 的菜单权限`">
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<div :style="{ maxHeight: '78vh', height: '78vh' }">
|
||||
<n-input v-model:value="pattern" placeholder="输入菜单名称或权限路径搜索" class="mb-2">
|
||||
<template #suffix>
|
||||
<n-icon size="18" class="cursor-pointer">
|
||||
<SearchOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
<n-tree
|
||||
block-line
|
||||
checkable
|
||||
check-on-click
|
||||
default-expand-all
|
||||
:options="editRoleOption"
|
||||
key-field="id"
|
||||
label-field="name"
|
||||
:on-update:value="handleImportSelect"
|
||||
virtual-scroll
|
||||
:data="treeData"
|
||||
:pattern="pattern"
|
||||
:filter="filterTreeNode"
|
||||
:expandedKeys="expandedKeys"
|
||||
:checked-keys="checkedKeys"
|
||||
style="max-height: 950px; overflow: hidden"
|
||||
@update:checked-keys="checkedTree"
|
||||
@update:expanded-keys="onExpandedKeys"
|
||||
/>
|
||||
<div class="mr-2"></div>
|
||||
<n-button ghost @click="showImportSelect = false" size="small"> 取消 </n-button>
|
||||
</n-input-group>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-spin>
|
||||
<template #footer>
|
||||
<n-space v-if="showImportSelect">
|
||||
<n-input-group>
|
||||
<n-tree-select
|
||||
placeholder="请选择一个要导入的角色"
|
||||
:consistent-menu-width="false"
|
||||
clearable
|
||||
filterable
|
||||
default-expand-all
|
||||
:options="editRoleOption"
|
||||
key-field="id"
|
||||
label-field="name"
|
||||
:on-update:value="handleImportSelect"
|
||||
style="width: 300px"
|
||||
/>
|
||||
<div class="mr-2"></div>
|
||||
<n-button ghost @click="showImportSelect = false"> 取消 </n-button>
|
||||
</n-input-group>
|
||||
</n-space>
|
||||
|
||||
<n-space class="mt-6 space-group" v-if="!showImportSelect" size="small">
|
||||
<n-button ghost @click="showImportSelect = true" size="small"> 导入权限 </n-button>
|
||||
<n-button type="info" ghost icon-placement="left" @click="packHandle" size="small">
|
||||
全部{{ expandedKeys.length ? '收起' : '展开' }}
|
||||
</n-button>
|
||||
<n-button type="info" ghost icon-placement="left" @click="checkedAllHandle" size="small">
|
||||
全部{{ checkedAll ? '取消' : '选择' }}
|
||||
</n-button>
|
||||
<n-space v-if="!showImportSelect">
|
||||
<n-button ghost @click="showImportSelect = true"> 导入权限 </n-button>
|
||||
<n-button type="info" ghost icon-placement="left" @click="packHandle">
|
||||
全部{{ expandedKeys.length ? '收起' : '展开' }}
|
||||
</n-button>
|
||||
<n-button type="info" ghost icon-placement="left" @click="checkedAllHandle">
|
||||
全部{{ checkedAll ? '取消' : '选择' }}
|
||||
</n-button>
|
||||
|
||||
<n-popconfirm @positive-click="confirmForm">
|
||||
<template #trigger>
|
||||
<n-button type="primary" :loading="formBtnLoading" size="small">提交</n-button>
|
||||
</template>
|
||||
你正在修改 {{ formValue.name }} 的菜单权限,确定要提交吗?
|
||||
</n-popconfirm>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
<n-popconfirm @positive-click="confirmForm">
|
||||
<template #trigger>
|
||||
<n-button type="primary" :loading="formBtnLoading">提交</n-button>
|
||||
</template>
|
||||
你正在修改 {{ formValue.name }} 的菜单权限,确定要提交吗?
|
||||
</n-popconfirm>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { GetPermissions, getRoleList, UpdatePermissions } from '@/api/system/role';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { NButton, useMessage } from 'naive-ui';
|
||||
import { adaModalWidth, getTreeKeys } from '@/utils/hotgo';
|
||||
import { findTreeNode, getAllExpandKeys } from '@/utils';
|
||||
@@ -86,7 +82,6 @@
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const message = useMessage();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref<State>(newState(null));
|
||||
@@ -111,7 +106,7 @@
|
||||
});
|
||||
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(840);
|
||||
return adaModalWidth(630);
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
@@ -134,11 +129,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function checkedTree(keys) {
|
||||
checkedKeys.value = keys;
|
||||
}
|
||||
@@ -165,6 +155,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 按名称和权限搜索
|
||||
function filterTreeNode(pattern: string, node: any) {
|
||||
if (!pattern) return true;
|
||||
const searchText = pattern.toLowerCase();
|
||||
|
||||
const label = (node.label || node.title || '').toLowerCase();
|
||||
if (label.includes(searchText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const permissions = node.permissions || '';
|
||||
if (permissions) {
|
||||
const permissionsLower = permissions.toLowerCase();
|
||||
if (permissionsLower.includes(searchText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleImportSelect(key: number) {
|
||||
showImportSelect.value = false;
|
||||
showModal.value = true;
|
||||
@@ -208,9 +218,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.space-group {
|
||||
margin-left: -8px;
|
||||
margin-right: -8px;
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
label: '添加',
|
||||
label: '添加子角色',
|
||||
onClick: handleAddSub.bind(null, record),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"build/**/*.d.ts",
|
||||
"mock/**/*.ts",
|
||||
"components.d.ts",
|
||||
"auto-imports.d.ts",
|
||||
"vite.config.ts"
|
||||
],
|
||||
"exclude": [
|
||||
|
||||
Reference in New Issue
Block a user