mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 15:53:43 +08:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			v1.1.5
			...
			thin-v0.10
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					685e0b5030 | 
							
								
								
									
										2
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								.env
									
									
									
									
									
								
							@@ -10,7 +10,7 @@ VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
 | 
				
			|||||||
VITE_AUTH_ROUTE_MODE=static
 | 
					VITE_AUTH_ROUTE_MODE=static
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 路由首页(根路由重定向), 用于static模式的权限路由,dynamic模式取决于后端返回的路由首页
 | 
					# 路由首页(根路由重定向), 用于static模式的权限路由,dynamic模式取决于后端返回的路由首页
 | 
				
			||||||
VITE_ROUTE_HOME_PATH=/dashboard/analysis
 | 
					VITE_ROUTE_HOME_PATH=/multi-menu/first/second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# iconify图标作为组件的前缀
 | 
					# iconify图标作为组件的前缀
 | 
				
			||||||
VITE_ICON_PREFIX=icon
 | 
					VITE_ICON_PREFIX=icon
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1 @@
 | 
				
			|||||||
VITE_VISUALIZER=N
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VITE_COMPRESS=N
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# gzip | brotliCompress | deflate | deflateRaw
 | 
					 | 
				
			||||||
VITE_COMPRESS_TYPE=gzip
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VITE_PWA=N
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VITE_PROD_MOCK=Y
 | 
					VITE_PROD_MOCK=Y
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,16 +0,0 @@
 | 
				
			|||||||
ImageTag ?=v0.9.6
 | 
					 | 
				
			||||||
SoybeanAdminImg ?= soybeanjs/soybean-admin:$(ImageTag)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VERSION=$(shell git rev-parse --short HEAD)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
soybean-admin: soybean-admin-build soybean-admin-push
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
soybean-admin-build:
 | 
					 | 
				
			||||||
	docker build --build-arg version=$(VERSION) -t ${SoybeanAdminImg} -f docker/Dockerfile .
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
soybean-admin-push:
 | 
					 | 
				
			||||||
	docker push ${SoybeanAdminImg}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# run tauri app:
 | 
					 | 
				
			||||||
run:
 | 
					 | 
				
			||||||
	pnpm tauri dev
 | 
					 | 
				
			||||||
@@ -1,8 +0,0 @@
 | 
				
			|||||||
import dayjs from 'dayjs';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** 项目构建时间 */
 | 
					 | 
				
			||||||
const PROJECT_BUILD_TIME = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const viteDefine = {
 | 
					 | 
				
			||||||
  PROJECT_BUILD_TIME
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,2 +1 @@
 | 
				
			|||||||
export * from './define';
 | 
					 | 
				
			||||||
export * from './proxy';
 | 
					export * from './proxy';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +0,0 @@
 | 
				
			|||||||
import ViteCompression from 'vite-plugin-compression';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default (viteEnv: ImportMetaEnv) => {
 | 
					 | 
				
			||||||
  const { VITE_COMPRESS_TYPE = 'gzip' } = viteEnv;
 | 
					 | 
				
			||||||
  return ViteCompression({ algorithm: VITE_COMPRESS_TYPE });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -2,15 +2,10 @@ import type { PluginOption } from 'vite';
 | 
				
			|||||||
import vue from '@vitejs/plugin-vue';
 | 
					import vue from '@vitejs/plugin-vue';
 | 
				
			||||||
import vueJsx from '@vitejs/plugin-vue-jsx';
 | 
					import vueJsx from '@vitejs/plugin-vue-jsx';
 | 
				
			||||||
import unocss from '@unocss/vite';
 | 
					import unocss from '@unocss/vite';
 | 
				
			||||||
import progress from 'vite-plugin-progress';
 | 
					 | 
				
			||||||
import VueDevtools from 'vite-plugin-vue-devtools';
 | 
					import VueDevtools from 'vite-plugin-vue-devtools';
 | 
				
			||||||
import pageRoute from '@soybeanjs/vite-plugin-vue-page-route';
 | 
					import pageRoute from '@soybeanjs/vite-plugin-vue-page-route';
 | 
				
			||||||
import { webUpdateNotice } from '@plugin-web-update-notification/vite';
 | 
					 | 
				
			||||||
import unplugin from './unplugin';
 | 
					import unplugin from './unplugin';
 | 
				
			||||||
import mock from './mock';
 | 
					import mock from './mock';
 | 
				
			||||||
import visualizer from './visualizer';
 | 
					 | 
				
			||||||
import compress from './compress';
 | 
					 | 
				
			||||||
import pwa from './pwa';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * vite插件
 | 
					 * vite插件
 | 
				
			||||||
@@ -27,27 +22,9 @@ export function setupVitePlugins(viteEnv: ImportMetaEnv): (PluginOption | Plugin
 | 
				
			|||||||
    VueDevtools(),
 | 
					    VueDevtools(),
 | 
				
			||||||
    ...unplugin(viteEnv),
 | 
					    ...unplugin(viteEnv),
 | 
				
			||||||
    unocss(),
 | 
					    unocss(),
 | 
				
			||||||
    mock(viteEnv),
 | 
					    mock(viteEnv)
 | 
				
			||||||
    progress(),
 | 
					 | 
				
			||||||
    webUpdateNotice({
 | 
					 | 
				
			||||||
      notificationProps: {
 | 
					 | 
				
			||||||
        title: '👋 有新版本了',
 | 
					 | 
				
			||||||
        description: '点击刷新页面获取最新版本',
 | 
					 | 
				
			||||||
        buttonText: '刷新',
 | 
					 | 
				
			||||||
        dismissButtonText: '忽略'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (viteEnv.VITE_VISUALIZER === 'Y') {
 | 
					 | 
				
			||||||
    plugins.push(visualizer as PluginOption);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (viteEnv.VITE_COMPRESS === 'Y') {
 | 
					 | 
				
			||||||
    plugins.push(compress(viteEnv));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (viteEnv.VITE_PWA === 'Y' || viteEnv.VITE_VERCEL === 'Y') {
 | 
					 | 
				
			||||||
    plugins.push(pwa());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (viteEnv.VITE_SOYBEAN_ROUTE_PLUGIN === 'Y') {
 | 
					  if (viteEnv.VITE_SOYBEAN_ROUTE_PLUGIN === 'Y') {
 | 
				
			||||||
    plugins.push(pageRoute());
 | 
					    plugins.push(pageRoute());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
import { VitePWA } from 'vite-plugin-pwa';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default function setupVitePwa() {
 | 
					 | 
				
			||||||
  return VitePWA({
 | 
					 | 
				
			||||||
    registerType: 'autoUpdate',
 | 
					 | 
				
			||||||
    includeAssets: ['favicon.ico'],
 | 
					 | 
				
			||||||
    manifest: {
 | 
					 | 
				
			||||||
      name: 'SoybeanAdmin',
 | 
					 | 
				
			||||||
      short_name: 'SoybeanAdmin',
 | 
					 | 
				
			||||||
      theme_color: '#fff',
 | 
					 | 
				
			||||||
      icons: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          src: '/logo.png',
 | 
					 | 
				
			||||||
          sizes: '192x192',
 | 
					 | 
				
			||||||
          type: 'image/png'
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          src: '/logo.png',
 | 
					 | 
				
			||||||
          sizes: '512x512',
 | 
					 | 
				
			||||||
          type: 'image/png'
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          src: '/logo.png',
 | 
					 | 
				
			||||||
          sizes: '512x512',
 | 
					 | 
				
			||||||
          type: 'image/png',
 | 
					 | 
				
			||||||
          purpose: 'any maskable'
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
import { visualizer } from 'rollup-plugin-visualizer';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default visualizer({
 | 
					 | 
				
			||||||
  gzipSize: true,
 | 
					 | 
				
			||||||
  brotliSize: true,
 | 
					 | 
				
			||||||
  open: true
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@@ -1,32 +0,0 @@
 | 
				
			|||||||
node_modules
 | 
					 | 
				
			||||||
.DS_Store
 | 
					 | 
				
			||||||
dist
 | 
					 | 
				
			||||||
.npmrc
 | 
					 | 
				
			||||||
.cache
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
tests/server/static
 | 
					 | 
				
			||||||
tests/server/static/upload
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.local
 | 
					 | 
				
			||||||
# local env files
 | 
					 | 
				
			||||||
.env.local
 | 
					 | 
				
			||||||
.env.*.local
 | 
					 | 
				
			||||||
.eslintcache
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Log files
 | 
					 | 
				
			||||||
npm-debug.log*
 | 
					 | 
				
			||||||
yarn-debug.log*
 | 
					 | 
				
			||||||
yarn-error.log*
 | 
					 | 
				
			||||||
pnpm-debug.log*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Editor directories and files
 | 
					 | 
				
			||||||
.idea
 | 
					 | 
				
			||||||
# .vscode
 | 
					 | 
				
			||||||
*.suo
 | 
					 | 
				
			||||||
*.ntvs*
 | 
					 | 
				
			||||||
*.njsproj
 | 
					 | 
				
			||||||
*.sln
 | 
					 | 
				
			||||||
*.sw?
 | 
					 | 
				
			||||||
yarn.lock
 | 
					 | 
				
			||||||
pnpm-lock.yaml
 | 
					 | 
				
			||||||
/vite-profile.cpuprofile
 | 
					 | 
				
			||||||
@@ -1,24 +0,0 @@
 | 
				
			|||||||
FROM node:16.17.0 as builder
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ENV WORKDIR=/soybean-admin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WORKDIR $WORKDIR
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY ./ $WORKDIR/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ARG version
 | 
					 | 
				
			||||||
ENV COMMITID=$version
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN npm i -g pnpm
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN pnpm install
 | 
					 | 
				
			||||||
RUN pnpm build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FROM nginx:alpine as prod
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN mkdir /soybean
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY --from=builder /soybean-admin/dist /soybean-admin
 | 
					 | 
				
			||||||
COPY --from=builder /soybean-admin/docker/nginx.conf /etc/nginx/nginx.conf
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
EXPOSE 80
 | 
					 | 
				
			||||||
@@ -1,54 +0,0 @@
 | 
				
			|||||||
user  nginx;
 | 
					 | 
				
			||||||
worker_processes  1;
 | 
					 | 
				
			||||||
error_log  /var/log/nginx/error.log warn;
 | 
					 | 
				
			||||||
pid        /var/run/nginx.pid;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
events {
 | 
					 | 
				
			||||||
  worker_connections  1024;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
http {
 | 
					 | 
				
			||||||
  include       /etc/nginx/mime.types;
 | 
					 | 
				
			||||||
  default_type  application/octet-stream;
 | 
					 | 
				
			||||||
  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
 | 
					 | 
				
			||||||
                    '$status $body_bytes_sent "$http_referer" '
 | 
					 | 
				
			||||||
                    '"$http_user_agent" "$http_x_forwarded_for"';
 | 
					 | 
				
			||||||
  access_log  /var/log/nginx/access.log  main;
 | 
					 | 
				
			||||||
  sendfile        on;
 | 
					 | 
				
			||||||
  keepalive_timeout  65;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  server {
 | 
					 | 
				
			||||||
    listen       80;
 | 
					 | 
				
			||||||
    server_name  localhost;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    location / {
 | 
					 | 
				
			||||||
      # 不缓存html,防止程序更新后缓存继续生效
 | 
					 | 
				
			||||||
      if ($request_filename ~* .*\.(?:htm|html)$) {
 | 
					 | 
				
			||||||
        add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
 | 
					 | 
				
			||||||
        access_log on;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      root   /soybean-admin/;
 | 
					 | 
				
			||||||
      index  index.html index.htm;
 | 
					 | 
				
			||||||
      try_files $uri $uri/ /index.html;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # location /soybean/soybean-webserver/v1 {
 | 
					 | 
				
			||||||
    #     proxy_set_header Host $host;
 | 
					 | 
				
			||||||
    #     proxy_set_header X-Real-IP $remote_addr;
 | 
					 | 
				
			||||||
    #     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
					 | 
				
			||||||
    #     proxy_set_header REMOTE-HOST $remote_addr;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #     # 后台接口地址
 | 
					 | 
				
			||||||
    #     proxy_pass http://192.168.1.99:30597/v1;
 | 
					 | 
				
			||||||
    #     proxy_redirect default;
 | 
					 | 
				
			||||||
    #     add_header Access-Control-Allow-Origin *;
 | 
					 | 
				
			||||||
    #     add_header Access-Control-Allow-Headers X-Requested-With;
 | 
					 | 
				
			||||||
    #     add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
 | 
					 | 
				
			||||||
    # }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    error_page   500 502 503 504  /50x.html;
 | 
					 | 
				
			||||||
    location = /50x.html {
 | 
					 | 
				
			||||||
      root   /usr/share/nginx/html;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
import auth from './auth';
 | 
					import auth from './auth';
 | 
				
			||||||
import route from './route';
 | 
					import route from './route';
 | 
				
			||||||
import management from './management';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default [...auth, ...route, ...management];
 | 
					export default [...auth, ...route];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,33 +0,0 @@
 | 
				
			|||||||
import { mock } from 'mockjs';
 | 
					 | 
				
			||||||
import type { MockMethod } from 'vite-plugin-mock';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const apis: MockMethod[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    url: '/mock/getAllUserList',
 | 
					 | 
				
			||||||
    method: 'post',
 | 
					 | 
				
			||||||
    response: (): Service.MockServiceResult<ApiUserManagement.User[]> => {
 | 
					 | 
				
			||||||
      const data = mock({
 | 
					 | 
				
			||||||
        'list|1000': [
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            id: '@id',
 | 
					 | 
				
			||||||
            userName: '@cname',
 | 
					 | 
				
			||||||
            'age|18-56': 56,
 | 
					 | 
				
			||||||
            'gender|1': ['0', '1', null],
 | 
					 | 
				
			||||||
            phone:
 | 
					 | 
				
			||||||
              /^[1](([3][0-9])|([4][01456789])|([5][012356789])|([6][2567])|([7][0-8])|([8][0-9])|([9][012356789]))[0-9]{8}$/,
 | 
					 | 
				
			||||||
            'email|1': ['@email("qq.com")', null],
 | 
					 | 
				
			||||||
            'userStatus|1': ['1', '2', '3', '4', null]
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return {
 | 
					 | 
				
			||||||
        code: 200,
 | 
					 | 
				
			||||||
        message: 'ok',
 | 
					 | 
				
			||||||
        data: data.list
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default apis;
 | 
					 | 
				
			||||||
@@ -8,7 +8,7 @@ const apis: MockMethod[] = [
 | 
				
			|||||||
    response: (options: Service.MockOption): Service.MockServiceResult => {
 | 
					    response: (options: Service.MockOption): Service.MockServiceResult => {
 | 
				
			||||||
      const { userId = undefined } = options.body;
 | 
					      const { userId = undefined } = options.body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const routeHomeName: AuthRoute.LastDegreeRouteKey = 'dashboard_analysis';
 | 
					      const routeHomeName: AuthRoute.LastDegreeRouteKey = 'multi-menu_first_second';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const role = userModel.find(item => item.userId === userId)?.userRole || 'user';
 | 
					      const role = userModel.find(item => item.userId === userId)?.userRole || 'user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1084
									
								
								mock/model/route.ts
									
									
									
									
									
								
							
							
						
						
									
										1084
									
								
								mock/model/route.ts
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										28
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								package.json
									
									
									
									
									
								
							@@ -49,14 +49,10 @@
 | 
				
			|||||||
    "commit": "soy git-commit",
 | 
					    "commit": "soy git-commit",
 | 
				
			||||||
    "cleanup": "soy cleanup",
 | 
					    "cleanup": "soy cleanup",
 | 
				
			||||||
    "update-pkg": "soy ncu",
 | 
					    "update-pkg": "soy ncu",
 | 
				
			||||||
    "release": "soy release",
 | 
					 | 
				
			||||||
    "tsx": "tsx",
 | 
					    "tsx": "tsx",
 | 
				
			||||||
    "logo": "tsx ./scripts/logo.ts",
 | 
					    "logo": "tsx ./scripts/logo.ts"
 | 
				
			||||||
    "prepare": "soy init-simple-git-hooks"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@antv/data-set": "0.11.8",
 | 
					 | 
				
			||||||
    "@antv/g2": "4.2.10",
 | 
					 | 
				
			||||||
    "@better-scroll/core": "2.5.1",
 | 
					    "@better-scroll/core": "2.5.1",
 | 
				
			||||||
    "@soybeanjs/vue-materials": "0.2.0",
 | 
					    "@soybeanjs/vue-materials": "0.2.0",
 | 
				
			||||||
    "@vueuse/core": "10.4.1",
 | 
					    "@vueuse/core": "10.4.1",
 | 
				
			||||||
@@ -65,32 +61,22 @@
 | 
				
			|||||||
    "colord": "2.9.3",
 | 
					    "colord": "2.9.3",
 | 
				
			||||||
    "crypto-js": "4.1.1",
 | 
					    "crypto-js": "4.1.1",
 | 
				
			||||||
    "dayjs": "1.11.10",
 | 
					    "dayjs": "1.11.10",
 | 
				
			||||||
    "echarts": "5.4.3",
 | 
					 | 
				
			||||||
    "form-data": "4.0.0",
 | 
					    "form-data": "4.0.0",
 | 
				
			||||||
    "lodash-es": "4.17.21",
 | 
					    "lodash-es": "4.17.21",
 | 
				
			||||||
    "naive-ui": "2.34.4",
 | 
					    "naive-ui": "2.34.4",
 | 
				
			||||||
    "pinia": "2.1.6",
 | 
					    "pinia": "2.1.6",
 | 
				
			||||||
    "print-js": "1.6.0",
 | 
					 | 
				
			||||||
    "qs": "6.11.2",
 | 
					    "qs": "6.11.2",
 | 
				
			||||||
    "socket.io-client": "4.7.2",
 | 
					 | 
				
			||||||
    "swiper": "10.2.0",
 | 
					 | 
				
			||||||
    "ua-parser-js": "1.0.36",
 | 
					    "ua-parser-js": "1.0.36",
 | 
				
			||||||
    "vditor": "3.9.5",
 | 
					 | 
				
			||||||
    "vue": "3.3.4",
 | 
					    "vue": "3.3.4",
 | 
				
			||||||
    "vue-i18n": "9.4.1",
 | 
					    "vue-i18n": "9.4.1",
 | 
				
			||||||
    "vue-router": "4.2.4",
 | 
					    "vue-router": "4.2.4",
 | 
				
			||||||
    "vuedraggable": "4.1.0",
 | 
					 | 
				
			||||||
    "wangeditor": "4.7.15",
 | 
					 | 
				
			||||||
    "xgplayer": "3.0.9"
 | 
					    "xgplayer": "3.0.9"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@amap/amap-jsapi-types": "0.0.13",
 | 
					 | 
				
			||||||
    "@iconify/json": "2.2.118",
 | 
					    "@iconify/json": "2.2.118",
 | 
				
			||||||
    "@iconify/vue": "4.1.1",
 | 
					    "@iconify/vue": "4.1.1",
 | 
				
			||||||
    "@plugin-web-update-notification/vite": "^1.6.5",
 | 
					 | 
				
			||||||
    "@soybeanjs/cli": "0.7.1",
 | 
					    "@soybeanjs/cli": "0.7.1",
 | 
				
			||||||
    "@soybeanjs/vite-plugin-vue-page-route": "0.0.10",
 | 
					    "@soybeanjs/vite-plugin-vue-page-route": "0.0.10",
 | 
				
			||||||
    "@types/bmapgl": "0.0.7",
 | 
					 | 
				
			||||||
    "@types/crypto-js": "4.1.2",
 | 
					    "@types/crypto-js": "4.1.2",
 | 
				
			||||||
    "@types/node": "20.6.3",
 | 
					    "@types/node": "20.6.3",
 | 
				
			||||||
    "@types/qs": "6.9.8",
 | 
					    "@types/qs": "6.9.8",
 | 
				
			||||||
@@ -104,18 +90,13 @@
 | 
				
			|||||||
    "eslint": "8.49.0",
 | 
					    "eslint": "8.49.0",
 | 
				
			||||||
    "eslint-config-soybeanjs": "0.5.6",
 | 
					    "eslint-config-soybeanjs": "0.5.6",
 | 
				
			||||||
    "mockjs": "1.1.0",
 | 
					    "mockjs": "1.1.0",
 | 
				
			||||||
    "rollup-plugin-visualizer": "5.9.2",
 | 
					 | 
				
			||||||
    "sass": "1.67.0",
 | 
					    "sass": "1.67.0",
 | 
				
			||||||
    "simple-git-hooks": "2.9.0",
 | 
					 | 
				
			||||||
    "tsx": "3.12.10",
 | 
					    "tsx": "3.12.10",
 | 
				
			||||||
    "typescript": "5.2.2",
 | 
					    "typescript": "5.2.2",
 | 
				
			||||||
    "unplugin-icons": "0.17.0",
 | 
					    "unplugin-icons": "0.17.0",
 | 
				
			||||||
    "unplugin-vue-components": "0.25.2",
 | 
					    "unplugin-vue-components": "0.25.2",
 | 
				
			||||||
    "vite": "4.4.9",
 | 
					    "vite": "4.4.9",
 | 
				
			||||||
    "vite-plugin-compression": "0.5.1",
 | 
					 | 
				
			||||||
    "vite-plugin-mock": "2.9.8",
 | 
					    "vite-plugin-mock": "2.9.8",
 | 
				
			||||||
    "vite-plugin-progress": "0.0.7",
 | 
					 | 
				
			||||||
    "vite-plugin-pwa": "0.16.5",
 | 
					 | 
				
			||||||
    "vite-plugin-svg-icons": "2.0.1",
 | 
					    "vite-plugin-svg-icons": "2.0.1",
 | 
				
			||||||
    "vite-plugin-vue-devtools": "1.0.0-rc.4",
 | 
					    "vite-plugin-vue-devtools": "1.0.0-rc.4",
 | 
				
			||||||
    "vue-tsc": "1.8.13"
 | 
					    "vue-tsc": "1.8.13"
 | 
				
			||||||
@@ -124,12 +105,5 @@
 | 
				
			|||||||
    "patchedDependencies": {
 | 
					    "patchedDependencies": {
 | 
				
			||||||
      "mockjs@1.1.0": "patches/mockjs@1.1.0.patch"
 | 
					      "mockjs@1.1.0": "patches/mockjs@1.1.0.patch"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "simple-git-hooks": {
 | 
					 | 
				
			||||||
    "commit-msg": "pnpm soy git-commit-verify",
 | 
					 | 
				
			||||||
    "pre-commit": "pnpm typecheck && pnpm soy lint-staged"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "soybean": {
 | 
					 | 
				
			||||||
    "useSoybeanToken": true
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2259
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2259
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,17 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <web-site-link label="github地址:" :link="link" />
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import WebSiteLink from './web-site-link.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'GithubLink' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  /** github链接 */
 | 
					 | 
				
			||||||
  link: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineProps<Props>();
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,77 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-popover placement="bottom-end" trigger="click">
 | 
					 | 
				
			||||||
    <template #trigger>
 | 
					 | 
				
			||||||
      <n-input v-model:value="modelValue" readonly placeholder="点击选择图标">
 | 
					 | 
				
			||||||
        <template #suffix>
 | 
					 | 
				
			||||||
          <svg-icon :icon="selectedIcon" class="text-30px p-5px" />
 | 
					 | 
				
			||||||
        </template>
 | 
					 | 
				
			||||||
      </n-input>
 | 
					 | 
				
			||||||
    </template>
 | 
					 | 
				
			||||||
    <template #header>
 | 
					 | 
				
			||||||
      <n-input v-model:value="searchValue" placeholder="搜索图标"></n-input>
 | 
					 | 
				
			||||||
    </template>
 | 
					 | 
				
			||||||
    <div v-if="iconsList.length > 0" class="grid grid-cols-9 h-auto overflow-auto">
 | 
					 | 
				
			||||||
      <span v-for="iconItem in iconsList" :key="iconItem" @click="handleChange(iconItem)">
 | 
					 | 
				
			||||||
        <svg-icon
 | 
					 | 
				
			||||||
          :icon="iconItem"
 | 
					 | 
				
			||||||
          class="border-1px border-#d9d9d9 text-30px m-2px p-5px cursor-pointer"
 | 
					 | 
				
			||||||
          :class="{ 'border-primary': modelValue === iconItem }"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </span>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <n-empty v-else class="w-306px" description="你什么也找不到" />
 | 
					 | 
				
			||||||
  </n-popover>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { computed, ref } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'IconSelect' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  /** 选中的图标 */
 | 
					 | 
				
			||||||
  value: string;
 | 
					 | 
				
			||||||
  /** 图标列表 */
 | 
					 | 
				
			||||||
  icons: string[];
 | 
					 | 
				
			||||||
  /** 未选中图标 */
 | 
					 | 
				
			||||||
  emptyIcon?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = withDefaults(defineProps<Props>(), {
 | 
					 | 
				
			||||||
  emptyIcon: 'mdi:apps'
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Emits {
 | 
					 | 
				
			||||||
  (e: 'update:value', val: string): void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const emit = defineEmits<Emits>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const modelValue = computed({
 | 
					 | 
				
			||||||
  get() {
 | 
					 | 
				
			||||||
    return props.value;
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  set(val: string) {
 | 
					 | 
				
			||||||
    emit('update:value', val);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const selectedIcon = computed(() => modelValue.value || props.emptyIcon);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const searchValue = ref('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const iconsList = computed(() => props.icons.filter(v => v.includes(searchValue.value)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleChange(iconItem: string) {
 | 
					 | 
				
			||||||
  modelValue.value = iconItem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss" scoped>
 | 
					 | 
				
			||||||
:deep(.n-input-wrapper) {
 | 
					 | 
				
			||||||
  padding-right: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
:deep(.n-input__suffix) {
 | 
					 | 
				
			||||||
  border: 1px solid #d9d9d9;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div>
 | 
					 | 
				
			||||||
    <canvas ref="domRef" width="152" height="40" class="cursor-pointer" @click="getImgCode"></canvas>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { watch } from 'vue';
 | 
					 | 
				
			||||||
import { useImageVerify } from '@/hooks';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'ImageVerify' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  code?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = withDefaults(defineProps<Props>(), {
 | 
					 | 
				
			||||||
  code: ''
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Emits {
 | 
					 | 
				
			||||||
  (e: 'update:code', code: string): void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const emit = defineEmits<Emits>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(
 | 
					 | 
				
			||||||
  () => props.code,
 | 
					 | 
				
			||||||
  newValue => {
 | 
					 | 
				
			||||||
    setImgCode(newValue);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
watch(imgCode, newValue => {
 | 
					 | 
				
			||||||
  emit('update:code', newValue);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineExpose({ getImgCode });
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <p>
 | 
					 | 
				
			||||||
    <span>{{ label }}</span>
 | 
					 | 
				
			||||||
    <a class="text-blue-500" :href="link" target="_blank">
 | 
					 | 
				
			||||||
      {{ link }}
 | 
					 | 
				
			||||||
    </a>
 | 
					 | 
				
			||||||
  </p>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
defineOptions({ name: 'WebSiteLink' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  /** 网址名称 */
 | 
					 | 
				
			||||||
  label: string;
 | 
					 | 
				
			||||||
  /** 网址链接 */
 | 
					 | 
				
			||||||
  link: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineProps<Props>();
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,174 +0,0 @@
 | 
				
			|||||||
import { nextTick, effectScope, onScopeDispose, ref, watch } from 'vue';
 | 
					 | 
				
			||||||
import type { ComputedRef, Ref } from 'vue';
 | 
					 | 
				
			||||||
import * as echarts from 'echarts/core';
 | 
					 | 
				
			||||||
import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts';
 | 
					 | 
				
			||||||
import type {
 | 
					 | 
				
			||||||
  BarSeriesOption,
 | 
					 | 
				
			||||||
  GaugeSeriesOption,
 | 
					 | 
				
			||||||
  LineSeriesOption,
 | 
					 | 
				
			||||||
  PictorialBarSeriesOption,
 | 
					 | 
				
			||||||
  PieSeriesOption,
 | 
					 | 
				
			||||||
  RadarSeriesOption,
 | 
					 | 
				
			||||||
  ScatterSeriesOption
 | 
					 | 
				
			||||||
} from 'echarts/charts';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  DatasetComponent,
 | 
					 | 
				
			||||||
  GridComponent,
 | 
					 | 
				
			||||||
  LegendComponent,
 | 
					 | 
				
			||||||
  TitleComponent,
 | 
					 | 
				
			||||||
  ToolboxComponent,
 | 
					 | 
				
			||||||
  TooltipComponent,
 | 
					 | 
				
			||||||
  TransformComponent
 | 
					 | 
				
			||||||
} from 'echarts/components';
 | 
					 | 
				
			||||||
import type {
 | 
					 | 
				
			||||||
  DatasetComponentOption,
 | 
					 | 
				
			||||||
  GridComponentOption,
 | 
					 | 
				
			||||||
  LegendComponentOption,
 | 
					 | 
				
			||||||
  TitleComponentOption,
 | 
					 | 
				
			||||||
  ToolboxComponentOption,
 | 
					 | 
				
			||||||
  TooltipComponentOption
 | 
					 | 
				
			||||||
} from 'echarts/components';
 | 
					 | 
				
			||||||
import { LabelLayout, UniversalTransition } from 'echarts/features';
 | 
					 | 
				
			||||||
import { CanvasRenderer } from 'echarts/renderers';
 | 
					 | 
				
			||||||
import { useElementSize } from '@vueuse/core';
 | 
					 | 
				
			||||||
import { useThemeStore } from '@/store';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type ECOption = echarts.ComposeOption<
 | 
					 | 
				
			||||||
  | BarSeriesOption
 | 
					 | 
				
			||||||
  | LineSeriesOption
 | 
					 | 
				
			||||||
  | PieSeriesOption
 | 
					 | 
				
			||||||
  | ScatterSeriesOption
 | 
					 | 
				
			||||||
  | PictorialBarSeriesOption
 | 
					 | 
				
			||||||
  | RadarSeriesOption
 | 
					 | 
				
			||||||
  | GaugeSeriesOption
 | 
					 | 
				
			||||||
  | TitleComponentOption
 | 
					 | 
				
			||||||
  | LegendComponentOption
 | 
					 | 
				
			||||||
  | TooltipComponentOption
 | 
					 | 
				
			||||||
  | GridComponentOption
 | 
					 | 
				
			||||||
  | ToolboxComponentOption
 | 
					 | 
				
			||||||
  | DatasetComponentOption
 | 
					 | 
				
			||||||
>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
echarts.use([
 | 
					 | 
				
			||||||
  TitleComponent,
 | 
					 | 
				
			||||||
  LegendComponent,
 | 
					 | 
				
			||||||
  TooltipComponent,
 | 
					 | 
				
			||||||
  GridComponent,
 | 
					 | 
				
			||||||
  DatasetComponent,
 | 
					 | 
				
			||||||
  TransformComponent,
 | 
					 | 
				
			||||||
  ToolboxComponent,
 | 
					 | 
				
			||||||
  BarChart,
 | 
					 | 
				
			||||||
  LineChart,
 | 
					 | 
				
			||||||
  PieChart,
 | 
					 | 
				
			||||||
  ScatterChart,
 | 
					 | 
				
			||||||
  PictorialBarChart,
 | 
					 | 
				
			||||||
  RadarChart,
 | 
					 | 
				
			||||||
  GaugeChart,
 | 
					 | 
				
			||||||
  LabelLayout,
 | 
					 | 
				
			||||||
  UniversalTransition,
 | 
					 | 
				
			||||||
  CanvasRenderer
 | 
					 | 
				
			||||||
]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Echarts hooks函数
 | 
					 | 
				
			||||||
 * @param options - 图表配置
 | 
					 | 
				
			||||||
 * @param renderFun - 图表渲染函数(例如:图表监听函数)
 | 
					 | 
				
			||||||
 * @description 按需引入图表组件,没注册的组件需要先引入
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function useEcharts(
 | 
					 | 
				
			||||||
  options: Ref<ECOption> | ComputedRef<ECOption>,
 | 
					 | 
				
			||||||
  renderFun?: (chartInstance: echarts.ECharts) => void
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  const theme = useThemeStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const domRef = ref<HTMLElement>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const initialSize = { width: 0, height: 0 };
 | 
					 | 
				
			||||||
  const { width, height } = useElementSize(domRef, initialSize);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let chart: echarts.ECharts | null = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function canRender() {
 | 
					 | 
				
			||||||
    return initialSize.width > 0 && initialSize.height > 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function isRendered() {
 | 
					 | 
				
			||||||
    return Boolean(domRef.value && chart);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function update(updateOptions: ECOption) {
 | 
					 | 
				
			||||||
    if (isRendered()) {
 | 
					 | 
				
			||||||
      chart?.clear();
 | 
					 | 
				
			||||||
      chart!.setOption({ ...updateOptions, backgroundColor: 'transparent' });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async function render() {
 | 
					 | 
				
			||||||
    if (domRef.value) {
 | 
					 | 
				
			||||||
      const chartTheme = theme.darkMode ? 'dark' : 'light';
 | 
					 | 
				
			||||||
      await nextTick();
 | 
					 | 
				
			||||||
      chart = echarts.init(domRef.value, chartTheme);
 | 
					 | 
				
			||||||
      if (renderFun) {
 | 
					 | 
				
			||||||
        renderFun(chart);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      update(options.value);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function resize() {
 | 
					 | 
				
			||||||
    chart?.resize();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function destroy() {
 | 
					 | 
				
			||||||
    chart?.dispose();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function updateTheme() {
 | 
					 | 
				
			||||||
    destroy();
 | 
					 | 
				
			||||||
    render();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const scope = effectScope();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  scope.run(() => {
 | 
					 | 
				
			||||||
    watch([width, height], ([newWidth, newHeight]) => {
 | 
					 | 
				
			||||||
      initialSize.width = newWidth;
 | 
					 | 
				
			||||||
      initialSize.height = newHeight;
 | 
					 | 
				
			||||||
      if (newWidth === 0 && newHeight === 0) {
 | 
					 | 
				
			||||||
        // 节点被删除 将chart置为空
 | 
					 | 
				
			||||||
        chart = null;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (canRender()) {
 | 
					 | 
				
			||||||
        if (!isRendered()) {
 | 
					 | 
				
			||||||
          render();
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          resize();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    watch(
 | 
					 | 
				
			||||||
      options,
 | 
					 | 
				
			||||||
      newValue => {
 | 
					 | 
				
			||||||
        update(newValue);
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      { deep: true }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    watch(
 | 
					 | 
				
			||||||
      () => theme.darkMode,
 | 
					 | 
				
			||||||
      () => {
 | 
					 | 
				
			||||||
        updateTheme();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onScopeDispose(() => {
 | 
					 | 
				
			||||||
    destroy();
 | 
					 | 
				
			||||||
    scope.stop();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    domRef
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -48,7 +48,7 @@ export const useIconRender = () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!icon && !localIcon) {
 | 
					    if (!icon && !localIcon) {
 | 
				
			||||||
      window.console.warn('没有传递图标名称,请确保给icon或localIcon传递有效值!');
 | 
					      throw Error('没有传递图标名称,请确保给icon或localIcon传递有效值!');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return () => h(SvgIcon, { icon, localIcon, style });
 | 
					    return () => h(SvgIcon, { icon, localIcon, style });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,4 @@ export * from './system';
 | 
				
			|||||||
export * from './router';
 | 
					export * from './router';
 | 
				
			||||||
export * from './layout';
 | 
					export * from './layout';
 | 
				
			||||||
export * from './events';
 | 
					export * from './events';
 | 
				
			||||||
export * from './echarts';
 | 
					 | 
				
			||||||
export * from './icon';
 | 
					export * from './icon';
 | 
				
			||||||
export * from './websocket';
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,50 +0,0 @@
 | 
				
			|||||||
import { io } from 'socket.io-client';
 | 
					 | 
				
			||||||
import type { Socket } from 'socket.io-client';
 | 
					 | 
				
			||||||
import { useAppStore } from '../store';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ListenEvents = {
 | 
					 | 
				
			||||||
  update: (id: string, data: { name: string; age: number }) => void;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type EmitEvents = {
 | 
					 | 
				
			||||||
  update: (id: string, data: { name: string; age: number }) => void;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function useWebsocket() {
 | 
					 | 
				
			||||||
  const app = useAppStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const socket: Socket<ListenEvents, EmitEvents> = (app.socket || io('ws://localhost:8080')) as Socket<
 | 
					 | 
				
			||||||
    ListenEvents,
 | 
					 | 
				
			||||||
    EmitEvents
 | 
					 | 
				
			||||||
  >;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!app.socket) {
 | 
					 | 
				
			||||||
    app.setSocket(socket);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function init() {
 | 
					 | 
				
			||||||
    window.console.log('[socket.io] connecting...');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    socket.on('connect', () => {
 | 
					 | 
				
			||||||
      window.console.log('[socket.io] connected.');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    socket.on('disconnect', () => {
 | 
					 | 
				
			||||||
      window.console.log('[socket.io] disconnected.');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    socket.on('update', (id, data) => {
 | 
					 | 
				
			||||||
      window.console.log('[socket.io] update', id, data);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function handleUpdate(id: string, data: { name: string; age: number }) {
 | 
					 | 
				
			||||||
    socket.emit('update', id, data);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  init();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    handleUpdate
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,3 +1,2 @@
 | 
				
			|||||||
export * from './service';
 | 
					export * from './service';
 | 
				
			||||||
export * from './regexp';
 | 
					export * from './regexp';
 | 
				
			||||||
export * from './map-sdk';
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,19 +15,3 @@ export const userRoleLabels: Record<Auth.RoleType, string> = {
 | 
				
			|||||||
  user: $t('page.login.pwdLogin.user')
 | 
					  user: $t('page.login.pwdLogin.user')
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
export const userRoleOptions = transformObjectToOption(userRoleLabels);
 | 
					export const userRoleOptions = transformObjectToOption(userRoleLabels);
 | 
				
			||||||
 | 
					 | 
				
			||||||
/** 用户性别 */
 | 
					 | 
				
			||||||
export const genderLabels: Record<UserManagement.GenderKey, string> = {
 | 
					 | 
				
			||||||
  0: '女',
 | 
					 | 
				
			||||||
  1: '男'
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
export const genderOptions = transformObjectToOption(genderLabels);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** 用户状态 */
 | 
					 | 
				
			||||||
export const userStatusLabels: Record<UserManagement.UserStatusKey, string> = {
 | 
					 | 
				
			||||||
  1: '启用',
 | 
					 | 
				
			||||||
  2: '禁用',
 | 
					 | 
				
			||||||
  3: '冻结',
 | 
					 | 
				
			||||||
  4: '软删除'
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
export const userStatusOptions = transformObjectToOption(userStatusLabels);
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
import useCountDown from './use-count-down';
 | 
					import useCountDown from './use-count-down';
 | 
				
			||||||
import useSmsCode from './use-sms-code';
 | 
					import useSmsCode from './use-sms-code';
 | 
				
			||||||
import useImageVerify from './use-image-verify';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { useCountDown, useSmsCode, useImageVerify };
 | 
					export { useCountDown, useSmsCode };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,87 +0,0 @@
 | 
				
			|||||||
import { onMounted, ref } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 绘制图形验证码
 | 
					 | 
				
			||||||
 * @param width - 图形宽度
 | 
					 | 
				
			||||||
 * @param height - 图形高度
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export default function useImageVerify(width = 152, height = 40) {
 | 
					 | 
				
			||||||
  const domRef = ref<HTMLCanvasElement>();
 | 
					 | 
				
			||||||
  const imgCode = ref('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function setImgCode(code: string) {
 | 
					 | 
				
			||||||
    imgCode.value = code;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function getImgCode() {
 | 
					 | 
				
			||||||
    if (!domRef.value) return;
 | 
					 | 
				
			||||||
    imgCode.value = draw(domRef.value, width, height);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onMounted(() => {
 | 
					 | 
				
			||||||
    getImgCode();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    domRef,
 | 
					 | 
				
			||||||
    imgCode,
 | 
					 | 
				
			||||||
    setImgCode,
 | 
					 | 
				
			||||||
    getImgCode
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function randomNum(min: number, max: number) {
 | 
					 | 
				
			||||||
  const num = Math.floor(Math.random() * (max - min) + min);
 | 
					 | 
				
			||||||
  return num;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function randomColor(min: number, max: number) {
 | 
					 | 
				
			||||||
  const r = randomNum(min, max);
 | 
					 | 
				
			||||||
  const g = randomNum(min, max);
 | 
					 | 
				
			||||||
  const b = randomNum(min, max);
 | 
					 | 
				
			||||||
  return `rgb(${r},${g},${b})`;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function draw(dom: HTMLCanvasElement, width: number, height: number) {
 | 
					 | 
				
			||||||
  let imgCode = '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const NUMBER_STRING = '0123456789';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const ctx = dom.getContext('2d');
 | 
					 | 
				
			||||||
  if (!ctx) return imgCode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ctx.fillStyle = randomColor(180, 230);
 | 
					 | 
				
			||||||
  ctx.fillRect(0, 0, width, height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (let i = 0; i < 4; i += 1) {
 | 
					 | 
				
			||||||
    const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)];
 | 
					 | 
				
			||||||
    imgCode += text;
 | 
					 | 
				
			||||||
    const fontSize = randomNum(18, 41);
 | 
					 | 
				
			||||||
    const deg = randomNum(-30, 30);
 | 
					 | 
				
			||||||
    ctx.font = `${fontSize}px Simhei`;
 | 
					 | 
				
			||||||
    ctx.textBaseline = 'top';
 | 
					 | 
				
			||||||
    ctx.fillStyle = randomColor(80, 150);
 | 
					 | 
				
			||||||
    ctx.save();
 | 
					 | 
				
			||||||
    ctx.translate(30 * i + 23, 15);
 | 
					 | 
				
			||||||
    ctx.rotate((deg * Math.PI) / 180);
 | 
					 | 
				
			||||||
    ctx.fillText(text, -15 + 5, -15);
 | 
					 | 
				
			||||||
    ctx.restore();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  for (let i = 0; i < 5; i += 1) {
 | 
					 | 
				
			||||||
    ctx.beginPath();
 | 
					 | 
				
			||||||
    ctx.moveTo(randomNum(0, width), randomNum(0, height));
 | 
					 | 
				
			||||||
    ctx.lineTo(randomNum(0, width), randomNum(0, height));
 | 
					 | 
				
			||||||
    ctx.strokeStyle = randomColor(180, 230);
 | 
					 | 
				
			||||||
    ctx.closePath();
 | 
					 | 
				
			||||||
    ctx.stroke();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  for (let i = 0; i < 41; i += 1) {
 | 
					 | 
				
			||||||
    ctx.beginPath();
 | 
					 | 
				
			||||||
    ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI);
 | 
					 | 
				
			||||||
    ctx.closePath();
 | 
					 | 
				
			||||||
    ctx.fillStyle = randomColor(150, 200);
 | 
					 | 
				
			||||||
    ctx.fill();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return imgCode;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <hover-container
 | 
					 | 
				
			||||||
    tooltip-content="github"
 | 
					 | 
				
			||||||
    class="w-40px h-full"
 | 
					 | 
				
			||||||
    :inverted="theme.header.inverted"
 | 
					 | 
				
			||||||
    @click="handleClickLink"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <icon-mdi-github class="text-20px" />
 | 
					 | 
				
			||||||
  </hover-container>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { useThemeStore } from '@/store';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'GithubSite' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const theme = useThemeStore();
 | 
					 | 
				
			||||||
function handleClickLink() {
 | 
					 | 
				
			||||||
  window.open('https://github.com/honghuangdc/soybean-admin', '_blank');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,23 +1,10 @@
 | 
				
			|||||||
import MenuCollapse from './menu-collapse.vue';
 | 
					import MenuCollapse from './menu-collapse.vue';
 | 
				
			||||||
import GlobalBreadcrumb from './global-breadcrumb.vue';
 | 
					import GlobalBreadcrumb from './global-breadcrumb.vue';
 | 
				
			||||||
import HeaderMenu from './header-menu.vue';
 | 
					import HeaderMenu from './header-menu.vue';
 | 
				
			||||||
import GithubSite from './github-site.vue';
 | 
					 | 
				
			||||||
import FullScreen from './full-screen.vue';
 | 
					import FullScreen from './full-screen.vue';
 | 
				
			||||||
import ThemeMode from './theme-mode.vue';
 | 
					import ThemeMode from './theme-mode.vue';
 | 
				
			||||||
import UserAvatar from './user-avatar.vue';
 | 
					import UserAvatar from './user-avatar.vue';
 | 
				
			||||||
import SystemMessage from './system-message.vue';
 | 
					 | 
				
			||||||
import SettingButton from './setting-button.vue';
 | 
					import SettingButton from './setting-button.vue';
 | 
				
			||||||
import ToggleLang from './toggle-lang.vue';
 | 
					import ToggleLang from './toggle-lang.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export { MenuCollapse, GlobalBreadcrumb, HeaderMenu, FullScreen, ThemeMode, UserAvatar, SettingButton, ToggleLang };
 | 
				
			||||||
  MenuCollapse,
 | 
					 | 
				
			||||||
  GlobalBreadcrumb,
 | 
					 | 
				
			||||||
  HeaderMenu,
 | 
					 | 
				
			||||||
  GithubSite,
 | 
					 | 
				
			||||||
  FullScreen,
 | 
					 | 
				
			||||||
  ThemeMode,
 | 
					 | 
				
			||||||
  UserAvatar,
 | 
					 | 
				
			||||||
  SystemMessage,
 | 
					 | 
				
			||||||
  SettingButton,
 | 
					 | 
				
			||||||
  ToggleLang
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,57 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-scrollbar class="max-h-360px">
 | 
					 | 
				
			||||||
    <n-list>
 | 
					 | 
				
			||||||
      <n-list-item
 | 
					 | 
				
			||||||
        v-for="(item, index) in list"
 | 
					 | 
				
			||||||
        :key="item.id"
 | 
					 | 
				
			||||||
        class="hover:bg-#f6f6f6 dark:hover:bg-dark cursor-pointer"
 | 
					 | 
				
			||||||
        @click="handleRead(index)"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-thing class="px-15px" :class="{ 'opacity-30': item.isRead }">
 | 
					 | 
				
			||||||
          <template #avatar>
 | 
					 | 
				
			||||||
            <n-avatar v-if="item.avatar" :src="item.avatar" />
 | 
					 | 
				
			||||||
            <svg-icon v-else class="text-34px text-primary" :icon="item.icon" :local-icon="item.svgIcon" />
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
          <template #header>
 | 
					 | 
				
			||||||
            <n-ellipsis :line-clamp="1">
 | 
					 | 
				
			||||||
              {{ item.title }}
 | 
					 | 
				
			||||||
              <template #tooltip>
 | 
					 | 
				
			||||||
                {{ item.title }}
 | 
					 | 
				
			||||||
              </template>
 | 
					 | 
				
			||||||
            </n-ellipsis>
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
          <template v-if="item.tagTitle" #header-extra>
 | 
					 | 
				
			||||||
            <n-tag v-bind="item.tagProps" size="small">{{ item.tagTitle }}</n-tag>
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
          <template #description>
 | 
					 | 
				
			||||||
            <n-ellipsis v-if="item.description" :line-clamp="2">
 | 
					 | 
				
			||||||
              {{ item.description }}
 | 
					 | 
				
			||||||
            </n-ellipsis>
 | 
					 | 
				
			||||||
            <p>{{ item.date }}</p>
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
        </n-thing>
 | 
					 | 
				
			||||||
      </n-list-item>
 | 
					 | 
				
			||||||
    </n-list>
 | 
					 | 
				
			||||||
  </n-scrollbar>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
defineOptions({ name: 'MessageList' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  list?: App.MessageList[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
withDefaults(defineProps<Props>(), {
 | 
					 | 
				
			||||||
  list: () => []
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Emits {
 | 
					 | 
				
			||||||
  (e: 'read', val: number): void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const emit = defineEmits<Emits>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleRead(index: number) {
 | 
					 | 
				
			||||||
  emit('read', index);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
@@ -1,217 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-popover class="!p-0" trigger="click" placement="bottom">
 | 
					 | 
				
			||||||
    <template #trigger>
 | 
					 | 
				
			||||||
      <hover-container tooltip-content="消息通知" :inverted="theme.header.inverted" class="relative w-40px h-full">
 | 
					 | 
				
			||||||
        <icon-clarity:notification-line class="text-18px" />
 | 
					 | 
				
			||||||
        <n-badge
 | 
					 | 
				
			||||||
          :value="count"
 | 
					 | 
				
			||||||
          :max="99"
 | 
					 | 
				
			||||||
          :class="[count < 10 ? '-right-2px' : '-right-10px']"
 | 
					 | 
				
			||||||
          class="absolute top-10px"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </hover-container>
 | 
					 | 
				
			||||||
    </template>
 | 
					 | 
				
			||||||
    <n-tabs
 | 
					 | 
				
			||||||
      v-model:value="currentTab"
 | 
					 | 
				
			||||||
      :class="[isMobile ? 'w-276px' : 'w-360px']"
 | 
					 | 
				
			||||||
      type="line"
 | 
					 | 
				
			||||||
      justify-content="space-evenly"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-tab-pane v-for="(item, index) in tabData" :key="item.key" :name="index">
 | 
					 | 
				
			||||||
        <template #tab>
 | 
					 | 
				
			||||||
          <div class="flex-x-center items-center" :class="[isMobile ? 'w-92px' : 'w-120px']">
 | 
					 | 
				
			||||||
            <span class="mr-5px">{{ item.name }}</span>
 | 
					 | 
				
			||||||
            <n-badge
 | 
					 | 
				
			||||||
              v-bind="item.badgeProps"
 | 
					 | 
				
			||||||
              :value="item.list.filter(message => !message.isRead).length"
 | 
					 | 
				
			||||||
              :max="99"
 | 
					 | 
				
			||||||
              show-zero
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </template>
 | 
					 | 
				
			||||||
        <loading-empty-wrapper
 | 
					 | 
				
			||||||
          class="h-360px"
 | 
					 | 
				
			||||||
          :loading="loading"
 | 
					 | 
				
			||||||
          :empty="item.list.length === 0"
 | 
					 | 
				
			||||||
          placeholder-class="bg-$n-color transition-background-color duration-300 ease-in-out"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <message-list :list="item.list" @read="handleRead" />
 | 
					 | 
				
			||||||
        </loading-empty-wrapper>
 | 
					 | 
				
			||||||
      </n-tab-pane>
 | 
					 | 
				
			||||||
    </n-tabs>
 | 
					 | 
				
			||||||
    <div v-if="showAction" class="flex border-t border-$n-divider-color cursor-pointer">
 | 
					 | 
				
			||||||
      <div class="flex-1 text-center py-10px" @click="handleClear">清空</div>
 | 
					 | 
				
			||||||
      <div class="flex-1 text-center py-10px border-l border-$n-divider-color" @click="handleAllRead">全部已读</div>
 | 
					 | 
				
			||||||
      <div class="flex-1 text-center py-10px border-l border-$n-divider-color" @click="handleLoadMore">查看更多</div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </n-popover>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { computed, ref } from 'vue';
 | 
					 | 
				
			||||||
import { useThemeStore } from '@/store';
 | 
					 | 
				
			||||||
import { useBasicLayout } from '@/composables';
 | 
					 | 
				
			||||||
import { useBoolean } from '@/hooks';
 | 
					 | 
				
			||||||
import MessageList from './message-list.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'SystemMessage' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const theme = useThemeStore();
 | 
					 | 
				
			||||||
const { isMobile } = useBasicLayout();
 | 
					 | 
				
			||||||
const { bool: loading, setBool: setLoading } = useBoolean();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const currentTab = ref(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const tabData = ref<App.MessageTab[]>([
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 1,
 | 
					 | 
				
			||||||
    name: '通知',
 | 
					 | 
				
			||||||
    badgeProps: { type: 'warning' },
 | 
					 | 
				
			||||||
    list: [
 | 
					 | 
				
			||||||
      { id: 1, icon: 'ri:message-3-line', title: '你收到了5条新消息', date: '2022-06-17' },
 | 
					 | 
				
			||||||
      { id: 4, icon: 'ri:message-3-line', title: 'Soybean Admin 1.0.0 版本正在筹备中', date: '2022-06-17' },
 | 
					 | 
				
			||||||
      { id: 2, icon: 'ri:message-3-line', title: 'Soybean Admin 0.9.6 版本发布了', date: '2022-06-16' },
 | 
					 | 
				
			||||||
      { id: 3, icon: 'ri:message-3-line', title: 'Soybean Admin 0.9.5 版本发布了', date: '2022-06-07' },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        icon: 'ri:message-3-line',
 | 
					 | 
				
			||||||
        title: '测试超长标题测试超长标题测试超长标题测试超长标题测试超长标题测试超长标题测试超长标题测试超长标题',
 | 
					 | 
				
			||||||
        date: '2022-06-17'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 2,
 | 
					 | 
				
			||||||
    name: '消息',
 | 
					 | 
				
			||||||
    badgeProps: { type: 'error' },
 | 
					 | 
				
			||||||
    list: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        title: '项目动态',
 | 
					 | 
				
			||||||
        svgIcon: 'avatar',
 | 
					 | 
				
			||||||
        description: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!',
 | 
					 | 
				
			||||||
        date: '2021-11-07 22:45:32'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        title: '项目动态',
 | 
					 | 
				
			||||||
        svgIcon: 'avatar',
 | 
					 | 
				
			||||||
        description: 'Soybean 正在忙于为soybean-admin写项目说明文档!',
 | 
					 | 
				
			||||||
        date: '2021-11-03 20:33:31'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        title: '项目动态',
 | 
					 | 
				
			||||||
        svgIcon: 'avatar',
 | 
					 | 
				
			||||||
        description: 'Soybean 准备为soybean-admin 1.0的发布做充分的准备工作!',
 | 
					 | 
				
			||||||
        date: '2021-10-31 22:43:12'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 4,
 | 
					 | 
				
			||||||
        title: '项目动态',
 | 
					 | 
				
			||||||
        svgIcon: 'avatar',
 | 
					 | 
				
			||||||
        description: '@yanbowe 向soybean-admin提交了一个bug,多标签栏不会自适应。',
 | 
					 | 
				
			||||||
        date: '2021-10-27 10:24:54'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        title: '项目动态',
 | 
					 | 
				
			||||||
        svgIcon: 'avatar',
 | 
					 | 
				
			||||||
        description: 'Soybean 在2021年5月28日创建了开源项目soybean-admin!',
 | 
					 | 
				
			||||||
        date: '2021-05-28 22:22:22'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 3,
 | 
					 | 
				
			||||||
    name: '待办',
 | 
					 | 
				
			||||||
    badgeProps: { type: 'info' },
 | 
					 | 
				
			||||||
    list: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        icon: 'ri:calendar-todo-line',
 | 
					 | 
				
			||||||
        title: '缓存主题配置',
 | 
					 | 
				
			||||||
        description: '任务正在计划中',
 | 
					 | 
				
			||||||
        date: '2022-06-17',
 | 
					 | 
				
			||||||
        tagTitle: '未开始',
 | 
					 | 
				
			||||||
        tagProps: { type: 'default' }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        icon: 'ri:calendar-todo-line',
 | 
					 | 
				
			||||||
        title: '添加锁屏组件、全局Iframe组件',
 | 
					 | 
				
			||||||
        description: '任务正在计划中',
 | 
					 | 
				
			||||||
        date: '2022-06-17',
 | 
					 | 
				
			||||||
        tagTitle: '未开始',
 | 
					 | 
				
			||||||
        tagProps: { type: 'default' }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        icon: 'ri:calendar-todo-line',
 | 
					 | 
				
			||||||
        title: '示例页面完善',
 | 
					 | 
				
			||||||
        description: '任务正在计划中',
 | 
					 | 
				
			||||||
        date: '2022-06-17',
 | 
					 | 
				
			||||||
        tagTitle: '未开始',
 | 
					 | 
				
			||||||
        tagProps: { type: 'default' }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 4,
 | 
					 | 
				
			||||||
        icon: 'ri:calendar-todo-line',
 | 
					 | 
				
			||||||
        title: '表单、表格示例',
 | 
					 | 
				
			||||||
        description: '任务正在计划中',
 | 
					 | 
				
			||||||
        date: '2022-06-17',
 | 
					 | 
				
			||||||
        tagTitle: '未开始',
 | 
					 | 
				
			||||||
        tagProps: { type: 'default' }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        icon: 'ri:calendar-todo-line',
 | 
					 | 
				
			||||||
        title: '性能优化(优化递归函数)',
 | 
					 | 
				
			||||||
        description: '任务正在计划中',
 | 
					 | 
				
			||||||
        date: '2022-06-17',
 | 
					 | 
				
			||||||
        tagTitle: '未开始',
 | 
					 | 
				
			||||||
        tagProps: { type: 'default' }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 6,
 | 
					 | 
				
			||||||
        icon: 'ri:calendar-todo-line',
 | 
					 | 
				
			||||||
        title: '精简版(新分支thin)',
 | 
					 | 
				
			||||||
        description: '任务正在计划中',
 | 
					 | 
				
			||||||
        date: '2022-06-17',
 | 
					 | 
				
			||||||
        tagTitle: '未开始',
 | 
					 | 
				
			||||||
        tagProps: { type: 'default' }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const count = computed(() => {
 | 
					 | 
				
			||||||
  return tabData.value.reduce((acc, cur) => {
 | 
					 | 
				
			||||||
    return acc + cur.list.filter(item => !item.isRead).length;
 | 
					 | 
				
			||||||
  }, 0);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const showAction = computed(() => tabData.value[currentTab.value].list.length > 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleRead(index: number) {
 | 
					 | 
				
			||||||
  tabData.value[currentTab.value].list[index].isRead = true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleAllRead() {
 | 
					 | 
				
			||||||
  tabData.value[currentTab.value].list.forEach(item => Object.assign(item, { isRead: true }));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleClear() {
 | 
					 | 
				
			||||||
  tabData.value[currentTab.value].list = [];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleLoadMore() {
 | 
					 | 
				
			||||||
  const { list } = tabData.value[currentTab.value];
 | 
					 | 
				
			||||||
  setLoading(true);
 | 
					 | 
				
			||||||
  setTimeout(() => {
 | 
					 | 
				
			||||||
    list.push(...tabData.value[currentTab.value].list);
 | 
					 | 
				
			||||||
    setLoading(false);
 | 
					 | 
				
			||||||
  }, 1000);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -7,12 +7,9 @@
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <header-menu v-else />
 | 
					    <header-menu v-else />
 | 
				
			||||||
    <div class="flex justify-end h-full">
 | 
					    <div class="flex justify-end h-full">
 | 
				
			||||||
      <global-search />
 | 
					 | 
				
			||||||
      <github-site />
 | 
					 | 
				
			||||||
      <full-screen />
 | 
					      <full-screen />
 | 
				
			||||||
      <theme-mode />
 | 
					      <theme-mode />
 | 
				
			||||||
      <toggle-lang />
 | 
					      <toggle-lang />
 | 
				
			||||||
      <system-message />
 | 
					 | 
				
			||||||
      <setting-button v-if="showButton" />
 | 
					      <setting-button v-if="showButton" />
 | 
				
			||||||
      <user-avatar />
 | 
					      <user-avatar />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@@ -23,15 +20,12 @@
 | 
				
			|||||||
import { useThemeStore } from '@/store';
 | 
					import { useThemeStore } from '@/store';
 | 
				
			||||||
import { useBasicLayout } from '@/composables';
 | 
					import { useBasicLayout } from '@/composables';
 | 
				
			||||||
import GlobalLogo from '../global-logo/index.vue';
 | 
					import GlobalLogo from '../global-logo/index.vue';
 | 
				
			||||||
import GlobalSearch from '../global-search/index.vue';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  FullScreen,
 | 
					  FullScreen,
 | 
				
			||||||
  GithubSite,
 | 
					 | 
				
			||||||
  GlobalBreadcrumb,
 | 
					  GlobalBreadcrumb,
 | 
				
			||||||
  HeaderMenu,
 | 
					  HeaderMenu,
 | 
				
			||||||
  MenuCollapse,
 | 
					  MenuCollapse,
 | 
				
			||||||
  SettingButton,
 | 
					  SettingButton,
 | 
				
			||||||
  SystemMessage,
 | 
					 | 
				
			||||||
  ThemeMode,
 | 
					  ThemeMode,
 | 
				
			||||||
  UserAvatar,
 | 
					  UserAvatar,
 | 
				
			||||||
  ToggleLang
 | 
					  ToggleLang
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
import SearchModal from './search-modal.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { SearchModal };
 | 
					 | 
				
			||||||
@@ -1,147 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-modal
 | 
					 | 
				
			||||||
    v-model:show="show"
 | 
					 | 
				
			||||||
    :segmented="{ footer: 'soft' }"
 | 
					 | 
				
			||||||
    :closable="false"
 | 
					 | 
				
			||||||
    preset="card"
 | 
					 | 
				
			||||||
    footer-style="padding: 0; margin: 0"
 | 
					 | 
				
			||||||
    class="fixed left-0 right-0"
 | 
					 | 
				
			||||||
    :class="[isMobile ? 'wh-full top-0px rounded-0' : 'w-630px top-50px']"
 | 
					 | 
				
			||||||
    @after-leave="handleClose"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <n-input-group>
 | 
					 | 
				
			||||||
      <n-input ref="inputRef" v-model:value="keyword" clearable placeholder="请输入关键词搜索" @input="handleSearch">
 | 
					 | 
				
			||||||
        <template #prefix>
 | 
					 | 
				
			||||||
          <icon-uil-search class="text-15px text-#c2c2c2" />
 | 
					 | 
				
			||||||
        </template>
 | 
					 | 
				
			||||||
      </n-input>
 | 
					 | 
				
			||||||
      <n-button v-if="isMobile" type="primary" ghost @click="handleClose">取消</n-button>
 | 
					 | 
				
			||||||
    </n-input-group>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <div class="mt-20px">
 | 
					 | 
				
			||||||
      <n-empty v-if="resultOptions.length === 0" description="暂无搜索结果" />
 | 
					 | 
				
			||||||
      <search-result v-else v-model:value="activePath" :options="resultOptions" @enter="handleEnter" />
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <template #footer>
 | 
					 | 
				
			||||||
      <search-footer v-if="!isMobile" />
 | 
					 | 
				
			||||||
    </template>
 | 
					 | 
				
			||||||
  </n-modal>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { computed, nextTick, ref, shallowRef, watch } from 'vue';
 | 
					 | 
				
			||||||
import { useRouter } from 'vue-router';
 | 
					 | 
				
			||||||
import { onKeyStroke, useDebounceFn } from '@vueuse/core';
 | 
					 | 
				
			||||||
import { useRouteStore } from '@/store';
 | 
					 | 
				
			||||||
import { useBasicLayout } from '@/composables';
 | 
					 | 
				
			||||||
import SearchResult from './search-result.vue';
 | 
					 | 
				
			||||||
import SearchFooter from './search-footer.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'SearchModal' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  /** 弹窗显隐 */
 | 
					 | 
				
			||||||
  value: boolean;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps<Props>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Emits {
 | 
					 | 
				
			||||||
  (e: 'update:value', val: boolean): void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const emit = defineEmits<Emits>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { isMobile } = useBasicLayout();
 | 
					 | 
				
			||||||
const router = useRouter();
 | 
					 | 
				
			||||||
const routeStore = useRouteStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const keyword = ref('');
 | 
					 | 
				
			||||||
const activePath = ref('');
 | 
					 | 
				
			||||||
const resultOptions = shallowRef<AuthRoute.Route[]>([]);
 | 
					 | 
				
			||||||
const inputRef = ref<HTMLInputElement>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const handleSearch = useDebounceFn(search, 300);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const show = computed({
 | 
					 | 
				
			||||||
  get() {
 | 
					 | 
				
			||||||
    return props.value;
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  set(val: boolean) {
 | 
					 | 
				
			||||||
    emit('update:value', val);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(show, async val => {
 | 
					 | 
				
			||||||
  if (val) {
 | 
					 | 
				
			||||||
    /** 自动聚焦 */
 | 
					 | 
				
			||||||
    await nextTick();
 | 
					 | 
				
			||||||
    inputRef.value?.focus();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** 查询 */
 | 
					 | 
				
			||||||
function search() {
 | 
					 | 
				
			||||||
  resultOptions.value = routeStore.searchMenus.filter(
 | 
					 | 
				
			||||||
    menu => keyword.value && menu.meta?.title.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim())
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  if (resultOptions.value?.length > 0) {
 | 
					 | 
				
			||||||
    activePath.value = resultOptions.value[0].path;
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    activePath.value = '';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleClose() {
 | 
					 | 
				
			||||||
  show.value = false;
 | 
					 | 
				
			||||||
  /** 延时处理防止用户看到某些操作 */
 | 
					 | 
				
			||||||
  setTimeout(() => {
 | 
					 | 
				
			||||||
    resultOptions.value = [];
 | 
					 | 
				
			||||||
    keyword.value = '';
 | 
					 | 
				
			||||||
  }, 200);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** key up */
 | 
					 | 
				
			||||||
function handleUp() {
 | 
					 | 
				
			||||||
  const { length } = resultOptions.value;
 | 
					 | 
				
			||||||
  if (length === 0) return;
 | 
					 | 
				
			||||||
  const index = resultOptions.value.findIndex(item => item.path === activePath.value);
 | 
					 | 
				
			||||||
  if (index === 0) {
 | 
					 | 
				
			||||||
    activePath.value = resultOptions.value[length - 1].path;
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    activePath.value = resultOptions.value[index - 1].path;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** key down */
 | 
					 | 
				
			||||||
function handleDown() {
 | 
					 | 
				
			||||||
  const { length } = resultOptions.value;
 | 
					 | 
				
			||||||
  if (length === 0) return;
 | 
					 | 
				
			||||||
  const index = resultOptions.value.findIndex(item => item.path === activePath.value);
 | 
					 | 
				
			||||||
  if (index + 1 === length) {
 | 
					 | 
				
			||||||
    activePath.value = resultOptions.value[0].path;
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    activePath.value = resultOptions.value[index + 1].path;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** key enter */
 | 
					 | 
				
			||||||
function handleEnter() {
 | 
					 | 
				
			||||||
  const { length } = resultOptions.value;
 | 
					 | 
				
			||||||
  if (length === 0 || activePath.value === '') return;
 | 
					 | 
				
			||||||
  const routeItem = resultOptions.value.find(item => item.path === activePath.value);
 | 
					 | 
				
			||||||
  if (routeItem?.meta?.href) {
 | 
					 | 
				
			||||||
    window.open(activePath.value, '__blank');
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    router.push(activePath.value);
 | 
					 | 
				
			||||||
    handleClose();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
onKeyStroke('Escape', handleClose);
 | 
					 | 
				
			||||||
onKeyStroke('Enter', handleEnter);
 | 
					 | 
				
			||||||
onKeyStroke('ArrowUp', handleUp);
 | 
					 | 
				
			||||||
onKeyStroke('ArrowDown', handleDown);
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss" scoped></style>
 | 
					 | 
				
			||||||
@@ -1,64 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-scrollbar>
 | 
					 | 
				
			||||||
    <div class="pb-12px">
 | 
					 | 
				
			||||||
      <template v-for="item in options" :key="item.path">
 | 
					 | 
				
			||||||
        <div
 | 
					 | 
				
			||||||
          class="bg-#e5e7eb dark:bg-dark h-56px mt-8px px-14px rounded-4px cursor-pointer flex-y-center justify-between"
 | 
					 | 
				
			||||||
          :style="{
 | 
					 | 
				
			||||||
            background: item.path === active ? theme.themeColor : '',
 | 
					 | 
				
			||||||
            color: item.path === active ? '#fff' : ''
 | 
					 | 
				
			||||||
          }"
 | 
					 | 
				
			||||||
          @click="handleTo"
 | 
					 | 
				
			||||||
          @mouseenter="handleMouse(item)"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <svg-icon :icon="item.meta.icon" :local-icon="item.meta.localIcon" />
 | 
					 | 
				
			||||||
          <span class="flex-1 ml-5px">{{ item.meta?.title }}</span>
 | 
					 | 
				
			||||||
          <icon-ant-design-enter-outlined class="icon text-20px p-2px mr-3px" />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </template>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </n-scrollbar>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { computed } from 'vue';
 | 
					 | 
				
			||||||
import { useThemeStore } from '@/store';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'SearchResult' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  value: string;
 | 
					 | 
				
			||||||
  options: AuthRoute.Route[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps<Props>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Emits {
 | 
					 | 
				
			||||||
  (e: 'update:value', val: string): void;
 | 
					 | 
				
			||||||
  (e: 'enter'): void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const emit = defineEmits<Emits>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const theme = useThemeStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const active = computed({
 | 
					 | 
				
			||||||
  get() {
 | 
					 | 
				
			||||||
    return props.value;
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  set(val: string) {
 | 
					 | 
				
			||||||
    emit('update:value', val);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** 鼠标移入 */
 | 
					 | 
				
			||||||
async function handleMouse(item: AuthRoute.Route) {
 | 
					 | 
				
			||||||
  active.value = item.path;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleTo() {
 | 
					 | 
				
			||||||
  emit('enter');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss" scoped></style>
 | 
					 | 
				
			||||||
@@ -1,30 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div>
 | 
					 | 
				
			||||||
    <hover-container
 | 
					 | 
				
			||||||
      class="w-40px h-full"
 | 
					 | 
				
			||||||
      tooltip-content="搜索"
 | 
					 | 
				
			||||||
      :inverted="theme.header.inverted"
 | 
					 | 
				
			||||||
      @click="handleSearch"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <icon-uil-search class="text-20px" />
 | 
					 | 
				
			||||||
    </hover-container>
 | 
					 | 
				
			||||||
    <search-modal v-model:value="show" />
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { useThemeStore } from '@/store';
 | 
					 | 
				
			||||||
import { useBoolean } from '@/hooks';
 | 
					 | 
				
			||||||
import { SearchModal } from './components';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'GlobalSearch' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { bool: show, toggle } = useBoolean();
 | 
					 | 
				
			||||||
const theme = useThemeStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleSearch() {
 | 
					 | 
				
			||||||
  toggle();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss" scoped></style>
 | 
					 | 
				
			||||||
@@ -1,8 +1,5 @@
 | 
				
			|||||||
import 'uno.css';
 | 
					import 'uno.css';
 | 
				
			||||||
import '@soybeanjs/vue-materials/dist/style.css';
 | 
					import '@soybeanjs/vue-materials/dist/style.css';
 | 
				
			||||||
import 'swiper/css';
 | 
					 | 
				
			||||||
import 'swiper/css/navigation';
 | 
					 | 
				
			||||||
import 'swiper/css/pagination';
 | 
					 | 
				
			||||||
import 'virtual:svg-icons-register';
 | 
					import 'virtual:svg-icons-register';
 | 
				
			||||||
import '../styles/css/global.css';
 | 
					import '../styles/css/global.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
const about: AuthRoute.Route = {
 | 
					 | 
				
			||||||
  name: 'about',
 | 
					 | 
				
			||||||
  path: '/about',
 | 
					 | 
				
			||||||
  component: 'self',
 | 
					 | 
				
			||||||
  meta: {
 | 
					 | 
				
			||||||
    title: '关于',
 | 
					 | 
				
			||||||
    i18nTitle: 'routes.about',
 | 
					 | 
				
			||||||
    requiresAuth: true,
 | 
					 | 
				
			||||||
    keepAlive: true,
 | 
					 | 
				
			||||||
    singleLayout: 'basic',
 | 
					 | 
				
			||||||
    permissions: ['super', 'admin', 'user'],
 | 
					 | 
				
			||||||
    icon: 'fluent:book-information-24-regular',
 | 
					 | 
				
			||||||
    order: 10
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default about;
 | 
					 | 
				
			||||||
@@ -1,38 +0,0 @@
 | 
				
			|||||||
const authDemo: AuthRoute.Route = {
 | 
					 | 
				
			||||||
  name: 'auth-demo',
 | 
					 | 
				
			||||||
  path: '/auth-demo',
 | 
					 | 
				
			||||||
  component: 'basic',
 | 
					 | 
				
			||||||
  children: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'auth-demo_permission',
 | 
					 | 
				
			||||||
      path: '/auth-demo/permission',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '权限切换',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.auth-demo.permission',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'ic:round-construction'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'auth-demo_super',
 | 
					 | 
				
			||||||
      path: '/auth-demo/super',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '超级管理员可见',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.auth-demo.super',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        permissions: ['super'],
 | 
					 | 
				
			||||||
        icon: 'ic:round-supervisor-account'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  meta: {
 | 
					 | 
				
			||||||
    title: '权限示例',
 | 
					 | 
				
			||||||
    i18nTitle: 'routes.auth-demo._value',
 | 
					 | 
				
			||||||
    icon: 'ic:baseline-security',
 | 
					 | 
				
			||||||
    order: 5
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default authDemo;
 | 
					 | 
				
			||||||
@@ -1,48 +0,0 @@
 | 
				
			|||||||
const component: AuthRoute.Route = {
 | 
					 | 
				
			||||||
  name: 'component',
 | 
					 | 
				
			||||||
  path: '/component',
 | 
					 | 
				
			||||||
  component: 'basic',
 | 
					 | 
				
			||||||
  children: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'component_button',
 | 
					 | 
				
			||||||
      path: '/component/button',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '按钮',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.component.button',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'mdi:button-cursor'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'component_card',
 | 
					 | 
				
			||||||
      path: '/component/card',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '卡片',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.component.card',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'mdi:card-outline'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'component_table',
 | 
					 | 
				
			||||||
      path: '/component/table',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '表格',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.component.table',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'mdi:table-large'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  meta: {
 | 
					 | 
				
			||||||
    title: '组件示例',
 | 
					 | 
				
			||||||
    i18nTitle: 'routes.component._value',
 | 
					 | 
				
			||||||
    icon: 'cib:app-store',
 | 
					 | 
				
			||||||
    order: 3
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default component;
 | 
					 | 
				
			||||||
@@ -1,37 +0,0 @@
 | 
				
			|||||||
const dashboard: AuthRoute.Route = {
 | 
					 | 
				
			||||||
  name: 'dashboard',
 | 
					 | 
				
			||||||
  path: '/dashboard',
 | 
					 | 
				
			||||||
  component: 'basic',
 | 
					 | 
				
			||||||
  children: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'dashboard_analysis',
 | 
					 | 
				
			||||||
      path: '/dashboard/analysis',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '分析页',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'icon-park-outline:analysis',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.dashboard.analysis'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'dashboard_workbench',
 | 
					 | 
				
			||||||
      path: '/dashboard/workbench',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '工作台',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'icon-park-outline:workbench',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.dashboard.workbench'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  meta: {
 | 
					 | 
				
			||||||
    title: '仪表盘',
 | 
					 | 
				
			||||||
    icon: 'mdi:monitor-dashboard',
 | 
					 | 
				
			||||||
    order: 1,
 | 
					 | 
				
			||||||
    i18nTitle: 'routes.dashboard._value'
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default dashboard;
 | 
					 | 
				
			||||||
@@ -1,70 +0,0 @@
 | 
				
			|||||||
const document: AuthRoute.Route = {
 | 
					 | 
				
			||||||
  name: 'document',
 | 
					 | 
				
			||||||
  path: '/document',
 | 
					 | 
				
			||||||
  component: 'basic',
 | 
					 | 
				
			||||||
  children: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'document_vue',
 | 
					 | 
				
			||||||
      path: '/document/vue',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: 'vue文档',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.document.vue',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'logos:vue'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'document_vite',
 | 
					 | 
				
			||||||
      path: '/document/vite',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: 'vite文档',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.document.vite',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'logos:vitejs'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'document_naive',
 | 
					 | 
				
			||||||
      path: '/document/naive',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: 'naive文档',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.document.naive',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'logos:naiveui'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'document_project',
 | 
					 | 
				
			||||||
      path: '/document/project',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '项目文档',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.document.project',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        localIcon: 'logo'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'document_project-link',
 | 
					 | 
				
			||||||
      path: '/document/project-link',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '项目文档(外链)',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.document.project-link',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        localIcon: 'logo',
 | 
					 | 
				
			||||||
        href: 'https://admin-docs.soybeanjs.cn/'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  meta: {
 | 
					 | 
				
			||||||
    title: '文档',
 | 
					 | 
				
			||||||
    i18nTitle: 'routes.document._value',
 | 
					 | 
				
			||||||
    icon: 'mdi:file-document-multiple-outline',
 | 
					 | 
				
			||||||
    order: 2
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default document;
 | 
					 | 
				
			||||||
@@ -1,48 +0,0 @@
 | 
				
			|||||||
const exception: AuthRoute.Route = {
 | 
					 | 
				
			||||||
  name: 'exception',
 | 
					 | 
				
			||||||
  path: '/exception',
 | 
					 | 
				
			||||||
  component: 'basic',
 | 
					 | 
				
			||||||
  children: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'exception_403',
 | 
					 | 
				
			||||||
      path: '/exception/403',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '异常页403',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.exception.403',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-block'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'exception_404',
 | 
					 | 
				
			||||||
      path: '/exception/404',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '异常页404',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.exception.404',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-web-asset-off'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'exception_500',
 | 
					 | 
				
			||||||
      path: '/exception/500',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '异常页500',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.exception.500',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-wifi-off'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  meta: {
 | 
					 | 
				
			||||||
    i18nTitle: 'routes.exception._value',
 | 
					 | 
				
			||||||
    title: '异常页',
 | 
					 | 
				
			||||||
    icon: 'ant-design:exception-outlined',
 | 
					 | 
				
			||||||
    order: 7
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default exception;
 | 
					 | 
				
			||||||
@@ -1,51 +0,0 @@
 | 
				
			|||||||
const functionRoute: AuthRoute.Route = {
 | 
					 | 
				
			||||||
  name: 'function',
 | 
					 | 
				
			||||||
  path: '/function',
 | 
					 | 
				
			||||||
  component: 'basic',
 | 
					 | 
				
			||||||
  children: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'function_tab',
 | 
					 | 
				
			||||||
      path: '/function/tab',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: 'Tab',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.function.tab',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'ic:round-tab'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'function_tab-detail',
 | 
					 | 
				
			||||||
      path: '/function/tab-detail',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: 'Tab Detail',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        hide: true,
 | 
					 | 
				
			||||||
        activeMenu: 'function_tab',
 | 
					 | 
				
			||||||
        icon: 'ic:round-tab'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'function_tab-multi-detail',
 | 
					 | 
				
			||||||
      path: '/function/tab-multi-detail',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: 'Tab Multi Detail',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        hide: true,
 | 
					 | 
				
			||||||
        multiTab: true,
 | 
					 | 
				
			||||||
        activeMenu: 'function_tab',
 | 
					 | 
				
			||||||
        icon: 'ic:round-tab'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  meta: {
 | 
					 | 
				
			||||||
    title: '功能',
 | 
					 | 
				
			||||||
    i18nTitle: 'routes.function._value',
 | 
					 | 
				
			||||||
    icon: 'icon-park-outline:all-application',
 | 
					 | 
				
			||||||
    order: 6
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default functionRoute;
 | 
					 | 
				
			||||||
@@ -1,63 +0,0 @@
 | 
				
			|||||||
const management: AuthRoute.Route = {
 | 
					 | 
				
			||||||
  name: 'management',
 | 
					 | 
				
			||||||
  path: '/management',
 | 
					 | 
				
			||||||
  component: 'basic',
 | 
					 | 
				
			||||||
  children: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'management_auth',
 | 
					 | 
				
			||||||
      path: '/management/auth',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '权限管理',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.management.auth',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        keepAlive: true,
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-security'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'management_role',
 | 
					 | 
				
			||||||
      path: '/management/role',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '角色管理',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.management.role',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        keepAlive: true,
 | 
					 | 
				
			||||||
        icon: 'carbon:user-role'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'management_user',
 | 
					 | 
				
			||||||
      path: '/management/user',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '用户管理',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.management.user',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        keepAlive: true,
 | 
					 | 
				
			||||||
        icon: 'ic:round-manage-accounts'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'management_route',
 | 
					 | 
				
			||||||
      path: '/management/route',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '路由管理',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.management.route',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        keepAlive: true,
 | 
					 | 
				
			||||||
        icon: 'material-symbols:route'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  meta: {
 | 
					 | 
				
			||||||
    title: '系统管理',
 | 
					 | 
				
			||||||
    i18nTitle: 'routes.management._value',
 | 
					 | 
				
			||||||
    icon: 'carbon:cloud-service-management',
 | 
					 | 
				
			||||||
    order: 9
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default management;
 | 
					 | 
				
			||||||
@@ -1,149 +0,0 @@
 | 
				
			|||||||
const plugin: AuthRoute.Route = {
 | 
					 | 
				
			||||||
  name: 'plugin',
 | 
					 | 
				
			||||||
  path: '/plugin',
 | 
					 | 
				
			||||||
  component: 'basic',
 | 
					 | 
				
			||||||
  children: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'plugin_charts',
 | 
					 | 
				
			||||||
      path: '/plugin/charts',
 | 
					 | 
				
			||||||
      component: 'multi',
 | 
					 | 
				
			||||||
      children: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: 'plugin_charts_echarts',
 | 
					 | 
				
			||||||
          path: '/plugin/charts/echarts',
 | 
					 | 
				
			||||||
          component: 'self',
 | 
					 | 
				
			||||||
          meta: {
 | 
					 | 
				
			||||||
            title: 'ECharts',
 | 
					 | 
				
			||||||
            i18nTitle: 'routes.plugin.charts.echarts',
 | 
					 | 
				
			||||||
            requiresAuth: true,
 | 
					 | 
				
			||||||
            icon: 'simple-icons:apacheecharts'
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: 'plugin_charts_antv',
 | 
					 | 
				
			||||||
          path: '/plugin/charts/antv',
 | 
					 | 
				
			||||||
          component: 'self',
 | 
					 | 
				
			||||||
          meta: {
 | 
					 | 
				
			||||||
            title: 'AntV',
 | 
					 | 
				
			||||||
            i18nTitle: 'routes.plugin.charts.antv',
 | 
					 | 
				
			||||||
            requiresAuth: true,
 | 
					 | 
				
			||||||
            icon: 'simple-icons:antdesign'
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '图表',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.plugin.charts._value',
 | 
					 | 
				
			||||||
        icon: 'mdi:chart-areaspline'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'plugin_map',
 | 
					 | 
				
			||||||
      path: '/plugin/map',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '地图',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.plugin.map',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'mdi:map'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'plugin_video',
 | 
					 | 
				
			||||||
      path: '/plugin/video',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '视频',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.plugin.video',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'mdi:video'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'plugin_editor',
 | 
					 | 
				
			||||||
      path: '/plugin/editor',
 | 
					 | 
				
			||||||
      component: 'multi',
 | 
					 | 
				
			||||||
      children: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: 'plugin_editor_quill',
 | 
					 | 
				
			||||||
          path: '/plugin/editor/quill',
 | 
					 | 
				
			||||||
          component: 'self',
 | 
					 | 
				
			||||||
          meta: {
 | 
					 | 
				
			||||||
            title: '富文本编辑器',
 | 
					 | 
				
			||||||
            i18nTitle: 'routes.plugin.editor.quill',
 | 
					 | 
				
			||||||
            requiresAuth: true,
 | 
					 | 
				
			||||||
            icon: 'mdi:file-document-edit-outline'
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: 'plugin_editor_markdown',
 | 
					 | 
				
			||||||
          path: '/plugin/editor/markdown',
 | 
					 | 
				
			||||||
          component: 'self',
 | 
					 | 
				
			||||||
          meta: {
 | 
					 | 
				
			||||||
            title: 'markdown编辑器',
 | 
					 | 
				
			||||||
            i18nTitle: 'routes.plugin.editor.markdown',
 | 
					 | 
				
			||||||
            requiresAuth: true,
 | 
					 | 
				
			||||||
            icon: 'ri:markdown-line'
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '编辑器',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.plugin.editor._value',
 | 
					 | 
				
			||||||
        icon: 'icon-park-outline:editor'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'plugin_swiper',
 | 
					 | 
				
			||||||
      path: '/plugin/swiper',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: 'Swiper插件',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.plugin.swiper',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'simple-icons:swiper'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'plugin_copy',
 | 
					 | 
				
			||||||
      path: '/plugin/copy',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '剪贴板',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.plugin.copy',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'mdi:clipboard-outline'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'plugin_icon',
 | 
					 | 
				
			||||||
      path: '/plugin/icon',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '图标',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.plugin.icon',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        localIcon: 'custom-icon'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      name: 'plugin_print',
 | 
					 | 
				
			||||||
      path: '/plugin/print',
 | 
					 | 
				
			||||||
      component: 'self',
 | 
					 | 
				
			||||||
      meta: {
 | 
					 | 
				
			||||||
        title: '打印',
 | 
					 | 
				
			||||||
        i18nTitle: 'routes.plugin.print',
 | 
					 | 
				
			||||||
        requiresAuth: true,
 | 
					 | 
				
			||||||
        icon: 'mdi:printer'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  meta: {
 | 
					 | 
				
			||||||
    title: '插件示例',
 | 
					 | 
				
			||||||
    i18nTitle: 'routes.plugin._value',
 | 
					 | 
				
			||||||
    icon: 'clarity:plugin-line',
 | 
					 | 
				
			||||||
    order: 4
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default plugin;
 | 
					 | 
				
			||||||
@@ -1,2 +1 @@
 | 
				
			|||||||
export * from './auth';
 | 
					export * from './auth';
 | 
				
			||||||
export * from './management';
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
export function adapterOfFetchUserList(data: ApiUserManagement.User[] | null): UserManagement.User[] {
 | 
					 | 
				
			||||||
  if (!data) return [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return data.map((item, index) => {
 | 
					 | 
				
			||||||
    const user: UserManagement.User = {
 | 
					 | 
				
			||||||
      index: index + 1,
 | 
					 | 
				
			||||||
      key: item.id,
 | 
					 | 
				
			||||||
      ...item
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return user;
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
import { adapter } from '@/utils';
 | 
					 | 
				
			||||||
import { mockRequest } from '../request';
 | 
					 | 
				
			||||||
import { adapterOfFetchUserList } from './management.adapter';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** 获取用户列表 */
 | 
					 | 
				
			||||||
export const fetchUserList = async () => {
 | 
					 | 
				
			||||||
  const data = await mockRequest.post<ApiUserManagement.User[] | null>('/getAllUserList');
 | 
					 | 
				
			||||||
  return adapter(adapterOfFetchUserList, data);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
import { nextTick } from 'vue';
 | 
					import { nextTick } from 'vue';
 | 
				
			||||||
import { defineStore } from 'pinia';
 | 
					import { defineStore } from 'pinia';
 | 
				
			||||||
import type { Socket } from 'socket.io-client';
 | 
					 | 
				
			||||||
import { LAYOUT_SCROLL_EL_ID } from '@soybeanjs/vue-materials';
 | 
					import { LAYOUT_SCROLL_EL_ID } from '@soybeanjs/vue-materials';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface AppState {
 | 
					interface AppState {
 | 
				
			||||||
@@ -18,8 +17,6 @@ interface AppState {
 | 
				
			|||||||
  siderCollapse: boolean;
 | 
					  siderCollapse: boolean;
 | 
				
			||||||
  /** vertical-mix模式下 侧边栏的固定状态 */
 | 
					  /** vertical-mix模式下 侧边栏的固定状态 */
 | 
				
			||||||
  mixSiderFixed: boolean;
 | 
					  mixSiderFixed: boolean;
 | 
				
			||||||
  /** socket.io 实例 */
 | 
					 | 
				
			||||||
  socket: Socket | null;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useAppStore = defineStore('app-store', {
 | 
					export const useAppStore = defineStore('app-store', {
 | 
				
			||||||
@@ -30,8 +27,7 @@ export const useAppStore = defineStore('app-store', {
 | 
				
			|||||||
    reloadFlag: true,
 | 
					    reloadFlag: true,
 | 
				
			||||||
    settingDrawerVisible: false,
 | 
					    settingDrawerVisible: false,
 | 
				
			||||||
    siderCollapse: false,
 | 
					    siderCollapse: false,
 | 
				
			||||||
    mixSiderFixed: false,
 | 
					    mixSiderFixed: false
 | 
				
			||||||
    socket: null
 | 
					 | 
				
			||||||
  }),
 | 
					  }),
 | 
				
			||||||
  actions: {
 | 
					  actions: {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@@ -101,10 +97,6 @@ export const useAppStore = defineStore('app-store', {
 | 
				
			|||||||
    /** 设置主体内容全屏 */
 | 
					    /** 设置主体内容全屏 */
 | 
				
			||||||
    setContentFull(full: boolean) {
 | 
					    setContentFull(full: boolean) {
 | 
				
			||||||
      this.contentFull = full;
 | 
					      this.contentFull = full;
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    /** 设置socket实例 */
 | 
					 | 
				
			||||||
    setSocket<T extends Socket = Socket>(socket: T) {
 | 
					 | 
				
			||||||
      this.socket = socket;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								src/typings/api.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										29
									
								
								src/typings/api.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -21,32 +21,3 @@ declare namespace ApiRoute {
 | 
				
			|||||||
    home: AuthRoute.AllRouteKey;
 | 
					    home: AuthRoute.AllRouteKey;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
declare namespace ApiUserManagement {
 | 
					 | 
				
			||||||
  interface User {
 | 
					 | 
				
			||||||
    /** 用户id */
 | 
					 | 
				
			||||||
    id: string;
 | 
					 | 
				
			||||||
    /** 用户名 */
 | 
					 | 
				
			||||||
    userName: string | null;
 | 
					 | 
				
			||||||
    /** 用户年龄 */
 | 
					 | 
				
			||||||
    age: number | null;
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 用户性别
 | 
					 | 
				
			||||||
     * - 0: 女
 | 
					 | 
				
			||||||
     * - 1: 男
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    gender: '0' | '1' | null;
 | 
					 | 
				
			||||||
    /** 用户手机号码 */
 | 
					 | 
				
			||||||
    phone: string;
 | 
					 | 
				
			||||||
    /** 用户邮箱 */
 | 
					 | 
				
			||||||
    email: string | null;
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 用户状态
 | 
					 | 
				
			||||||
     * - 1: 启用
 | 
					 | 
				
			||||||
     * - 2: 禁用
 | 
					 | 
				
			||||||
     * - 3: 冻结
 | 
					 | 
				
			||||||
     * - 4: 软删除
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    userStatus: '1' | '2' | '3' | '4' | null;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								src/typings/business.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								src/typings/business.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -18,28 +18,3 @@ declare namespace Auth {
 | 
				
			|||||||
    userRole: RoleType;
 | 
					    userRole: RoleType;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
declare namespace UserManagement {
 | 
					 | 
				
			||||||
  interface User extends ApiUserManagement.User {
 | 
					 | 
				
			||||||
    /** 序号 */
 | 
					 | 
				
			||||||
    index: number;
 | 
					 | 
				
			||||||
    /** 表格的key(id) */
 | 
					 | 
				
			||||||
    key: string;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * 用户性别
 | 
					 | 
				
			||||||
   * - 0: 女
 | 
					 | 
				
			||||||
   * - 1: 男
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  type GenderKey = NonNullable<User['gender']>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * 用户状态
 | 
					 | 
				
			||||||
   * - 1: 启用
 | 
					 | 
				
			||||||
   * - 2: 禁用
 | 
					 | 
				
			||||||
   * - 3: 冻结
 | 
					 | 
				
			||||||
   * - 4: 软删除
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  type UserStatusKey = NonNullable<User['userStatus']>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								src/typings/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/typings/global.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -24,6 +24,3 @@ declare namespace Common {
 | 
				
			|||||||
  /** 选项数据 */
 | 
					  /** 选项数据 */
 | 
				
			||||||
  type OptionWithKey<K> = { value: K; label: string };
 | 
					  type OptionWithKey<K> = { value: K; label: string };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
/** 构建时间 */
 | 
					 | 
				
			||||||
declare const PROJECT_BUILD_TIME: string;
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								src/typings/naive-ui.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/typings/naive-ui.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +0,0 @@
 | 
				
			|||||||
declare namespace NaiveUI {
 | 
					 | 
				
			||||||
  type ThemeColor = 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										9
									
								
								src/typings/package.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								src/typings/package.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,9 +0,0 @@
 | 
				
			|||||||
/// <reference types="@amap/amap-jsapi-types" />
 | 
					 | 
				
			||||||
/// <reference types="bmapgl" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
declare namespace BMap {
 | 
					 | 
				
			||||||
  class Map extends BMapGL.Map {}
 | 
					 | 
				
			||||||
  class Point extends BMapGL.Point {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
declare const TMap: any;
 | 
					 | 
				
			||||||
							
								
								
									
										80
									
								
								src/typings/page-route.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										80
									
								
								src/typings/page-route.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -22,55 +22,11 @@ declare namespace PageRoute {
 | 
				
			|||||||
    | 'constant-page'
 | 
					    | 'constant-page'
 | 
				
			||||||
    | 'login'
 | 
					    | 'login'
 | 
				
			||||||
    | 'not-found'
 | 
					    | 'not-found'
 | 
				
			||||||
    | 'about'
 | 
					 | 
				
			||||||
    | 'auth-demo'
 | 
					 | 
				
			||||||
    | 'auth-demo_permission'
 | 
					 | 
				
			||||||
    | 'auth-demo_super'
 | 
					 | 
				
			||||||
    | 'component'
 | 
					 | 
				
			||||||
    | 'component_button'
 | 
					 | 
				
			||||||
    | 'component_card'
 | 
					 | 
				
			||||||
    | 'component_table'
 | 
					 | 
				
			||||||
    | 'dashboard'
 | 
					 | 
				
			||||||
    | 'dashboard_analysis'
 | 
					 | 
				
			||||||
    | 'dashboard_workbench'
 | 
					 | 
				
			||||||
    | 'document'
 | 
					 | 
				
			||||||
    | 'document_naive'
 | 
					 | 
				
			||||||
    | 'document_project-link'
 | 
					 | 
				
			||||||
    | 'document_project'
 | 
					 | 
				
			||||||
    | 'document_vite'
 | 
					 | 
				
			||||||
    | 'document_vue'
 | 
					 | 
				
			||||||
    | 'exception'
 | 
					 | 
				
			||||||
    | 'exception_403'
 | 
					 | 
				
			||||||
    | 'exception_404'
 | 
					 | 
				
			||||||
    | 'exception_500'
 | 
					 | 
				
			||||||
    | 'function'
 | 
					 | 
				
			||||||
    | 'function_tab-detail'
 | 
					 | 
				
			||||||
    | 'function_tab-multi-detail'
 | 
					 | 
				
			||||||
    | 'function_tab'
 | 
					 | 
				
			||||||
    | 'function_websocket'
 | 
					 | 
				
			||||||
    | 'management'
 | 
					 | 
				
			||||||
    | 'management_auth'
 | 
					 | 
				
			||||||
    | 'management_role'
 | 
					 | 
				
			||||||
    | 'management_route'
 | 
					 | 
				
			||||||
    | 'management_user'
 | 
					 | 
				
			||||||
    | 'multi-menu'
 | 
					    | 'multi-menu'
 | 
				
			||||||
    | 'multi-menu_first'
 | 
					    | 'multi-menu_first'
 | 
				
			||||||
    | 'multi-menu_first_second-new'
 | 
					    | 'multi-menu_first_second-new'
 | 
				
			||||||
    | 'multi-menu_first_second-new_third'
 | 
					    | 'multi-menu_first_second-new_third'
 | 
				
			||||||
    | 'multi-menu_first_second'
 | 
					    | 'multi-menu_first_second';
 | 
				
			||||||
    | 'plugin'
 | 
					 | 
				
			||||||
    | 'plugin_charts'
 | 
					 | 
				
			||||||
    | 'plugin_charts_antv'
 | 
					 | 
				
			||||||
    | 'plugin_charts_echarts'
 | 
					 | 
				
			||||||
    | 'plugin_copy'
 | 
					 | 
				
			||||||
    | 'plugin_editor'
 | 
					 | 
				
			||||||
    | 'plugin_editor_markdown'
 | 
					 | 
				
			||||||
    | 'plugin_editor_quill'
 | 
					 | 
				
			||||||
    | 'plugin_icon'
 | 
					 | 
				
			||||||
    | 'plugin_map'
 | 
					 | 
				
			||||||
    | 'plugin_print'
 | 
					 | 
				
			||||||
    | 'plugin_swiper'
 | 
					 | 
				
			||||||
    | 'plugin_video';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * last degree route key, which has the page file
 | 
					   * last degree route key, which has the page file
 | 
				
			||||||
@@ -84,41 +40,7 @@ declare namespace PageRoute {
 | 
				
			|||||||
    | 'constant-page'
 | 
					    | 'constant-page'
 | 
				
			||||||
    | 'login'
 | 
					    | 'login'
 | 
				
			||||||
    | 'not-found'
 | 
					    | 'not-found'
 | 
				
			||||||
    | 'about'
 | 
					 | 
				
			||||||
    | 'auth-demo_permission'
 | 
					 | 
				
			||||||
    | 'auth-demo_super'
 | 
					 | 
				
			||||||
    | 'component_button'
 | 
					 | 
				
			||||||
    | 'component_card'
 | 
					 | 
				
			||||||
    | 'component_table'
 | 
					 | 
				
			||||||
    | 'dashboard_analysis'
 | 
					 | 
				
			||||||
    | 'dashboard_workbench'
 | 
					 | 
				
			||||||
    | 'document_naive'
 | 
					 | 
				
			||||||
    | 'document_project-link'
 | 
					 | 
				
			||||||
    | 'document_project'
 | 
					 | 
				
			||||||
    | 'document_vite'
 | 
					 | 
				
			||||||
    | 'document_vue'
 | 
					 | 
				
			||||||
    | 'exception_403'
 | 
					 | 
				
			||||||
    | 'exception_404'
 | 
					 | 
				
			||||||
    | 'exception_500'
 | 
					 | 
				
			||||||
    | 'function_tab-detail'
 | 
					 | 
				
			||||||
    | 'function_tab-multi-detail'
 | 
					 | 
				
			||||||
    | 'function_tab'
 | 
					 | 
				
			||||||
    | 'function_websocket'
 | 
					 | 
				
			||||||
    | 'management_auth'
 | 
					 | 
				
			||||||
    | 'management_role'
 | 
					 | 
				
			||||||
    | 'management_route'
 | 
					 | 
				
			||||||
    | 'management_user'
 | 
					 | 
				
			||||||
    | 'multi-menu_first_second-new_third'
 | 
					    | 'multi-menu_first_second-new_third'
 | 
				
			||||||
    | 'multi-menu_first_second'
 | 
					    | 'multi-menu_first_second'
 | 
				
			||||||
    | 'plugin_charts_antv'
 | 
					 | 
				
			||||||
    | 'plugin_charts_echarts'
 | 
					 | 
				
			||||||
    | 'plugin_copy'
 | 
					 | 
				
			||||||
    | 'plugin_editor_markdown'
 | 
					 | 
				
			||||||
    | 'plugin_editor_quill'
 | 
					 | 
				
			||||||
    | 'plugin_icon'
 | 
					 | 
				
			||||||
    | 'plugin_map'
 | 
					 | 
				
			||||||
    | 'plugin_print'
 | 
					 | 
				
			||||||
    | 'plugin_swiper'
 | 
					 | 
				
			||||||
    | 'plugin_video'
 | 
					 | 
				
			||||||
  >;
 | 
					  >;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-card title="开发环境依赖" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
    <n-descriptions label-placement="left" bordered size="small">
 | 
					 | 
				
			||||||
      <n-descriptions-item v-for="item in devDependencies" :key="item.name" :label="item.name">
 | 
					 | 
				
			||||||
        {{ item.version }}
 | 
					 | 
				
			||||||
      </n-descriptions-item>
 | 
					 | 
				
			||||||
    </n-descriptions>
 | 
					 | 
				
			||||||
  </n-card>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { pkgJson } from './model';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'DevDependency' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { devDependencies } = pkgJson;
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,6 +0,0 @@
 | 
				
			|||||||
import ProjectIntroduction from './project-introduction.vue';
 | 
					 | 
				
			||||||
import ProjectInfo from './project-info.vue';
 | 
					 | 
				
			||||||
import ProDependency from './pro-dependency.vue';
 | 
					 | 
				
			||||||
import DevDependency from './dev-dependency.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { ProjectIntroduction, ProjectInfo, ProDependency, DevDependency };
 | 
					 | 
				
			||||||
@@ -1,39 +0,0 @@
 | 
				
			|||||||
import pkg from '~/package.json';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** npm依赖包版本信息 */
 | 
					 | 
				
			||||||
export interface PkgVersionInfo {
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  version: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Package {
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  version: string;
 | 
					 | 
				
			||||||
  dependencies: Record<string, string>;
 | 
					 | 
				
			||||||
  devDependencies: Record<string, string>;
 | 
					 | 
				
			||||||
  [key: string]: any;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface PkgJson {
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  version: string;
 | 
					 | 
				
			||||||
  dependencies: PkgVersionInfo[];
 | 
					 | 
				
			||||||
  devDependencies: PkgVersionInfo[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const pkgWithType = pkg as Package;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function transformVersionData(tuple: [string, string]): PkgVersionInfo {
 | 
					 | 
				
			||||||
  const [name, version] = tuple;
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    name,
 | 
					 | 
				
			||||||
    version
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const pkgJson: PkgJson = {
 | 
					 | 
				
			||||||
  name: pkgWithType.name,
 | 
					 | 
				
			||||||
  version: pkgWithType.version,
 | 
					 | 
				
			||||||
  dependencies: Object.entries(pkgWithType.dependencies).map(item => transformVersionData(item)),
 | 
					 | 
				
			||||||
  devDependencies: Object.entries(pkgWithType.devDependencies).map(item => transformVersionData(item))
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,19 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-card title="生产环境依赖" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
    <n-descriptions label-placement="left" bordered size="small">
 | 
					 | 
				
			||||||
      <n-descriptions-item v-for="item in dependencies" :key="item.name" :label="item.name">
 | 
					 | 
				
			||||||
        {{ item.version }}
 | 
					 | 
				
			||||||
      </n-descriptions-item>
 | 
					 | 
				
			||||||
    </n-descriptions>
 | 
					 | 
				
			||||||
  </n-card>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { pkgJson } from './model';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'ProDependency' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { dependencies } = pkgJson;
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,29 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-card title="项目信息" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
    <n-descriptions label-placement="left" bordered size="small" :column="2">
 | 
					 | 
				
			||||||
      <n-descriptions-item label="版本">
 | 
					 | 
				
			||||||
        <n-tag type="primary">{{ version }}</n-tag>
 | 
					 | 
				
			||||||
      </n-descriptions-item>
 | 
					 | 
				
			||||||
      <n-descriptions-item label="最后编译时间">
 | 
					 | 
				
			||||||
        <n-tag type="primary">{{ latestBuildTime }}</n-tag>
 | 
					 | 
				
			||||||
      </n-descriptions-item>
 | 
					 | 
				
			||||||
      <n-descriptions-item label="Github地址">
 | 
					 | 
				
			||||||
        <a class="text-primary" href="https://github.com/honghuangdc/soybean-admin" target="_blank">Github地址</a>
 | 
					 | 
				
			||||||
      </n-descriptions-item>
 | 
					 | 
				
			||||||
      <n-descriptions-item label="预览地址">
 | 
					 | 
				
			||||||
        <a class="text-primary" href="https://admin.soybeanjs.cn" target="_blank">预览地址</a>
 | 
					 | 
				
			||||||
      </n-descriptions-item>
 | 
					 | 
				
			||||||
    </n-descriptions>
 | 
					 | 
				
			||||||
  </n-card>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { pkgJson } from './model';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'ProjectInfo' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { version } = pkgJson;
 | 
					 | 
				
			||||||
const latestBuildTime = PROJECT_BUILD_TIME;
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,14 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-card title="关于" :bordered="false" size="large" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
    <p class="leading-24px">
 | 
					 | 
				
			||||||
      Soybean Admin 是一个基于 Vue3、Vite、Naive UI、TypeScript
 | 
					 | 
				
			||||||
      的中后台解决方案,它使用了最新的前端技术栈,并提炼了典型的业务模型,页面,包括二次封装组件、动态菜单、权限校验、粒子化权限控制等功能,它可以帮助你快速搭建企业级中后台项目,相信不管是从新技术使用还是其他方面,都能帮助到你。
 | 
					 | 
				
			||||||
    </p>
 | 
					 | 
				
			||||||
  </n-card>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
defineOptions({ name: 'ProjectIntroduction' });
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-space :vertical="true" :size="16">
 | 
					 | 
				
			||||||
    <project-introduction />
 | 
					 | 
				
			||||||
    <project-info />
 | 
					 | 
				
			||||||
    <pro-dependency />
 | 
					 | 
				
			||||||
    <dev-dependency />
 | 
					 | 
				
			||||||
  </n-space>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { onActivated, onMounted } from 'vue';
 | 
					 | 
				
			||||||
import { DevDependency, ProDependency, ProjectInfo, ProjectIntroduction } from './components';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
onActivated(() => {
 | 
					 | 
				
			||||||
  console.log('about page activated');
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
onMounted(() => {
 | 
					 | 
				
			||||||
  console.log('about page mounted');
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,55 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="h-full">
 | 
					 | 
				
			||||||
    <n-card title="权限切换" :bordered="false" class="h-full rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
      <div class="pb-12px">
 | 
					 | 
				
			||||||
        <n-gradient-text type="primary" :size="20">当前用户的权限:{{ auth.userInfo.userRole }}</n-gradient-text>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <n-select
 | 
					 | 
				
			||||||
        :value="auth.userInfo.userRole"
 | 
					 | 
				
			||||||
        class="w-120px"
 | 
					 | 
				
			||||||
        size="small"
 | 
					 | 
				
			||||||
        :options="options"
 | 
					 | 
				
			||||||
        @update:value="auth.updateUserRole"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <div class="py-12px">
 | 
					 | 
				
			||||||
        <n-gradient-text type="primary" :size="20">权限指令 v-permission</n-gradient-text>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div>
 | 
					 | 
				
			||||||
        <n-button v-permission="'super'" class="mr-12px">super可见</n-button>
 | 
					 | 
				
			||||||
        <n-button v-permission="'admin'" class="mr-12px">admin可见</n-button>
 | 
					 | 
				
			||||||
        <n-button v-permission="['admin', 'user']">admin和test可见</n-button>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div class="py-12px">
 | 
					 | 
				
			||||||
        <n-gradient-text type="primary" :size="20">权限函数 hasPermission</n-gradient-text>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <n-space>
 | 
					 | 
				
			||||||
        <n-button v-if="hasPermission('super')">super可见</n-button>
 | 
					 | 
				
			||||||
        <n-button v-if="hasPermission('admin')">admin可见</n-button>
 | 
					 | 
				
			||||||
        <n-button v-if="hasPermission(['admin', 'user'])">admin和user可见</n-button>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </n-card>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { watch } from 'vue';
 | 
					 | 
				
			||||||
import type { SelectOption } from 'naive-ui';
 | 
					 | 
				
			||||||
import { userRoleOptions } from '@/constants';
 | 
					 | 
				
			||||||
import { useAppStore, useAuthStore } from '@/store';
 | 
					 | 
				
			||||||
import { usePermission } from '@/composables';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const app = useAppStore();
 | 
					 | 
				
			||||||
const auth = useAuthStore();
 | 
					 | 
				
			||||||
const { hasPermission } = usePermission();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const options: SelectOption[] = userRoleOptions;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(
 | 
					 | 
				
			||||||
  () => auth.userInfo.userRole,
 | 
					 | 
				
			||||||
  async () => {
 | 
					 | 
				
			||||||
    app.reloadPage();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="h-full">
 | 
					 | 
				
			||||||
    <n-card title="当前页面只有super才能看到" :bordered="false" class="h-full rounded-8px shadow-sm"></n-card>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts"></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,575 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div>
 | 
					 | 
				
			||||||
    <n-card title="按钮" :bordered="false" class="h-full rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
      <n-grid cols="s:1 m:2" responsive="screen" :x-gap="16" :y-gap="16">
 | 
					 | 
				
			||||||
        <n-grid-item v-for="item in buttonExample" :key="item.id">
 | 
					 | 
				
			||||||
          <n-card :title="item.label" class="min-h-180px">
 | 
					 | 
				
			||||||
            <p v-if="item.desc" class="pb-16px">{{ item.desc }}</p>
 | 
					 | 
				
			||||||
            <n-space>
 | 
					 | 
				
			||||||
              <n-button
 | 
					 | 
				
			||||||
                v-for="button in item.buttons"
 | 
					 | 
				
			||||||
                :key="button.id"
 | 
					 | 
				
			||||||
                v-bind="button.props"
 | 
					 | 
				
			||||||
                :style="`--icon-margin: ${button.props.circle ? 0 : 6}px`"
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <template v-if="button.icon" #icon>
 | 
					 | 
				
			||||||
                  <svg-icon :icon="button.icon" />
 | 
					 | 
				
			||||||
                </template>
 | 
					 | 
				
			||||||
                {{ button.label }}
 | 
					 | 
				
			||||||
              </n-button>
 | 
					 | 
				
			||||||
            </n-space>
 | 
					 | 
				
			||||||
          </n-card>
 | 
					 | 
				
			||||||
        </n-grid-item>
 | 
					 | 
				
			||||||
        <n-grid-item class="h-180px">
 | 
					 | 
				
			||||||
          <n-card title="加载中" class="h-full">
 | 
					 | 
				
			||||||
            <p class="pb-16px">按钮有加载状态。</p>
 | 
					 | 
				
			||||||
            <n-space>
 | 
					 | 
				
			||||||
              <n-button :loading="loading" type="primary" @click="startLoading">开始加载</n-button>
 | 
					 | 
				
			||||||
              <n-button @click="endLoading">取消加载</n-button>
 | 
					 | 
				
			||||||
            </n-space>
 | 
					 | 
				
			||||||
          </n-card>
 | 
					 | 
				
			||||||
        </n-grid-item>
 | 
					 | 
				
			||||||
      </n-grid>
 | 
					 | 
				
			||||||
    </n-card>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import type { ButtonProps } from 'naive-ui';
 | 
					 | 
				
			||||||
import { useLoading } from '@/hooks';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface ButtonDetail {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  props: ButtonProps & { href?: string; target?: string };
 | 
					 | 
				
			||||||
  label?: string;
 | 
					 | 
				
			||||||
  icon?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface ButtonExample {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  label: string;
 | 
					 | 
				
			||||||
  buttons: ButtonDetail[];
 | 
					 | 
				
			||||||
  desc?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { loading, startLoading, endLoading } = useLoading();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const buttonExample: ButtonExample[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 0,
 | 
					 | 
				
			||||||
    label: '基础',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: {},
 | 
					 | 
				
			||||||
        label: 'Default'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        props: { type: 'tertiary' },
 | 
					 | 
				
			||||||
        label: 'Tertiary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        props: { type: 'primary' },
 | 
					 | 
				
			||||||
        label: 'Primary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        props: { type: 'info' },
 | 
					 | 
				
			||||||
        label: 'Info'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 4,
 | 
					 | 
				
			||||||
        props: { type: 'success' },
 | 
					 | 
				
			||||||
        label: 'Success'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        props: { type: 'warning' },
 | 
					 | 
				
			||||||
        label: 'Warning'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 6,
 | 
					 | 
				
			||||||
        props: { type: 'error' },
 | 
					 | 
				
			||||||
        label: 'Error'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    desc: '按钮的 type 分别为 default、primary、info、success、warning 和 error。'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 1,
 | 
					 | 
				
			||||||
    label: '次要按钮',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true },
 | 
					 | 
				
			||||||
        label: 'Default'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, type: 'tertiary' },
 | 
					 | 
				
			||||||
        label: 'Tertiary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, type: 'primary' },
 | 
					 | 
				
			||||||
        label: 'Primary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, type: 'info' },
 | 
					 | 
				
			||||||
        label: 'Info'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 4,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, type: 'success' },
 | 
					 | 
				
			||||||
        label: 'Success'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, type: 'warning' },
 | 
					 | 
				
			||||||
        label: 'Warning'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 6,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, type: 'error' },
 | 
					 | 
				
			||||||
        label: 'Error'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 7,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, round: true },
 | 
					 | 
				
			||||||
        label: 'Default'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 8,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, round: true, type: 'tertiary' },
 | 
					 | 
				
			||||||
        label: 'Tertiary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 9,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, round: true, type: 'primary' },
 | 
					 | 
				
			||||||
        label: 'Primary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 10,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, round: true, type: 'info' },
 | 
					 | 
				
			||||||
        label: 'Info'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 11,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, round: true, type: 'success' },
 | 
					 | 
				
			||||||
        label: 'Success'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 12,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, round: true, type: 'warning' },
 | 
					 | 
				
			||||||
        label: 'Warning'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 13,
 | 
					 | 
				
			||||||
        props: { strong: true, secondary: true, round: true, type: 'error' },
 | 
					 | 
				
			||||||
        label: 'Error'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 2,
 | 
					 | 
				
			||||||
    label: '次次要按钮',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: { tertiary: true },
 | 
					 | 
				
			||||||
        label: 'Default'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        props: { tertiary: true, type: 'primary' },
 | 
					 | 
				
			||||||
        label: 'Primary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        props: { tertiary: true, type: 'info' },
 | 
					 | 
				
			||||||
        label: 'Info'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        props: { tertiary: true, type: 'success' },
 | 
					 | 
				
			||||||
        label: 'Success'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 4,
 | 
					 | 
				
			||||||
        props: { tertiary: true, type: 'warning' },
 | 
					 | 
				
			||||||
        label: 'Warning'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        props: { tertiary: true, type: 'error' },
 | 
					 | 
				
			||||||
        label: 'Error'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 6,
 | 
					 | 
				
			||||||
        props: { tertiary: true, round: true },
 | 
					 | 
				
			||||||
        label: 'Default'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 7,
 | 
					 | 
				
			||||||
        props: { tertiary: true, round: true, type: 'primary' },
 | 
					 | 
				
			||||||
        label: 'Primary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 8,
 | 
					 | 
				
			||||||
        props: { tertiary: true, round: true, type: 'info' },
 | 
					 | 
				
			||||||
        label: 'Info'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 9,
 | 
					 | 
				
			||||||
        props: { tertiary: true, round: true, type: 'success' },
 | 
					 | 
				
			||||||
        label: 'Success'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 10,
 | 
					 | 
				
			||||||
        props: { tertiary: true, round: true, type: 'warning' },
 | 
					 | 
				
			||||||
        label: 'Warning'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 11,
 | 
					 | 
				
			||||||
        props: { tertiary: true, round: true, type: 'error' },
 | 
					 | 
				
			||||||
        label: 'Error'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 3,
 | 
					 | 
				
			||||||
    label: '次次次要按钮',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: { quaternary: true },
 | 
					 | 
				
			||||||
        label: 'Default'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        props: { quaternary: true, type: 'primary' },
 | 
					 | 
				
			||||||
        label: 'Primary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        props: { quaternary: true, type: 'info' },
 | 
					 | 
				
			||||||
        label: 'Info'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        props: { quaternary: true, type: 'success' },
 | 
					 | 
				
			||||||
        label: 'Success'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 4,
 | 
					 | 
				
			||||||
        props: { quaternary: true, type: 'warning' },
 | 
					 | 
				
			||||||
        label: 'Warning'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        props: { quaternary: true, type: 'error' },
 | 
					 | 
				
			||||||
        label: 'Error'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 6,
 | 
					 | 
				
			||||||
        props: { quaternary: true, round: true },
 | 
					 | 
				
			||||||
        label: 'Default'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 7,
 | 
					 | 
				
			||||||
        props: { quaternary: true, round: true, type: 'primary' },
 | 
					 | 
				
			||||||
        label: 'Primary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 8,
 | 
					 | 
				
			||||||
        props: { quaternary: true, round: true, type: 'info' },
 | 
					 | 
				
			||||||
        label: 'Info'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 9,
 | 
					 | 
				
			||||||
        props: { quaternary: true, round: true, type: 'success' },
 | 
					 | 
				
			||||||
        label: 'Success'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 10,
 | 
					 | 
				
			||||||
        props: { quaternary: true, round: true, type: 'warning' },
 | 
					 | 
				
			||||||
        label: 'Warning'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 11,
 | 
					 | 
				
			||||||
        props: { quaternary: true, round: true, type: 'error' },
 | 
					 | 
				
			||||||
        label: 'Error'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 4,
 | 
					 | 
				
			||||||
    label: '虚线按钮',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: { dashed: true },
 | 
					 | 
				
			||||||
        label: 'Default'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        props: { dashed: true, type: 'tertiary' },
 | 
					 | 
				
			||||||
        label: 'Tertiary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        props: { dashed: true, type: 'primary' },
 | 
					 | 
				
			||||||
        label: 'Primary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        props: { dashed: true, type: 'info' },
 | 
					 | 
				
			||||||
        label: 'Info'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 4,
 | 
					 | 
				
			||||||
        props: { dashed: true, type: 'success' },
 | 
					 | 
				
			||||||
        label: 'Success'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        props: { dashed: true, type: 'warning' },
 | 
					 | 
				
			||||||
        label: 'Warning'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 6,
 | 
					 | 
				
			||||||
        props: { dashed: true, type: 'error' },
 | 
					 | 
				
			||||||
        label: 'Error'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 5,
 | 
					 | 
				
			||||||
    label: '尺寸',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: { size: 'tiny', strong: true },
 | 
					 | 
				
			||||||
        label: '小小'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        props: { size: 'small', strong: true },
 | 
					 | 
				
			||||||
        label: '小'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        props: { size: 'medium', strong: true },
 | 
					 | 
				
			||||||
        label: '不小'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        props: { size: 'large', strong: true },
 | 
					 | 
				
			||||||
        label: '不不小'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 6,
 | 
					 | 
				
			||||||
    label: '文本按钮',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: { text: true },
 | 
					 | 
				
			||||||
        label: '那车头依然吐着烟',
 | 
					 | 
				
			||||||
        icon: 'mdi:train'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 7,
 | 
					 | 
				
			||||||
    label: '自定义标签按钮',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          text: true,
 | 
					 | 
				
			||||||
          tag: 'a',
 | 
					 | 
				
			||||||
          href: 'https://github.com/honghuangdc/soybean-admin',
 | 
					 | 
				
			||||||
          target: '_blank',
 | 
					 | 
				
			||||||
          type: 'primary'
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: 'soybean-admin'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    desc: '你可以把按钮渲染成不同的标签,比如 a标签 。'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 8,
 | 
					 | 
				
			||||||
    label: '按钮禁用',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          disabled: true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '不许点'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    desc: '按钮可以被禁用'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 9,
 | 
					 | 
				
			||||||
    label: '图标按钮',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          secondary: true,
 | 
					 | 
				
			||||||
          strong: true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '+100元',
 | 
					 | 
				
			||||||
        icon: 'mdi:cash-100'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          iconPlacement: 'right',
 | 
					 | 
				
			||||||
          secondary: true,
 | 
					 | 
				
			||||||
          strong: true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '+100元',
 | 
					 | 
				
			||||||
        icon: 'mdi:cash-100'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    desc: '在按钮上使用图标。'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 10,
 | 
					 | 
				
			||||||
    label: '不同形状按钮',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          circle: true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        icon: 'mdi:cash-100'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          round: true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '圆角'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        props: {},
 | 
					 | 
				
			||||||
        label: '方'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    desc: '按钮拥有不同的形状。'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 11,
 | 
					 | 
				
			||||||
    label: '透明背景按钮',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: { ghost: true },
 | 
					 | 
				
			||||||
        label: 'Default'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        props: { ghost: true, type: 'tertiary' },
 | 
					 | 
				
			||||||
        label: 'Tertiary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        props: { ghost: true, type: 'primary' },
 | 
					 | 
				
			||||||
        label: 'Primary'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        props: { ghost: true, type: 'info' },
 | 
					 | 
				
			||||||
        label: 'Info'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 4,
 | 
					 | 
				
			||||||
        props: { ghost: true, type: 'success' },
 | 
					 | 
				
			||||||
        label: 'Success'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        props: { ghost: true, type: 'warning' },
 | 
					 | 
				
			||||||
        label: 'Warning'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 6,
 | 
					 | 
				
			||||||
        props: { ghost: true, type: 'error' },
 | 
					 | 
				
			||||||
        label: 'Error'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    desc: 'Ghost 按钮有透明的背景。'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 12,
 | 
					 | 
				
			||||||
    label: '自定义颜色',
 | 
					 | 
				
			||||||
    buttons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 0,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          color: '#8a2be2'
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '#8a2be2',
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-color-lens'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 1,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          color: '#ff69b4'
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '#ff69b4',
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-color-lens'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 2,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          color: '#8a2be2',
 | 
					 | 
				
			||||||
          ghost: true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '#8a2be2',
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-color-lens'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 3,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          color: '#ff69b4',
 | 
					 | 
				
			||||||
          ghost: true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '#ff69b4',
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-color-lens'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 4,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          color: '#8a2be2',
 | 
					 | 
				
			||||||
          text: true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '#8a2be2',
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-color-lens'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        id: 5,
 | 
					 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          color: '#ff69b4',
 | 
					 | 
				
			||||||
          text: true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        label: '#ff69b4',
 | 
					 | 
				
			||||||
        icon: 'ic:baseline-color-lens'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    desc: '这两个颜色看起来像毒蘑菇。'
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div>
 | 
					 | 
				
			||||||
    <n-card title="卡片" :bordered="false" class="h-full rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
      <n-space :vertical="true">
 | 
					 | 
				
			||||||
        <n-card title="基本用法">
 | 
					 | 
				
			||||||
          <p class="pb-16px">基础卡片</p>
 | 
					 | 
				
			||||||
          <n-card title="卡片">卡片内容</n-card>
 | 
					 | 
				
			||||||
        </n-card>
 | 
					 | 
				
			||||||
        <n-card title="尺寸">
 | 
					 | 
				
			||||||
          <p class="pb-16px">卡片有 small、medium、large、huge 尺寸。</p>
 | 
					 | 
				
			||||||
          <n-space vertical>
 | 
					 | 
				
			||||||
            <n-card title="小卡片" size="small">卡片内容</n-card>
 | 
					 | 
				
			||||||
            <n-card title="中卡片" size="medium">卡片内容</n-card>
 | 
					 | 
				
			||||||
            <n-card title="大卡片" size="large">卡片内容</n-card>
 | 
					 | 
				
			||||||
            <n-card title="超大卡片" size="huge">卡片内容</n-card>
 | 
					 | 
				
			||||||
          </n-space>
 | 
					 | 
				
			||||||
        </n-card>
 | 
					 | 
				
			||||||
        <n-card title="文本按钮">
 | 
					 | 
				
			||||||
          <p class="pb-16px">
 | 
					 | 
				
			||||||
            content 和 footer 可以被 hard 或 soft 分段,action 可以被分段。分段分割线会在区域的上方出现。
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
          <n-card
 | 
					 | 
				
			||||||
            title="卡片分段示例"
 | 
					 | 
				
			||||||
            :segmented="{
 | 
					 | 
				
			||||||
              content: true,
 | 
					 | 
				
			||||||
              footer: 'soft'
 | 
					 | 
				
			||||||
            }"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <template #header-extra>#header-extra</template>
 | 
					 | 
				
			||||||
            卡片内容
 | 
					 | 
				
			||||||
            <template #footer>#footer</template>
 | 
					 | 
				
			||||||
            <template #action>#action</template>
 | 
					 | 
				
			||||||
          </n-card>
 | 
					 | 
				
			||||||
        </n-card>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </n-card>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts"></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,124 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="h-full overflow-hidden">
 | 
					 | 
				
			||||||
    <n-card title="表格" :bordered="false" class="h-full rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
      <n-space :vertical="true">
 | 
					 | 
				
			||||||
        <n-space>
 | 
					 | 
				
			||||||
          <n-button @click="getDataSource">有数据</n-button>
 | 
					 | 
				
			||||||
          <n-button @click="getEmptyDataSource">空数据</n-button>
 | 
					 | 
				
			||||||
        </n-space>
 | 
					 | 
				
			||||||
        <loading-empty-wrapper class="h-480px" :loading="loading" :empty="empty">
 | 
					 | 
				
			||||||
          <n-data-table :columns="columns" :data="dataSource" :flex-height="true" class="h-480px" />
 | 
					 | 
				
			||||||
        </loading-empty-wrapper>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </n-card>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="tsx">
 | 
					 | 
				
			||||||
import { onMounted, ref } from 'vue';
 | 
					 | 
				
			||||||
import { NSpace, NButton, NPopconfirm } from 'naive-ui';
 | 
					 | 
				
			||||||
import type { DataTableColumn } from 'naive-ui';
 | 
					 | 
				
			||||||
import { useLoadingEmpty } from '@/hooks';
 | 
					 | 
				
			||||||
import { getRandomInteger } from '@/utils';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface DataSource {
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  age: number;
 | 
					 | 
				
			||||||
  address: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { loading, startLoading, endLoading, empty, setEmpty } = useLoadingEmpty();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const columns: DataTableColumn<DataSource>[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    title: 'Name',
 | 
					 | 
				
			||||||
    key: 'name',
 | 
					 | 
				
			||||||
    align: 'center'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    title: 'Age',
 | 
					 | 
				
			||||||
    key: 'age',
 | 
					 | 
				
			||||||
    align: 'center'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    title: 'Address',
 | 
					 | 
				
			||||||
    key: 'address',
 | 
					 | 
				
			||||||
    align: 'center'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 'action',
 | 
					 | 
				
			||||||
    title: 'Action',
 | 
					 | 
				
			||||||
    align: 'center',
 | 
					 | 
				
			||||||
    render: row => {
 | 
					 | 
				
			||||||
      return (
 | 
					 | 
				
			||||||
        <NSpace justify={'center'}>
 | 
					 | 
				
			||||||
          <NButton
 | 
					 | 
				
			||||||
            size={'small'}
 | 
					 | 
				
			||||||
            onClick={() => {
 | 
					 | 
				
			||||||
              handleEdit(row.name);
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            编辑
 | 
					 | 
				
			||||||
          </NButton>
 | 
					 | 
				
			||||||
          <NPopconfirm
 | 
					 | 
				
			||||||
            onPositiveClick={() => {
 | 
					 | 
				
			||||||
              handleDelete(row.name);
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            {{
 | 
					 | 
				
			||||||
              default: () => '确认删除',
 | 
					 | 
				
			||||||
              trigger: () => <NButton size={'small'}>删除</NButton>
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
          </NPopconfirm>
 | 
					 | 
				
			||||||
        </NSpace>
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const dataSource = ref<DataSource[]>([]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function createDataSource(): DataSource[] {
 | 
					 | 
				
			||||||
  return Array(100)
 | 
					 | 
				
			||||||
    .fill(1)
 | 
					 | 
				
			||||||
    .map((_item, index) => {
 | 
					 | 
				
			||||||
      return {
 | 
					 | 
				
			||||||
        name: `Name${index}`,
 | 
					 | 
				
			||||||
        age: getRandomInteger(30, 20),
 | 
					 | 
				
			||||||
        address: '中国'
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getDataSource() {
 | 
					 | 
				
			||||||
  startLoading();
 | 
					 | 
				
			||||||
  setTimeout(() => {
 | 
					 | 
				
			||||||
    dataSource.value = createDataSource();
 | 
					 | 
				
			||||||
    endLoading();
 | 
					 | 
				
			||||||
    setEmpty(!dataSource.value.length);
 | 
					 | 
				
			||||||
  }, 1000);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getEmptyDataSource() {
 | 
					 | 
				
			||||||
  startLoading();
 | 
					 | 
				
			||||||
  setTimeout(() => {
 | 
					 | 
				
			||||||
    dataSource.value = [];
 | 
					 | 
				
			||||||
    endLoading();
 | 
					 | 
				
			||||||
    setEmpty(!dataSource.value.length);
 | 
					 | 
				
			||||||
  }, 1000);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleEdit(_name: string) {
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleDelete(_name: string) {
 | 
					 | 
				
			||||||
  //
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
onMounted(() => {
 | 
					 | 
				
			||||||
  getDataSource();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,136 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-grid :x-gap="16" :y-gap="16" :item-responsive="true">
 | 
					 | 
				
			||||||
    <n-grid-item span="0:24 640:24 1024:8">
 | 
					 | 
				
			||||||
      <n-card title="时间线" :bordered="false" class="h-full rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
        <n-timeline>
 | 
					 | 
				
			||||||
          <n-timeline-item v-for="item in timelines" :key="item.type" v-bind="item" />
 | 
					 | 
				
			||||||
        </n-timeline>
 | 
					 | 
				
			||||||
      </n-card>
 | 
					 | 
				
			||||||
    </n-grid-item>
 | 
					 | 
				
			||||||
    <n-grid-item span="0:24 640:24 1024:16">
 | 
					 | 
				
			||||||
      <n-card title="表格" :bordered="false" class="h-full rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
        <n-data-table size="small" :columns="columns" :data="tableData" />
 | 
					 | 
				
			||||||
      </n-card>
 | 
					 | 
				
			||||||
    </n-grid-item>
 | 
					 | 
				
			||||||
  </n-grid>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { h } from 'vue';
 | 
					 | 
				
			||||||
import { NTag } from 'naive-ui';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'DashboardAnalysisBottomPart' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface TimelineData {
 | 
					 | 
				
			||||||
  type: 'default' | 'info' | 'success' | 'warning' | 'error';
 | 
					 | 
				
			||||||
  title: string;
 | 
					 | 
				
			||||||
  content: string;
 | 
					 | 
				
			||||||
  time: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface TableData {
 | 
					 | 
				
			||||||
  key: number;
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  age: number;
 | 
					 | 
				
			||||||
  address: string;
 | 
					 | 
				
			||||||
  tags: string[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const timelines: TimelineData[] = [
 | 
					 | 
				
			||||||
  { type: 'default', title: '啊', content: '', time: '2021-10-10 20:46' },
 | 
					 | 
				
			||||||
  { type: 'success', title: '成功', content: '哪里成功', time: '2021-10-10 20:46' },
 | 
					 | 
				
			||||||
  { type: 'error', title: '错误', content: '哪里错误', time: '2021-10-10 20:46' },
 | 
					 | 
				
			||||||
  { type: 'warning', title: '警告', content: '哪里警告', time: '2021-10-10 20:46' },
 | 
					 | 
				
			||||||
  { type: 'info', title: '信息', content: '是的', time: '2021-10-10 20:46' }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const columns = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    title: 'Name',
 | 
					 | 
				
			||||||
    key: 'name'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    title: 'Age',
 | 
					 | 
				
			||||||
    key: 'age'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    title: 'Address',
 | 
					 | 
				
			||||||
    key: 'address'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    title: 'Tags',
 | 
					 | 
				
			||||||
    key: 'tags',
 | 
					 | 
				
			||||||
    render(row: TableData) {
 | 
					 | 
				
			||||||
      const tags = row.tags.map(tagKey => {
 | 
					 | 
				
			||||||
        return h(
 | 
					 | 
				
			||||||
          NTag,
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            style: {
 | 
					 | 
				
			||||||
              marginRight: '6px'
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            type: 'info'
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            default: () => tagKey
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      return tags;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const tableData: TableData[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 0,
 | 
					 | 
				
			||||||
    name: 'John Brown',
 | 
					 | 
				
			||||||
    age: 32,
 | 
					 | 
				
			||||||
    address: 'New York No. 1 Lake Park',
 | 
					 | 
				
			||||||
    tags: ['nice', 'developer']
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 1,
 | 
					 | 
				
			||||||
    name: 'Jim Green',
 | 
					 | 
				
			||||||
    age: 42,
 | 
					 | 
				
			||||||
    address: 'London No. 1 Lake Park',
 | 
					 | 
				
			||||||
    tags: ['wow']
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 2,
 | 
					 | 
				
			||||||
    name: 'Joe Black',
 | 
					 | 
				
			||||||
    age: 32,
 | 
					 | 
				
			||||||
    address: 'Sidney No. 1 Lake Park',
 | 
					 | 
				
			||||||
    tags: ['cool', 'teacher']
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 3,
 | 
					 | 
				
			||||||
    name: 'Soybean',
 | 
					 | 
				
			||||||
    age: 25,
 | 
					 | 
				
			||||||
    address: 'China Shenzhen',
 | 
					 | 
				
			||||||
    tags: ['handsome', 'programmer']
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 4,
 | 
					 | 
				
			||||||
    name: 'John Brown',
 | 
					 | 
				
			||||||
    age: 32,
 | 
					 | 
				
			||||||
    address: 'New York No. 1 Lake Park',
 | 
					 | 
				
			||||||
    tags: ['nice', 'developer']
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 5,
 | 
					 | 
				
			||||||
    name: 'Jim Green',
 | 
					 | 
				
			||||||
    age: 42,
 | 
					 | 
				
			||||||
    address: 'London No. 1 Lake Park',
 | 
					 | 
				
			||||||
    tags: ['wow']
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    key: 6,
 | 
					 | 
				
			||||||
    name: 'Joe Black',
 | 
					 | 
				
			||||||
    age: 32,
 | 
					 | 
				
			||||||
    address: 'Sidney No. 1 Lake Park',
 | 
					 | 
				
			||||||
    tags: ['cool', 'teacher']
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="p-16px rounded-8px text-white" :style="{ backgroundImage: gradientStyle }">
 | 
					 | 
				
			||||||
    <slot></slot>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { computed } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  /** 渐变开始的颜色 */
 | 
					 | 
				
			||||||
  startColor?: string;
 | 
					 | 
				
			||||||
  /** 渐变结束的颜色 */
 | 
					 | 
				
			||||||
  endColor?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = withDefaults(defineProps<Props>(), {
 | 
					 | 
				
			||||||
  startColor: '#56cdf3',
 | 
					 | 
				
			||||||
  endColor: '#719de3'
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const gradientStyle = computed(() => `linear-gradient(to bottom right, ${props.startColor}, ${props.endColor})`);
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
import GradientBg from './gradient-bg.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { GradientBg };
 | 
					 | 
				
			||||||
@@ -1,72 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-card :bordered="false" class="h-full rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
    <n-grid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
 | 
					 | 
				
			||||||
      <n-grid-item v-for="item in cardData" :key="item.id">
 | 
					 | 
				
			||||||
        <gradient-bg class="h-100px" :start-color="item.colors[0]" :end-color="item.colors[1]">
 | 
					 | 
				
			||||||
          <h3 class="text-16px">{{ item.title }}</h3>
 | 
					 | 
				
			||||||
          <div class="flex justify-between pt-12px">
 | 
					 | 
				
			||||||
            <svg-icon :icon="item.icon" class="text-32px" />
 | 
					 | 
				
			||||||
            <count-to
 | 
					 | 
				
			||||||
              :prefix="item.unit"
 | 
					 | 
				
			||||||
              :start-value="1"
 | 
					 | 
				
			||||||
              :end-value="item.value"
 | 
					 | 
				
			||||||
              class="text-30px text-white dark:text-dark"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </gradient-bg>
 | 
					 | 
				
			||||||
      </n-grid-item>
 | 
					 | 
				
			||||||
    </n-grid>
 | 
					 | 
				
			||||||
  </n-card>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { GradientBg } from './components';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'DashboardAnalysisDataCard' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface CardData {
 | 
					 | 
				
			||||||
  id: string;
 | 
					 | 
				
			||||||
  title: string;
 | 
					 | 
				
			||||||
  value: number;
 | 
					 | 
				
			||||||
  unit: string;
 | 
					 | 
				
			||||||
  colors: [string, string];
 | 
					 | 
				
			||||||
  icon: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const cardData: CardData[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 'visit',
 | 
					 | 
				
			||||||
    title: '访问量',
 | 
					 | 
				
			||||||
    value: 1000000,
 | 
					 | 
				
			||||||
    unit: '',
 | 
					 | 
				
			||||||
    colors: ['#ec4786', '#b955a4'],
 | 
					 | 
				
			||||||
    icon: 'ant-design:bar-chart-outlined'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 'amount',
 | 
					 | 
				
			||||||
    title: '成交额',
 | 
					 | 
				
			||||||
    value: 234567.89,
 | 
					 | 
				
			||||||
    unit: '$',
 | 
					 | 
				
			||||||
    colors: ['#865ec0', '#5144b4'],
 | 
					 | 
				
			||||||
    icon: 'ant-design:money-collect-outlined'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 'download',
 | 
					 | 
				
			||||||
    title: '下载数',
 | 
					 | 
				
			||||||
    value: 666666,
 | 
					 | 
				
			||||||
    unit: '',
 | 
					 | 
				
			||||||
    colors: ['#56cdf3', '#719de3'],
 | 
					 | 
				
			||||||
    icon: 'carbon:document-download'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 'trade',
 | 
					 | 
				
			||||||
    title: '成交数',
 | 
					 | 
				
			||||||
    value: 999999,
 | 
					 | 
				
			||||||
    unit: '',
 | 
					 | 
				
			||||||
    colors: ['#fcbc25', '#f68057'],
 | 
					 | 
				
			||||||
    icon: 'ant-design:trademark-circle-outlined'
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,5 +0,0 @@
 | 
				
			|||||||
import TopChart from './top-chart/index.vue';
 | 
					 | 
				
			||||||
import DataCard from './data-card/index.vue';
 | 
					 | 
				
			||||||
import BottomPart from './bottom-part/index.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { TopChart, DataCard, BottomPart };
 | 
					 | 
				
			||||||
@@ -1,184 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-grid :x-gap="16" :y-gap="16" :item-responsive="true">
 | 
					 | 
				
			||||||
    <n-grid-item span="0:24 640:24 1024:6">
 | 
					 | 
				
			||||||
      <n-card :bordered="false" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
        <div class="w-full h-360px py-12px">
 | 
					 | 
				
			||||||
          <h3 class="text-16px font-bold">Dashboard</h3>
 | 
					 | 
				
			||||||
          <p class="text-#aaa">Overview Of Lasted Month</p>
 | 
					 | 
				
			||||||
          <h3 class="pt-32px text-24px font-bold">
 | 
					 | 
				
			||||||
            <count-to prefix="$" :start-value="0" :end-value="7754" />
 | 
					 | 
				
			||||||
          </h3>
 | 
					 | 
				
			||||||
          <p class="text-#aaa">Current Month Earnings</p>
 | 
					 | 
				
			||||||
          <h3 class="pt-32px text-24px font-bold">
 | 
					 | 
				
			||||||
            <count-to :start-value="0" :end-value="1234" />
 | 
					 | 
				
			||||||
          </h3>
 | 
					 | 
				
			||||||
          <p class="text-#aaa">Current Month Sales</p>
 | 
					 | 
				
			||||||
          <n-button class="mt-24px whitespace-pre-wrap" type="primary">Last Month Summary</n-button>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </n-card>
 | 
					 | 
				
			||||||
    </n-grid-item>
 | 
					 | 
				
			||||||
    <n-grid-item span="0:24 640:24 1024:10">
 | 
					 | 
				
			||||||
      <n-card :bordered="false" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
        <div ref="lineRef" class="w-full h-360px"></div>
 | 
					 | 
				
			||||||
      </n-card>
 | 
					 | 
				
			||||||
    </n-grid-item>
 | 
					 | 
				
			||||||
    <n-grid-item span="0:24 640:24 1024:8">
 | 
					 | 
				
			||||||
      <n-card :bordered="false" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
        <div ref="pieRef" class="w-full h-360px"></div>
 | 
					 | 
				
			||||||
      </n-card>
 | 
					 | 
				
			||||||
    </n-grid-item>
 | 
					 | 
				
			||||||
  </n-grid>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { ref } from 'vue';
 | 
					 | 
				
			||||||
import type { Ref } from 'vue';
 | 
					 | 
				
			||||||
import { type ECOption, useEcharts } from '@/composables';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'DashboardAnalysisTopCard' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const lineOptions = ref<ECOption>({
 | 
					 | 
				
			||||||
  tooltip: {
 | 
					 | 
				
			||||||
    trigger: 'axis',
 | 
					 | 
				
			||||||
    axisPointer: {
 | 
					 | 
				
			||||||
      type: 'cross',
 | 
					 | 
				
			||||||
      label: {
 | 
					 | 
				
			||||||
        backgroundColor: '#6a7985'
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  legend: {
 | 
					 | 
				
			||||||
    data: ['下载量', '注册数']
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  grid: {
 | 
					 | 
				
			||||||
    left: '3%',
 | 
					 | 
				
			||||||
    right: '4%',
 | 
					 | 
				
			||||||
    bottom: '3%',
 | 
					 | 
				
			||||||
    containLabel: true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  xAxis: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      type: 'category',
 | 
					 | 
				
			||||||
      boundaryGap: false,
 | 
					 | 
				
			||||||
      data: ['06:00', '08:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '24:00']
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  yAxis: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      type: 'value'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  series: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      color: '#8e9dff',
 | 
					 | 
				
			||||||
      name: '下载量',
 | 
					 | 
				
			||||||
      type: 'line',
 | 
					 | 
				
			||||||
      smooth: true,
 | 
					 | 
				
			||||||
      stack: 'Total',
 | 
					 | 
				
			||||||
      areaStyle: {
 | 
					 | 
				
			||||||
        color: {
 | 
					 | 
				
			||||||
          type: 'linear',
 | 
					 | 
				
			||||||
          x: 0,
 | 
					 | 
				
			||||||
          y: 0,
 | 
					 | 
				
			||||||
          x2: 0,
 | 
					 | 
				
			||||||
          y2: 1,
 | 
					 | 
				
			||||||
          colorStops: [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
              offset: 0.25,
 | 
					 | 
				
			||||||
              color: '#8e9dff'
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
              offset: 1,
 | 
					 | 
				
			||||||
              color: '#fff'
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          ]
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      emphasis: {
 | 
					 | 
				
			||||||
        focus: 'series'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      data: [4623, 6145, 6268, 6411, 1890, 4251, 2978, 3880, 3606, 4311]
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      color: '#26deca',
 | 
					 | 
				
			||||||
      name: '注册数',
 | 
					 | 
				
			||||||
      type: 'line',
 | 
					 | 
				
			||||||
      smooth: true,
 | 
					 | 
				
			||||||
      stack: 'Total',
 | 
					 | 
				
			||||||
      areaStyle: {
 | 
					 | 
				
			||||||
        color: {
 | 
					 | 
				
			||||||
          type: 'linear',
 | 
					 | 
				
			||||||
          x: 0,
 | 
					 | 
				
			||||||
          y: 0,
 | 
					 | 
				
			||||||
          x2: 0,
 | 
					 | 
				
			||||||
          y2: 1,
 | 
					 | 
				
			||||||
          colorStops: [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
              offset: 0.25,
 | 
					 | 
				
			||||||
              color: '#26deca'
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
              offset: 1,
 | 
					 | 
				
			||||||
              color: '#fff'
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          ]
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      emphasis: {
 | 
					 | 
				
			||||||
        focus: 'series'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      data: [2208, 2016, 2916, 4512, 8281, 2008, 1963, 2367, 2956, 678]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}) as Ref<ECOption>;
 | 
					 | 
				
			||||||
const { domRef: lineRef } = useEcharts(lineOptions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const pieOptions = ref<ECOption>({
 | 
					 | 
				
			||||||
  tooltip: {
 | 
					 | 
				
			||||||
    trigger: 'item'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  legend: {
 | 
					 | 
				
			||||||
    bottom: '1%',
 | 
					 | 
				
			||||||
    left: 'center',
 | 
					 | 
				
			||||||
    itemStyle: {
 | 
					 | 
				
			||||||
      borderWidth: 0
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  series: [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      color: ['#5da8ff', '#8e9dff', '#fedc69', '#26deca'],
 | 
					 | 
				
			||||||
      name: '时间安排',
 | 
					 | 
				
			||||||
      type: 'pie',
 | 
					 | 
				
			||||||
      radius: ['45%', '75%'],
 | 
					 | 
				
			||||||
      avoidLabelOverlap: false,
 | 
					 | 
				
			||||||
      itemStyle: {
 | 
					 | 
				
			||||||
        borderRadius: 10,
 | 
					 | 
				
			||||||
        borderColor: '#fff',
 | 
					 | 
				
			||||||
        borderWidth: 1
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      label: {
 | 
					 | 
				
			||||||
        show: false,
 | 
					 | 
				
			||||||
        position: 'center'
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      emphasis: {
 | 
					 | 
				
			||||||
        label: {
 | 
					 | 
				
			||||||
          show: true,
 | 
					 | 
				
			||||||
          fontSize: '12'
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      labelLine: {
 | 
					 | 
				
			||||||
        show: false
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      data: [
 | 
					 | 
				
			||||||
        { value: 20, name: '学习' },
 | 
					 | 
				
			||||||
        { value: 10, name: '娱乐' },
 | 
					 | 
				
			||||||
        { value: 30, name: '工作' },
 | 
					 | 
				
			||||||
        { value: 40, name: '休息' }
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}) as Ref<ECOption>;
 | 
					 | 
				
			||||||
const { domRef: pieRef } = useEcharts(pieOptions);
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-space :vertical="true" :size="16">
 | 
					 | 
				
			||||||
    <top-chart />
 | 
					 | 
				
			||||||
    <data-card />
 | 
					 | 
				
			||||||
    <bottom-part />
 | 
					 | 
				
			||||||
  </n-space>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { BottomPart, DataCard, TopChart } from './components';
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,4 +0,0 @@
 | 
				
			|||||||
import WorkbenchHeader from './workbench-header/index.vue';
 | 
					 | 
				
			||||||
import WorkbenchMain from './workbench-main/index.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { WorkbenchHeader, WorkbenchMain };
 | 
					 | 
				
			||||||
@@ -1,50 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-card :bordered="false" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
    <div class="flex-y-center justify-between">
 | 
					 | 
				
			||||||
      <div class="flex-y-center">
 | 
					 | 
				
			||||||
        <icon-local-avatar class="text-70px" />
 | 
					 | 
				
			||||||
        <div class="pl-12px">
 | 
					 | 
				
			||||||
          <h3 class="text-18px font-semibold">早安,{{ auth.userInfo.userName }}, 今天又是充满活力的一天!</h3>
 | 
					 | 
				
			||||||
          <p class="leading-30px text-#999">今日多云转晴,20℃ - 25℃!</p>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <n-space :size="24" :wrap="false">
 | 
					 | 
				
			||||||
        <n-statistic v-for="item in statisticData" :key="item.id" class="whitespace-nowrap" v-bind="item"></n-statistic>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </n-card>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { useAuthStore } from '@/store';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'DashboardWorkbenchHeader' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const auth = useAuthStore();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface StatisticData {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  label: string;
 | 
					 | 
				
			||||||
  value: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const statisticData: StatisticData[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 0,
 | 
					 | 
				
			||||||
    label: '项目数',
 | 
					 | 
				
			||||||
    value: '25'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 1,
 | 
					 | 
				
			||||||
    label: '待办',
 | 
					 | 
				
			||||||
    value: '4/16'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 2,
 | 
					 | 
				
			||||||
    label: '消息',
 | 
					 | 
				
			||||||
    value: '12'
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,4 +0,0 @@
 | 
				
			|||||||
import TechnologyCard from './technology-card.vue';
 | 
					 | 
				
			||||||
import ShortcutsCard from './shortcuts-card.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export { TechnologyCard, ShortcutsCard };
 | 
					 | 
				
			||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div
 | 
					 | 
				
			||||||
    class="flex-col-center h-120px p-12px border-1px border-#efeff5 dark:border-#ffffff17 rounded-4px hover:shadow-sm cursor-pointer"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <svg-icon :icon="icon" :style="{ color: iconColor }" class="text-30px" />
 | 
					 | 
				
			||||||
    <p class="py-8px text-16px">{{ label }}</p>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
defineOptions({ name: 'DashboardWorkbenchMainShortcutsCard' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  /** 快捷操作名称 */
 | 
					 | 
				
			||||||
  label: string;
 | 
					 | 
				
			||||||
  /** 图标 */
 | 
					 | 
				
			||||||
  icon: string;
 | 
					 | 
				
			||||||
  /** 图标颜色 */
 | 
					 | 
				
			||||||
  iconColor: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineProps<Props>();
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div
 | 
					 | 
				
			||||||
    class="h-120px p-4px border-1px border-#efeff5 dark:border-#ffffff17 rounded-4px hover:shadow-sm cursor-pointer"
 | 
					 | 
				
			||||||
    @click="handleOpenSite"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <header class="flex-y-center">
 | 
					 | 
				
			||||||
      <svg-icon :icon="icon" :style="{ color: iconColor }" class="text-30px" />
 | 
					 | 
				
			||||||
      <h3 class="pl-12px text-18px font-semibold">{{ name }}</h3>
 | 
					 | 
				
			||||||
    </header>
 | 
					 | 
				
			||||||
    <p class="py-8px h-56px text-#999">{{ description }}</p>
 | 
					 | 
				
			||||||
    <div class="flex justify-end">
 | 
					 | 
				
			||||||
      <span>{{ author }}</span>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
defineOptions({ name: 'DashboardWorkbenchMainTechnologyCard' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Props {
 | 
					 | 
				
			||||||
  /** 技术名称 */
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  /** 技术描述 */
 | 
					 | 
				
			||||||
  description: string;
 | 
					 | 
				
			||||||
  /** 技术作者 */
 | 
					 | 
				
			||||||
  author: string;
 | 
					 | 
				
			||||||
  /** 技术官网 */
 | 
					 | 
				
			||||||
  site: string;
 | 
					 | 
				
			||||||
  /** 技术图标 */
 | 
					 | 
				
			||||||
  icon: string;
 | 
					 | 
				
			||||||
  /** 图标颜色 */
 | 
					 | 
				
			||||||
  iconColor?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const props = defineProps<Props>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleOpenSite() {
 | 
					 | 
				
			||||||
  window.open(props.site, '_blank');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,146 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-grid :item-responsive="true" :x-gap="16" :y-gap="16">
 | 
					 | 
				
			||||||
    <n-grid-item span="0:24 640:24 1024:16">
 | 
					 | 
				
			||||||
      <n-space :vertical="true" :size="16">
 | 
					 | 
				
			||||||
        <n-card title="项目主要技术栈" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
          <template #header-extra>
 | 
					 | 
				
			||||||
            <a class="text-primary" href="javascript:;">更多技术栈</a>
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
          <n-grid :item-responsive="true" responsive="screen" cols="m:2 l:3" :x-gap="8" :y-gap="8">
 | 
					 | 
				
			||||||
            <n-grid-item v-for="item in technology" :key="item.id">
 | 
					 | 
				
			||||||
              <technology-card v-bind="item" />
 | 
					 | 
				
			||||||
            </n-grid-item>
 | 
					 | 
				
			||||||
          </n-grid>
 | 
					 | 
				
			||||||
        </n-card>
 | 
					 | 
				
			||||||
        <n-card title="动态" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
          <template #header-extra>
 | 
					 | 
				
			||||||
            <a class="text-primary" href="javascript:;">更多动态</a>
 | 
					 | 
				
			||||||
          </template>
 | 
					 | 
				
			||||||
          <n-list>
 | 
					 | 
				
			||||||
            <n-list-item v-for="item in activity" :key="item.id">
 | 
					 | 
				
			||||||
              <template #prefix>
 | 
					 | 
				
			||||||
                <icon-local-avatar class="text-48px" />
 | 
					 | 
				
			||||||
              </template>
 | 
					 | 
				
			||||||
              <n-thing :title="item.content" :description="item.time" />
 | 
					 | 
				
			||||||
            </n-list-item>
 | 
					 | 
				
			||||||
          </n-list>
 | 
					 | 
				
			||||||
        </n-card>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </n-grid-item>
 | 
					 | 
				
			||||||
    <n-grid-item span="0:24 640:24 1024:8">
 | 
					 | 
				
			||||||
      <n-space :vertical="true" :size="16">
 | 
					 | 
				
			||||||
        <n-card title="快捷操作" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
          <n-grid :item-responsive="true" responsive="screen" cols="m:2 l:3" :x-gap="8" :y-gap="8">
 | 
					 | 
				
			||||||
            <n-grid-item v-for="item in shortcuts" :key="item.id">
 | 
					 | 
				
			||||||
              <shortcuts-card v-bind="item" />
 | 
					 | 
				
			||||||
            </n-grid-item>
 | 
					 | 
				
			||||||
          </n-grid>
 | 
					 | 
				
			||||||
        </n-card>
 | 
					 | 
				
			||||||
        <n-card title="创意" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
          <div class="flex-center h-380px">
 | 
					 | 
				
			||||||
            <icon-local-banner class="text-400px sm:text-320px text-primary" />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </n-card>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </n-grid-item>
 | 
					 | 
				
			||||||
  </n-grid>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { ShortcutsCard, TechnologyCard } from './components';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
defineOptions({ name: 'DashboardWorkbenchMain' });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Technology {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  description: string;
 | 
					 | 
				
			||||||
  author: string;
 | 
					 | 
				
			||||||
  site: string;
 | 
					 | 
				
			||||||
  icon: string;
 | 
					 | 
				
			||||||
  iconColor?: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const technology: Technology[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 0,
 | 
					 | 
				
			||||||
    name: 'Vue',
 | 
					 | 
				
			||||||
    description: '一套用于构建用户界面的渐进式框架',
 | 
					 | 
				
			||||||
    author: '尤雨溪 - Evan You',
 | 
					 | 
				
			||||||
    site: 'https://v3.cn.vuejs.org/',
 | 
					 | 
				
			||||||
    icon: 'logos:vue'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 1,
 | 
					 | 
				
			||||||
    name: 'TypeScript',
 | 
					 | 
				
			||||||
    description: 'JavaScript类型的超集,它可以编译成纯JavaScript',
 | 
					 | 
				
			||||||
    author: '微软 - Microsoft',
 | 
					 | 
				
			||||||
    site: 'https://www.typescriptlang.org/',
 | 
					 | 
				
			||||||
    icon: 'logos:typescript-icon'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 2,
 | 
					 | 
				
			||||||
    name: 'Vite',
 | 
					 | 
				
			||||||
    description: '下一代前端开发与构建工具',
 | 
					 | 
				
			||||||
    author: '尤雨溪 - Evan You',
 | 
					 | 
				
			||||||
    site: 'https://vitejs.cn/',
 | 
					 | 
				
			||||||
    icon: 'logos:vitejs'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 3,
 | 
					 | 
				
			||||||
    name: 'NaiveUI',
 | 
					 | 
				
			||||||
    description: '一个 Vue 3 组件库',
 | 
					 | 
				
			||||||
    author: '图森未来 - TuSimple',
 | 
					 | 
				
			||||||
    site: 'https://www.naiveui.com/zh-CN/os-theme',
 | 
					 | 
				
			||||||
    icon: 'logos:naiveui'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 4,
 | 
					 | 
				
			||||||
    name: 'UnoCSS',
 | 
					 | 
				
			||||||
    description: '下一代实用优先的CSS框架',
 | 
					 | 
				
			||||||
    author: 'Anthony Fu',
 | 
					 | 
				
			||||||
    site: 'https://uno.antfu.me/?s=',
 | 
					 | 
				
			||||||
    icon: 'logos:unocss'
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    id: 5,
 | 
					 | 
				
			||||||
    name: 'Pinia',
 | 
					 | 
				
			||||||
    description: 'vue状态管理框架,支持vue2、vue3',
 | 
					 | 
				
			||||||
    author: 'Posva',
 | 
					 | 
				
			||||||
    site: 'https://pinia.esm.dev/',
 | 
					 | 
				
			||||||
    icon: 'noto:pineapple'
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Activity {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  content: string;
 | 
					 | 
				
			||||||
  time: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const activity: Activity[] = [
 | 
					 | 
				
			||||||
  { id: 4, content: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!', time: '2021-11-07 22:45:32' },
 | 
					 | 
				
			||||||
  { id: 3, content: 'Soybean 正在忙于为soybean-admin写项目说明文档!', time: '2021-11-03 20:33:31' },
 | 
					 | 
				
			||||||
  { id: 2, content: 'Soybean 准备为soybean-admin 1.0的发布做充分的准备工作!', time: '2021-10-31 22:43:12' },
 | 
					 | 
				
			||||||
  { id: 1, content: '@yanbowe 向soybean-admin提交了一个bug,多标签栏不会自适应。', time: '2021-10-27 10:24:54' },
 | 
					 | 
				
			||||||
  { id: 0, content: 'Soybean 在2021年5月28日创建了开源项目soybean-admin!', time: '2021-05-28 22:22:22' }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Shortcuts {
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  label: string;
 | 
					 | 
				
			||||||
  icon: string;
 | 
					 | 
				
			||||||
  iconColor: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const shortcuts: Shortcuts[] = [
 | 
					 | 
				
			||||||
  { id: 0, label: '主控台', icon: 'mdi:desktop-mac-dashboard', iconColor: '#409eff' },
 | 
					 | 
				
			||||||
  { id: 1, label: '系统管理', icon: 'ic:outline-settings', iconColor: '#7238d1' },
 | 
					 | 
				
			||||||
  { id: 2, label: '权限管理', icon: 'mdi:family-tree', iconColor: '#f56c6c' },
 | 
					 | 
				
			||||||
  { id: 3, label: '组件', icon: 'fluent:app-store-24-filled', iconColor: '#19a2f1' },
 | 
					 | 
				
			||||||
  { id: 4, label: '表格', icon: 'mdi:table-large', iconColor: '#fab251' },
 | 
					 | 
				
			||||||
  { id: 5, label: '图表', icon: 'mdi:chart-areaspline', iconColor: '#8aca6b' }
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-space :vertical="true" :size="16">
 | 
					 | 
				
			||||||
    <workbench-header />
 | 
					 | 
				
			||||||
    <workbench-main />
 | 
					 | 
				
			||||||
  </n-space>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup>
 | 
					 | 
				
			||||||
import { WorkbenchHeader, WorkbenchMain } from './components';
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="h-full">
 | 
					 | 
				
			||||||
    <iframe class="wh-full" :src="src"></iframe>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { ref } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const src = ref('https://www.naiveui.com/zh-CN/os-theme/docs/introduction');
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div></div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts"></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="h-full">
 | 
					 | 
				
			||||||
    <iframe class="wh-full" :src="src"></iframe>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { ref } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const src = ref('https://admin-docs.soybeanjs.cn/');
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="h-full">
 | 
					 | 
				
			||||||
    <iframe class="wh-full" :src="src"></iframe>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { ref } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const src = ref('https://cn.vitejs.dev/');
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div class="h-full">
 | 
					 | 
				
			||||||
    <iframe class="wh-full" :src="src"></iframe>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { ref } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const src = ref('https://v3.cn.vuejs.org/');
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <exception-base type="403" />
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <exception-base type="404" />
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <exception-base type="500" />
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts" setup></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,27 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-space :vertical="true" :size="16">
 | 
					 | 
				
			||||||
    <n-card title="Tab Detail" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
      <n-space :vertical="true" :size="12">
 | 
					 | 
				
			||||||
        <div>当前路由的描述数据(meta):</div>
 | 
					 | 
				
			||||||
        <div>{{ route.meta }}</div>
 | 
					 | 
				
			||||||
        <n-button @click="handleToTab">返回Tab</n-button>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </n-card>
 | 
					 | 
				
			||||||
  </n-space>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { useRoute } from 'vue-router';
 | 
					 | 
				
			||||||
import { routeName } from '@/router';
 | 
					 | 
				
			||||||
import { useRouterPush } from '@/composables';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { routerPush } = useRouterPush();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const route = useRoute();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleToTab() {
 | 
					 | 
				
			||||||
  routerPush({ name: routeName('function_tab') });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,28 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-space :vertical="true" :size="16">
 | 
					 | 
				
			||||||
    <n-card title="Tab Detail" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
      <n-space :vertical="true" :size="12">
 | 
					 | 
				
			||||||
        <div>当前路由的描述数据(meta):</div>
 | 
					 | 
				
			||||||
        <div>{{ route.meta }}</div>
 | 
					 | 
				
			||||||
        <div>当前路由的查询数据(query):</div>
 | 
					 | 
				
			||||||
        <div>{{ route.query }}</div>
 | 
					 | 
				
			||||||
        <n-button @click="handleToTab">返回Tab</n-button>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </n-card>
 | 
					 | 
				
			||||||
  </n-space>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { useRoute } from 'vue-router';
 | 
					 | 
				
			||||||
import { routeName } from '@/router';
 | 
					 | 
				
			||||||
import { useRouterPush } from '@/composables';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const route = useRoute();
 | 
					 | 
				
			||||||
const { routerPush } = useRouterPush();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleToTab() {
 | 
					 | 
				
			||||||
  routerPush({ name: routeName('function_tab') });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,44 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <n-space :vertical="true" :size="16">
 | 
					 | 
				
			||||||
    <n-card title="Tab Home" :bordered="false" size="small" class="rounded-8px shadow-sm">
 | 
					 | 
				
			||||||
      <n-space :vertical="true" :size="12">
 | 
					 | 
				
			||||||
        <n-button @click="handleToTabDetail">跳转Tab Detail</n-button>
 | 
					 | 
				
			||||||
        <n-button @click="handleToTabMultiDetail(1)">跳转Tab Multi Detail 1</n-button>
 | 
					 | 
				
			||||||
        <n-button @click="handleToTabMultiDetail(2)">跳转Tab Multi Detail 2</n-button>
 | 
					 | 
				
			||||||
        <n-input-group>
 | 
					 | 
				
			||||||
          <n-input v-model:value="title" />
 | 
					 | 
				
			||||||
          <n-button type="primary" @click="handleSetTitle">设置当前Tab页标题</n-button>
 | 
					 | 
				
			||||||
        </n-input-group>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </n-card>
 | 
					 | 
				
			||||||
  </n-space>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts">
 | 
					 | 
				
			||||||
import { ref } from 'vue';
 | 
					 | 
				
			||||||
import { routeName } from '@/router';
 | 
					 | 
				
			||||||
import { useTabStore } from '@/store';
 | 
					 | 
				
			||||||
import { useRouterPush } from '@/composables';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const { routerPush } = useRouterPush();
 | 
					 | 
				
			||||||
const tabStore = useTabStore();
 | 
					 | 
				
			||||||
const title = ref('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleToTabDetail() {
 | 
					 | 
				
			||||||
  routerPush({ name: routeName('function_tab-detail'), query: { name: 'abc' }, hash: '#DEMO_HASH' });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleToTabMultiDetail(num: number) {
 | 
					 | 
				
			||||||
  routerPush({ name: routeName('function_tab-multi-detail'), query: { name: 'abc', num }, hash: '#DEMO_HASH' });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleSetTitle() {
 | 
					 | 
				
			||||||
  if (!title.value) {
 | 
					 | 
				
			||||||
    window.$message?.warning('请输入要设置的标题名称');
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    tabStore.setActiveTabTitle(title.value);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div></div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts"></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -10,40 +10,6 @@ export const views: Record<
 | 
				
			|||||||
  'constant-page': () => import('./_builtin/constant-page/index.vue'),
 | 
					  'constant-page': () => import('./_builtin/constant-page/index.vue'),
 | 
				
			||||||
  login: () => import('./_builtin/login/index.vue'),
 | 
					  login: () => import('./_builtin/login/index.vue'),
 | 
				
			||||||
  'not-found': () => import('./_builtin/not-found/index.vue'),
 | 
					  'not-found': () => import('./_builtin/not-found/index.vue'),
 | 
				
			||||||
  about: () => import('./about/index.vue'),
 | 
					 | 
				
			||||||
  'auth-demo_permission': () => import('./auth-demo/permission/index.vue'),
 | 
					 | 
				
			||||||
  'auth-demo_super': () => import('./auth-demo/super/index.vue'),
 | 
					 | 
				
			||||||
  component_button: () => import('./component/button/index.vue'),
 | 
					 | 
				
			||||||
  component_card: () => import('./component/card/index.vue'),
 | 
					 | 
				
			||||||
  component_table: () => import('./component/table/index.vue'),
 | 
					 | 
				
			||||||
  dashboard_analysis: () => import('./dashboard/analysis/index.vue'),
 | 
					 | 
				
			||||||
  dashboard_workbench: () => import('./dashboard/workbench/index.vue'),
 | 
					 | 
				
			||||||
  document_naive: () => import('./document/naive/index.vue'),
 | 
					 | 
				
			||||||
  'document_project-link': () => import('./document/project-link/index.vue'),
 | 
					 | 
				
			||||||
  document_project: () => import('./document/project/index.vue'),
 | 
					 | 
				
			||||||
  document_vite: () => import('./document/vite/index.vue'),
 | 
					 | 
				
			||||||
  document_vue: () => import('./document/vue/index.vue'),
 | 
					 | 
				
			||||||
  exception_403: () => import('./exception/403/index.vue'),
 | 
					 | 
				
			||||||
  exception_404: () => import('./exception/404/index.vue'),
 | 
					 | 
				
			||||||
  exception_500: () => import('./exception/500/index.vue'),
 | 
					 | 
				
			||||||
  'function_tab-detail': () => import('./function/tab-detail/index.vue'),
 | 
					 | 
				
			||||||
  'function_tab-multi-detail': () => import('./function/tab-multi-detail/index.vue'),
 | 
					 | 
				
			||||||
  function_tab: () => import('./function/tab/index.vue'),
 | 
					 | 
				
			||||||
  function_websocket: () => import('./function/websocket/index.vue'),
 | 
					 | 
				
			||||||
  management_auth: () => import('./management/auth/index.vue'),
 | 
					 | 
				
			||||||
  management_role: () => import('./management/role/index.vue'),
 | 
					 | 
				
			||||||
  management_route: () => import('./management/route/index.vue'),
 | 
					 | 
				
			||||||
  management_user: () => import('./management/user/index.vue'),
 | 
					 | 
				
			||||||
  'multi-menu_first_second-new_third': () => import('./multi-menu/first/second-new/third/index.vue'),
 | 
					  'multi-menu_first_second-new_third': () => import('./multi-menu/first/second-new/third/index.vue'),
 | 
				
			||||||
  'multi-menu_first_second': () => import('./multi-menu/first/second/index.vue'),
 | 
					  'multi-menu_first_second': () => import('./multi-menu/first/second/index.vue')
 | 
				
			||||||
  plugin_charts_antv: () => import('./plugin/charts/antv/index.vue'),
 | 
					 | 
				
			||||||
  plugin_charts_echarts: () => import('./plugin/charts/echarts/index.vue'),
 | 
					 | 
				
			||||||
  plugin_copy: () => import('./plugin/copy/index.vue'),
 | 
					 | 
				
			||||||
  plugin_editor_markdown: () => import('./plugin/editor/markdown/index.vue'),
 | 
					 | 
				
			||||||
  plugin_editor_quill: () => import('./plugin/editor/quill/index.vue'),
 | 
					 | 
				
			||||||
  plugin_icon: () => import('./plugin/icon/index.vue'),
 | 
					 | 
				
			||||||
  plugin_map: () => import('./plugin/map/index.vue'),
 | 
					 | 
				
			||||||
  plugin_print: () => import('./plugin/print/index.vue'),
 | 
					 | 
				
			||||||
  plugin_swiper: () => import('./plugin/swiper/index.vue'),
 | 
					 | 
				
			||||||
  plugin_video: () => import('./plugin/video/index.vue')
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div>权限管理</div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="tsx"></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
<template>
 | 
					 | 
				
			||||||
  <div>角色管理</div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script setup lang="ts"></script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped></style>
 | 
					 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user