diff --git a/.env b/.env index 6b854e2..8cfed79 100644 --- a/.env +++ b/.env @@ -12,7 +12,7 @@ VITE_BASE_URL = VITE_API_URL = https://cmp-api.boss160.cn # 权限模式( frontend | backend ) -VITE_ACCESS_MODE = frontend +VITE_ACCESS_MODE = backend # 是否打开路由信息 VITE_OPEN_ROUTE_INFO = false diff --git a/src/api/modules/storage.ts b/src/api/modules/storage.ts index 4321deb..c5abff1 100644 --- a/src/api/modules/storage.ts +++ b/src/api/modules/storage.ts @@ -8,7 +8,7 @@ import type { BaseResponse } from '@/types/api' /** * 文件用途枚举 */ -export type FilePurpose = 'iot_import' | 'export' | 'attachment' +export type FilePurpose = 'iot_import' | 'device_import' | 'export' | 'attachment' /** * 获取上传 URL 请求参数 @@ -56,18 +56,18 @@ export class StorageService extends BaseService { * - 预签名 URL 有效期 15 分钟,请及时使用 * - 上传时 Content-Type 需与请求时一致 * - file_key 在上传成功后永久有效 + * - 开发环境通过代理上传,生产环境直接上传 * - * @param uploadUrl 预签名 URL + * @param uploadUrl 预签名 URL(由对象存储生成) * @param file 文件 * @param contentType 文件类型(需与 getUploadUrl 请求时保持一致) */ static async uploadFile(uploadUrl: string, file: File, contentType?: string): Promise { try { - // 在开发环境下,使用代理路径避免 CORS 问题 + // 开发环境使用代理解决 CORS 问题 let finalUrl = uploadUrl if (import.meta.env.DEV) { - // 将对象存储的域名替换为代理路径 - // 例如:http://obs-helf.cucloud.cn/cmp/... -> /obs-proxy/cmp/... + // 将对象存储域名替换为代理路径 finalUrl = uploadUrl.replace(/^https?:\/\/obs-helf\.cucloud\.cn/, '/obs-proxy') } @@ -81,8 +81,7 @@ export class StorageService extends BaseService { const response = await fetch(finalUrl, { method: 'PUT', body: file, - headers, - mode: 'cors' // 明确指定 CORS 模式 + headers }) if (!response.ok) { diff --git a/src/composables/useAuth.ts b/src/composables/useAuth.ts index 1cea0ef..629bc99 100644 --- a/src/composables/useAuth.ts +++ b/src/composables/useAuth.ts @@ -1,46 +1,57 @@ -import { useRoute } from 'vue-router' -import { storeToRefs } from 'pinia' import { useUserStore } from '@/store/modules/user' -import { useCommon } from '@/composables/useCommon' -import type { AppRouteRecord } from '@/types/router' - -type AuthItem = NonNullable[number] - -const userStore = useUserStore() /** - * 按钮权限(前后端模式通用) + * 权限判断组合式函数 + * 用于在 setup 函数或表格 formatter 中判断权限 + * * 用法: - * const { hasAuth } = useAuth() - * hasAuth('add') // 检查是否拥有新增权限 + * const { hasAuth, hasAllAuth } = useAuth() + * hasAuth('role:add') // 检查是否拥有权限 + * hasAuth(['role:add', 'role:edit']) // 检查是否拥有任意一个权限 + * hasAllAuth(['role:add', 'role:edit']) // 检查是否拥有全部权限 */ export const useAuth = () => { - const route = useRoute() - const { isFrontendMode } = useCommon() - const { info } = storeToRefs(userStore) - - // 前端按钮权限(例如:['add', 'edit']) - const frontendAuthList = info.value?.buttons ?? [] - - // 后端路由 meta 配置的权限列表(例如:[{ auth_mark: 'add' }]) - const backendAuthList: AuthItem[] = Array.isArray(route.meta.authList) - ? (route.meta.authList as AuthItem[]) - : [] + const userStore = useUserStore() /** - * 检查是否拥有某权限标识 - * @param auth 权限标识 + * 检查是否有指定权限(满足任意一个即可) + * @param permission 权限标识,可以是字符串或字符串数组 * @returns 是否有权限 */ - const hasAuth = (auth: string): boolean => { - if (isFrontendMode.value) { - return frontendAuthList.includes(auth) + const hasAuth = (permission: string | string[]): boolean => { + if (!permission) return false + + if (typeof permission === 'string') { + // 单个权限(同时检查 permissions 和 buttons) + return userStore.hasPermission(permission) || userStore.hasButton(permission) } - return backendAuthList.some((item) => item?.auth_mark === auth) + if (Array.isArray(permission)) { + // 多个权限(满足任意一个即可) + return permission.some( + (perm) => userStore.hasPermission(perm) || userStore.hasButton(perm) + ) + } + + return false + } + + /** + * 检查是否有所有指定权限(需要全部满足) + * @param permissions 权限标识数组 + * @returns 是否有全部权限 + */ + const hasAllAuth = (permissions: string[]): boolean => { + if (!permissions || !Array.isArray(permissions)) return false + + // 需要全部满足 + return permissions.every( + (perm) => userStore.hasPermission(perm) || userStore.hasButton(perm) + ) } return { - hasAuth + hasAuth, + hasAllAuth } } diff --git a/src/composables/useLogin.ts b/src/composables/useLogin.ts index 50f6153..680eaa6 100644 --- a/src/composables/useLogin.ts +++ b/src/composables/useLogin.ts @@ -208,12 +208,26 @@ export function useLogin() { return } + // 保存权限、菜单和按钮 + if (response.data.permissions) { + userStore.setPermissions(response.data.permissions) + } + if (response.data.menus) { + userStore.setMenus(response.data.menus) + } + if (response.data.buttons) { + userStore.setButtons(response.data.buttons) + } + // 保存记住密码 saveCredentials(formData.username, formData.password, formData.rememberPassword) // 显示登录成功提示 showLoginSuccessNotice() + // 等待数据持久化完成 + await new Promise((resolve) => setTimeout(resolve, 100)) + // 跳转到重定向页面或首页 const redirectPath = getRedirectPath(route) await router.push(redirectPath || HOME_PAGE) diff --git a/src/composables/usePermission.ts b/src/composables/usePermission.ts index f025590..bc800fb 100644 Binary files a/src/composables/usePermission.ts and b/src/composables/usePermission.ts differ diff --git a/src/directives/permission.ts b/src/directives/permission.ts index 00b11ad..4a79389 100644 --- a/src/directives/permission.ts +++ b/src/directives/permission.ts @@ -22,16 +22,20 @@ const permissionDirective: Directive = { let hasPermission = false if (typeof value === 'string') { - // 单个权限 - hasPermission = userStore.hasPermission(value) + // 单个权限(同时检查 permissions 和 buttons) + hasPermission = userStore.hasPermission(value) || userStore.hasButton(value) } else if (Array.isArray(value)) { // 多个权限 if (arg === 'all') { // 需要全部满足 - hasPermission = userStore.hasAllPermissions(value) + hasPermission = value.every( + (perm) => userStore.hasPermission(perm) || userStore.hasButton(perm) + ) } else { // 满足任意一个即可 - hasPermission = userStore.hasAnyPermission(value) + hasPermission = value.some( + (perm) => userStore.hasPermission(perm) || userStore.hasButton(perm) + ) } } diff --git a/src/router/guards/beforeEach.ts b/src/router/guards/beforeEach.ts index 5f79d27..733418c 100644 --- a/src/router/guards/beforeEach.ts +++ b/src/router/guards/beforeEach.ts @@ -20,7 +20,7 @@ import { isInWhiteList, hasRoutePermission, isTokenValid, buildLoginRedirect } f const isRouteRegistered = ref(false) // 临时开发模式:跳过所有权限验证(开发静态页面时使用) -const DEV_MODE_SKIP_AUTH = true +const DEV_MODE_SKIP_AUTH = false /** * 路由全局前置守卫 @@ -232,20 +232,125 @@ async function processFrontendMenu(router: Router): Promise { } /** - * 处理后端控制模式的菜单逻辑 + * 处理后端控制模式的菜单 */ async function processBackendMenu(router: Router): Promise { const closeLoading = loadingService.showLoading() try { - const { menuList } = await menuService.getMenuList() - await registerAndStoreMenu(router, menuList, closeLoading) + const userStore = useUserStore() + const backendMenus = userStore.menus || [] + const routeMap = buildRouteMap(asyncRoutes) + + const menuList = backendMenus + .map((menu) => convertBackendMenuToRoute(menu, routeMap)) + .filter((route) => route !== null) + + const finalMenuList = mergeDefaultMenus(menuList, routeMap) + await registerAndStoreMenu(router, finalMenuList, closeLoading) } catch (error) { closeLoading() throw error } } +/** + * 合并默认菜单 + * 将配置的默认菜单合并到后端返回的菜单中 + */ +function mergeDefaultMenus( + backendMenus: AppRouteRecord[], + routeMap: Map +): AppRouteRecord[] { + const defaultMenuPaths = ['/dashboard'] + + const defaultMenus: AppRouteRecord[] = defaultMenuPaths + .map((path) => { + const route = routeMap.get(path) + return route ? menuDataToRouter(route) : null + }) + .filter((menu): menu is AppRouteRecord => menu !== null) + + const backendPaths = new Set(backendMenus.map((m) => m.path)) + const filteredDefaultMenus = defaultMenus.filter((m) => !backendPaths.has(m.path)) + + return [...filteredDefaultMenus, ...backendMenus] +} + +/** + * 构建 URL 到路由的映射表 + */ +function buildRouteMap(routes: AppRouteRecord[], parentPath = ''): Map { + const map = new Map() + + routes.forEach((route) => { + // 构建完整路径 + const fullPath = route.path.startsWith('/') + ? route.path + : parentPath + ? `${parentPath}/${route.path}`.replace(/\/+/g, '/') + : `/${route.path}` + + // 存储路由映射 + map.set(fullPath, route) + + // 递归处理子路由 + if (route.children && route.children.length > 0) { + const childMap = buildRouteMap(route.children, fullPath) + childMap.forEach((childRoute, childPath) => { + map.set(childPath, childRoute) + }) + } + }) + + return map +} + +/** + * 将后端菜单数据转换为路由格式 + */ +function convertBackendMenuToRoute( + menu: any, + routeMap: Map, + parentPath = '' +): AppRouteRecord | null { + const menuUrl = menu.url || '/' + const matchedRoute = routeMap.get(menuUrl) + + if (!matchedRoute) { + console.warn(`未找到与菜单 URL "${menuUrl}" 匹配的路由定义: ${menu.name}`) + return null + } + + const route: AppRouteRecord = { + path: menuUrl, + name: matchedRoute.name, + component: matchedRoute.component, + meta: { + ...matchedRoute.meta, + title: menu.name, + permission: menu.perm_code, + sort: menu.sort || matchedRoute.meta?.sort || 0, + icon: menu.icon || matchedRoute.meta?.icon, + // 清除前端定义的 roles 和 permissions,使用后端权限控制 + roles: undefined, + permissions: undefined + } + } + + if (menu.children && menu.children.length > 0) { + const children = menu.children + .map((child: any) => convertBackendMenuToRoute(child, routeMap, menuUrl)) + .filter((child: AppRouteRecord | null) => child !== null) + + if (children.length > 0) { + route.children = children + } + } + + return route +} + /** * 注册路由并存储菜单数据 */ diff --git a/src/router/guards/permission.ts b/src/router/guards/permission.ts index 445e923..c02762c 100644 --- a/src/router/guards/permission.ts +++ b/src/router/guards/permission.ts @@ -4,6 +4,7 @@ import type { RouteLocationNormalized } from 'vue-router' import type { UserInfo } from '@/types/api' +import { useUserStore } from '@/store/modules/user' /** * 不需要登录的路由白名单 @@ -33,6 +34,7 @@ export const isInWhiteList = (path: string): boolean => { /** * 检查用户是否有权限访问路由 + * 根据文档要求:菜单访问基于 URL,按钮访问基于权限标识 */ export const hasRoutePermission = ( route: RouteLocationNormalized, @@ -40,7 +42,30 @@ export const hasRoutePermission = ( ): boolean => { const { roles = [], permissions = [] } = userInfo - // 如果路由没有设置权限要求,直接通过 + // 如果是超级管理员,直接通过 + if (userInfo.user_type === 1) { + return true + } + + // 默认始终可访问的路由(不需要菜单权限) + const defaultAllowedPaths = ['/dashboard'] + const isDefaultRoute = defaultAllowedPaths.some( + (allowedPath) => route.path === allowedPath || route.path.startsWith(allowedPath + '/') + ) + + // 检查菜单访问权限(基于 URL) + const userStore = useUserStore() + const userMenus = userStore.menus || [] + + // 如果是默认路由,跳过菜单权限检查 + if (!isDefaultRoute && userMenus.length > 0) { + const hasMenuAccess = checkMenuAccess(route.path, userMenus) + if (!hasMenuAccess) { + return false + } + } + + // 如果路由没有设置额外的权限要求,直接通过 if (!route.meta?.roles && !route.meta?.permissions) { return true } @@ -58,11 +83,9 @@ export const hasRoutePermission = ( if (route.meta.permissions) { const routePermissions = route.meta.permissions as string[] const hasPermission = routePermissions.some((permission) => { - // 支持通配符权限 *:*:* if (permissions.includes('*:*:*')) { return true } - // 精确匹配或前缀匹配 return permissions.some((userPermission) => { if (userPermission.endsWith('*')) { const prefix = userPermission.slice(0, -1) @@ -79,6 +102,25 @@ export const hasRoutePermission = ( return true } +/** + * 递归检查菜单访问权限(基于 URL) + */ +function checkMenuAccess(path: string, menus: any[]): boolean { + for (const menu of menus) { + // 检查当前菜单的 URL 是否匹配 + if (menu.url && (path === menu.url || path.startsWith(menu.url + '/'))) { + return true + } + // 递归检查子菜单 + if (menu.children && menu.children.length > 0) { + if (checkMenuAccess(path, menu.children)) { + return true + } + } + } + return false +} + /** * 检查 Token 是否有效 * 简单检查,真实项目中应该验证 JWT 或者调用后端接口 diff --git a/src/router/routes/asyncRoutes.ts b/src/router/routes/asyncRoutes.ts index 0afc907..cf9000c 100644 --- a/src/router/routes/asyncRoutes.ts +++ b/src/router/routes/asyncRoutes.ts @@ -306,32 +306,32 @@ export const asyncRoutes: AppRouteRecord[] = [ keepAlive: true, isHideTab: true } - }, - { - path: 'menu', - name: 'Menus', - component: RoutesAlias.Menu, - meta: { - title: 'menus.system.menu', - keepAlive: true, - roles: ['R_SUPER'], - authList: [ - { - title: '新增', - auth_mark: 'add' - }, - { - title: '编辑', - auth_mark: 'edit' - }, - { - title: '删除', - auth_mark: 'delete' - } - ] - } } // { + // path: 'menu', + // name: 'Menus', + // component: RoutesAlias.Menu, + // meta: { + // title: 'menus.system.menu', + // keepAlive: true, + // roles: ['R_SUPER'], + // authList: [ + // { + // title: '新增', + // auth_mark: 'add' + // }, + // { + // title: '编辑', + // auth_mark: 'edit' + // }, + // { + // title: '删除', + // auth_mark: 'delete' + // } + // ] + // } + // } + // { // path: 'nested', // name: 'Nested', // component: '', @@ -741,6 +741,26 @@ export const asyncRoutes: AppRouteRecord[] = [ } ] }, + { + path: '/shop-management', + name: 'ShopManagement', + component: RoutesAlias.Home, + meta: { + title: 'menus.product.shop', + icon: '' + }, + children: [ + { + path: 'list', + name: 'Shop', + component: RoutesAlias.Shop, + meta: { + title: 'menus.product.shop', + keepAlive: true + } + } + ] + }, { path: '/account-management', name: 'AccountManagement', @@ -816,71 +836,71 @@ export const asyncRoutes: AppRouteRecord[] = [ } ] }, - { - path: '/product', - name: 'Product', - component: RoutesAlias.Home, - meta: { - title: 'menus.product.title', - icon: '' - }, - children: [ - // { - // path: 'sim-card', - // name: 'SimCardManagement', - // component: RoutesAlias.SimCardManagement, - // meta: { - // title: 'menus.product.simCard', - // keepAlive: true - // } - // }, - // { - // path: 'sim-card-assign', - // name: 'SimCardAssign', - // component: RoutesAlias.SimCardAssign, - // meta: { - // title: 'menus.product.simCardAssign', - // keepAlive: true - // } - // }, - // { - // path: 'package-series', - // name: 'PackageSeries', - // component: RoutesAlias.PackageSeries, - // meta: { - // title: 'menus.product.packageSeries', - // keepAlive: true - // } - // }, - // { - // path: 'package-list', - // name: 'PackageList', - // component: RoutesAlias.PackageList, - // meta: { - // title: 'menus.product.packageList', - // keepAlive: true - // } - // }, - // { - // path: 'package-assign', - // name: 'PackageAssign', - // component: RoutesAlias.PackageAssign, - // meta: { - // title: 'menus.product.packageAssign', - // keepAlive: true - // } - // }, - { - path: 'shop', - name: 'Shop', - component: RoutesAlias.Shop, - meta: { - title: 'menus.product.shop', - keepAlive: true - } - } - ] - }, + // { + // path: '/product', + // name: 'Product', + // component: RoutesAlias.Home, + // meta: { + // title: 'menus.product.title', + // icon: '' + // }, + // children: [ + // // { + // // path: 'sim-card', + // // name: 'SimCardManagement', + // // component: RoutesAlias.SimCardManagement, + // // meta: { + // // title: 'menus.product.simCard', + // // keepAlive: true + // // } + // // }, + // // { + // // path: 'sim-card-assign', + // // name: 'SimCardAssign', + // // component: RoutesAlias.SimCardAssign, + // // meta: { + // // title: 'menus.product.simCardAssign', + // // keepAlive: true + // // } + // // }, + // // { + // // path: 'package-series', + // // name: 'PackageSeries', + // // component: RoutesAlias.PackageSeries, + // // meta: { + // // title: 'menus.product.packageSeries', + // // keepAlive: true + // // } + // // }, + // // { + // // path: 'package-list', + // // name: 'PackageList', + // // component: RoutesAlias.PackageList, + // // meta: { + // // title: 'menus.product.packageList', + // // keepAlive: true + // // } + // // }, + // // { + // // path: 'package-assign', + // // name: 'PackageAssign', + // // component: RoutesAlias.PackageAssign, + // // meta: { + // // title: 'menus.product.packageAssign', + // // keepAlive: true + // // } + // // }, + // { + // path: 'shop', + // name: 'Shop', + // component: RoutesAlias.Shop, + // meta: { + // title: 'menus.product.shop', + // keepAlive: true + // } + // } + // ] + // }, { path: '/asset-management', name: 'AssetManagement', diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 86f9bb0..d02e28d 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -24,11 +24,15 @@ export const useUserStore = defineStore( const accessToken = ref('') const refreshToken = ref('') const permissions = ref([]) + const menus = ref([]) + const buttons = ref([]) const getUserInfo = computed(() => info.value) const getSettingState = computed(() => useSettingStore().$state) const getWorktabState = computed(() => useWorktabStore().$state) const getPermissions = computed(() => permissions.value) + const getMenus = computed(() => menus.value) + const getButtons = computed(() => buttons.value) const setUserInfo = (newInfo: UserInfo) => { info.value = newInfo @@ -38,6 +42,14 @@ export const useUserStore = defineStore( permissions.value = perms } + const setMenus = (menuList: any[]) => { + menus.value = menuList + } + + const setButtons = (buttonList: string[]) => { + buttons.value = buttonList + } + // 检查是否是超级管理员 const isSuperAdmin = computed(() => info.value.user_type === 1) @@ -62,6 +74,13 @@ export const useUserStore = defineStore( return perms.every((perm) => permissions.value.includes(perm)) } + // 检查是否有某个按钮权限 + const hasButton = (buttonCode: string): boolean => { + // 超级管理员拥有所有权限 + if (isSuperAdmin.value) return true + return buttons.value.includes(buttonCode) + } + const setLoginStatus = (status: boolean) => { isLogin.value = status } @@ -105,6 +124,8 @@ export const useUserStore = defineStore( accessToken.value = '' refreshToken.value = '' permissions.value = [] + menus.value = [] + buttons.value = [] useWorktabStore().opened = [] sessionStorage.removeItem('iframeRoutes') resetRouterState(router) @@ -122,16 +143,23 @@ export const useUserStore = defineStore( accessToken, refreshToken, permissions, + menus, + buttons, getUserInfo, getSettingState, getWorktabState, getPermissions, + getMenus, + getButtons, isSuperAdmin, setUserInfo, setPermissions, + setMenus, + setButtons, hasPermission, hasAnyPermission, hasAllPermissions, + hasButton, setLoginStatus, setLanguage, setSearchHistory, @@ -145,8 +173,18 @@ export const useUserStore = defineStore( persist: { key: 'user', storage: localStorage, - // 只持久化 token 和登录状态,用户信息每次刷新都从接口获取 - paths: ['accessToken', 'refreshToken', 'isLogin', 'language', 'isLock', 'lockPassword'] + paths: [ + 'accessToken', + 'refreshToken', + 'isLogin', + 'language', + 'isLock', + 'lockPassword', + 'info', + 'permissions', + 'menus', + 'buttons' + ] } } ) diff --git a/src/types/auto-imports.d.ts b/src/types/auto-imports.d.ts index eeef0a6..13a3b2b 100644 --- a/src/types/auto-imports.d.ts +++ b/src/types/auto-imports.d.ts @@ -6,7 +6,7 @@ // biome-ignore lint: disable export {} declare global { - const EffectScope: (typeof import('vue'))['EffectScope'] + const EffectScope: typeof import('vue')['EffectScope'] const ElButton: (typeof import('element-plus/es'))['ElButton'] const ElMessage: (typeof import('element-plus/es'))['ElMessage'] const ElMessageBox: (typeof import('element-plus/es'))['ElMessageBox'] @@ -14,319 +14,304 @@ declare global { const ElPopconfirm: (typeof import('element-plus/es'))['ElPopconfirm'] const ElPopover: (typeof import('element-plus/es'))['ElPopover'] const ElTableColumn: (typeof import('element-plus/es'))['ElTableColumn'] - const ElTag: (typeof import('element-plus/es'))['ElTag'] - const acceptHMRUpdate: (typeof import('pinia'))['acceptHMRUpdate'] - const asyncComputed: (typeof import('@vueuse/core'))['asyncComputed'] - const autoResetRef: (typeof import('@vueuse/core'))['autoResetRef'] - const computed: (typeof import('vue'))['computed'] - const computedAsync: (typeof import('@vueuse/core'))['computedAsync'] - const computedEager: (typeof import('@vueuse/core'))['computedEager'] - const computedInject: (typeof import('@vueuse/core'))['computedInject'] - const computedWithControl: (typeof import('@vueuse/core'))['computedWithControl'] - const controlledComputed: (typeof import('@vueuse/core'))['controlledComputed'] - const controlledRef: (typeof import('@vueuse/core'))['controlledRef'] - const createApp: (typeof import('vue'))['createApp'] - const createEventHook: (typeof import('@vueuse/core'))['createEventHook'] - const createGlobalState: (typeof import('@vueuse/core'))['createGlobalState'] - const createInjectionState: (typeof import('@vueuse/core'))['createInjectionState'] - const createPinia: (typeof import('pinia'))['createPinia'] - const createReactiveFn: (typeof import('@vueuse/core'))['createReactiveFn'] - const createReusableTemplate: (typeof import('@vueuse/core'))['createReusableTemplate'] - const createSharedComposable: (typeof import('@vueuse/core'))['createSharedComposable'] - const createTemplatePromise: (typeof import('@vueuse/core'))['createTemplatePromise'] - const createUnrefFn: (typeof import('@vueuse/core'))['createUnrefFn'] - const customRef: (typeof import('vue'))['customRef'] - const debouncedRef: (typeof import('@vueuse/core'))['debouncedRef'] - const debouncedWatch: (typeof import('@vueuse/core'))['debouncedWatch'] - const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent'] - const defineComponent: (typeof import('vue'))['defineComponent'] - const defineStore: (typeof import('pinia'))['defineStore'] - const eagerComputed: (typeof import('@vueuse/core'))['eagerComputed'] - const effectScope: (typeof import('vue'))['effectScope'] - const extendRef: (typeof import('@vueuse/core'))['extendRef'] - const getActivePinia: (typeof import('pinia'))['getActivePinia'] - const getCurrentInstance: (typeof import('vue'))['getCurrentInstance'] - const getCurrentScope: (typeof import('vue'))['getCurrentScope'] - const h: (typeof import('vue'))['h'] - const ignorableWatch: (typeof import('@vueuse/core'))['ignorableWatch'] - const inject: (typeof import('vue'))['inject'] - const injectLocal: (typeof import('@vueuse/core'))['injectLocal'] - const isDefined: (typeof import('@vueuse/core'))['isDefined'] - 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 makeDestructurable: (typeof import('@vueuse/core'))['makeDestructurable'] - const mapActions: (typeof import('pinia'))['mapActions'] - const mapGetters: (typeof import('pinia'))['mapGetters'] - const mapState: (typeof import('pinia'))['mapState'] - const mapStores: (typeof import('pinia'))['mapStores'] - const mapWritableState: (typeof import('pinia'))['mapWritableState'] - 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 onClickOutside: (typeof import('@vueuse/core'))['onClickOutside'] - const onDeactivated: (typeof import('vue'))['onDeactivated'] - const onErrorCaptured: (typeof import('vue'))['onErrorCaptured'] - const onKeyStroke: (typeof import('@vueuse/core'))['onKeyStroke'] - const onLongPress: (typeof import('@vueuse/core'))['onLongPress'] - 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 onStartTyping: (typeof import('@vueuse/core'))['onStartTyping'] - const onUnmounted: (typeof import('vue'))['onUnmounted'] - const onUpdated: (typeof import('vue'))['onUpdated'] - const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup'] - const pausableWatch: (typeof import('@vueuse/core'))['pausableWatch'] - const provide: (typeof import('vue'))['provide'] - const provideLocal: (typeof import('@vueuse/core'))['provideLocal'] - const reactify: (typeof import('@vueuse/core'))['reactify'] - const reactifyObject: (typeof import('@vueuse/core'))['reactifyObject'] - const reactive: (typeof import('vue'))['reactive'] - const reactiveComputed: (typeof import('@vueuse/core'))['reactiveComputed'] - const reactiveOmit: (typeof import('@vueuse/core'))['reactiveOmit'] - const reactivePick: (typeof import('@vueuse/core'))['reactivePick'] - const readonly: (typeof import('vue'))['readonly'] - const ref: (typeof import('vue'))['ref'] - const refAutoReset: (typeof import('@vueuse/core'))['refAutoReset'] - const refDebounced: (typeof import('@vueuse/core'))['refDebounced'] - const refDefault: (typeof import('@vueuse/core'))['refDefault'] - const refThrottled: (typeof import('@vueuse/core'))['refThrottled'] - const refWithControl: (typeof import('@vueuse/core'))['refWithControl'] - const resolveComponent: (typeof import('vue'))['resolveComponent'] - const resolveRef: (typeof import('@vueuse/core'))['resolveRef'] - const resolveUnref: (typeof import('@vueuse/core'))['resolveUnref'] - const setActivePinia: (typeof import('pinia'))['setActivePinia'] - const setMapStoreSuffix: (typeof import('pinia'))['setMapStoreSuffix'] - const shallowReactive: (typeof import('vue'))['shallowReactive'] - const shallowReadonly: (typeof import('vue'))['shallowReadonly'] - const shallowRef: (typeof import('vue'))['shallowRef'] - const storeToRefs: (typeof import('pinia'))['storeToRefs'] - const syncRef: (typeof import('@vueuse/core'))['syncRef'] - const syncRefs: (typeof import('@vueuse/core'))['syncRefs'] - const templateRef: (typeof import('@vueuse/core'))['templateRef'] - const throttledRef: (typeof import('@vueuse/core'))['throttledRef'] - const throttledWatch: (typeof import('@vueuse/core'))['throttledWatch'] - const toRaw: (typeof import('vue'))['toRaw'] - const toReactive: (typeof import('@vueuse/core'))['toReactive'] - 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 tryOnBeforeMount: (typeof import('@vueuse/core'))['tryOnBeforeMount'] - const tryOnBeforeUnmount: (typeof import('@vueuse/core'))['tryOnBeforeUnmount'] - const tryOnMounted: (typeof import('@vueuse/core'))['tryOnMounted'] - const tryOnScopeDispose: (typeof import('@vueuse/core'))['tryOnScopeDispose'] - const tryOnUnmounted: (typeof import('@vueuse/core'))['tryOnUnmounted'] - const unref: (typeof import('vue'))['unref'] - const unrefElement: (typeof import('@vueuse/core'))['unrefElement'] - const until: (typeof import('@vueuse/core'))['until'] - const useActiveElement: (typeof import('@vueuse/core'))['useActiveElement'] - const useAnimate: (typeof import('@vueuse/core'))['useAnimate'] - const useArrayDifference: (typeof import('@vueuse/core'))['useArrayDifference'] - const useArrayEvery: (typeof import('@vueuse/core'))['useArrayEvery'] - const useArrayFilter: (typeof import('@vueuse/core'))['useArrayFilter'] - const useArrayFind: (typeof import('@vueuse/core'))['useArrayFind'] - const useArrayFindIndex: (typeof import('@vueuse/core'))['useArrayFindIndex'] - const useArrayFindLast: (typeof import('@vueuse/core'))['useArrayFindLast'] - const useArrayIncludes: (typeof import('@vueuse/core'))['useArrayIncludes'] - const useArrayJoin: (typeof import('@vueuse/core'))['useArrayJoin'] - const useArrayMap: (typeof import('@vueuse/core'))['useArrayMap'] - const useArrayReduce: (typeof import('@vueuse/core'))['useArrayReduce'] - const useArraySome: (typeof import('@vueuse/core'))['useArraySome'] - const useArrayUnique: (typeof import('@vueuse/core'))['useArrayUnique'] - const useAsyncQueue: (typeof import('@vueuse/core'))['useAsyncQueue'] - const useAsyncState: (typeof import('@vueuse/core'))['useAsyncState'] - const useAttrs: (typeof import('vue'))['useAttrs'] - const useBase64: (typeof import('@vueuse/core'))['useBase64'] - const useBattery: (typeof import('@vueuse/core'))['useBattery'] - const useBluetooth: (typeof import('@vueuse/core'))['useBluetooth'] - const useBreakpoints: (typeof import('@vueuse/core'))['useBreakpoints'] - const useBroadcastChannel: (typeof import('@vueuse/core'))['useBroadcastChannel'] - const useBrowserLocation: (typeof import('@vueuse/core'))['useBrowserLocation'] - const useCached: (typeof import('@vueuse/core'))['useCached'] - const useClipboard: (typeof import('@vueuse/core'))['useClipboard'] - const useClipboardItems: (typeof import('@vueuse/core'))['useClipboardItems'] - const useCloned: (typeof import('@vueuse/core'))['useCloned'] - const useColorMode: (typeof import('@vueuse/core'))['useColorMode'] - const useConfirmDialog: (typeof import('@vueuse/core'))['useConfirmDialog'] - const useCounter: (typeof import('@vueuse/core'))['useCounter'] - const useCssModule: (typeof import('vue'))['useCssModule'] - const useCssVar: (typeof import('@vueuse/core'))['useCssVar'] - const useCssVars: (typeof import('vue'))['useCssVars'] - const useCurrentElement: (typeof import('@vueuse/core'))['useCurrentElement'] - const useCycleList: (typeof import('@vueuse/core'))['useCycleList'] - const useDark: (typeof import('@vueuse/core'))['useDark'] - const useDateFormat: (typeof import('@vueuse/core'))['useDateFormat'] - const useDebounce: (typeof import('@vueuse/core'))['useDebounce'] - const useDebounceFn: (typeof import('@vueuse/core'))['useDebounceFn'] - const useDebouncedRefHistory: (typeof import('@vueuse/core'))['useDebouncedRefHistory'] - const useDeviceMotion: (typeof import('@vueuse/core'))['useDeviceMotion'] - const useDeviceOrientation: (typeof import('@vueuse/core'))['useDeviceOrientation'] - const useDevicePixelRatio: (typeof import('@vueuse/core'))['useDevicePixelRatio'] - const useDevicesList: (typeof import('@vueuse/core'))['useDevicesList'] - const useDisplayMedia: (typeof import('@vueuse/core'))['useDisplayMedia'] - const useDocumentVisibility: (typeof import('@vueuse/core'))['useDocumentVisibility'] - const useDraggable: (typeof import('@vueuse/core'))['useDraggable'] - const useDropZone: (typeof import('@vueuse/core'))['useDropZone'] - const useElementBounding: (typeof import('@vueuse/core'))['useElementBounding'] - const useElementByPoint: (typeof import('@vueuse/core'))['useElementByPoint'] - const useElementHover: (typeof import('@vueuse/core'))['useElementHover'] - const useElementSize: (typeof import('@vueuse/core'))['useElementSize'] - const useElementVisibility: (typeof import('@vueuse/core'))['useElementVisibility'] - const useEventBus: (typeof import('@vueuse/core'))['useEventBus'] - const useEventListener: (typeof import('@vueuse/core'))['useEventListener'] - const useEventSource: (typeof import('@vueuse/core'))['useEventSource'] - const useEyeDropper: (typeof import('@vueuse/core'))['useEyeDropper'] - const useFavicon: (typeof import('@vueuse/core'))['useFavicon'] - const useFetch: (typeof import('@vueuse/core'))['useFetch'] - const useFileDialog: (typeof import('@vueuse/core'))['useFileDialog'] - const useFileSystemAccess: (typeof import('@vueuse/core'))['useFileSystemAccess'] - const useFocus: (typeof import('@vueuse/core'))['useFocus'] - const useFocusWithin: (typeof import('@vueuse/core'))['useFocusWithin'] - const useFps: (typeof import('@vueuse/core'))['useFps'] - const useFullscreen: (typeof import('@vueuse/core'))['useFullscreen'] - const useGamepad: (typeof import('@vueuse/core'))['useGamepad'] - const useGeolocation: (typeof import('@vueuse/core'))['useGeolocation'] - const useId: (typeof import('vue'))['useId'] - const useIdle: (typeof import('@vueuse/core'))['useIdle'] - const useImage: (typeof import('@vueuse/core'))['useImage'] - const useInfiniteScroll: (typeof import('@vueuse/core'))['useInfiniteScroll'] - const useIntersectionObserver: (typeof import('@vueuse/core'))['useIntersectionObserver'] - const useInterval: (typeof import('@vueuse/core'))['useInterval'] - const useIntervalFn: (typeof import('@vueuse/core'))['useIntervalFn'] - const useKeyModifier: (typeof import('@vueuse/core'))['useKeyModifier'] - const useLastChanged: (typeof import('@vueuse/core'))['useLastChanged'] - const useLink: (typeof import('vue-router'))['useLink'] - const useLocalStorage: (typeof import('@vueuse/core'))['useLocalStorage'] - const useMagicKeys: (typeof import('@vueuse/core'))['useMagicKeys'] - const useManualRefHistory: (typeof import('@vueuse/core'))['useManualRefHistory'] - const useMediaControls: (typeof import('@vueuse/core'))['useMediaControls'] - const useMediaQuery: (typeof import('@vueuse/core'))['useMediaQuery'] - const useMemoize: (typeof import('@vueuse/core'))['useMemoize'] - const useMemory: (typeof import('@vueuse/core'))['useMemory'] - const useModel: (typeof import('vue'))['useModel'] - const useMounted: (typeof import('@vueuse/core'))['useMounted'] - const useMouse: (typeof import('@vueuse/core'))['useMouse'] - const useMouseInElement: (typeof import('@vueuse/core'))['useMouseInElement'] - const useMousePressed: (typeof import('@vueuse/core'))['useMousePressed'] - const useMutationObserver: (typeof import('@vueuse/core'))['useMutationObserver'] - const useNavigatorLanguage: (typeof import('@vueuse/core'))['useNavigatorLanguage'] - const useNetwork: (typeof import('@vueuse/core'))['useNetwork'] - const useNow: (typeof import('@vueuse/core'))['useNow'] - const useObjectUrl: (typeof import('@vueuse/core'))['useObjectUrl'] - const useOffsetPagination: (typeof import('@vueuse/core'))['useOffsetPagination'] - const useOnline: (typeof import('@vueuse/core'))['useOnline'] - const usePageLeave: (typeof import('@vueuse/core'))['usePageLeave'] - const useParallax: (typeof import('@vueuse/core'))['useParallax'] - const useParentElement: (typeof import('@vueuse/core'))['useParentElement'] - const usePerformanceObserver: (typeof import('@vueuse/core'))['usePerformanceObserver'] - const usePermission: (typeof import('@vueuse/core'))['usePermission'] - const usePointer: (typeof import('@vueuse/core'))['usePointer'] - const usePointerLock: (typeof import('@vueuse/core'))['usePointerLock'] - const usePointerSwipe: (typeof import('@vueuse/core'))['usePointerSwipe'] - const usePreferredColorScheme: (typeof import('@vueuse/core'))['usePreferredColorScheme'] - const usePreferredContrast: (typeof import('@vueuse/core'))['usePreferredContrast'] - const usePreferredDark: (typeof import('@vueuse/core'))['usePreferredDark'] - const usePreferredLanguages: (typeof import('@vueuse/core'))['usePreferredLanguages'] - const usePreferredReducedMotion: (typeof import('@vueuse/core'))['usePreferredReducedMotion'] - const usePrevious: (typeof import('@vueuse/core'))['usePrevious'] - const useRafFn: (typeof import('@vueuse/core'))['useRafFn'] - const useRefHistory: (typeof import('@vueuse/core'))['useRefHistory'] - const useResizeObserver: (typeof import('@vueuse/core'))['useResizeObserver'] - const useRoute: (typeof import('vue-router'))['useRoute'] - const useRouter: (typeof import('vue-router'))['useRouter'] - const useScreenOrientation: (typeof import('@vueuse/core'))['useScreenOrientation'] - const useScreenSafeArea: (typeof import('@vueuse/core'))['useScreenSafeArea'] - const useScriptTag: (typeof import('@vueuse/core'))['useScriptTag'] - const useScroll: (typeof import('@vueuse/core'))['useScroll'] - const useScrollLock: (typeof import('@vueuse/core'))['useScrollLock'] - const useSessionStorage: (typeof import('@vueuse/core'))['useSessionStorage'] - const useShare: (typeof import('@vueuse/core'))['useShare'] - const useSlots: (typeof import('vue'))['useSlots'] - const useSorted: (typeof import('@vueuse/core'))['useSorted'] - const useSpeechRecognition: (typeof import('@vueuse/core'))['useSpeechRecognition'] - const useSpeechSynthesis: (typeof import('@vueuse/core'))['useSpeechSynthesis'] - const useStepper: (typeof import('@vueuse/core'))['useStepper'] - const useStorage: (typeof import('@vueuse/core'))['useStorage'] - const useStorageAsync: (typeof import('@vueuse/core'))['useStorageAsync'] - const useStyleTag: (typeof import('@vueuse/core'))['useStyleTag'] - const useSupported: (typeof import('@vueuse/core'))['useSupported'] - const useSwipe: (typeof import('@vueuse/core'))['useSwipe'] - const useTemplateRef: (typeof import('vue'))['useTemplateRef'] - const useTemplateRefsList: (typeof import('@vueuse/core'))['useTemplateRefsList'] - const useTextDirection: (typeof import('@vueuse/core'))['useTextDirection'] - const useTextSelection: (typeof import('@vueuse/core'))['useTextSelection'] - const useTextareaAutosize: (typeof import('@vueuse/core'))['useTextareaAutosize'] - const useThrottle: (typeof import('@vueuse/core'))['useThrottle'] - const useThrottleFn: (typeof import('@vueuse/core'))['useThrottleFn'] - const useThrottledRefHistory: (typeof import('@vueuse/core'))['useThrottledRefHistory'] - const useTimeAgo: (typeof import('@vueuse/core'))['useTimeAgo'] - const useTimeout: (typeof import('@vueuse/core'))['useTimeout'] - const useTimeoutFn: (typeof import('@vueuse/core'))['useTimeoutFn'] - const useTimeoutPoll: (typeof import('@vueuse/core'))['useTimeoutPoll'] - const useTimestamp: (typeof import('@vueuse/core'))['useTimestamp'] - const useTitle: (typeof import('@vueuse/core'))['useTitle'] - const useToNumber: (typeof import('@vueuse/core'))['useToNumber'] - const useToString: (typeof import('@vueuse/core'))['useToString'] - const useToggle: (typeof import('@vueuse/core'))['useToggle'] - const useTransition: (typeof import('@vueuse/core'))['useTransition'] - const useUrlSearchParams: (typeof import('@vueuse/core'))['useUrlSearchParams'] - const useUserMedia: (typeof import('@vueuse/core'))['useUserMedia'] - const useVModel: (typeof import('@vueuse/core'))['useVModel'] - const useVModels: (typeof import('@vueuse/core'))['useVModels'] - const useVibrate: (typeof import('@vueuse/core'))['useVibrate'] - const useVirtualList: (typeof import('@vueuse/core'))['useVirtualList'] - const useWakeLock: (typeof import('@vueuse/core'))['useWakeLock'] - const useWebNotification: (typeof import('@vueuse/core'))['useWebNotification'] - const useWebSocket: (typeof import('@vueuse/core'))['useWebSocket'] - const useWebWorker: (typeof import('@vueuse/core'))['useWebWorker'] - const useWebWorkerFn: (typeof import('@vueuse/core'))['useWebWorkerFn'] - const useWindowFocus: (typeof import('@vueuse/core'))['useWindowFocus'] - const useWindowScroll: (typeof import('@vueuse/core'))['useWindowScroll'] - const useWindowSize: (typeof import('@vueuse/core'))['useWindowSize'] - const watch: (typeof import('vue'))['watch'] - const watchArray: (typeof import('@vueuse/core'))['watchArray'] - const watchAtMost: (typeof import('@vueuse/core'))['watchAtMost'] - const watchDebounced: (typeof import('@vueuse/core'))['watchDebounced'] - const watchDeep: (typeof import('@vueuse/core'))['watchDeep'] - const watchEffect: (typeof import('vue'))['watchEffect'] - const watchIgnorable: (typeof import('@vueuse/core'))['watchIgnorable'] - const watchImmediate: (typeof import('@vueuse/core'))['watchImmediate'] - const watchOnce: (typeof import('@vueuse/core'))['watchOnce'] - const watchPausable: (typeof import('@vueuse/core'))['watchPausable'] - const watchPostEffect: (typeof import('vue'))['watchPostEffect'] - const watchSyncEffect: (typeof import('vue'))['watchSyncEffect'] - const watchThrottled: (typeof import('@vueuse/core'))['watchThrottled'] - const watchTriggerable: (typeof import('@vueuse/core'))['watchTriggerable'] - const watchWithFilter: (typeof import('@vueuse/core'))['watchWithFilter'] - const whenever: (typeof import('@vueuse/core'))['whenever'] + const ElTag: typeof import('element-plus/es')['ElTag'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] + const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] + const computed: typeof import('vue')['computed'] + const computedAsync: typeof import('@vueuse/core')['computedAsync'] + const computedEager: typeof import('@vueuse/core')['computedEager'] + const computedInject: typeof import('@vueuse/core')['computedInject'] + const computedWithControl: typeof import('@vueuse/core')['computedWithControl'] + const controlledComputed: typeof import('@vueuse/core')['controlledComputed'] + const controlledRef: typeof import('@vueuse/core')['controlledRef'] + const createApp: typeof import('vue')['createApp'] + const createEventHook: typeof import('@vueuse/core')['createEventHook'] + const createGlobalState: typeof import('@vueuse/core')['createGlobalState'] + const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] + const createPinia: typeof import('pinia')['createPinia'] + const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] + const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate'] + const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] + const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise'] + const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] + const customRef: typeof import('vue')['customRef'] + const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] + const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] + const effectScope: typeof import('vue')['effectScope'] + const extendRef: typeof import('@vueuse/core')['extendRef'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] + const inject: typeof import('vue')['inject'] + const injectLocal: typeof import('@vueuse/core')['injectLocal'] + const isDefined: typeof import('@vueuse/core')['isDefined'] + 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 makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + 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 onClickOutside: typeof import('@vueuse/core')['onClickOutside'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] + const onLongPress: typeof import('@vueuse/core')['onLongPress'] + 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 onStartTyping: typeof import('@vueuse/core')['onStartTyping'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] + const provide: typeof import('vue')['provide'] + const provideLocal: typeof import('@vueuse/core')['provideLocal'] + const reactify: typeof import('@vueuse/core')['reactify'] + const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] + const reactive: typeof import('vue')['reactive'] + const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed'] + const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit'] + const reactivePick: typeof import('@vueuse/core')['reactivePick'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const refAutoReset: typeof import('@vueuse/core')['refAutoReset'] + const refDebounced: typeof import('@vueuse/core')['refDebounced'] + const refDefault: typeof import('@vueuse/core')['refDefault'] + const refThrottled: typeof import('@vueuse/core')['refThrottled'] + const refWithControl: typeof import('@vueuse/core')['refWithControl'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const resolveRef: typeof import('@vueuse/core')['resolveRef'] + const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const syncRef: typeof import('@vueuse/core')['syncRef'] + const syncRefs: typeof import('@vueuse/core')['syncRefs'] + const templateRef: typeof import('@vueuse/core')['templateRef'] + const throttledRef: typeof import('@vueuse/core')['throttledRef'] + const throttledWatch: typeof import('@vueuse/core')['throttledWatch'] + const toRaw: typeof import('vue')['toRaw'] + const toReactive: typeof import('@vueuse/core')['toReactive'] + 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 tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount'] + const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount'] + const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted'] + const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose'] + const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted'] + const unref: typeof import('vue')['unref'] + const unrefElement: typeof import('@vueuse/core')['unrefElement'] + const until: typeof import('@vueuse/core')['until'] + const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] + const useAnimate: typeof import('@vueuse/core')['useAnimate'] + const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] + const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery'] + const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] + const useArrayFind: typeof import('@vueuse/core')['useArrayFind'] + const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex'] + const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast'] + const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes'] + const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin'] + const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] + const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] + const useArraySome: typeof import('@vueuse/core')['useArraySome'] + const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique'] + const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] + const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] + const useAttrs: typeof import('vue')['useAttrs'] + const useBase64: typeof import('@vueuse/core')['useBase64'] + const useBattery: typeof import('@vueuse/core')['useBattery'] + const useBluetooth: typeof import('@vueuse/core')['useBluetooth'] + const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] + const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] + const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] + const useCached: typeof import('@vueuse/core')['useCached'] + const useClipboard: typeof import('@vueuse/core')['useClipboard'] + const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems'] + const useCloned: typeof import('@vueuse/core')['useCloned'] + const useColorMode: typeof import('@vueuse/core')['useColorMode'] + const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] + const useCounter: typeof import('@vueuse/core')['useCounter'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVar: typeof import('@vueuse/core')['useCssVar'] + const useCssVars: typeof import('vue')['useCssVars'] + const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] + const useCycleList: typeof import('@vueuse/core')['useCycleList'] + const useDark: typeof import('@vueuse/core')['useDark'] + const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] + const useDebounce: typeof import('@vueuse/core')['useDebounce'] + const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] + const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] + const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] + const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] + const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] + const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] + const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] + const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] + const useDraggable: typeof import('@vueuse/core')['useDraggable'] + const useDropZone: typeof import('@vueuse/core')['useDropZone'] + const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] + const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] + const useElementHover: typeof import('@vueuse/core')['useElementHover'] + const useElementSize: typeof import('@vueuse/core')['useElementSize'] + const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] + const useEventBus: typeof import('@vueuse/core')['useEventBus'] + const useEventListener: typeof import('@vueuse/core')['useEventListener'] + const useEventSource: typeof import('@vueuse/core')['useEventSource'] + const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper'] + const useFavicon: typeof import('@vueuse/core')['useFavicon'] + const useFetch: typeof import('@vueuse/core')['useFetch'] + const useFileDialog: typeof import('@vueuse/core')['useFileDialog'] + const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] + const useFocus: typeof import('@vueuse/core')['useFocus'] + const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] + const useFps: typeof import('@vueuse/core')['useFps'] + const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] + const useGamepad: typeof import('@vueuse/core')['useGamepad'] + const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] + const useId: typeof import('vue')['useId'] + const useIdle: typeof import('@vueuse/core')['useIdle'] + const useImage: typeof import('@vueuse/core')['useImage'] + const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] + const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] + const useInterval: typeof import('@vueuse/core')['useInterval'] + const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] + const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] + const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] + const useLink: typeof import('vue-router')['useLink'] + const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] + const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] + const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] + const useMediaControls: typeof import('@vueuse/core')['useMediaControls'] + const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery'] + const useMemoize: typeof import('@vueuse/core')['useMemoize'] + const useMemory: typeof import('@vueuse/core')['useMemory'] + const useModel: typeof import('vue')['useModel'] + const useMounted: typeof import('@vueuse/core')['useMounted'] + const useMouse: typeof import('@vueuse/core')['useMouse'] + const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] + const useMousePressed: typeof import('@vueuse/core')['useMousePressed'] + const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] + const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] + const useNetwork: typeof import('@vueuse/core')['useNetwork'] + const useNow: typeof import('@vueuse/core')['useNow'] + const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl'] + const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] + const useOnline: typeof import('@vueuse/core')['useOnline'] + const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] + const useParallax: typeof import('@vueuse/core')['useParallax'] + const useParentElement: typeof import('@vueuse/core')['useParentElement'] + const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] + const usePermission: typeof import('@vueuse/core')['usePermission'] + const usePointer: typeof import('@vueuse/core')['usePointer'] + const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] + const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] + const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] + const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] + const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] + const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] + const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] + const usePrevious: typeof import('@vueuse/core')['usePrevious'] + const useRafFn: typeof import('@vueuse/core')['useRafFn'] + const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] + const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation'] + const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] + const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] + const useScroll: typeof import('@vueuse/core')['useScroll'] + const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] + const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] + const useShare: typeof import('@vueuse/core')['useShare'] + const useSlots: typeof import('vue')['useSlots'] + const useSorted: typeof import('@vueuse/core')['useSorted'] + const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] + const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis'] + const useStepper: typeof import('@vueuse/core')['useStepper'] + const useStorage: typeof import('@vueuse/core')['useStorage'] + const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] + const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] + const useSupported: typeof import('@vueuse/core')['useSupported'] + const useSwipe: typeof import('@vueuse/core')['useSwipe'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] + const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] + const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] + const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] + const useThrottle: typeof import('@vueuse/core')['useThrottle'] + const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] + const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] + const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] + const useTimeout: typeof import('@vueuse/core')['useTimeout'] + const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] + const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] + const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] + const useTitle: typeof import('@vueuse/core')['useTitle'] + const useToNumber: typeof import('@vueuse/core')['useToNumber'] + const useToString: typeof import('@vueuse/core')['useToString'] + const useToggle: typeof import('@vueuse/core')['useToggle'] + const useTransition: typeof import('@vueuse/core')['useTransition'] + const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] + const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] + const useVModel: typeof import('@vueuse/core')['useVModel'] + const useVModels: typeof import('@vueuse/core')['useVModels'] + const useVibrate: typeof import('@vueuse/core')['useVibrate'] + const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] + const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] + const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] + const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] + const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] + const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] + const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] + const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] + const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] + const watch: typeof import('vue')['watch'] + const watchArray: typeof import('@vueuse/core')['watchArray'] + const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] + const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] + const watchDeep: typeof import('@vueuse/core')['watchDeep'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] + const watchImmediate: typeof import('@vueuse/core')['watchImmediate'] + const watchOnce: typeof import('@vueuse/core')['watchOnce'] + const watchPausable: typeof import('@vueuse/core')['watchPausable'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] + const watchThrottled: typeof import('@vueuse/core')['watchThrottled'] + const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable'] + const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] + const whenever: typeof import('@vueuse/core')['whenever'] } // for type re-export declare global { // @ts-ignore - export type { - Component, - ComponentPublicInstance, - ComputedRef, - DirectiveBinding, - ExtractDefaultPropTypes, - ExtractPropTypes, - ExtractPublicPropTypes, - InjectionKey, - PropType, - Ref, - MaybeRef, - MaybeRefOrGetter, - VNode, - WritableComputedRef - } from 'vue' + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' import('vue') } diff --git a/src/views/asset-management/device-list/index.vue b/src/views/asset-management/device-list/index.vue index d57cb6a..2dddaab 100644 --- a/src/views/asset-management/device-list/index.vue +++ b/src/views/asset-management/device-list/index.vue @@ -30,7 +30,6 @@ 批量设置套餐系列 - 导入设备 @@ -300,22 +299,13 @@ {{ currentDeviceDetail.status_name }} - {{ - currentDeviceDetail.shop_name || '--' - }} - {{ + {{ currentDeviceDetail.batch_no || '--' }} - {{ - currentDeviceDetail.activated_at || '--' - }} - {{ + {{ currentDeviceDetail.created_at || '--' }} - {{ - currentDeviceDetail.updated_at || '--' - }} @@ -385,12 +375,9 @@ device_no: '', device_name: '', status: undefined as DeviceStatus | undefined, - shop_id: undefined as number | undefined, batch_no: '', device_type: '', - manufacturer: '', - created_at_start: '', - created_at_end: '' + manufacturer: '' } // 搜索表单 @@ -478,9 +465,7 @@ { label: '最大插槽数', prop: 'max_sim_slots' }, { label: '已绑定卡数', prop: 'bound_card_count' }, { label: '状态', prop: 'status' }, - { label: '店铺', prop: 'shop_name' }, { label: '批次号', prop: 'batch_no' }, - { label: '激活时间', prop: 'activated_at' }, { label: '创建时间', prop: 'created_at' }, { label: '操作', prop: 'operation' } ] @@ -610,24 +595,12 @@ return h(ElTag, { type: status.type }, () => status.text) } }, - { - prop: 'shop_name', - label: '店铺', - minWidth: 120, - formatter: (row: Device) => row.shop_name || '-' - }, { prop: 'batch_no', label: '批次号', - minWidth: 120, + minWidth: 160, formatter: (row: Device) => row.batch_no || '-' }, - { - prop: 'activated_at', - label: '激活时间', - width: 180, - formatter: (row: Device) => (row.activated_at ? formatDateTime(row.activated_at) : '-') - }, { prop: 'created_at', label: '创建时间', @@ -637,14 +610,10 @@ { prop: 'operation', label: '操作', - width: 150, + width: 100, fixed: 'right', formatter: (row: Device) => { return h('div', { style: 'display: flex; gap: 8px;' }, [ - h(ArtButtonTable, { - type: 'view', - onClick: () => viewDeviceDetail(row) - }), h(ArtButtonTable, { type: 'delete', onClick: () => deleteDevice(row) @@ -681,16 +650,13 @@ device_no: searchForm.device_no || undefined, device_name: searchForm.device_name || undefined, status: searchForm.status, - shop_id: searchForm.shop_id, batch_no: searchForm.batch_no || undefined, device_type: searchForm.device_type || undefined, - manufacturer: searchForm.manufacturer || undefined, - created_at_start: searchForm.created_at_start || undefined, - created_at_end: searchForm.created_at_end || undefined + manufacturer: searchForm.manufacturer || undefined } const res = await DeviceService.getDevices(params) if (res.code === 0 && res.data) { - deviceList.value = res.data.list || [] + deviceList.value = res.data.items || [] pagination.total = res.data.total || 0 } } catch (error) { @@ -735,14 +701,6 @@ selectedDevices.value = selection } - // 查看设备详情 - const viewDeviceDetail = (row: Device) => { - router.push({ - path: '/asset-management/device-detail', - query: { id: row.id } - }) - } - // 删除设备 const deleteDevice = (row: Device) => { ElMessageBox.confirm(`确定删除设备 ${row.device_no} 吗?删除后将自动解绑所有卡。`, '删除确认', { @@ -869,11 +827,6 @@ recallForm.remark = '' } - // 导入设备 - const handleImportDevice = () => { - router.push('/batch/device-import') - } - // 批量设置套餐系列 const handleBatchSetSeries = async () => { if (selectedDevices.value.length === 0) { diff --git a/src/views/asset-management/device-task/index.vue b/src/views/asset-management/device-task/index.vue index 7570767..de329aa 100644 --- a/src/views/asset-management/device-task/index.vue +++ b/src/views/asset-management/device-task/index.vue @@ -50,17 +50,11 @@ @@ -77,12 +71,12 @@ :auto-upload="false" :on-change="handleFileChange" :limit="1" - accept=".csv" + accept=".xlsx" > -
将 CSV 文件拖到此处,或点击选择
+
将 Excel 文件拖到此处,或点击选择
@@ -131,18 +125,51 @@ }}
+ 跳过明细 +
+ + + + + + + +
+ + + 警告明细 +
+ + + + + + + +
+ + 失败明细
- - - - + + + @@ -151,6 +178,22 @@ @@ -501,6 +490,14 @@
+ + + @@ -517,6 +514,8 @@ import type { SearchFormItem } from '@/types' import { useCheckedColumns } from '@/composables/useCheckedColumns' import { formatDateTime } from '@/utils/business/format' + import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue' + import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue' import type { StandaloneIotCard, StandaloneCardStatus, @@ -578,6 +577,9 @@ const cardDetailLoading = ref(false) const currentCardDetail = ref(null) + // 更多操作右键菜单 + const moreMenuRef = ref>() + // 搜索表单初始值 const initialSearchState = { status: undefined, @@ -1423,6 +1425,72 @@ }) } + // 更多操作菜单项配置 + const moreMenuItems = computed((): MenuItemType[] => [ + { + key: 'seriesBinding', + label: '批量设置套餐系列', + icon: '', + disabled: selectedCards.value.length === 0 + }, + { + key: 'distribution', + label: '网卡分销', + icon: '' + }, + { + key: 'recharge', + label: '批量充值', + icon: '' + }, + { + key: 'recycle', + label: '网卡回收', + icon: '' + }, + { + key: 'download', + label: '批量下载', + icon: '' + }, + { + key: 'changePackage', + label: '变更套餐', + icon: '' + } + ]) + + // 显示更多操作菜单 + const showMoreMenu = (e: MouseEvent) => { + e.preventDefault() + e.stopPropagation() + moreMenuRef.value?.show(e) + } + + // 处理更多操作菜单选择 + const handleMoreMenuSelect = (item: MenuItemType) => { + switch (item.key) { + case 'seriesBinding': + showSeriesBindingDialog() + break + case 'distribution': + cardDistribution() + break + case 'recharge': + batchRecharge() + break + case 'recycle': + cardRecycle() + break + case 'download': + batchDownload() + break + case 'changePackage': + changePackage() + break + } + } + // 网卡分销 - 正在开发中 const cardDistribution = () => { ElMessage.info('功能正在开发中') diff --git a/src/views/asset-management/iot-card-task/index.vue b/src/views/asset-management/iot-card-task/index.vue index c8150b9..a3a43c7 100644 --- a/src/views/asset-management/iot-card-task/index.vue +++ b/src/views/asset-management/iot-card-task/index.vue @@ -50,10 +50,10 @@ @@ -79,12 +79,12 @@ :auto-upload="false" :on-change="handleFileChange" :limit="1" - accept=".csv" + accept=".xlsx" > -
将 CSV 文件拖到此处,或点击选择
+
将 Excel 文件拖到此处,或点击选择
@@ -133,13 +133,31 @@ }}
+ 跳过明细 +
+ + + + + + + + +
+ + 失败明细
- + @@ -153,6 +171,14 @@