style: 💄 eslint --fix

This commit is contained in:
xiangshu233 2024-02-17 13:52:03 +08:00
parent 147022076f
commit cb91140ab8
104 changed files with 2524 additions and 2388 deletions

@ -1,6 +1,6 @@
/** /**
* The name of the configuration file entered in the production environment * The name of the configuration file entered in the production environment
*/ */
export const GLOB_CONFIG_FILE_NAME = 'app.config.js'; export const GLOB_CONFIG_FILE_NAME = 'app.config.js'
export const OUTPUT_DIR = 'dist/vant-mobile'; export const OUTPUT_DIR = 'dist/vant-mobile'

@ -2,8 +2,8 @@
* Get the configuration file variable name * Get the configuration file variable name
* @param env * @param env
*/ */
export const getConfigFileName = (env: Record<string, any>) => { export function getConfigFileName(env: Record<string, any>) {
return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__` return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
.toUpperCase() .toUpperCase()
.replace(/\s/g, ''); .replace(/\s/g, '')
}; }

@ -1,24 +1,24 @@
/** /**
* Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging * Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging
*/ */
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant'; import fs from 'fs-extra'
import fs from 'fs-extra'; import colors from 'picocolors'
import colors from 'picocolors'; import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant'
import { getRootPath, getEnvConfig } from '../utils'; import { getEnvConfig, getRootPath } from '../utils'
import { getConfigFileName } from '../getConfigFileName'; import { getConfigFileName } from '../getConfigFileName'
import pkg from '../../package.json'; import pkg from '../../package.json'
function createConfig( function createConfig(
{ {
configName, configName,
config, config,
configFileName = GLOB_CONFIG_FILE_NAME, configFileName = GLOB_CONFIG_FILE_NAME,
}: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} } }: { configName: string, config: any, configFileName?: string } = { configName: '', config: {} },
) { ) {
try { try {
const windowConf = `window.${configName}`; const windowConf = `window.${configName}`
// Ensure that the variable will not be modified // Ensure that the variable will not be modified
const configStr = `${windowConf}=${JSON.stringify(config)}; const configStr = `${windowConf}=${JSON.stringify(config)};
Object.freeze(${windowConf}); Object.freeze(${windowConf});
@ -26,19 +26,20 @@ function createConfig(
configurable: false, configurable: false,
writable: false, writable: false,
}); });
`.replace(/\s/g, ''); `.replace(/\s/g, '')
fs.mkdirp(getRootPath(OUTPUT_DIR)); fs.mkdirp(getRootPath(OUTPUT_DIR))
fs.writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr); fs.writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr)
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`); console.log(`${colors.cyan(`✨ [${pkg.name}]`)} - configuration file is build successfully:`)
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n'); console.log(`${colors.gray(`${OUTPUT_DIR}/${colors.green(configFileName)}`)}\n`)
} catch (error) { }
console.log(colors.red('configuration file configuration file failed to package:\n' + error)); catch (error) {
console.log(colors.red(`configuration file configuration file failed to package:\n${error}`))
} }
} }
export function runBuildConfig() { export function runBuildConfig() {
const config = getEnvConfig(); const config = getEnvConfig()
const configFileName = getConfigFileName(config); const configFileName = getConfigFileName(config)
createConfig({ config, configName: configFileName }); createConfig({ config, configName: configFileName })
} }

@ -1,23 +1,24 @@
// #!/usr/bin/env node // #!/usr/bin/env node
import { runBuildConfig } from './buildConf'; import colors from 'picocolors'
import colors from 'picocolors';
import pkg from '../../package.json'; import pkg from '../../package.json'
import { runBuildConfig } from './buildConf'
export const runBuild = async () => { export async function runBuild() {
try { try {
const argvList = process.argv.splice(2); const argvList = process.argv.splice(2)
// Generate configuration file // Generate configuration file
if (!argvList.includes('disabled-config')) { if (!argvList.includes('disabled-config')) {
await runBuildConfig(); await runBuildConfig()
} }
console.log(`${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!'); console.log(`${colors.cyan(`[${pkg.name}]`)} - build successfully!`)
} catch (error) {
console.log(colors.red('vite build error:\n' + error));
process.exit(1);
} }
}; catch (error) {
runBuild(); console.log(colors.red(`vite build error:\n${error}`))
process.exit(1)
}
}
runBuild()

@ -1,44 +1,45 @@
import fs from 'fs'; import fs from 'node:fs'
import path from 'path'; import path from 'node:path'
import dotenv from 'dotenv'; import dotenv from 'dotenv'
export function isDevFn(mode: string): boolean { export function isDevFn(mode: string): boolean {
return mode === 'development'; return mode === 'development'
} }
export function isProdFn(mode: string): boolean { export function isProdFn(mode: string): boolean {
return mode === 'production'; return mode === 'production'
} }
/** /**
* Whether to generate package preview * Whether to generate package preview
*/ */
export function isReportMode(): boolean { export function isReportMode(): boolean {
return process.env.REPORT === 'true'; return process.env.REPORT === 'true'
} }
// Read all environment variable configuration files to process.env // Read all environment variable configuration files to process.env
// 读取并处理所有环境变量配置文件 .env // 读取并处理所有环境变量配置文件 .env
export function wrapperEnv(envConf: Recordable): ViteEnv { export function wrapperEnv(envConf: Recordable): ViteEnv {
const ret: any = {}; const ret: any = {}
for (const envName of Object.keys(envConf)) { for (const envName of Object.keys(envConf)) {
// 去除空格 // 去除空格
let realName = envConf[envName].replace(/\\n/g, '\n'); let realName = envConf[envName].replace(/\\n/g, '\n')
realName = realName === 'true' ? true : realName === 'false' ? false : realName; realName = realName === 'true' ? true : realName === 'false' ? false : realName
if (envName === 'VITE_PORT') { if (envName === 'VITE_PORT') {
realName = Number(realName); realName = Number(realName)
} }
if (envName === 'VITE_PROXY') { if (envName === 'VITE_PROXY') {
try { try {
realName = JSON.parse(realName); realName = JSON.parse(realName)
} catch (error) {} }
catch (error) {}
} }
ret[envName] = realName; ret[envName] = realName
process.env[envName] = realName; process.env[envName] = realName
} }
return ret; return ret
} }
/** /**
@ -47,21 +48,22 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
* @param confFiles ext * @param confFiles ext
*/ */
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.production']) { export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.production']) {
let envConfig = {}; let envConfig = {}
confFiles.forEach((item) => { confFiles.forEach((item) => {
try { try {
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item))); const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)))
envConfig = { ...envConfig, ...env }; envConfig = { ...envConfig, ...env }
} catch (error) {} }
}); catch (error) {}
})
Object.keys(envConfig).forEach((key) => { Object.keys(envConfig).forEach((key) => {
const reg = new RegExp(`^(${match})`); const reg = new RegExp(`^(${match})`)
if (!reg.test(key)) { if (!reg.test(key)) {
Reflect.deleteProperty(envConfig, key); Reflect.deleteProperty(envConfig, key)
} }
}); })
return envConfig; return envConfig
} }
/** /**
@ -69,5 +71,5 @@ export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.pr
* @param dir file path * @param dir file path
*/ */
export function getRootPath(...dir: string[]) { export function getRootPath(...dir: string[]) {
return path.resolve(process.cwd(), ...dir); return path.resolve(process.cwd(), ...dir)
} }

@ -2,25 +2,25 @@
* Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
* https://github.com/anncwb/vite-plugin-compression * https://github.com/anncwb/vite-plugin-compression
*/ */
import type { PluginOption } from 'vite'; import type { PluginOption } from 'vite'
import compressPlugin from 'vite-plugin-compression'; import compressPlugin from 'vite-plugin-compression'
export function configCompressPlugin( export function configCompressPlugin(
compress: 'gzip' | 'brotli' | 'none', compress: 'gzip' | 'brotli' | 'none',
deleteOriginFile = false deleteOriginFile = false,
): PluginOption | PluginOption[] { ): PluginOption | PluginOption[] {
const compressList = compress.split(','); const compressList = compress.split(',')
const plugins: PluginOption[] = []; const plugins: PluginOption[] = []
if (compressList.includes('gzip')) { if (compressList.includes('gzip')) {
plugins.push( plugins.push(
compressPlugin({ compressPlugin({
ext: '.gz', ext: '.gz',
deleteOriginFile, deleteOriginFile,
}) }),
); )
} }
if (compressList.includes('brotli')) { if (compressList.includes('brotli')) {
plugins.push( plugins.push(
@ -28,8 +28,8 @@ export function configCompressPlugin(
ext: '.br', ext: '.br',
algorithm: 'brotliCompress', algorithm: 'brotliCompress',
deleteOriginFile, deleteOriginFile,
}) }),
); )
} }
return plugins; return plugins
} }

@ -2,19 +2,19 @@
* Plugin to minimize and use ejs template syntax in index.html. * Plugin to minimize and use ejs template syntax in index.html.
* https://github.com/anncwb/vite-plugin-html * https://github.com/anncwb/vite-plugin-html
*/ */
import type { PluginOption } from 'vite'; import type { PluginOption } from 'vite'
import { createHtmlPlugin } from 'vite-plugin-html'; import { createHtmlPlugin } from 'vite-plugin-html'
import pkg from '../../../package.json'; import pkg from '../../../package.json'
import { GLOB_CONFIG_FILE_NAME } from '../../constant'; import { GLOB_CONFIG_FILE_NAME } from '../../constant'
export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env; const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env
const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`; const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`
const getAppConfigSrc = () => { const getAppConfigSrc = () => {
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`; return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`
}; }
// 当执行 yarn build 构建项目之后,会自动生成 _app.config.js 文件并插入 index.html // 当执行 yarn build 构建项目之后,会自动生成 _app.config.js 文件并插入 index.html
// _app.config.js 用于项目在打包后,需要动态修改配置的需求,如接口地址 // _app.config.js 用于项目在打包后,需要动态修改配置的需求,如接口地址
@ -41,6 +41,6 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
] ]
: [], : [],
}, },
}); })
return htmlPlugin; return htmlPlugin
} }

@ -1,14 +1,14 @@
import type { PluginOption } from 'vite'; import type { PluginOption } from 'vite'
import Components from 'unplugin-vue-components/vite'; import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'; import { VantResolver } from 'unplugin-vue-components/resolvers'
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'; import UnoCSS from 'unocss/vite'
import { configHtmlPlugin } from './html'; import { configHtmlPlugin } from './html'
import { configMockPlugin } from './mock'; import { configMockPlugin } from './mock'
import { configCompressPlugin } from './compress'; import { configCompressPlugin } from './compress'
import { configVisualizerConfig } from './visualizer'; import { configVisualizerConfig } from './visualizer'
import { configSvgIconsPlugin } from './svgSprite'; import { configSvgIconsPlugin } from './svgSprite'
/** /**
* vite * vite
@ -22,7 +22,7 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock:
// 如果你需要多种形式,你可以用','来分隔 // 如果你需要多种形式,你可以用','来分隔
// VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE 打包使用压缩时是否删除原始文件,默认为 false // VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE 打包使用压缩时是否删除原始文件,默认为 false
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv; const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv
const vitePlugins: (PluginOption | PluginOption[])[] = [ const vitePlugins: (PluginOption | PluginOption[])[] = [
// have to // have to
@ -33,30 +33,30 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock:
resolvers: [VantResolver()], resolvers: [VantResolver()],
types: [], types: [],
}), }),
]; ]
// UnoCSS // UnoCSS
vitePlugins.push(UnoCSS()); vitePlugins.push(UnoCSS())
// 加载 html 插件 vite-plugin-html // 加载 html 插件 vite-plugin-html
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild)); vitePlugins.push(configHtmlPlugin(viteEnv, isBuild))
// rollup-plugin-visualizer // rollup-plugin-visualizer
vitePlugins.push(configVisualizerConfig()); vitePlugins.push(configVisualizerConfig())
// vite-plugin-mock // vite-plugin-mock
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock)); VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock))
// vite-plugin-svg-icons // vite-plugin-svg-icons
vitePlugins.push(configSvgIconsPlugin(isBuild)); vitePlugins.push(configSvgIconsPlugin(isBuild))
if (isBuild) { if (isBuild) {
// rollup-plugin-gzip // rollup-plugin-gzip
// 加载 gzip 打包 // 加载 gzip 打包
vitePlugins.push( vitePlugins.push(
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE) configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE),
); )
} }
return vitePlugins; return vitePlugins
} }

@ -2,7 +2,7 @@
* Mock plugin for development and production. * Mock plugin for development and production.
* https://github.com/anncwb/vite-plugin-mock * https://github.com/anncwb/vite-plugin-mock
*/ */
import { viteMockServe } from 'vite-plugin-mock'; import { viteMockServe } from 'vite-plugin-mock'
export function configMockPlugin(isBuild: boolean, prodMock: boolean) { export function configMockPlugin(isBuild: boolean, prodMock: boolean) {
return viteMockServe({ return viteMockServe({
@ -15,5 +15,5 @@ export function configMockPlugin(isBuild: boolean, prodMock: boolean) {
setupProdMockServer(); setupProdMockServer();
`, `,
}); })
} }

@ -3,8 +3,8 @@
* https://github.com/anncwb/vite-plugin-svg-icons * https://github.com/anncwb/vite-plugin-svg-icons
*/ */
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; import path from 'node:path'
import path from 'path'; import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
export function configSvgIconsPlugin(isBuild: boolean) { export function configSvgIconsPlugin(isBuild: boolean) {
// 指定需要缓存的图标文件夹 // 指定需要缓存的图标文件夹
@ -14,6 +14,6 @@ export function configSvgIconsPlugin(isBuild: boolean) {
svgoOptions: isBuild, svgoOptions: isBuild,
// 指定symbolId格式 // 指定symbolId格式
symbolId: 'icon-[dir]-[name]', symbolId: 'icon-[dir]-[name]',
}); })
return svgIconsPlugin; return svgIconsPlugin
} }

@ -1,9 +1,9 @@
/** /**
* Package file volume analysis * Package file volume analysis
*/ */
import visualizer from 'rollup-plugin-visualizer'; import visualizer from 'rollup-plugin-visualizer'
import type { PluginOption } from 'vite'; import type { PluginOption } from 'vite'
import { isReportMode } from '../../utils'; import { isReportMode } from '../../utils'
export function configVisualizerConfig() { export function configVisualizerConfig() {
if (isReportMode()) { if (isReportMode()) {
@ -12,7 +12,7 @@ export function configVisualizerConfig() {
open: true, open: true,
gzipSize: true, gzipSize: true,
brotliSize: true, brotliSize: true,
}) as PluginOption; }) as PluginOption
} }
return []; return []
} }

@ -1,38 +1,38 @@
/** /**
* Used to parse the .env.development proxy configuration * Used to parse the .env.development proxy configuration
*/ */
import type { ProxyOptions } from 'vite'; import type { ProxyOptions } from 'vite'
type ProxyItem = [string, string]; type ProxyItem = [string, string]
type ProxyList = ProxyItem[]; type ProxyList = ProxyItem[]
type ProxyTargetList = Record<string, ProxyOptions & { rewrite: (path: string) => string }>; type ProxyTargetList = Record<string, ProxyOptions & { rewrite: (path: string) => string }>
const httpsRE = /^https:\/\//; const httpsRE = /^https:\/\//
/** /**
* Generate proxy * Generate proxy
* @param list * @param list
*/ */
export function createProxy(list: ProxyList = []) { export function createProxy(list: ProxyList = []) {
const ret: ProxyTargetList = {}; const ret: ProxyTargetList = {}
for (const [prefix, target] of list) { for (const [prefix, target] of list) {
const isHttps = httpsRE.test(target); const isHttps = httpsRE.test(target)
// https://github.com/http-party/node-http-proxy#options // https://github.com/http-party/node-http-proxy#options
ret[prefix] = { ret[prefix] = {
target: target, target,
changeOrigin: true, changeOrigin: true,
ws: true, ws: true,
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''), rewrite: path => path.replace(new RegExp(`^${prefix}`), ''),
// https is require secure=false // https is require secure=false
// 如果您secure="true"只允许来自 HTTPS 的请求则secure="false"意味着允许来自 HTTP 和 HTTPS 的请求。 // 如果您secure="true"只允许来自 HTTPS 的请求则secure="false"意味着允许来自 HTTP 和 HTTPS 的请求。
...(isHttps ? { secure: false } : {}), ...(isHttps ? { secure: false } : {}),
}; }
} }
return ret; return ret
// ret // ret
// { // {

@ -1,26 +1,26 @@
// commitlint.config.js // commitlint.config.js
const fs = require('fs'); const fs = require('node:fs')
const path = require('path'); const path = require('node:path')
const { execSync } = require('child_process'); const { execSync } = require('node:child_process')
const scopes = fs const scopes = fs
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true }) .readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
.filter((dirent) => dirent.isDirectory()) .filter(dirent => dirent.isDirectory())
.map((dirent) => dirent.name.replace(/s$/, '')); .map(dirent => dirent.name.replace(/s$/, ''))
// precomputed scope // precomputed scope
const scopeComplete = execSync('git status --porcelain || true') const scopeComplete = execSync('git status --porcelain || true')
.toString() .toString()
.trim() .trim()
.split('\n') .split('\n')
.find((r) => ~r.indexOf('M src')) .find(r => ~r.indexOf('M src'))
?.replace(/(\/)/g, '%%') ?.replace(/(\/)/g, '%%')
?.match(/src%%((\w|-)*)/)?.[1] ?.match(/src%%((\w|-)*)/)?.[1]
?.replace(/s$/, ''); ?.replace(/s$/, '')
/** @type {import('cz-git').UserConfig} */ /** @type {import('cz-git').UserConfig} */
module.exports = { module.exports = {
ignores: [(commit) => commit.includes('init')], ignores: [commit => commit.includes('init')],
extends: ['@commitlint/config-conventional'], extends: ['@commitlint/config-conventional'],
rules: { rules: {
'body-leading-blank': [2, 'always'], 'body-leading-blank': [2, 'always'],
@ -144,12 +144,12 @@ module.exports = {
emptyIssuePrefixsAlias: 'skip', emptyIssuePrefixsAlias: 'skip',
customIssuePrefixsAlias: 'custom', customIssuePrefixsAlias: 'custom',
confirmColorize: true, confirmColorize: true,
maxHeaderLength: Infinity, maxHeaderLength: Number.POSITIVE_INFINITY,
maxSubjectLength: Infinity, maxSubjectLength: Number.POSITIVE_INFINITY,
minSubjectLength: 0, minSubjectLength: 0,
scopeOverrides: undefined, scopeOverrides: undefined,
defaultBody: '', defaultBody: '',
defaultIssues: '', defaultIssues: '',
defaultSubject: '', defaultSubject: '',
}, },
}; }

@ -1,27 +1,26 @@
<!DOCTYPE html> <!doctype html>
<html lang="zh-cmn-Hans" id="htmlRoot" class> <html lang="zh-cmn-Hans" id="htmlRoot" class>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" /> <link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title> <title><%= title %></title>
<%= title %>
</title>
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<script> <script>
(() => { ;(() => {
let htmlRoot = document.getElementById('htmlRoot'); let htmlRoot = document.getElementById('htmlRoot')
const appDesignSetting = window.localStorage.getItem('DESIGN-SETTING') const appDesignSetting = window.localStorage.getItem('DESIGN-SETTING')
let darkMode = appDesignSetting && JSON.parse(appDesignSetting).darkMode let darkMode =
appDesignSetting && JSON.parse(appDesignSetting).darkMode
if (htmlRoot && darkMode) { if (htmlRoot && darkMode) {
htmlRoot.setAttribute('data-theme', darkMode); htmlRoot.setAttribute('data-theme', darkMode)
darkMode = htmlRoot = null; darkMode = htmlRoot = null
} else { } else {
htmlRoot.setAttribute('data-theme', 'light'); htmlRoot.setAttribute('data-theme', 'light')
} }
})(); })()
</script> </script>
<style> <style>
body { body {
@ -38,14 +37,14 @@
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }
.first-loading-wrap>h1 { .first-loading-wrap > h1 {
font-size: 128px font-size: 128px;
} }
.first-loading-wrap .loading-wrap { .first-loading-wrap .loading-wrap {
padding: 98px; padding: 98px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center align-items: center;
} }
.dot { .dot {
animation: antRotate 1.2s infinite linear; animation: antRotate 1.2s infinite linear;
@ -55,7 +54,7 @@
font-size: 52px; font-size: 52px;
width: 52px; width: 52px;
height: 52px; height: 52px;
box-sizing: border-box box-sizing: border-box;
} }
.dot i { .dot i {
width: 24px; width: 24px;
@ -64,53 +63,53 @@
display: block; display: block;
background-color: #1890ff; background-color: #1890ff;
border-radius: 100%; border-radius: 100%;
transform: scale(.75); transform: scale(0.75);
transform-origin: 50% 50%; transform-origin: 50% 50%;
opacity: .3; opacity: 0.3;
animation: antSpinMove 1s infinite linear alternate animation: antSpinMove 1s infinite linear alternate;
} }
.dot i:nth-child(1) { .dot i:nth-child(1) {
top: 0; top: 0;
left: 0 left: 0;
} }
.dot i:nth-child(2) { .dot i:nth-child(2) {
top: 0; top: 0;
right: 0; right: 0;
-webkit-animation-delay: .4s; -webkit-animation-delay: 0.4s;
animation-delay: .4s animation-delay: 0.4s;
} }
.dot i:nth-child(3) { .dot i:nth-child(3) {
right: 0; right: 0;
bottom: 0; bottom: 0;
-webkit-animation-delay: .8s; -webkit-animation-delay: 0.8s;
animation-delay: .8s animation-delay: 0.8s;
} }
.dot i:nth-child(4) { .dot i:nth-child(4) {
bottom: 0; bottom: 0;
left: 0; left: 0;
-webkit-animation-delay: 1.2s; -webkit-animation-delay: 1.2s;
animation-delay: 1.2s animation-delay: 1.2s;
} }
@keyframes antRotate { @keyframes antRotate {
to { to {
-webkit-transform: rotate(405deg); -webkit-transform: rotate(405deg);
transform: rotate(405deg) transform: rotate(405deg);
} }
} }
@-webkit-keyframes antRotate { @-webkit-keyframes antRotate {
to { to {
-webkit-transform: rotate(405deg); -webkit-transform: rotate(405deg);
transform: rotate(405deg) transform: rotate(405deg);
} }
} }
@keyframes antSpinMove { @keyframes antSpinMove {
to { to {
opacity: 1 opacity: 1;
} }
} }
@-webkit-keyframes antSpinMove { @-webkit-keyframes antSpinMove {
to { to {
opacity: 1 opacity: 1;
} }
} }
</style> </style>

@ -1,18 +1,18 @@
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
const modules = import.meta.glob('./**/*.ts', { eager: true }) as any; const modules = import.meta.glob('./**/*.ts', { eager: true }) as any
const mockModules: any[] = []; const mockModules: any[] = []
Object.keys(modules).forEach((key) => { Object.keys(modules).forEach((key) => {
if (key.includes('/_')) { if (key.includes('/_')) {
return; return
} }
mockModules.push(...modules[key].default); mockModules.push(...modules[key].default)
}); })
/** /**
* Used in a production environment. Need to manually import all modules * Used in a production environment. Need to manually import all modules
*/ */
export function setupProdMockServer() { export function setupProdMockServer() {
createProdMockServer(mockModules); createProdMockServer(mockModules)
} }

@ -1,5 +1,5 @@
import Mock from 'mockjs'; import Mock from 'mockjs'
import { ResultEnum } from '@/enums/httpEnum'; import { ResultEnum } from '@/enums/httpEnum'
export function resultSuccess<T = Recordable>(result: T, { message = 'ok' } = {}) { export function resultSuccess<T = Recordable>(result: T, { message = 'ok' } = {}) {
return Mock.mock({ return Mock.mock({
@ -7,16 +7,16 @@ export function resultSuccess<T = Recordable>(result: T, { message = 'ok' } = {}
result, result,
message, message,
type: 'success', type: 'success',
}); })
} }
export function resultPageSuccess<T = any>( export function resultPageSuccess<T = any>(
page: number, page: number,
pageSize: number, pageSize: number,
list: T[], list: T[],
{ message = 'ok' } = {} { message = 'ok' } = {},
) { ) {
const pageData = pagination(page, pageSize, list); const pageData = pagination(page, pageSize, list)
return { return {
...resultSuccess({ ...resultSuccess({
@ -26,46 +26,46 @@ export function resultPageSuccess<T = any>(
list: pageData, list: pageData,
}), }),
message, message,
}; }
} }
export function resultError( export function resultError(
message = 'Request failed', message = 'Request failed',
{ code = ResultEnum.ERROR, result = null } = {} { code = ResultEnum.ERROR, result = null } = {},
) { ) {
return { return {
code, code,
result, result,
message, message,
type: 'error', type: 'error',
}; }
} }
export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]): T[] { export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]): T[] {
const offset = (pageNo - 1) * Number(pageSize); const offset = (pageNo - 1) * Number(pageSize)
const ret = const ret
offset + Number(pageSize) >= array.length = offset + Number(pageSize) >= array.length
? array.slice(offset, array.length) ? array.slice(offset, array.length)
: array.slice(offset, offset + Number(pageSize)); : array.slice(offset, offset + Number(pageSize))
return ret; return ret
} }
/** /**
* @param {Number} times * @param {number} times
* @param {Function} callback * @param {Function} callback
*/ */
export function doCustomTimes(times: number, callback: any) { export function doCustomTimes(times: number, callback: any) {
let i = -1; let i = -1
while (++i < times) { while (++i < times) {
callback(i); callback(i)
} }
} }
export interface requestParams { export interface requestParams {
method: string; method: string
body: any; body: any
headers?: { authorization?: string }; headers?: { authorization?: string }
query: any; query: any
} }
/** /**
@ -73,5 +73,5 @@ export interface requestParams {
* *
*/ */
export function getRequestToken({ headers }: requestParams): string | undefined { export function getRequestToken({ headers }: requestParams): string | undefined {
return headers?.authorization; return headers?.authorization
} }

@ -1,6 +1,7 @@
import { MockMethod } from 'vite-plugin-mock'; import type { MockMethod } from 'vite-plugin-mock'
import { getRequestToken, requestParams, resultError, resultSuccess } from '../_util'; import type { requestParams } from '../_util'
import { ResultEnum } from '@/enums/httpEnum'; import { getRequestToken, resultError, resultSuccess } from '../_util'
import { ResultEnum } from '@/enums/httpEnum'
const fakeUserList = [ const fakeUserList = [
{ {
@ -32,7 +33,7 @@ const fakeUserList = [
phone: '18822137893', phone: '18822137893',
token: 'fakeToken2', token: 'fakeToken2',
}, },
]; ]
export default [ export default [
{ {
@ -40,21 +41,21 @@ export default [
timeout: 1000, timeout: 1000,
method: 'post', method: 'post',
response: ({ body }) => { response: ({ body }) => {
const { username, password } = body; const { username, password } = body
const checkUser = fakeUserList.find( const checkUser = fakeUserList.find(
(item) => item.username === username && password === item.password item => item.username === username && password === item.password,
); )
if (!checkUser) { if (!checkUser) {
return resultError('帐号或密码不正确'); return resultError('帐号或密码不正确')
} }
const { userId, username: _username, token, realname, sign } = checkUser; const { userId, username: _username, token, realname, sign } = checkUser
return resultSuccess({ return resultSuccess({
userId, userId,
username: _username, username: _username,
token, token,
realname, realname,
sign, sign,
}); })
}, },
}, },
{ {
@ -62,15 +63,17 @@ export default [
timeout: 1000, timeout: 1000,
method: 'get', method: 'get',
response: (request: requestParams) => { response: (request: requestParams) => {
const token = getRequestToken(request); const token = getRequestToken(request)
if (!token) return resultError('无效令牌'); if (!token) {
const checkUser = fakeUserList.find((item) => item.token === token); return resultError('无效令牌')
}
const checkUser = fakeUserList.find(item => item.token === token)
if (!checkUser) { if (!checkUser) {
return resultError('没有获取到对应的用户信息', { return resultError('没有获取到对应的用户信息', {
code: ResultEnum.TOKEN_EXPIRED, code: ResultEnum.TOKEN_EXPIRED,
}); })
} }
return resultSuccess(checkUser); return resultSuccess(checkUser)
}, },
}, },
{ {
@ -78,13 +81,15 @@ export default [
timeout: 1000, timeout: 1000,
method: 'post', method: 'post',
response: (request: requestParams) => { response: (request: requestParams) => {
const token = getRequestToken(request); const token = getRequestToken(request)
if (!token) return resultError('无效令牌'); if (!token) {
const checkUser = fakeUserList.find((item) => item.token === token); return resultError('无效令牌')
if (!checkUser) {
return resultError('无效令牌');
} }
return resultSuccess(undefined, { message: '令牌已被销毁' }); const checkUser = fakeUserList.find(item => item.token === token)
if (!checkUser) {
return resultError('无效令牌')
}
return resultSuccess(undefined, { message: '令牌已被销毁' })
}, },
}, },
] as MockMethod[]; ] as MockMethod[]

@ -15,8 +15,8 @@
// 所有*.js文件现在都解释为 ESM并且需要使用 ESM 语法。您可以使用扩展名重命名文件.cjs来继续使用 CJS。 // 所有*.js文件现在都解释为 ESM并且需要使用 ESM 语法。您可以使用扩展名重命名文件.cjs来继续使用 CJS。
// require 是cjs 语法 // require 是cjs 语法
const autoprefixer = require('autoprefixer'); import autoprefixer from 'autoprefixer'
const viewport = require('postcss-mobile-forever'); import viewport from 'postcss-mobile-forever'
const baseViewportOpts = { const baseViewportOpts = {
appSelector: '#app', // 根元素选择器,用于设置桌面端和横屏时的居中样式 appSelector: '#app', // 根元素选择器,用于设置桌面端和横屏时的居中样式
@ -37,16 +37,16 @@ const baseViewportOpts = {
// exclude: [/node_modules/], // 忽略某些文件夹下的文件或特定文件 // exclude: [/node_modules/], // 忽略某些文件夹下的文件或特定文件
// include: [/src/], // 如果设置了include那将只有匹配到的文件才会被转换 // include: [/src/], // 如果设置了include那将只有匹配到的文件才会被转换
mobileUnit: 'vw', // 指定需要转换成的视口单位,建议使用 vw mobileUnit: 'vw', // 指定需要转换成的视口单位,建议使用 vw
rootContainingBlockSelectorList: ["van-popup--bottom"], // 指定包含块是根包含块的选择器,这种选择器的定位通常是 `fixed`,但是选择器内没有 `position: fixed` rootContainingBlockSelectorList: ['van-popup--bottom'], // 指定包含块是根包含块的选择器,这种选择器的定位通常是 `fixed`,但是选择器内没有 `position: fixed`
}; }
module.exports = { export default {
plugins: [ plugins: [
autoprefixer(), autoprefixer(),
viewport({ viewport({
...baseViewportOpts, ...baseViewportOpts,
// 只将 vant 转为 350 设计稿的 viewport其它样式的视图宽度为 750 // 只将 vant 转为 350 设计稿的 viewport其它样式的视图宽度为 750
viewportWidth: (file) => (file.includes('node_modules/vant/') ? 375 : 750), viewportWidth: file => (file.includes('node_modules/vant/') ? 375 : 750),
}), }),
], ],
}; }

@ -1,7 +1,7 @@
<template> <template>
<vanConfigProvider :theme="getDarkMode" :theme-vars="getThemeVars()"> <vanConfigProvider :theme="getDarkMode" :theme-vars="getThemeVars()">
<routerView v-slot="{ Component }"> <routerView v-slot="{ Component }">
<div class="absolute top-0 bottom-0 w-full overflow-hidden"> <div class="absolute bottom-0 top-0 w-full overflow-hidden">
<transition :name="getTransitionName" mode="out-in" appear> <transition :name="getTransitionName" mode="out-in" appear>
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents"> <keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
<component :is="Component" /> <component :is="Component" />
@ -13,64 +13,64 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, unref } from 'vue'; import { computed, unref } from 'vue'
import { darken, lighten } from '@/utils/index'; import { darken, lighten } from '@/utils/index'
import { useRouteStore } from '@/store/modules/route'; import { useRouteStore } from '@/store/modules/route'
import { useDesignSetting } from '@/hooks/setting/useDesignSetting'; import { useDesignSetting } from '@/hooks/setting/useDesignSetting'
const routeStore = useRouteStore(); const routeStore = useRouteStore()
const { getDarkMode, getAppTheme, getIsPageAnimate, getPageAnimateType } = useDesignSetting(); const { getDarkMode, getAppTheme, getIsPageAnimate, getPageAnimateType } = useDesignSetting()
// //
const keepAliveComponents = computed(() => routeStore.keepAliveComponents); const keepAliveComponents = computed(() => routeStore.keepAliveComponents)
const getThemeVars = () => { function getThemeVars() {
const appTheme = unref(getAppTheme); const appTheme = unref(getAppTheme)
const darkenStr = darken(appTheme, 25); const darkenStr = darken(appTheme, 25)
const lightenStr = lighten(appTheme, 10); const lightenStr = lighten(appTheme, 10)
return { return {
actionSheetCancelTextColor: appTheme, actionSheetCancelTextColor: appTheme,
buttonPrimaryBackground: appTheme, buttonPrimaryBackground: appTheme,
buttonPrimaryBorderColor: appTheme, buttonPrimaryBorderColor: appTheme,
radioCheckedIconColor: appTheme, radioCheckedIconColor: appTheme,
sliderActiveBackground: appTheme, sliderActiveBackground: appTheme,
cascaderActiveColor: appTheme, cascaderActiveColor: appTheme,
checkboxCheckedIconColor: appTheme, checkboxCheckedIconColor: appTheme,
numberKeyboardButtonBackground: appTheme, numberKeyboardButtonBackground: appTheme,
pickerLoadingIconColor: appTheme, pickerLoadingIconColor: appTheme,
calendarRangeEdgeBackground: appTheme, calendarRangeEdgeBackground: appTheme,
calendarRangeMiddleColor: appTheme, calendarRangeMiddleColor: appTheme,
calendarSelectedDayBackground: appTheme, calendarSelectedDayBackground: appTheme,
stepperButtonRoundThemeColor: appTheme, stepperButtonRoundThemeColor: appTheme,
switchOnBackground: appTheme, switchOnBackground: appTheme,
dialogConfirmButtonTextColor: appTheme, dialogConfirmButtonTextColor: appTheme,
dropdownMenuOptionActiveColor: appTheme, dropdownMenuOptionActiveColor: appTheme,
dropdownMenuTitleActiveTextColor: appTheme, dropdownMenuTitleActiveTextColor: appTheme,
notifyPrimaryBackground: appTheme, notifyPrimaryBackground: appTheme,
circleColor: appTheme, circleColor: appTheme,
noticeBarBackground: lightenStr, noticeBarBackground: lightenStr,
noticeBarTextColor: darkenStr, noticeBarTextColor: darkenStr,
progressColor: appTheme, progressColor: appTheme,
progressPivotBackground: appTheme, progressPivotBackground: appTheme,
stepActiveColor: appTheme, stepActiveColor: appTheme,
stepFinishLineColor: appTheme, stepFinishLineColor: appTheme,
swipeIndicatorActiveBackground: appTheme, swipeIndicatorActiveBackground: appTheme,
tagPrimaryColor: appTheme, tagPrimaryColor: appTheme,
navBarIconColor: appTheme, navBarIconColor: appTheme,
navBarTextColor: appTheme, navBarTextColor: appTheme,
paginationItemDefaultColor: appTheme, paginationItemDefaultColor: appTheme,
sidebarSelectedBorderColor: appTheme, sidebarSelectedBorderColor: appTheme,
tabsDefaultColor: appTheme, tabsDefaultColor: appTheme,
tabsBottomBarColor: appTheme, tabsBottomBarColor: appTheme,
tabbarItemActiveColor: appTheme, tabbarItemActiveColor: appTheme,
treeSelectItemActiveColor: appTheme, treeSelectItemActiveColor: appTheme,
}; }
}; }
const getTransitionName = computed(() => { const getTransitionName = computed(() => {
return unref(getIsPageAnimate) ? unref(getPageAnimateType) : undefined; return unref(getIsPageAnimate) ? unref(getPageAnimateType) : undefined
}); })
</script> </script>
<style lang="less"> <style lang="less">

@ -1,9 +1,9 @@
import { http } from '@/utils/http/axios'; import { http } from '@/utils/http/axios'
export interface BasicResponseModel<T = any> { export interface BasicResponseModel<T = any> {
code: number; code: number
message: string; message: string
result: T; result: T
} }
/** /**
@ -18,8 +18,8 @@ export function login(params: any) {
}, },
{ {
isTransformResponse: false, isTransformResponse: false,
} },
); )
} }
/** /**
@ -29,7 +29,7 @@ export function getUserInfo() {
return http.request({ return http.request({
url: '/getUserInfo', url: '/getUserInfo',
method: 'get', method: 'get',
}); })
} }
/** /**
@ -39,7 +39,7 @@ export function doLogout() {
return http.request({ return http.request({
url: '/logout', url: '/logout',
method: 'POST', method: 'POST',
}); })
} }
/** /**
@ -54,6 +54,6 @@ export function changePassword(params: any, uid: any) {
}, },
{ {
isTransformResponse: false, isTransformResponse: false,
} },
); )
} }

@ -5,44 +5,45 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue'
import { defineComponent, computed } from 'vue'; import { computed, defineComponent } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'SvgIcon', name: 'SvgIcon',
props: { props: {
prefix: { prefix: {
type: String, type: String,
default: 'icon', default: 'icon',
},
name: {
type: String,
required: true,
},
size: {
type: [Number, String],
default: 16,
},
color: {
type: String,
default: '#333',
},
}, },
setup(props) { name: {
const symbolId = computed(() => `#${props.prefix}-${props.name}`); type: String,
required: true,
const getStyle = computed((): CSSProperties => {
const { size } = props;
let s = `${size}`;
s = `${s.replace('px', '')}px`;
return {
width: s,
height: s,
};
});
return { symbolId, getStyle };
}, },
}); size: {
type: [Number, String],
default: 16,
},
color: {
type: String,
default: '#333',
},
},
setup(props) {
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
const getStyle = computed((): CSSProperties => {
const { size } = props
let s = `${size}`
s = `${s.replace('px', '')}px`
return {
width: s,
height: s,
}
})
return { symbolId, getStyle }
},
})
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -16,13 +16,13 @@ export enum screenEnum {
XXL = 1600, XXL = 1600,
} }
const screenMap = new Map<sizeEnum, number>(); const screenMap = new Map<sizeEnum, number>()
screenMap.set(sizeEnum.XS, screenEnum.XS); screenMap.set(sizeEnum.XS, screenEnum.XS)
screenMap.set(sizeEnum.SM, screenEnum.SM); screenMap.set(sizeEnum.SM, screenEnum.SM)
screenMap.set(sizeEnum.MD, screenEnum.MD); screenMap.set(sizeEnum.MD, screenEnum.MD)
screenMap.set(sizeEnum.LG, screenEnum.LG); screenMap.set(sizeEnum.LG, screenEnum.LG)
screenMap.set(sizeEnum.XL, screenEnum.XL); screenMap.set(sizeEnum.XL, screenEnum.XL)
screenMap.set(sizeEnum.XXL, screenEnum.XXL); screenMap.set(sizeEnum.XXL, screenEnum.XXL)
export { screenMap }; export { screenMap }

@ -1,14 +1,14 @@
// token key // token key
export const TOKEN_KEY = 'TOKEN'; export const TOKEN_KEY = 'TOKEN'
// user info key // user info key
export const USER_INFO_KEY = 'USER__INFO__'; export const USER_INFO_KEY = 'USER__INFO__'
// role info key // role info key
export const ROLES_KEY = 'ROLES__KEY__'; export const ROLES_KEY = 'ROLES__KEY__'
// base global local key // base global local key
export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__'; export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__'
// base global session key // base global session key
export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'; export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'

@ -1,3 +1,4 @@
/* eslint-disable ts/no-duplicate-enum-values */
export enum PageEnum { export enum PageEnum {
// 登录 // 登录
BASE_LOGIN = '/login', BASE_LOGIN = '/login',

@ -1,47 +1,48 @@
import { ref, watch } from 'vue'; import { ref, watch } from 'vue'
import { tryOnUnmounted } from '@vueuse/core'; import { tryOnUnmounted } from '@vueuse/core'
import { isFunction } from '@/utils/is'; import { isFunction } from '@/utils/is'
export function useTimeoutFn(handle: Fn<any>, wait: number, native = false) { export function useTimeoutFn(handle: Fn<any>, wait: number, native = false) {
if (!isFunction(handle)) { if (!isFunction(handle)) {
throw new Error('handle is not Function!'); throw new Error('handle is not Function!')
} }
const { readyRef, stop, start } = useTimeoutRef(wait); const { readyRef, stop, start } = useTimeoutRef(wait)
if (native) { if (native) {
handle(); handle()
} else { }
else {
watch( watch(
readyRef, readyRef,
(maturity) => { (maturity) => {
maturity && handle(); maturity && handle()
}, },
{ immediate: false } { immediate: false },
); )
} }
return { readyRef, stop, start }; return { readyRef, stop, start }
} }
export function useTimeoutRef(wait: number) { export function useTimeoutRef(wait: number) {
const readyRef = ref(false); const readyRef = ref(false)
let timer: TimeoutHandle; let timer: TimeoutHandle
function stop(): void { function stop(): void {
readyRef.value = false; readyRef.value = false
timer && window.clearTimeout(timer); timer && window.clearTimeout(timer)
} }
function start(): void { function start(): void {
stop(); stop()
timer = setTimeout(() => { timer = setTimeout(() => {
readyRef.value = true; readyRef.value = true
}, wait); }, wait)
} }
start(); start()
tryOnUnmounted(stop); tryOnUnmounted(stop)
return { readyRef, stop, start }; return { readyRef, stop, start }
} }

@ -1,18 +1,19 @@
import { ref, computed, ComputedRef, unref } from 'vue'; import type { ComputedRef } from 'vue'
import { useEventListener } from '@/hooks/event/useEventListener'; import { computed, ref, unref } from 'vue'
import { screenMap, sizeEnum, screenEnum } from '@/enums/breakpointEnum'; import { useEventListener } from '@/hooks/event/useEventListener'
import { screenEnum, screenMap, sizeEnum } from '@/enums/breakpointEnum'
let globalScreenRef: ComputedRef<sizeEnum | undefined>; let globalScreenRef: ComputedRef<sizeEnum | undefined>
let globalWidthRef: ComputedRef<number>; let globalWidthRef: ComputedRef<number>
let globalRealWidthRef: ComputedRef<number>; let globalRealWidthRef: ComputedRef<number>
export interface CreateCallbackParams { export interface CreateCallbackParams {
screen: ComputedRef<sizeEnum | undefined>; screen: ComputedRef<sizeEnum | undefined>
width: ComputedRef<number>; width: ComputedRef<number>
realWidth: ComputedRef<number>; realWidth: ComputedRef<number>
screenEnum: typeof screenEnum; screenEnum: typeof screenEnum
screenMap: Map<sizeEnum, number>; screenMap: Map<sizeEnum, number>
sizeEnum: typeof sizeEnum; sizeEnum: typeof sizeEnum
} }
export function useBreakpoint() { export function useBreakpoint() {
@ -21,35 +22,40 @@ export function useBreakpoint() {
widthRef: globalWidthRef, widthRef: globalWidthRef,
screenEnum, screenEnum,
realWidthRef: globalRealWidthRef, realWidthRef: globalRealWidthRef,
}; }
} }
// Just call it once // Just call it once
export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void) { export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void) {
const screenRef = ref<sizeEnum>(sizeEnum.XL); const screenRef = ref<sizeEnum>(sizeEnum.XL)
const realWidthRef = ref(window.innerWidth); const realWidthRef = ref(window.innerWidth)
function getWindowWidth() { function getWindowWidth() {
const width = document.body.clientWidth; const width = document.body.clientWidth
const xs = screenMap.get(sizeEnum.XS)!; const xs = screenMap.get(sizeEnum.XS)!
const sm = screenMap.get(sizeEnum.SM)!; const sm = screenMap.get(sizeEnum.SM)!
const md = screenMap.get(sizeEnum.MD)!; const md = screenMap.get(sizeEnum.MD)!
const lg = screenMap.get(sizeEnum.LG)!; const lg = screenMap.get(sizeEnum.LG)!
const xl = screenMap.get(sizeEnum.XL)!; const xl = screenMap.get(sizeEnum.XL)!
if (width < xs) { if (width < xs) {
screenRef.value = sizeEnum.XS; screenRef.value = sizeEnum.XS
} else if (width < sm) {
screenRef.value = sizeEnum.SM;
} else if (width < md) {
screenRef.value = sizeEnum.MD;
} else if (width < lg) {
screenRef.value = sizeEnum.LG;
} else if (width < xl) {
screenRef.value = sizeEnum.XL;
} else {
screenRef.value = sizeEnum.XXL;
} }
realWidthRef.value = width; else if (width < sm) {
screenRef.value = sizeEnum.SM
}
else if (width < md) {
screenRef.value = sizeEnum.MD
}
else if (width < lg) {
screenRef.value = sizeEnum.LG
}
else if (width < xl) {
screenRef.value = sizeEnum.XL
}
else {
screenRef.value = sizeEnum.XXL
}
realWidthRef.value = width
} }
useEventListener({ useEventListener({
@ -57,16 +63,16 @@ export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void)
name: 'resize', name: 'resize',
listener: () => { listener: () => {
getWindowWidth(); getWindowWidth()
resizeFn(); resizeFn()
}, },
// wait: 100, // wait: 100,
}); })
getWindowWidth(); getWindowWidth()
globalScreenRef = computed(() => unref(screenRef)); globalScreenRef = computed(() => unref(screenRef))
globalWidthRef = computed((): number => screenMap.get(unref(screenRef)!)!); globalWidthRef = computed((): number => screenMap.get(unref(screenRef)!)!)
globalRealWidthRef = computed((): number => unref(realWidthRef)); globalRealWidthRef = computed((): number => unref(realWidthRef))
function resizeFn() { function resizeFn() {
fn?.({ fn?.({
@ -76,14 +82,14 @@ export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void)
screenEnum, screenEnum,
screenMap, screenMap,
sizeEnum, sizeEnum,
}); })
} }
resizeFn(); resizeFn()
return { return {
screenRef: globalScreenRef, screenRef: globalScreenRef,
screenEnum, screenEnum,
widthRef: globalWidthRef, widthRef: globalWidthRef,
realWidthRef: globalRealWidthRef, realWidthRef: globalRealWidthRef,
}; }
} }

@ -1,18 +1,18 @@
import type { Ref } from 'vue'; import type { Ref } from 'vue'
import { ref, watch, unref } from 'vue'; import { ref, unref, watch } from 'vue'
import { useThrottleFn, useDebounceFn } from '@vueuse/core'; import { useDebounceFn, useThrottleFn } from '@vueuse/core'
export type RemoveEventFn = () => void; export type RemoveEventFn = () => void
export interface UseEventParams { export interface UseEventParams {
el?: Element | Ref<Element | undefined> | Window | any; el?: Element | Ref<Element | undefined> | Window | any
name: string; name: string
listener: EventListener; listener: EventListener
options?: boolean | AddEventListenerOptions; options?: boolean | AddEventListenerOptions
autoRemove?: boolean; autoRemove?: boolean
isDebounce?: boolean; isDebounce?: boolean
wait?: number; wait?: number
} }
export function useEventListener({ export function useEventListener({
@ -24,39 +24,38 @@ export function useEventListener({
isDebounce = true, isDebounce = true,
wait = 80, wait = 80,
}: UseEventParams): { removeEvent: RemoveEventFn } { }: UseEventParams): { removeEvent: RemoveEventFn } {
/* eslint-disable-next-line */ let remove: RemoveEventFn = () => {
let remove: RemoveEventFn = () => { }
}; const isAddRef = ref(false)
const isAddRef = ref(false);
if (el) { if (el) {
const element: Ref<Element> = ref(el as Element); const element: Ref<Element> = ref(el as Element)
const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait); const handler = isDebounce ? useDebounceFn(listener, wait) : useThrottleFn(listener, wait)
const realHandler = wait ? handler : listener; const realHandler = wait ? handler : listener
const removeEventListener = (e: Element) => { const removeEventListener = (e: Element) => {
isAddRef.value = true; isAddRef.value = true
e.removeEventListener(name, realHandler, options); e.removeEventListener(name, realHandler, options)
}; }
const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options); const addEventListener = (e: Element) => e.addEventListener(name, realHandler, options)
const removeWatch = watch( const removeWatch = watch(
element, element,
(v, _ov, cleanUp) => { (v, _ov, cleanUp) => {
if (v) { if (v) {
!unref(isAddRef) && addEventListener(v); !unref(isAddRef) && addEventListener(v)
cleanUp(() => { cleanUp(() => {
autoRemove && removeEventListener(v); autoRemove && removeEventListener(v)
}); })
} }
}, },
{ immediate: true } { immediate: true },
); )
remove = () => { remove = () => {
removeEventListener(element.value); removeEventListener(element.value)
removeWatch(); removeWatch()
}; }
} }
return { removeEvent: remove }; return { removeEvent: remove }
} }

@ -1,36 +1,35 @@
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'; import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'
import { useDebounceFn } from '@vueuse/core';
interface WindowSizeOptions { interface WindowSizeOptions {
once?: boolean; once?: boolean
immediate?: boolean; immediate?: boolean
listenerOptions?: AddEventListenerOptions | boolean; listenerOptions?: AddEventListenerOptions | boolean
} }
export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOptions) { export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOptions) {
let handler = () => { let handler = () => {
fn(); fn()
}; }
const handleSize = useDebounceFn(handler, wait); const handleSize = useDebounceFn(handler, wait)
handler = handleSize; handler = handleSize
const start = () => { const start = () => {
if (options && options.immediate) { if (options && options.immediate) {
handler(); handler()
} }
window.addEventListener('resize', handler); window.addEventListener('resize', handler)
}; }
const stop = () => { const stop = () => {
window.removeEventListener('resize', handler); window.removeEventListener('resize', handler)
}; }
tryOnMounted(() => { tryOnMounted(() => {
start(); start()
}); })
tryOnUnmounted(() => { tryOnUnmounted(() => {
stop(); stop()
}); })
return [start, stop]; return [start, stop]
} }

@ -1,3 +1,3 @@
import { useAsync } from './use-async'; import { useAsync } from './use-async'
export { useAsync }; export { useAsync }

@ -1,8 +1,8 @@
import { warn } from '@/utils/log'; import { warn } from '@/utils/log'
import { getAppEnvConfig } from '@/utils/env'; import { getAppEnvConfig } from '@/utils/env'
import { GlobConfig } from '#/config'; import type { GlobConfig } from '#/config'
export const useGlobSetting = (): Readonly<GlobConfig> => { export function useGlobSetting(): Readonly<GlobConfig> {
const { const {
VITE_GLOB_APP_TITLE, VITE_GLOB_APP_TITLE,
VITE_GLOB_APP_TITLE_CN, VITE_GLOB_APP_TITLE_CN,
@ -12,12 +12,12 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
VITE_GLOB_UPLOAD_URL, VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK, VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL, VITE_GLOB_IMG_URL,
} = getAppEnvConfig(); } = getAppEnvConfig()
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
warn( warn(
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`,
); )
} }
// Take global configuration // Take global configuration
@ -30,6 +30,6 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
uploadUrl: VITE_GLOB_UPLOAD_URL, uploadUrl: VITE_GLOB_UPLOAD_URL,
prodMock: VITE_GLOB_PROD_MOCK, prodMock: VITE_GLOB_PROD_MOCK,
imgUrl: VITE_GLOB_IMG_URL, imgUrl: VITE_GLOB_IMG_URL,
}; }
return glob as Readonly<GlobConfig>; return glob as Readonly<GlobConfig>
}; }

@ -1,18 +1,18 @@
import { computed } from 'vue'; import { computed } from 'vue'
import { useDesignSettingStore } from '@/store/modules/designSetting'; import { useDesignSettingStore } from '@/store/modules/designSetting'
export function useDesignSetting() { export function useDesignSetting() {
const designStore = useDesignSettingStore(); const designStore = useDesignSettingStore()
const getDarkMode = computed(() => designStore.darkMode); const getDarkMode = computed(() => designStore.darkMode)
const getAppTheme = computed(() => designStore.appTheme); const getAppTheme = computed(() => designStore.appTheme)
const getAppThemeList = computed(() => designStore.appThemeList); const getAppThemeList = computed(() => designStore.appThemeList)
const getIsPageAnimate = computed(() => designStore.isPageAnimate); const getIsPageAnimate = computed(() => designStore.isPageAnimate)
const getPageAnimateType = computed(() => designStore.pageAnimateType); const getPageAnimateType = computed(() => designStore.pageAnimateType)
return { return {
getDarkMode, getDarkMode,
@ -20,5 +20,5 @@ export function useDesignSetting() {
getAppThemeList, getAppThemeList,
getIsPageAnimate, getIsPageAnimate,
getPageAnimateType, getPageAnimateType,
}; }
} }

@ -1,15 +1,16 @@
import { isReactive, isRef } from 'vue'; import { isReactive, isRef } from 'vue'
function setLoading(loading, val) { function setLoading(loading, val) {
if (loading != undefined && isRef(loading)) { if (loading !== undefined && isRef(loading)) {
loading.value = val; loading.value = val
} else if (loading != undefined && isReactive(loading)) { }
loading.loading = val; else if (loading !== undefined && isReactive(loading)) {
loading.loading = val
} }
} }
export const useAsync = async (func: Promise<any>, loading: any): Promise<any> => { export async function useAsync(func: Promise<any>, loading: any): Promise<any> {
setLoading(loading, true); setLoading(loading, true)
return await func.finally(() => setLoading(loading, false)); return await func.finally(() => setLoading(loading, false))
}; }

@ -1,23 +1,23 @@
import { ref, onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue'
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es'
/** /**
* description: 获取页面宽度 * description: 获取页面宽度
*/ */
export function useDomWidth() { export function useDomWidth() {
const domWidth = ref(window.innerWidth); const domWidth = ref(window.innerWidth)
function resize() { function resize() {
domWidth.value = document.body.clientWidth; domWidth.value = document.body.clientWidth
} }
onMounted(() => { onMounted(() => {
window.addEventListener('resize', debounce(resize, 80)); window.addEventListener('resize', debounce(resize, 80))
}); })
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', resize); window.removeEventListener('resize', resize)
}); })
return domWidth; return domWidth
} }

@ -1,30 +1,30 @@
import { ref, onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue'
/** /**
* @description * @description
* */ */
export function useOnline() { export function useOnline() {
const online = ref(true); const online = ref(true)
const showStatus = (val) => { const showStatus = (val) => {
online.value = typeof val == 'boolean' ? val : val.target.online; online.value = typeof val == 'boolean' ? val : val.target.online
}; }
// 在页面加载后,设置正确的网络状态 // 在页面加载后,设置正确的网络状态
navigator.onLine ? showStatus(true) : showStatus(false); navigator.onLine ? showStatus(true) : showStatus(false)
onMounted(() => { onMounted(() => {
// 开始监听网络状态的变化 // 开始监听网络状态的变化
window.addEventListener('online', showStatus); window.addEventListener('online', showStatus)
window.addEventListener('offline', showStatus); window.addEventListener('offline', showStatus)
}); })
onUnmounted(() => { onUnmounted(() => {
// 移除监听网络状态的变化 // 移除监听网络状态的变化
window.removeEventListener('online', showStatus); window.removeEventListener('online', showStatus)
window.removeEventListener('offline', showStatus); window.removeEventListener('offline', showStatus)
}); })
return { online }; return { online }
} }

@ -1,33 +1,33 @@
import { ref, onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue'
/** /**
* @description * @description
*/ */
export function useTime() { export function useTime() {
let timer; // 定时器 let timer // 定时器
const year = ref(0); // 年份 const year = ref(0) // 年份
const month = ref(0); // 月份 const month = ref(0) // 月份
const week = ref(''); // 星期几 const week = ref('') // 星期几
const day = ref(0); // 天数 const day = ref(0) // 天数
const hour = ref<number | string>(0); // 小时 const hour = ref<number | string>(0) // 小时
const minute = ref<number | string>(0); // 分钟 const minute = ref<number | string>(0) // 分钟
const second = ref(0); // 秒 const second = ref(0) // 秒
// 更新时间 // 更新时间
const updateTime = () => { const updateTime = () => {
const date = new Date(); const date = new Date()
year.value = date.getFullYear(); year.value = date.getFullYear()
month.value = date.getMonth() + 1; month.value = date.getMonth() + 1
week.value = '日一二三四五六'.charAt(date.getDay()); week.value = '日一二三四五六'.charAt(date.getDay())
day.value = date.getDate(); day.value = date.getDate()
hour.value = hour.value
(date.getHours() + '')?.padStart(2, '0') || = (`${date.getHours()}`)?.padStart(2, '0')
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours()); || new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours())
minute.value = minute.value
(date.getMinutes() + '')?.padStart(2, '0') || = (`${date.getMinutes()}`)?.padStart(2, '0')
new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes()); || new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes())
second.value = date.getSeconds(); second.value = date.getSeconds()
}; }
// 原生时间格式化 // 原生时间格式化
// new Intl.DateTimeFormat('zh', { // new Intl.DateTimeFormat('zh', {
@ -40,16 +40,16 @@ export function useTime() {
// hour12: false // hour12: false
// }).format(new Date()) // }).format(new Date())
updateTime(); updateTime()
onMounted(() => { onMounted(() => {
clearInterval(timer); clearInterval(timer)
timer = setInterval(() => updateTime(), 1000); timer = setInterval(() => updateTime(), 1000)
}); })
onUnmounted(() => { onUnmounted(() => {
clearInterval(timer); clearInterval(timer)
}); })
return { month, day, hour, minute, second, week }; return { month, day, hour, minute, second, week }
} }

@ -1,111 +1,116 @@
import type { EChartsOption } from 'echarts'; import type { EChartsOption } from 'echarts'
import type { Ref } from 'vue'; import type { Ref } from 'vue'
import { useTimeoutFn } from '@/hooks/core/useTimeout'; import type { Fn } from '@vueuse/core'
import { Fn, tryOnUnmounted } from '@vueuse/core'; import { tryOnUnmounted, useDebounceFn } from '@vueuse/core'
import { unref, nextTick, watch, computed, ref } from 'vue'; import { computed, nextTick, ref, unref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'; import { useTimeoutFn } from '@/hooks/core/useTimeout'
import { useEventListener } from '@/hooks/event/useEventListener';
import { useBreakpoint } from '@/hooks/event/useBreakpoint'; import { useEventListener } from '@/hooks/event/useEventListener'
import { useDesignSettingStore } from '@/store/modules/designSetting'; import { useBreakpoint } from '@/hooks/event/useBreakpoint'
import echarts from '@/utils/lib/echarts'; import { useDesignSettingStore } from '@/store/modules/designSetting'
import echarts from '@/utils/lib/echarts'
export function useECharts( export function useECharts(
elRef: Ref<HTMLDivElement>, elRef: Ref<HTMLDivElement>,
theme: 'light' | 'dark' | 'default' = 'default' theme: 'light' | 'dark' | 'default' = 'default',
) { ) {
const designStore = useDesignSettingStore(); const designStore = useDesignSettingStore()
const getDarkMode = computed(() => { const getDarkMode = computed(() => {
return theme === 'default' ? designStore.getDarkMode : theme; return theme === 'default' ? designStore.getDarkMode : theme
}); })
let chartInstance: echarts.ECharts | null = null; let chartInstance: echarts.ECharts | null = null
let resizeFn: Fn = resize; let resizeFn: Fn = resize
const cacheOptions = ref({}); const cacheOptions = ref({})
let removeResizeFn: Fn = () => {}; let removeResizeFn: Fn = () => {}
resizeFn = useDebounceFn(resize, 200); resizeFn = useDebounceFn(resize, 200)
const getOptions = computed((): EChartsOption => { const getOptions = computed((): EChartsOption => {
if (getDarkMode.value !== 'dark') { if (getDarkMode.value !== 'dark') {
return cacheOptions.value; return cacheOptions.value
} }
return { return {
backgroundColor: 'transparent', backgroundColor: 'transparent',
...cacheOptions.value, ...cacheOptions.value,
}; }
}); })
function initCharts(t = theme) { function initCharts(t = theme) {
const el = unref(elRef); const el = unref(elRef)
if (!el || !unref(el)) { if (!el || !unref(el)) {
return; return
} }
chartInstance = echarts.init(el, t); chartInstance = echarts.init(el, t)
const { removeEvent } = useEventListener({ const { removeEvent } = useEventListener({
el: window, el: window,
name: 'resize', name: 'resize',
listener: resizeFn, listener: resizeFn,
}); })
removeResizeFn = removeEvent; removeResizeFn = removeEvent
const { widthRef, screenEnum } = useBreakpoint(); const { widthRef, screenEnum } = useBreakpoint()
if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) { if (unref(widthRef) <= screenEnum.MD || el.offsetHeight === 0) {
useTimeoutFn(() => { useTimeoutFn(() => {
resizeFn(); resizeFn()
}, 30); }, 30)
} }
} }
function setOptions(options: EChartsOption, clear = true) { function setOptions(options: EChartsOption, clear = true) {
cacheOptions.value = options; cacheOptions.value = options
if (unref(elRef)?.offsetHeight === 0) { if (unref(elRef)?.offsetHeight === 0) {
useTimeoutFn(() => { useTimeoutFn(() => {
setOptions(unref(getOptions)); setOptions(unref(getOptions))
}, 30); }, 30)
return; return
} }
nextTick(() => { nextTick(() => {
useTimeoutFn(() => { useTimeoutFn(() => {
if (!chartInstance) { if (!chartInstance) {
initCharts(getDarkMode.value as 'default'); initCharts(getDarkMode.value as 'default')
if (!chartInstance) return; if (!chartInstance) {
return
}
} }
clear && chartInstance?.clear(); clear && chartInstance?.clear()
chartInstance?.setOption(unref(getOptions)); chartInstance?.setOption(unref(getOptions))
}, 30); }, 30)
}); })
} }
function resize() { function resize() {
chartInstance?.resize(); chartInstance?.resize()
} }
watch( watch(
() => getDarkMode.value, () => getDarkMode.value,
(theme) => { (theme) => {
if (chartInstance) { if (chartInstance) {
chartInstance.dispose(); chartInstance.dispose()
initCharts(theme as 'default'); initCharts(theme as 'default')
setOptions(cacheOptions.value); setOptions(cacheOptions.value)
} }
} },
); )
tryOnUnmounted(() => { tryOnUnmounted(() => {
if (!chartInstance) return; if (!chartInstance) {
removeResizeFn(); return
chartInstance.dispose(); }
chartInstance = null; removeResizeFn()
}); chartInstance.dispose()
chartInstance = null
})
function getInstance(): echarts.ECharts | null { function getInstance(): echarts.ECharts | null {
if (!chartInstance) { if (!chartInstance) {
initCharts(getDarkMode.value as 'default'); initCharts(getDarkMode.value as 'default')
} }
return chartInstance; return chartInstance
} }
return { return {
@ -113,5 +118,5 @@ export function useECharts(
resize, resize,
echarts, echarts,
getInstance, getInstance,
}; }
} }

@ -1,6 +1,7 @@
<!-- eslint-disable prettier/prettier -->
<template> <template>
<div class="h-screen flex flex-col"> <div class="h-screen flex flex-col">
<van-nav-bar v-if="getShowHeader" fixed placeholder :title="getTitle" /> <van-nav-bar v-if="getShowHeader" placeholder fixed :title="getTitle" />
<routerView class="flex-1 overflow-x-hidden"> <routerView class="flex-1 overflow-x-hidden">
<template #default="{ Component, route }"> <template #default="{ Component, route }">
<!-- <!--
@ -13,42 +14,45 @@
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents"> <keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
<component :is="Component" :key="route.fullPath" /> <component :is="Component" :key="route.fullPath" />
</keep-alive> </keep-alive>
<component v-else :is="Component" :key="route.fullPath" /> <component :is="Component" v-else :key="route.fullPath" />
</template> </template>
</routerView> </routerView>
<van-tabbar route fixed placeholder> <van-tabbar placeholder route fixed>
<van-tabbar-item <van-tabbar-item
replace
v-for="menu in getMenus" v-for="menu in getMenus"
:key="menu.name" :key="menu.name"
replace
:to="menu.path" :to="menu.path"
:icon="(menu.meta?.icon as string)" :icon="(menu.meta?.icon as string)"
>{{ menu.meta?.title }} >
{{ menu.meta?.title }}
</van-tabbar-item> </van-tabbar-item>
</van-tabbar> </van-tabbar>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import type { ComputedRef } from 'vue'
import { useRoute } from 'vue-router'; import { computed } from 'vue'
import { useRouteStore } from '@/store/modules/route'; import { useRoute } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import { useRouteStore } from '@/store/modules/route'
const routeStore = useRouteStore(); const routeStore = useRouteStore()
// //
const keepAliveComponents = computed(() => routeStore.keepAliveComponents); const keepAliveComponents = computed(() => routeStore.keepAliveComponents)
const currentRoute = useRoute(); const currentRoute = useRoute()
const getTitle = computed(() => currentRoute.meta.title as string); const getTitle = computed(() => currentRoute.meta.title as string)
// //
const getMenus = computed(() => const getMenus: ComputedRef<RouteRecordRaw[]> = computed(() =>
routeStore.menus.filter((item) => { routeStore.menus.filter((item) => {
return !item.meta?.innerPage; return !item.meta?.innerPage
}) }),
); )
const getShowHeader = computed(() => !currentRoute.meta.hiddenHeader); const getShowHeader = computed(() => !currentRoute.meta.hiddenHeader)
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -1,31 +1,31 @@
import 'virtual:uno.css'; import 'virtual:uno.css'
import 'vant/es/toast/style'; import 'vant/es/toast/style'
import 'vant/es/dialog/style'; import 'vant/es/dialog/style'
import '@unocss/reset/tailwind.css'; import '@unocss/reset/tailwind.css'
import '@unocss/reset/tailwind-compat.css'; import '@unocss/reset/tailwind-compat.css'
// Register icon sprite // Register icon sprite
import 'virtual:svg-icons-register'; import 'virtual:svg-icons-register'
import { createApp } from 'vue'; import { createApp } from 'vue'
import App from './App.vue'; import App from './App.vue'
import { setupStore } from '@/store'; import router, { setupRouter } from './router'
import router, { setupRouter } from './router'; import { updateDarkSign } from './theme'
import { updateDarkSign } from './theme'; import { setupStore } from '@/store'
async function bootstrap() { async function bootstrap() {
const app = createApp(App); const app = createApp(App)
// 挂载状态管理 // 挂载状态管理
setupStore(app); setupStore(app)
// 挂载路由 // 挂载路由
setupRouter(app); setupRouter(app)
await router.isReady(); await router.isReady()
// 路由准备就绪后挂载APP实例 // 路由准备就绪后挂载APP实例
app.mount('#app', true); app.mount('#app', true)
// 根节点挂载 dark 标识 // 根节点挂载 dark 标识
const appDesignSetting = window.localStorage.getItem('DESIGN-SETTING'); const appDesignSetting = window.localStorage.getItem('DESIGN-SETTING')
const darkMode = appDesignSetting && JSON.parse(appDesignSetting).darkMode; const darkMode = appDesignSetting && JSON.parse(appDesignSetting).darkMode
updateDarkSign(darkMode); updateDarkSign(darkMode)
} }
void bootstrap(); void bootstrap()

@ -1,7 +1,7 @@
import { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router'
import { PageEnum } from '@/enums/pageEnum'; import { PageEnum } from '@/enums/pageEnum'
const Layout = () => import('@/layout/index.vue'); const Layout = () => import('@/layout/index.vue')
// 404 on a page // 404 on a page
export const ErrorPageRoute: RouteRecordRaw = { export const ErrorPageRoute: RouteRecordRaw = {
@ -23,7 +23,7 @@ export const ErrorPageRoute: RouteRecordRaw = {
}, },
}, },
], ],
}; }
export const RootRoute: RouteRecordRaw = { export const RootRoute: RouteRecordRaw = {
path: '/', path: '/',
@ -32,7 +32,7 @@ export const RootRoute: RouteRecordRaw = {
meta: { meta: {
title: 'Root', title: 'Root',
}, },
}; }
export const LoginRoute: RouteRecordRaw = { export const LoginRoute: RouteRecordRaw = {
path: '/login', path: '/login',
@ -41,4 +41,4 @@ export const LoginRoute: RouteRecordRaw = {
meta: { meta: {
title: '登录', title: '登录',
}, },
}; }

@ -1,31 +1,32 @@
import { App } from 'vue'; import type { App } from 'vue'
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router'
import { LoginRoute, RootRoute, ErrorPageRoute } from '@/router/base'; import { createRouter, createWebHashHistory } from 'vue-router'
import { createRouterGuards } from './router-guards'; import { createRouterGuards } from './router-guards'
import { useRouteStoreWidthOut } from '@/store/modules/route'; import routeModuleList from './modules'
import { ErrorPageRoute, LoginRoute, RootRoute } from '@/router/base'
import { useRouteStoreWidthOut } from '@/store/modules/route'
// 菜单 // 菜单
import routeModuleList from './modules';
// 普通路由 // 普通路由
export const constantRouter: RouteRecordRaw[] = [LoginRoute, RootRoute, ErrorPageRoute]; export const constantRouter: RouteRecordRaw[] = [LoginRoute, RootRoute, ErrorPageRoute]
const routeStore = useRouteStoreWidthOut(); const routeStore = useRouteStoreWidthOut()
routeStore.setMenus(routeModuleList); routeStore.setMenus(routeModuleList)
routeStore.setRouters(constantRouter.concat(routeModuleList)); routeStore.setRouters(constantRouter.concat(routeModuleList))
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(''), history: createWebHashHistory(''),
routes: constantRouter.concat(...routeModuleList), routes: constantRouter.concat(...routeModuleList),
strict: true, strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }), scrollBehavior: () => ({ left: 0, top: 0 }),
}); })
export function setupRouter(app: App) { export function setupRouter(app: App) {
app.use(router); app.use(router)
// 创建路由守卫 // 创建路由守卫
createRouterGuards(router); createRouterGuards(router)
} }
export default router; export default router

@ -1,6 +1,6 @@
import { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router'
const Layout = () => import('@/layout/index.vue'); const Layout = () => import('@/layout/index.vue')
const routeModuleList: Array<RouteRecordRaw> = [ const routeModuleList: Array<RouteRecordRaw> = [
{ {
@ -121,6 +121,6 @@ const routeModuleList: Array<RouteRecordRaw> = [
}, },
component: () => import('@/views/my/ThemeSetting.vue'), component: () => import('@/views/my/ThemeSetting.vue'),
}, },
]; ]
export default routeModuleList; export default routeModuleList

@ -1,88 +1,91 @@
import { isNavigationFailure, Router } from 'vue-router'; import type { Router } from 'vue-router'
import { useRouteStoreWidthOut } from '@/store/modules/route'; import { isNavigationFailure } from 'vue-router'
import { useUserStoreWidthOut } from '@/store/modules/user'; import { useRouteStoreWidthOut } from '@/store/modules/route'
import { ACCESS_TOKEN } from '@/store/mutation-types'; import { useUserStoreWidthOut } from '@/store/modules/user'
import { storage } from '@/utils/Storage'; import { ACCESS_TOKEN } from '@/store/mutation-types'
import { PageEnum } from '@/enums/pageEnum'; import { storage } from '@/utils/Storage'
import { PageEnum } from '@/enums/pageEnum'
const LOGIN_PATH = PageEnum.BASE_LOGIN; const LOGIN_PATH = PageEnum.BASE_LOGIN
const whitePathList = [LOGIN_PATH]; // no redirect whitelist const whitePathList = [LOGIN_PATH] // no redirect whitelist
export function createRouterGuards(router: Router) { export function createRouterGuards(router: Router) {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
// to: 即将要进入的目标 // to: 即将要进入的目标
// from: 当前导航正要离开的路由 // from: 当前导航正要离开的路由
const userStore = useUserStoreWidthOut(); const userStore = useUserStoreWidthOut()
if (from.path === LOGIN_PATH && to.name === PageEnum.ERROR_PAGE_NAME) { if (from.path === LOGIN_PATH && to.name === PageEnum.ERROR_PAGE_NAME) {
next(PageEnum.BASE_HOME); next(PageEnum.BASE_HOME)
return; return
} }
// Whitelist can be directly entered // Whitelist can be directly entered
if (whitePathList.includes(to.path as PageEnum)) { if (whitePathList.includes(to.path as PageEnum)) {
next(); next()
return; return
} }
const token = storage.get(ACCESS_TOKEN); const token = storage.get(ACCESS_TOKEN)
if (!token) { if (!token) {
// redirect login page // redirect login page
next(LOGIN_PATH); next(LOGIN_PATH)
return; return
} }
// 当上次更新时间为空时获取用户信息 // 当上次更新时间为空时获取用户信息
if (userStore.getLastUpdateTime === 0) { if (userStore.getLastUpdateTime === 0) {
try { try {
await userStore.GetUserInfo(); await userStore.GetUserInfo()
} catch (err) { }
next(); catch (err) {
return; next()
return
} }
} }
next(); next()
}); })
// 进入某个路由之后触发的钩子 // 进入某个路由之后触发的钩子
router.afterEach((to, _, failure) => { router.afterEach((to, _, failure) => {
// 设置每个页面的 title // 设置每个页面的 title
document.title = (to?.meta?.title as string) || document.title; document.title = (to?.meta?.title as string) || document.title
if (isNavigationFailure(failure)) { if (isNavigationFailure(failure)) {
console.log('failed navigation', failure); console.warn('failed navigation', failure)
} }
const routeStore = useRouteStoreWidthOut(); const routeStore = useRouteStoreWidthOut()
// 在这里设置需要缓存的组件名称 // 在这里设置需要缓存的组件名称
const keepAliveComponents = routeStore.keepAliveComponents; const keepAliveComponents = routeStore.keepAliveComponents
// 获取当前组件名 // 获取当前组件名
const currentComName: any = to.matched.find((item) => item.name == to.name)?.name; const currentComName: any = to.matched.find(item => item.name === to.name)?.name
// 如果 currentComName 且 keepAliveComponents 不包含 currentComName 且 即将要进入的路由 meta 属性里 keepAlive 为 true则缓存该组件 // 如果 currentComName 且 keepAliveComponents 不包含 currentComName 且 即将要进入的路由 meta 属性里 keepAlive 为 true则缓存该组件
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) { if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
// 需要缓存的组件 // 需要缓存的组件
keepAliveComponents.push(currentComName); keepAliveComponents.push(currentComName)
// keepAlive 为 false 则不缓存 // keepAlive 为 false 则不缓存
} else if (!to.meta?.keepAlive) { }
else if (!to.meta?.keepAlive) {
// 不需要缓存的组件 // 不需要缓存的组件
// 这里的作用一开始组件设置为缓存,之后又设置不缓存但是它还是存在 keepAliveComponents 数组中 // 这里的作用一开始组件设置为缓存,之后又设置不缓存但是它还是存在 keepAliveComponents 数组中
// keepAliveComponents 使用 findIndex 与 当前路由对比,如果存在则返回具体下标位置,不存在返回 -1 // keepAliveComponents 使用 findIndex 与 当前路由对比,如果存在则返回具体下标位置,不存在返回 -1
const index = routeStore.keepAliveComponents.findIndex((name) => name == currentComName); const index = routeStore.keepAliveComponents.findIndex(name => name === currentComName)
if (index != -1) { if (index !== -1) {
// 通过返回具体下标位置删除 keepAliveComponents 数组中缓存的 元素 // 通过返回具体下标位置删除 keepAliveComponents 数组中缓存的 元素
keepAliveComponents.splice(index, 1); keepAliveComponents.splice(index, 1)
} }
} }
routeStore.setKeepAliveComponents(keepAliveComponents); routeStore.setKeepAliveComponents(keepAliveComponents)
}); })
router.onError((error) => { router.onError((error) => {
console.error(error, '路由错误'); console.error(error, '路由错误')
}); })
} }

@ -5,4 +5,4 @@ export const animates = [
{ value: 'fade', text: '消退' }, { value: 'fade', text: '消退' },
{ value: 'fade-bottom', text: '底部消退' }, { value: 'fade-bottom', text: '底部消退' },
{ value: 'fade-scale', text: '缩放消退' }, { value: 'fade-scale', text: '缩放消退' },
]; ]

@ -1,15 +1,15 @@
export default { export default {
upload: { upload: {
//考虑接口规范不同 // 考虑接口规范不同
apiSetting: { apiSetting: {
// 集合字段名 // 集合字段名
infoField: 'result', infoField: 'result',
// 图片地址字段名 // 图片地址字段名
imgField: 'imagePath', imgField: 'imagePath',
}, },
//最大上传图片大小 // 最大上传图片大小
maxSize: 1, maxSize: 1,
//图片上传类型 // 图片上传类型
fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'], fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'],
}, },
}; }

@ -2,15 +2,15 @@
export interface DesignSettingState { export interface DesignSettingState {
// 系统主题 // 系统主题
darkMode: 'light' | 'dark'; darkMode: 'light' | 'dark'
// 系统风格 // 系统风格
appTheme: string; appTheme: string
// 系统内置风格 // 系统内置风格
appThemeList: string[]; appThemeList: string[]
// 是否开启路由动画 // 是否开启路由动画
isPageAnimate: boolean; isPageAnimate: boolean
// 路由动画类型 // 路由动画类型
pageAnimateType: string; pageAnimateType: string
} }
export const appThemeList: string[] = [ export const appThemeList: string[] = [
@ -33,19 +33,19 @@ export const appThemeList: string[] = [
'#FB9300', '#FB9300',
'#FC5404', '#FC5404',
'#8675ff', '#8675ff',
]; ]
const setting: DesignSettingState = { const setting: DesignSettingState = {
//深色主题 // 深色主题
darkMode: 'light', darkMode: 'light',
//系统主题色 // 系统主题色
appTheme: '#5d9dfe', appTheme: '#5d9dfe',
//系统内置主题色列表 // 系统内置主题色列表
appThemeList, appThemeList,
//是否开启路由动画 // 是否开启路由动画
isPageAnimate: true, isPageAnimate: true,
//路由动画类型 // 路由动画类型
pageAnimateType: 'zoom-fade', pageAnimateType: 'zoom-fade',
}; }
export default setting; export default setting

@ -1,12 +1,12 @@
import type { App } from 'vue'; import type { App } from 'vue'
import { createPinia } from 'pinia'; import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persist'; import piniaPersist from 'pinia-plugin-persist'
const store = createPinia(); const store = createPinia()
store.use(piniaPersist); store.use(piniaPersist)
export function setupStore(app: App<Element>) { export function setupStore(app: App<Element>) {
app.use(store); app.use(store)
} }
export { store }; export { store }

@ -1,9 +1,9 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia'
import { store } from '@/store'; import { store } from '@/store'
import designSetting from '@/settings/designSetting'; import designSetting from '@/settings/designSetting'
import type { DesignSettingState } from '@/settings/designSetting'; import type { DesignSettingState } from '@/settings/designSetting'
const { darkMode, appTheme, appThemeList, isPageAnimate, pageAnimateType } = designSetting; const { darkMode, appTheme, appThemeList, isPageAnimate, pageAnimateType } = designSetting
export const useDesignSettingStore = defineStore({ export const useDesignSettingStore = defineStore({
id: 'app-design-setting', id: 'app-design-setting',
@ -16,27 +16,27 @@ export const useDesignSettingStore = defineStore({
}), }),
getters: { getters: {
getDarkMode(): 'light' | 'dark' { getDarkMode(): 'light' | 'dark' {
return this.darkMode; return this.darkMode
}, },
getAppTheme(): string { getAppTheme(): string {
return this.appTheme; return this.appTheme
}, },
getAppThemeList(): string[] { getAppThemeList(): string[] {
return this.appThemeList; return this.appThemeList
}, },
getIsPageAnimate(): boolean { getIsPageAnimate(): boolean {
return this.isPageAnimate; return this.isPageAnimate
}, },
getPageAnimateType(): string { getPageAnimateType(): string {
return this.pageAnimateType; return this.pageAnimateType
}, },
}, },
actions: { actions: {
setDarkMode(mode: 'light' | 'dark'): void { setDarkMode(mode: 'light' | 'dark'): void {
this.darkMode = mode; this.darkMode = mode
}, },
setPageAnimateType(type: string): void { setPageAnimateType(type: string): void {
this.pageAnimateType = type; this.pageAnimateType = type
}, },
}, },
// 持久化 // 持久化
@ -49,9 +49,9 @@ export const useDesignSettingStore = defineStore({
}, },
], ],
}, },
}); })
// Need to be used outside the setup // Need to be used outside the setup
export function useDesignSettingWithOut() { export function useDesignSettingWithOut() {
return useDesignSettingStore(store); return useDesignSettingStore(store)
} }

@ -1,11 +1,11 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia'
import { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router'
import { store } from '@/store'; import { store } from '@/store'
export interface IRouteState { export interface IRouteState {
menus: RouteRecordRaw[]; menus: RouteRecordRaw[]
routers: RouteRecordRaw[]; routers: RouteRecordRaw[]
keepAliveComponents: string[]; keepAliveComponents: string[]
} }
export const useRouteStore = defineStore({ export const useRouteStore = defineStore({
@ -17,24 +17,24 @@ export const useRouteStore = defineStore({
}), }),
getters: { getters: {
getMenus(): RouteRecordRaw[] { getMenus(): RouteRecordRaw[] {
return this.menus; return this.menus
}, },
}, },
actions: { actions: {
setRouters(routers: RouteRecordRaw[]) { setRouters(routers: RouteRecordRaw[]) {
this.routers = routers; this.routers = routers
}, },
setMenus(menus: RouteRecordRaw[]) { setMenus(menus: RouteRecordRaw[]) {
this.menus = menus; this.menus = menus
}, },
setKeepAliveComponents(compNames: string[]) { setKeepAliveComponents(compNames: string[]) {
// 设置需要缓存的组件 // 设置需要缓存的组件
this.keepAliveComponents = compNames; this.keepAliveComponents = compNames
}, },
}, },
}); })
// Need to be used outside the setup // Need to be used outside the setup
export function useRouteStoreWidthOut() { export function useRouteStoreWidthOut() {
return useRouteStore(store); return useRouteStore(store)
} }

@ -1,35 +1,36 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia'
import { createStorage } from '@/utils/Storage'; import { createStorage } from '@/utils/Storage'
import { store } from '@/store'; import { store } from '@/store'
import { ACCESS_TOKEN, CURRENT_USER } from '@/store/mutation-types'; import { ACCESS_TOKEN, CURRENT_USER } from '@/store/mutation-types'
import { ResultEnum } from '@/enums/httpEnum'; import { ResultEnum } from '@/enums/httpEnum'
const Storage = createStorage({ storage: localStorage }); import { doLogout, getUserInfo, login } from '@/api/system/user'
import { getUserInfo, login, doLogout } from '@/api/system/user'; import { PageEnum } from '@/enums/pageEnum'
import { PageEnum } from '@/enums/pageEnum'; import router from '@/router'
import router from '@/router';
const Storage = createStorage({ storage: localStorage })
interface UserInfo { interface UserInfo {
userId: string | number; userId: string | number
username: string; username: string
realname: string; realname: string
nickname: string; nickname: string
avatar: string; avatar: string
cover: string; cover: string
gender: number; gender: number
phone: string; phone: string
sign?: string; sign?: string
industry?: number; industry?: number
} }
interface IUserState { interface IUserState {
token?: string; token?: string
userInfo: Nullable<UserInfo>; userInfo: Nullable<UserInfo>
lastUpdateTime: number; lastUpdateTime: number
} }
interface LoginParams { interface LoginParams {
username: string; username: string
password: string; password: string
} }
export const useUserStore = defineStore({ export const useUserStore = defineStore({
@ -41,37 +42,38 @@ export const useUserStore = defineStore({
}), }),
getters: { getters: {
getUserInfo(): UserInfo { getUserInfo(): UserInfo {
return this.userInfo || Storage.get(CURRENT_USER, '') || {}; return this.userInfo || Storage.get(CURRENT_USER, '') || {}
}, },
getToken(): string { getToken(): string {
return this.token || Storage.get(ACCESS_TOKEN, ''); return this.token || Storage.get(ACCESS_TOKEN, '')
}, },
getLastUpdateTime(): number { getLastUpdateTime(): number {
return this.lastUpdateTime; return this.lastUpdateTime
}, },
}, },
actions: { actions: {
setToken(token: string | undefined) { setToken(token: string | undefined) {
this.token = token ? token : ''; this.token = token || ''
Storage.set(ACCESS_TOKEN, token); Storage.set(ACCESS_TOKEN, token)
}, },
setUserInfo(info: UserInfo | null) { setUserInfo(info: UserInfo | null) {
this.userInfo = info; this.userInfo = info
this.lastUpdateTime = new Date().getTime(); this.lastUpdateTime = new Date().getTime()
Storage.set(CURRENT_USER, info); Storage.set(CURRENT_USER, info)
}, },
async Login(params: LoginParams) { async Login(params: LoginParams) {
try { try {
const response = await login(params); const response = await login(params)
const { result, code } = response; const { result, code } = response
if (code === ResultEnum.SUCCESS) { if (code === ResultEnum.SUCCESS) {
// save token // save token
this.setToken(result.token); this.setToken(result.token)
} }
return Promise.resolve(response); return Promise.resolve(response)
} catch (error) { }
return Promise.reject(error); catch (error) {
return Promise.reject(error)
} }
}, },
@ -79,34 +81,35 @@ export const useUserStore = defineStore({
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getUserInfo() getUserInfo()
.then((res) => { .then((res) => {
this.setUserInfo(res); this.setUserInfo(res)
resolve(res); resolve(res)
}) })
.catch((error) => { .catch((error) => {
reject(error); reject(error)
}); })
}); })
}, },
async Logout() { async Logout() {
if (this.getToken) { if (this.getToken) {
try { try {
await doLogout(); await doLogout()
} catch { }
console.error('注销Token失败'); catch {
console.error('注销Token失败')
} }
} }
this.setToken(undefined); this.setToken(undefined)
this.setUserInfo(null); this.setUserInfo(null)
Storage.remove(ACCESS_TOKEN); Storage.remove(ACCESS_TOKEN)
Storage.remove(CURRENT_USER); Storage.remove(CURRENT_USER)
router.push(PageEnum.BASE_LOGIN); router.push(PageEnum.BASE_LOGIN)
location.reload(); location.reload()
}, },
}, },
}); })
// Need to be used outside the setup // Need to be used outside the setup
export function useUserStoreWidthOut() { export function useUserStoreWidthOut() {
return useUserStore(store); return useUserStore(store)
} }

@ -1,4 +1,4 @@
export const FIRST_VISIT = 'FIRST-VISIT'; // 是否首次访问 export const FIRST_VISIT = 'FIRST-VISIT' // 是否首次访问
export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token export const ACCESS_TOKEN = 'ACCESS-TOKEN' // 用户token
export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息 export const CURRENT_USER = 'CURRENT-USER' // 当前用户信息
export const DESIGN_SETTING = 'DESIGN-SETTING'; // 当前用户主题信息 export const DESIGN_SETTING = 'DESIGN-SETTING' // 当前用户主题信息

@ -11,7 +11,6 @@ html {
} }
[data-theme='dark'] { [data-theme='dark'] {
&, &,
* { * {
color-scheme: dark !important; color-scheme: dark !important;
@ -24,7 +23,6 @@ html {
} }
[data-theme='light'] { [data-theme='light'] {
&, &,
* { * {
color-scheme: light !important; color-scheme: light !important;
@ -80,7 +78,9 @@ a:hover {
.zoom-fade-enter-active, .zoom-fade-enter-active,
.zoom-fade-leave-active { .zoom-fade-leave-active {
transition: transform 0.35s, opacity 0.28s ease-in-out; transition:
transform 0.35s,
opacity 0.28s ease-in-out;
} }
.zoom-fade-enter-from { .zoom-fade-enter-from {

@ -31,7 +31,9 @@
// Speed: 1x // Speed: 1x
.fade-bottom-enter-active, .fade-bottom-enter-active,
.fade-bottom-leave-active { .fade-bottom-leave-active {
transition: opacity 0.25s, transform 0.3s; transition:
opacity 0.25s,
transform 0.3s;
} }
.fade-bottom-enter-from { .fade-bottom-enter-from {
@ -67,7 +69,9 @@
// Speed: 1x // Speed: 1x
.fade-top-enter-active, .fade-top-enter-active,
.fade-top-leave-active { .fade-top-leave-active {
transition: opacity 0.2s, transform 0.25s; transition:
opacity 0.2s,
transform 0.25s;
} }
.fade-top-enter-from { .fade-top-enter-from {

@ -6,5 +6,8 @@
@import './zoom.less'; @import './zoom.less';
.collapse-transition { .collapse-transition {
transition: 0.2s height ease-in-out, 0.2s padding-top ease-in-out, 0.2s padding-bottom ease-in-out; transition:
0.2s height ease-in-out,
0.2s padding-top ease-in-out,
0.2s padding-bottom ease-in-out;
} }

@ -1,7 +1,9 @@
// zoom-out // zoom-out
.zoom-out-enter-active, .zoom-out-enter-active,
.zoom-out-leave-active { .zoom-out-leave-active {
transition: opacity 0.1 ease-in-out, transform 0.15s ease-out; transition:
opacity 0.1 ease-in-out,
transform 0.15s ease-out;
} }
.zoom-out-enter-from, .zoom-out-enter-from,
@ -13,7 +15,9 @@
// zoom-fade // zoom-fade
.zoom-fade-enter-active, .zoom-fade-enter-active,
.zoom-fade-leave-active { .zoom-fade-leave-active {
transition: transform 0.2s, opacity 0.3s ease-out; transition:
transform 0.2s,
opacity 0.3s ease-out;
} }
.zoom-fade-enter-from { .zoom-fade-enter-from {

@ -1,24 +1,25 @@
import { addClass, removeClass, hasClass } from '@/utils/domUtils'; import { addClass, hasClass, removeClass } from '@/utils/domUtils'
/** /**
* html / * html /
*/ */
export function updateDarkSign(mode: 'light' | 'dark') { export function updateDarkSign(mode: 'light' | 'dark') {
const htmlRoot = document.getElementById('htmlRoot'); const htmlRoot = document.getElementById('htmlRoot')
if (!htmlRoot) { if (!htmlRoot) {
return; return
} }
const hasDarkClass = hasClass(htmlRoot, 'dark'); const hasDarkClass = hasClass(htmlRoot, 'dark')
if (mode === 'dark') { if (mode === 'dark') {
htmlRoot.setAttribute('data-theme', 'dark'); htmlRoot.setAttribute('data-theme', 'dark')
if (!hasDarkClass) { if (!hasDarkClass) {
addClass(htmlRoot, 'dark'); addClass(htmlRoot, 'dark')
} }
} else { }
htmlRoot.setAttribute('data-theme', 'light'); else {
htmlRoot.setAttribute('data-theme', 'light')
if (hasDarkClass) { if (hasDarkClass) {
removeClass(htmlRoot, 'dark'); removeClass(htmlRoot, 'dark')
} }
} }
} }

@ -1,22 +1,21 @@
// 默认缓存期限为7天 // 默认缓存期限为7天
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7
/** /**
* *
* @param {string=} prefixKey - * @param {string} prefixKey -
* @param {Object} [storage=localStorage] - sessionStorage | localStorage
*/ */
export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) => { export function createStorage({ prefixKey = '', storage = localStorage } = {}) {
/** /**
* *
* @class Storage * @class Storage
*/ */
const Storage = class { const Storage = class {
private storage = storage; private storage = storage
private prefixKey?: string = prefixKey; private prefixKey?: string = prefixKey
private getKey(key: string) { private getKey(key: string) {
return `${this.prefixKey}${key}`.toUpperCase(); return `${this.prefixKey}${key}`.toUpperCase()
} }
/** /**
@ -29,8 +28,8 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
const stringData = JSON.stringify({ const stringData = JSON.stringify({
value, value,
expire: expire !== null ? new Date().getTime() + expire * 1000 : null, expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
}); })
this.storage.setItem(this.getKey(key), stringData); this.storage.setItem(this.getKey(key), stringData)
} }
/** /**
@ -39,21 +38,22 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
* @param {*=} def * @param {*=} def
*/ */
get(key: string, def: any = null) { get(key: string, def: any = null) {
const item = this.storage.getItem(this.getKey(key)); const item = this.storage.getItem(this.getKey(key))
if (item) { if (item) {
try { try {
const data = JSON.parse(item); const data = JSON.parse(item)
const { value, expire } = data; const { value, expire } = data
// 在有效期内直接返回 // 在有效期内直接返回
if (expire === null || expire >= Date.now()) { if (expire === null || expire >= Date.now()) {
return value; return value
} }
this.remove(key); this.remove(key)
} catch (e) { }
return def; catch (e) {
return def
} }
} }
return def; return def
} }
/** /**
@ -61,7 +61,7 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
* @param {string} key * @param {string} key
*/ */
remove(key: string) { remove(key: string) {
this.storage.removeItem(this.getKey(key)); this.storage.removeItem(this.getKey(key))
} }
/** /**
@ -69,7 +69,7 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
* @memberOf Cache * @memberOf Cache
*/ */
clear(): void { clear(): void {
this.storage.clear(); this.storage.clear()
} }
/** /**
@ -81,7 +81,7 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
* @example * @example
*/ */
setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) { setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`; document.cookie = `${this.getKey(name)}=${value}; Max-Age=${expire}`
} }
/** /**
@ -89,14 +89,14 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
* @param name * @param name
*/ */
getCookie(name: string): string { getCookie(name: string): string {
const cookieArr = document.cookie.split('; '); const cookieArr = document.cookie.split('; ')
for (let i = 0, length = cookieArr.length; i < length; i++) { for (let i = 0, length = cookieArr.length; i < length; i++) {
const kv = cookieArr[i].split('='); const kv = cookieArr[i].split('=')
if (kv[0] === this.getKey(name)) { if (kv[0] === this.getKey(name)) {
return kv[1]; return kv[1]
} }
} }
return ''; return ''
} }
/** /**
@ -104,24 +104,24 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
* @param {string} key * @param {string} key
*/ */
removeCookie(key: string) { removeCookie(key: string) {
this.setCookie(key, 1, -1); this.setCookie(key, 1, -1)
} }
/** /**
* cookie使cookie失效 * cookie使cookie失效
*/ */
clearCookie(): void { clearCookie(): void {
const keys = document.cookie.match(/[^ =;]+(?==)/g); const keys = document.cookie.match(/[^ =;]+(?==)/g)
if (keys) { if (keys) {
for (let i = keys.length; i--; ) { for (let i = keys.length; i--;) {
document.cookie = keys[i] + '=0;expire=' + new Date(0).toUTCString(); document.cookie = `${keys[i]}=0;expire=${new Date(0).toUTCString()}`
} }
} }
} }
}; }
return new Storage(); return new Storage()
}; }
export const storage = createStorage(); export const storage = createStorage()
export default Storage; export default Storage

@ -1,12 +1,12 @@
import { format } from 'date-fns'; import { format } from 'date-fns'
const DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm:ss'; const DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm:ss'
const DATE_FORMAT = 'YYYY-MM-DD '; const DATE_FORMAT = 'YYYY-MM-DD '
export function formatToDateTime(date: number | Date, formatStr = DATE_TIME_FORMAT): string { export function formatToDateTime(date: number | Date, formatStr = DATE_TIME_FORMAT): string {
return format(date, formatStr); return format(date, formatStr)
} }
export function formatToDate(date: number | Date, formatStr = DATE_FORMAT): string { export function formatToDate(date: number | Date, formatStr = DATE_FORMAT): string {
return format(date, formatStr); return format(date, formatStr)
} }

@ -1,76 +1,92 @@
import type { FunctionArgs } from '@vueuse/core'; /* eslint-disable ts/ban-ts-comment */
import { upperFirst } from 'lodash-es'; import type { FunctionArgs } from '@vueuse/core'
import { upperFirst } from 'lodash-es'
export interface ViewportOffsetResult { export interface ViewportOffsetResult {
left: number; left: number
top: number; top: number
right: number; right: number
bottom: number; bottom: number
rightIncludeBody: number; rightIncludeBody: number
bottomIncludeBody: number; bottomIncludeBody: number
} }
export function getBoundingClientRect(element: Element): DOMRect | number { export function getBoundingClientRect(element: Element): DOMRect | number {
if (!element || !element.getBoundingClientRect) { if (!element || !element.getBoundingClientRect) {
return 0; return 0
} }
return element.getBoundingClientRect(); return element.getBoundingClientRect()
} }
function trim(string: string) { function trim(string: string) {
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, ''); return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '')
} }
/* istanbul ignore next */ /* istanbul ignore next */
export function hasClass(el: Element, cls: string) { export function hasClass(el: Element, cls: string) {
if (!el || !cls) return false; if (!el || !cls) {
if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.'); return false
}
if (cls.includes(' ')) {
throw new Error('className should not contain space.')
}
if (el.classList) { if (el.classList) {
return el.classList.contains(cls); return el.classList.contains(cls)
} else { }
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1; else {
return (` ${el.className} `).includes(` ${cls} `)
} }
} }
/* istanbul ignore next */ /* istanbul ignore next */
export function addClass(el: Element, cls: string) { export function addClass(el: Element, cls: string) {
if (!el) return; if (!el) {
let curClass = el.className; return
const classes = (cls || '').split(' '); }
let curClass = el.className
const classes = (cls || '').split(' ')
for (let i = 0, j = classes.length; i < j; i++) { for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i]; const clsName = classes[i]
if (!clsName) continue; if (!clsName) {
continue
}
if (el.classList) { if (el.classList) {
el.classList.add(clsName); el.classList.add(clsName)
} else if (!hasClass(el, clsName)) { }
curClass += ' ' + clsName; else if (!hasClass(el, clsName)) {
curClass += ` ${clsName}`
} }
} }
if (!el.classList) { if (!el.classList) {
el.className = curClass; el.className = curClass
} }
} }
/* istanbul ignore next */ /* istanbul ignore next */
export function removeClass(el: Element, cls: string) { export function removeClass(el: Element, cls: string) {
if (!el || !cls) return; if (!el || !cls) {
const classes = cls.split(' '); return
let curClass = ' ' + el.className + ' '; }
const classes = cls.split(' ')
let curClass = ` ${el.className} `
for (let i = 0, j = classes.length; i < j; i++) { for (let i = 0, j = classes.length; i < j; i++) {
const clsName = classes[i]; const clsName = classes[i]
if (!clsName) continue; if (!clsName) {
continue
}
if (el.classList) { if (el.classList) {
el.classList.remove(clsName); el.classList.remove(clsName)
} else if (hasClass(el, clsName)) { }
curClass = curClass.replace(' ' + clsName + ' ', ' '); else if (hasClass(el, clsName)) {
curClass = curClass.replace(` ${clsName} `, ' ')
} }
} }
if (!el.classList) { if (!el.classList) {
el.className = trim(curClass); el.className = trim(curClass)
} }
} }
/** /**
@ -85,61 +101,61 @@ export function removeClass(el: Element, cls: string) {
* @description: * @description:
*/ */
export function getViewportOffset(element: Element): ViewportOffsetResult { export function getViewportOffset(element: Element): ViewportOffsetResult {
const doc = document.documentElement; const doc = document.documentElement
const docScrollLeft = doc.scrollLeft; const docScrollLeft = doc.scrollLeft
const docScrollTop = doc.scrollTop; const docScrollTop = doc.scrollTop
const docClientLeft = doc.clientLeft; const docClientLeft = doc.clientLeft
const docClientTop = doc.clientTop; const docClientTop = doc.clientTop
const pageXOffset = window.pageXOffset; const pageXOffset = window.pageXOffset
const pageYOffset = window.pageYOffset; const pageYOffset = window.pageYOffset
const box = getBoundingClientRect(element); const box = getBoundingClientRect(element)
const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect; const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect
const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0); const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0)
const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0); const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0)
const offsetLeft = retLeft + pageXOffset; const offsetLeft = retLeft + pageXOffset
const offsetTop = rectTop + pageYOffset; const offsetTop = rectTop + pageYOffset
const left = offsetLeft - scrollLeft; const left = offsetLeft - scrollLeft
const top = offsetTop - scrollTop; const top = offsetTop - scrollTop
const clientWidth = window.document.documentElement.clientWidth; const clientWidth = window.document.documentElement.clientWidth
const clientHeight = window.document.documentElement.clientHeight; const clientHeight = window.document.documentElement.clientHeight
return { return {
left: left, left,
top: top, top,
right: clientWidth - rectWidth - left, right: clientWidth - rectWidth - left,
bottom: clientHeight - rectHeight - top, bottom: clientHeight - rectHeight - top,
rightIncludeBody: clientWidth - left, rightIncludeBody: clientWidth - left,
bottomIncludeBody: clientHeight - top, bottomIncludeBody: clientHeight - top,
}; }
} }
export function hackCss(attr: string, value: string) { export function hackCss(attr: string, value: string) {
const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT']; const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT']
const styleObj: any = {}; const styleObj: any = {}
prefix.forEach((item) => { prefix.forEach((item) => {
styleObj[`${item}${upperFirst(attr)}`] = value; styleObj[`${item}${upperFirst(attr)}`] = value
}); })
return { return {
...styleObj, ...styleObj,
[attr]: value, [attr]: value,
}; }
} }
/* istanbul ignore next */ /* istanbul ignore next */
export function on( export function on(
element: Element | HTMLElement | Document | Window, element: Element | HTMLElement | Document | Window,
event: string, event: string,
handler: EventListenerOrEventListenerObject handler: EventListenerOrEventListenerObject,
): void { ): void {
if (element && event && handler) { if (element && event && handler) {
element.addEventListener(event, handler, false); element.addEventListener(event, handler, false)
} }
} }
@ -147,10 +163,10 @@ export function on(
export function off( export function off(
element: Element | HTMLElement | Document | Window, element: Element | HTMLElement | Document | Window,
event: string, event: string,
handler: Fn handler: Fn,
): void { ): void {
if (element && event && handler) { if (element && event && handler) {
element.removeEventListener(event, handler, false); element.removeEventListener(event, handler, false)
} }
} }
@ -158,24 +174,26 @@ export function off(
export function once(el: HTMLElement, event: string, fn: EventListener): void { export function once(el: HTMLElement, event: string, fn: EventListener): void {
const listener = function (this: any, ...args: unknown[]) { const listener = function (this: any, ...args: unknown[]) {
if (fn) { if (fn) {
// @ts-ignore // @ts-expect-error
fn.apply(this, args); fn.apply(this, args)
} }
off(el, event, listener); off(el, event, listener)
}; }
on(el, event, listener); on(el, event, listener)
} }
export function useRafThrottle<T extends FunctionArgs>(fn: T): T { export function useRafThrottle<T extends FunctionArgs>(fn: T): T {
let locked = false; let locked = false
// @ts-ignore // @ts-expect-error
return function (...args: any[]) { return function (...args: any[]) {
if (locked) return; if (locked) {
locked = true; return
}
locked = true
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
// @ts-ignore // @ts-expect-error
fn.apply(this, args); fn.apply(this, args)
locked = false; locked = false
}); })
}; }
} }

@ -1,26 +1,26 @@
import type { GlobEnvConfig } from '#/config'; import pkg from '../../package.json'
import { getConfigFileName } from '../../build/getConfigFileName'
import type { GlobEnvConfig } from '#/config'
import { warn } from '@/utils/log'; import { warn } from '@/utils/log'
import pkg from '../../package.json';
import { getConfigFileName } from '../../build/getConfigFileName';
export function getCommonStoragePrefix() { export function getCommonStoragePrefix() {
const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig(); const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig()
return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase(); return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase()
} }
// Generate cache key according to version // Generate cache key according to version
export function getStorageShortName() { export function getStorageShortName() {
return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase(); return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase()
} }
export function getAppEnvConfig() { export function getAppEnvConfig() {
const ENV_NAME = getConfigFileName(import.meta.env); const ENV_NAME = getConfigFileName(import.meta.env)
// Get the global configuration (the configuration will be extracted independently when packaging)
const ENV = (import.meta.env.DEV const ENV = (import.meta.env.DEV
? // Get the global configuration (the configuration will be extracted independently when packaging) ? (import.meta.env as unknown as GlobEnvConfig)
(import.meta.env as unknown as GlobEnvConfig) : window[ENV_NAME as any]) as unknown as GlobEnvConfig
: window[ENV_NAME as any]) as unknown as GlobEnvConfig;
const { const {
VITE_GLOB_APP_TITLE, VITE_GLOB_APP_TITLE,
@ -31,12 +31,12 @@ export function getAppEnvConfig() {
VITE_GLOB_UPLOAD_URL, VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK, VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL, VITE_GLOB_IMG_URL,
} = ENV; } = ENV
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) { if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
warn( warn(
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`,
); )
} }
return { return {
@ -48,18 +48,18 @@ export function getAppEnvConfig() {
VITE_GLOB_UPLOAD_URL, VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK, VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL, VITE_GLOB_IMG_URL,
}; }
} }
/** /**
* @description: Development model * @description: Development model
*/ */
export const devMode = 'development'; export const devMode = 'development'
/** /**
* @description: Production mode * @description: Production mode
*/ */
export const prodMode = 'production'; export const prodMode = 'production'
/** /**
* @description: Get environment variables * @description: Get environment variables
@ -67,7 +67,7 @@ export const prodMode = 'production';
* @example: * @example:
*/ */
export function getEnv(): string { export function getEnv(): string {
return import.meta.env.MODE; return import.meta.env.MODE
} }
/** /**
@ -76,7 +76,7 @@ export function getEnv(): string {
* @example: * @example:
*/ */
export function isDevMode(): boolean { export function isDevMode(): boolean {
return import.meta.env.DEV; return import.meta.env.DEV
} }
/** /**
@ -85,5 +85,5 @@ export function isDevMode(): boolean {
* @example: * @example:
*/ */
export function isProdMode(): boolean { export function isProdMode(): boolean {
return import.meta.env.PROD; return import.meta.env.PROD
} }

@ -1,31 +1,32 @@
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios'; /* eslint-disable ts/ban-ts-comment */
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import axios from 'axios'; import axios from 'axios'
import qs from 'qs'; import qs from 'qs'
import { AxiosCanceler } from './axiosCancel'; import { cloneDeep } from 'lodash-es'
import { isFunction } from '@/utils/is'; import { AxiosCanceler } from './axiosCancel'
import { cloneDeep } from 'lodash-es'; import type { CreateAxiosOptions, RequestOptions, Result, UploadFileParams } from './types'
import { isFunction } from '@/utils/is'
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types'; import { ContentTypeEnum, RequestEnum } from '@/enums/httpEnum'
import { ContentTypeEnum, RequestEnum } from '@/enums/httpEnum';
export * from './axiosTransform'; export * from './axiosTransform'
/** /**
* @description: axios模块 * @description: axios模块
*/ */
export class VAxios { export class VAxios {
private axiosInstance: AxiosInstance; private axiosInstance: AxiosInstance
private options: CreateAxiosOptions; private options: CreateAxiosOptions
constructor(options: CreateAxiosOptions) { constructor(options: CreateAxiosOptions) {
this.options = options; this.options = options
this.axiosInstance = axios.create(options); this.axiosInstance = axios.create(options)
this.setupInterceptors(); this.setupInterceptors()
} }
getAxios(): AxiosInstance { getAxios(): AxiosInstance {
return this.axiosInstance; return this.axiosInstance
} }
/** /**
@ -33,9 +34,9 @@ export class VAxios {
*/ */
configAxios(config: CreateAxiosOptions) { configAxios(config: CreateAxiosOptions) {
if (!this.axiosInstance) { if (!this.axiosInstance) {
return; return
} }
this.createAxios(config); this.createAxios(config)
} }
/** /**
@ -43,97 +44,99 @@ export class VAxios {
*/ */
setHeader(headers: any): void { setHeader(headers: any): void {
if (!this.axiosInstance) { if (!this.axiosInstance) {
return; return
} }
Object.assign(this.axiosInstance.defaults.headers, headers); Object.assign(this.axiosInstance.defaults.headers, headers)
} }
/** /**
* @description: * @description:
*/ */
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> { request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
let conf: AxiosRequestConfig = cloneDeep(config); let conf: AxiosRequestConfig = cloneDeep(config)
const transform = this.getTransform(); const transform = this.getTransform()
const { requestOptions } = this.options; const { requestOptions } = this.options
const opt: RequestOptions = Object.assign({}, requestOptions, options); const opt: RequestOptions = { ...requestOptions, ...options }
const { beforeRequestHook, requestCatch, transformRequestData } = transform || {}; const { beforeRequestHook, requestCatch, transformRequestData } = transform || {}
if (beforeRequestHook && isFunction(beforeRequestHook)) { if (beforeRequestHook && isFunction(beforeRequestHook)) {
conf = beforeRequestHook(conf, opt); conf = beforeRequestHook(conf, opt)
} }
//这里重新 赋值成最新的配置 // 这里重新 赋值成最新的配置
// @ts-ignore // @ts-expect-error
conf.requestOptions = opt; conf.requestOptions = opt
// 支持 FormData // 支持 FormData
conf = this.supportFormData(conf); conf = this.supportFormData(conf)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.axiosInstance this.axiosInstance
.request<any, AxiosResponse<Result>>(conf) .request<any, AxiosResponse<Result>>(conf)
.then((res: AxiosResponse<Result>) => { .then((res: AxiosResponse<Result>) => {
// 请求是否被取消 // 请求是否被取消
const isCancel = axios.isCancel(res); const isCancel = axios.isCancel(res)
if (transformRequestData && isFunction(transformRequestData) && !isCancel) { if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
try { try {
const ret = transformRequestData(res, opt); const ret = transformRequestData(res, opt)
resolve(ret); resolve(ret)
} catch (err) {
reject(err || new Error('request error!'));
} }
return; catch (err) {
reject(err || new Error('request error!'))
}
return
} }
resolve(res as unknown as Promise<T>); resolve(res as unknown as Promise<T>)
}) })
.catch((e: Error) => { .catch((e: Error) => {
if (requestCatch && isFunction(requestCatch)) { if (requestCatch && isFunction(requestCatch)) {
reject(requestCatch(e)); reject(requestCatch(e))
return; return
} }
reject(e); reject(e)
}); })
}); })
} }
/** /**
* @description: axios实例 * @description: axios实例
*/ */
private createAxios(config: CreateAxiosOptions): void { private createAxios(config: CreateAxiosOptions): void {
this.axiosInstance = axios.create(config); this.axiosInstance = axios.create(config)
} }
private getTransform() { private getTransform() {
const { transform } = this.options; const { transform } = this.options
return transform; return transform
} }
/** /**
* @description: * @description:
*/ */
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) { uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
const formData = new window.FormData(); const formData = new window.FormData()
const customFilename = params.name || 'file'; const customFilename = params.name || 'file'
if (params.filename) { if (params.filename) {
formData.append(customFilename, params.file, params.filename); formData.append(customFilename, params.file, params.filename)
} else { }
formData.append(customFilename, params.file); else {
formData.append(customFilename, params.file)
} }
if (params.data) { if (params.data) {
Object.keys(params.data).forEach((key) => { Object.keys(params.data).forEach((key) => {
const value = params.data![key]; const value = params.data![key]
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach((item) => { value.forEach((item) => {
formData.append(`${key}[]`, item); formData.append(`${key}[]`, item)
}); })
return; return
} }
formData.append(key, params.data![key]); formData.append(key, params.data![key])
}); })
} }
return this.axiosInstance.request<T>({ return this.axiosInstance.request<T>({
@ -141,80 +144,81 @@ export class VAxios {
data: formData, data: formData,
headers: { headers: {
'Content-type': ContentTypeEnum.FORM_DATA, 'Content-type': ContentTypeEnum.FORM_DATA,
ignoreCancelToken: true, 'ignoreCancelToken': true,
}, },
...config, ...config,
}); })
} }
// support form-data // support form-data
supportFormData(config: AxiosRequestConfig) { supportFormData(config: AxiosRequestConfig) {
const headers = config.headers || this.options.headers; const headers = config.headers || this.options.headers
const contentType = headers?.['Content-Type'] || headers?.['content-type']; const contentType = headers?.['Content-Type'] || headers?.['content-type']
if ( if (
contentType !== ContentTypeEnum.FORM_URLENCODED || contentType !== ContentTypeEnum.FORM_URLENCODED
!Reflect.has(config, 'data') || || !Reflect.has(config, 'data')
config.method?.toUpperCase() === RequestEnum.GET || config.method?.toUpperCase() === RequestEnum.GET
) { ) {
return config; return config
} }
return { return {
...config, ...config,
data: qs.stringify(config.data, { arrayFormat: 'brackets' }), data: qs.stringify(config.data, { arrayFormat: 'brackets' }),
}; }
} }
/** /**
* @description: * @description:
*/ */
private setupInterceptors() { private setupInterceptors() {
const transform = this.getTransform(); const transform = this.getTransform()
if (!transform) { if (!transform) {
return; return
} }
const { const {
requestInterceptors, requestInterceptors,
requestInterceptorsCatch, requestInterceptorsCatch,
responseInterceptors, responseInterceptors,
responseInterceptorsCatch, responseInterceptorsCatch,
} = transform; } = transform
const axiosCanceler = new AxiosCanceler(); const axiosCanceler = new AxiosCanceler()
// 请求拦截器配置处理 // 请求拦截器配置处理
// @ts-expect-error
this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => { this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
const { headers: { ignoreCancelToken } = { ignoreCancelToken: false } } = config; const { headers: { ignoreCancelToken } = { ignoreCancelToken: false } } = config
const ignoreCancel = const ignoreCancel
ignoreCancelToken !== undefined = ignoreCancelToken !== undefined
? ignoreCancelToken ? ignoreCancelToken
: this.options.requestOptions?.ignoreCancelToken; : this.options.requestOptions?.ignoreCancelToken
!ignoreCancel && axiosCanceler.addPending(config); !ignoreCancel && axiosCanceler.addPending(config)
if (requestInterceptors && isFunction(requestInterceptors)) { if (requestInterceptors && isFunction(requestInterceptors)) {
config = requestInterceptors(config, this.options); config = requestInterceptors(config, this.options)
} }
return config; return config
}, undefined); }, undefined)
// 请求拦截器错误捕获 // 请求拦截器错误捕获
requestInterceptorsCatch && requestInterceptorsCatch
isFunction(requestInterceptorsCatch) && && isFunction(requestInterceptorsCatch)
this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch); && this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch)
// 响应结果拦截器处理 // 响应结果拦截器处理
this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => { this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
res && axiosCanceler.removePending(res.config); res && axiosCanceler.removePending(res.config)
if (responseInterceptors && isFunction(responseInterceptors)) { if (responseInterceptors && isFunction(responseInterceptors)) {
res = responseInterceptors(res); res = responseInterceptors(res)
} }
return res; return res
}, undefined); }, undefined)
// 响应结果拦截器错误捕获 // 响应结果拦截器错误捕获
responseInterceptorsCatch && responseInterceptorsCatch
isFunction(responseInterceptorsCatch) && && isFunction(responseInterceptorsCatch)
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch); && this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch)
} }
} }

@ -1,31 +1,33 @@
import axios, { AxiosRequestConfig, Canceler } from 'axios'; import type { AxiosRequestConfig, Canceler } from 'axios'
import axios from 'axios'
import qs from 'qs'; import qs from 'qs'
import { isFunction } from '@/utils/is/index'; import { isFunction } from '@/utils/is/index'
// 声明一个 Map 用于存储每个请求的标识 和 取消函数 // 声明一个 Map 用于存储每个请求的标识 和 取消函数
let pendingMap = new Map<string, Canceler>(); let pendingMap = new Map<string, Canceler>()
export const getPendingUrl = (config: AxiosRequestConfig) => export function getPendingUrl(config: AxiosRequestConfig) {
[config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&'); return [config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&')
}
export class AxiosCanceler { export class AxiosCanceler {
/** /**
* *
* @param {Object} config * @param {object} config
*/ */
addPending(config: AxiosRequestConfig) { addPending(config: AxiosRequestConfig) {
this.removePending(config); this.removePending(config)
const url = getPendingUrl(config); const url = getPendingUrl(config)
config.cancelToken = config.cancelToken
config.cancelToken || = config.cancelToken
new axios.CancelToken((cancel) => { || new axios.CancelToken((cancel) => {
if (!pendingMap.has(url)) { if (!pendingMap.has(url)) {
// 如果 pending 中不存在当前请求,则添加进去 // 如果 pending 中不存在当前请求,则添加进去
pendingMap.set(url, cancel); pendingMap.set(url, cancel)
} }
}); })
} }
/** /**
@ -33,23 +35,23 @@ export class AxiosCanceler {
*/ */
removeAllPending() { removeAllPending() {
pendingMap.forEach((cancel) => { pendingMap.forEach((cancel) => {
cancel && isFunction(cancel) && cancel(); cancel && isFunction(cancel) && cancel()
}); })
pendingMap.clear(); pendingMap.clear()
} }
/** /**
* *
* @param {Object} config * @param {object} config
*/ */
removePending(config: AxiosRequestConfig) { removePending(config: AxiosRequestConfig) {
const url = getPendingUrl(config); const url = getPendingUrl(config)
if (pendingMap.has(url)) { if (pendingMap.has(url)) {
// 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除 // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
const cancel = pendingMap.get(url); const cancel = pendingMap.get(url)
cancel && cancel(url); cancel && cancel(url)
pendingMap.delete(url); pendingMap.delete(url)
} }
} }
@ -57,6 +59,6 @@ export class AxiosCanceler {
* @description: * @description:
*/ */
reset(): void { reset(): void {
pendingMap = new Map<string, Canceler>(); pendingMap = new Map<string, Canceler>()
} }
} }

@ -1,13 +1,13 @@
/** /**
* *
*/ */
import type { AxiosRequestConfig, AxiosResponse } from 'axios'; import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import type { RequestOptions, Result } from './types'; import type { RequestOptions, Result } from './types'
export interface CreateAxiosOptions extends AxiosRequestConfig { export interface CreateAxiosOptions extends AxiosRequestConfig {
authenticationScheme?: string; authenticationScheme?: string
transform?: AxiosTransform; transform?: AxiosTransform
requestOptions?: RequestOptions; requestOptions?: RequestOptions
} }
export abstract class AxiosTransform { export abstract class AxiosTransform {
@ -15,17 +15,17 @@ export abstract class AxiosTransform {
* @description: * @description:
* @description: Process configuration before request * @description: Process configuration before request
*/ */
beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig; beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig
/** /**
* @description: * @description:
*/ */
transformRequestData?: (res: AxiosResponse<Result>, options: RequestOptions) => any; transformRequestData?: (res: AxiosResponse<Result>, options: RequestOptions) => any
/** /**
* @description: * @description:
*/ */
requestCatch?: (e: Error) => Promise<any>; requestCatch?: (e: Error) => Promise<any>
/** /**
* @description: * @description:
@ -33,20 +33,20 @@ export abstract class AxiosTransform {
requestInterceptors?: ( requestInterceptors?: (
config: AxiosRequestConfig, config: AxiosRequestConfig,
options: CreateAxiosOptions options: CreateAxiosOptions
) => AxiosRequestConfig; ) => AxiosRequestConfig
/** /**
* @description: * @description:
*/ */
responseInterceptors?: (res: AxiosResponse<any>) => AxiosResponse<any>; responseInterceptors?: (res: AxiosResponse<any>) => AxiosResponse<any>
/** /**
* @description: * @description:
*/ */
requestInterceptorsCatch?: (error: Error) => void; requestInterceptorsCatch?: (error: Error) => void
/** /**
* @description: * @description:
*/ */
responseInterceptorsCatch?: (error: Error) => void; responseInterceptorsCatch?: (error: Error) => void
} }

@ -1,48 +1,48 @@
import { showFailToast } from 'vant'; import { showFailToast } from 'vant'
export function checkStatus(status: number, msg: string): void { export function checkStatus(status: number, msg: string): void {
switch (status) { switch (status) {
case 400: case 400:
showFailToast(msg); showFailToast(msg)
break; break
// 401: 未登录 // 401: 未登录
// 未登录则跳转登录页面,并携带当前页面的路径 // 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。 // 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401: case 401:
showFailToast('用户没有权限(令牌、用户名、密码错误)!'); showFailToast('用户没有权限(令牌、用户名、密码错误)!')
break; break
case 403: case 403:
showFailToast('用户得到授权,但是访问是被禁止的。!'); showFailToast('用户得到授权,但是访问是被禁止的。!')
break; break
// 404请求不存在 // 404请求不存在
case 404: case 404:
showFailToast('网络请求错误,未找到该资源!'); showFailToast('网络请求错误,未找到该资源!')
break; break
case 405: case 405:
showFailToast('网络请求错误,请求方法未允许!'); showFailToast('网络请求错误,请求方法未允许!')
break; break
case 408: case 408:
showFailToast('网络请求超时'); showFailToast('网络请求超时')
break; break
case 500: case 500:
showFailToast('服务器错误,请联系管理员!'); showFailToast('服务器错误,请联系管理员!')
break; break
case 501: case 501:
showFailToast('网络未实现'); showFailToast('网络未实现')
break; break
case 502: case 502:
showFailToast('网络错误'); showFailToast('网络错误')
break; break
case 503: case 503:
showFailToast('服务不可用,服务器暂时过载或维护!'); showFailToast('服务不可用,服务器暂时过载或维护!')
break; break
case 504: case 504:
showFailToast('网络超时'); showFailToast('网络超时')
break; break
case 505: case 505:
showFailToast('http版本不支持该请求!'); showFailToast('http版本不支持该请求!')
break; break
default: default:
showFailToast(msg); showFailToast(msg)
} }
} }

@ -1,21 +1,21 @@
import { isObject, isString } from '@/utils/is'; import { isObject, isString } from '@/utils/is'
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'
export function joinTimestamp<T extends boolean>( export function joinTimestamp<T extends boolean>(
join: boolean, join: boolean,
restful: T restful: T
): T extends true ? string : object; ): T extends true ? string : object
export function joinTimestamp(join: boolean, restful = false): string | object { export function joinTimestamp(join: boolean, restful = false): string | object {
if (!join) { if (!join) {
return restful ? '' : {}; return restful ? '' : {}
} }
const now = new Date().getTime(); const now = new Date().getTime()
if (restful) { if (restful) {
return `?_t=${now}`; return `?_t=${now}`
} }
return { _t: now }; return { _t: now }
} }
/** /**
@ -23,25 +23,26 @@ export function joinTimestamp(join: boolean, restful = false): string | object {
*/ */
export function formatRequestDate(params: Recordable) { export function formatRequestDate(params: Recordable) {
if (Object.prototype.toString.call(params) !== '[object Object]') { if (Object.prototype.toString.call(params) !== '[object Object]') {
return; return
} }
for (const key in params) { for (const key in params) {
if (params[key] && params[key]._isAMomentObject) { if (params[key] && params[key]._isAMomentObject) {
params[key] = params[key].format(DATE_TIME_FORMAT); params[key] = params[key].format(DATE_TIME_FORMAT)
} }
if (isString(key)) { if (isString(key)) {
const value = params[key]; const value = params[key]
if (value) { if (value) {
try { try {
params[key] = isString(value) ? value.trim() : value; params[key] = isString(value) ? value.trim() : value
} catch (error) { }
throw new Error(error as any); catch (error) {
throw new Error(error as any)
} }
} }
} }
if (isObject(params[key])) { if (isObject(params[key])) {
formatRequestDate(params[key]); formatRequestDate(params[key])
} }
} }
} }

@ -1,27 +1,27 @@
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动 // axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
import { VAxios } from './Axios'; import type { AxiosResponse } from 'axios'
import { AxiosTransform } from './axiosTransform'; import axios from 'axios'
import axios, { AxiosResponse } from 'axios'; import { showDialog, showFailToast } from 'vant'
import { checkStatus } from './checkStatus'; import { VAxios } from './Axios'
import { joinTimestamp, formatRequestDate } from './helper'; import type { AxiosTransform } from './axiosTransform'
import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum'; import { checkStatus } from './checkStatus'
import { PageEnum } from '@/enums/pageEnum'; import { formatRequestDate, joinTimestamp } from './helper'
import { useGlobSetting } from '@/hooks/setting'; import type { CreateAxiosOptions, RequestOptions, Result } from './types'
import { ContentTypeEnum, RequestEnum, ResultEnum } from '@/enums/httpEnum'
import { PageEnum } from '@/enums/pageEnum'
import { useGlobSetting } from '@/hooks/setting'
import { isString } from '@/utils/is/'; import { isString } from '@/utils/is/'
import { deepMerge, isUrl } from '@/utils'; import { deepMerge, isUrl } from '@/utils'
import { setObjToUrlParams } from '@/utils/urlUtils'; import { setObjToUrlParams } from '@/utils/urlUtils'
import { RequestOptions, Result, CreateAxiosOptions } from './types'; import { useUserStoreWidthOut } from '@/store/modules/user'
import { useUserStoreWidthOut } from '@/store/modules/user'; import router from '@/router'
import { storage } from '@/utils/Storage'
const globSetting = useGlobSetting(); const globSetting = useGlobSetting()
const urlPrefix = globSetting.urlPrefix || ''; const urlPrefix = globSetting.urlPrefix || ''
import router from '@/router';
import { storage } from '@/utils/Storage';
import { showFailToast, showDialog } from 'vant';
/** /**
* @description: 便 * @description: 便
@ -39,29 +39,29 @@ const transform: AxiosTransform = {
errorMessageText, errorMessageText,
isTransformResponse, isTransformResponse,
isReturnNativeResponse, isReturnNativeResponse,
} = options; } = options
// 是否返回原生响应头 比如:需要获取响应头时使用该属性 // 是否返回原生响应头 比如:需要获取响应头时使用该属性
if (isReturnNativeResponse) { if (isReturnNativeResponse) {
return res; return res
} }
// 不进行任何处理,直接返回 // 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取codedatamessage这些信息时开启 // 用于页面代码可能需要直接获取codedatamessage这些信息时开启
if (!isTransformResponse) { if (!isTransformResponse) {
return res.data; return res.data
} }
const { data } = res; const { data } = res
if (!data) { if (!data) {
// return '[HTTP] Request has no return value'; // return '[HTTP] Request has no return value';
throw new Error('请求出错,请稍候重试'); throw new Error('请求出错,请稍候重试')
} }
// 这里 coderesultmessage为 后台统一的字段,需要修改为项目自己的接口返回格式 // 这里 coderesultmessage为 后台统一的字段,需要修改为项目自己的接口返回格式
const { code, result, message } = data; const { code, result, message } = data
// 请求成功 // 请求成功
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS; const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS
// 是否显示提示信息 // 是否显示提示信息
if (isShowMessage) { if (isShowMessage) {
if (hasSuccess && (successMessageText || isShowSuccessMessage)) { if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
@ -69,109 +69,116 @@ const transform: AxiosTransform = {
message: successMessageText || message || '操作成功!', message: successMessageText || message || '操作成功!',
}).then(() => { }).then(() => {
// on close // on close
}); })
} else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) { }
else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
// 是否显示自定义信息提示 // 是否显示自定义信息提示
showFailToast(message || errorMessageText || '操作失败!'); showFailToast(message || errorMessageText || '操作失败!')
} else if (!hasSuccess && options.errorMessageMode === 'modal') { }
else if (!hasSuccess && options.errorMessageMode === 'modal') {
// errorMessageMode=custom-modal的时候会显示modal错误弹窗而不是消息提示用于一些比较重要的错误 // errorMessageMode=custom-modal的时候会显示modal错误弹窗而不是消息提示用于一些比较重要的错误
showDialog({ showDialog({
title: '提示', title: '提示',
message: message, message,
}).then(() => { }).then(() => {
// on close // on close
}); })
} }
} }
// 接口请求成功,直接返回结果 // 接口请求成功,直接返回结果
if (code === ResultEnum.SUCCESS) { if (code === ResultEnum.SUCCESS) {
return result; return result
} }
// 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改 // 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改
let errorMsg = message; let errorMsg = message
const LoginName = PageEnum.BASE_LOGIN_NAME
const LoginPath = PageEnum.BASE_LOGIN
switch (code) { switch (code) {
// 请求失败 // 请求失败
case ResultEnum.ERROR: case ResultEnum.ERROR:
showFailToast(errorMsg); showFailToast(errorMsg)
break; break
// token 过期 // token 过期
case ResultEnum.TOKEN_EXPIRED: case ResultEnum.TOKEN_EXPIRED:
const LoginName = PageEnum.BASE_LOGIN_NAME; if (router.currentRoute.value?.name === LoginName) {
const LoginPath = PageEnum.BASE_LOGIN; return
if (router.currentRoute.value?.name === LoginName) return; }
// 到登录页 // 到登录页
errorMsg = '登录超时,请重新登录!'; errorMsg = '登录超时,请重新登录!'
showDialog({ showDialog({
title: '提示', title: '提示',
message: '登录身份已失效,请重新登录!', message: '登录身份已失效,请重新登录!',
}) })
.then(() => { .then(() => {
storage.clear(); storage.clear()
window.location.href = LoginPath; window.location.href = LoginPath
}) })
.catch(() => { .catch(() => {
// on cancel // on cancel
}); })
break; break
} }
throw new Error(errorMsg); throw new Error(errorMsg)
}, },
// 请求之前处理config // 请求之前处理config
beforeRequestHook: (config, options) => { beforeRequestHook: (config, options) => {
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options; const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options
const isUrlStr = isUrl(config.url as string); const isUrlStr = isUrl(config.url as string)
if (!isUrlStr && joinPrefix) { if (!isUrlStr && joinPrefix) {
config.url = `${urlPrefix}${config.url}`; config.url = `${urlPrefix}${config.url}`
} }
if (!isUrlStr && apiUrl && isString(apiUrl)) { if (!isUrlStr && apiUrl && isString(apiUrl)) {
config.url = `${apiUrl}${config.url}`; config.url = `${apiUrl}${config.url}`
} }
const params = config.params || {}; const params = config.params || {}
const data = config.data || false; const data = config.data || false
if (config.method?.toUpperCase() === RequestEnum.GET) { if (config.method?.toUpperCase() === RequestEnum.GET) {
if (!isString(params)) { if (!isString(params)) {
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。 // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
config.params = Object.assign(params || {}, joinTimestamp(joinTime, false)); config.params = Object.assign(params || {}, joinTimestamp(joinTime, false))
} else {
// 兼容restful风格
config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
config.params = undefined;
} }
} else { else {
// 兼容restful风格
config.url = `${config.url + params}${joinTimestamp(joinTime, true)}`
config.params = undefined
}
}
else {
if (!isString(params)) { if (!isString(params)) {
formatDate && formatRequestDate(params); formatDate && formatRequestDate(params)
if ( if (
Reflect.has(config, 'data') && Reflect.has(config, 'data')
config.data && && config.data
(Object.keys(config.data).length > 0 || config.data instanceof FormData) && (Object.keys(config.data).length > 0 || config.data instanceof FormData)
) { ) {
config.data = data; config.data = data
config.params = params; config.params = params
} else { }
else {
// params 是添加到 url 的请求字符串中的,用于 get 请求 // params 是添加到 url 的请求字符串中的,用于 get 请求
// 非GET请求如果没有提供 data则将 params 视为 data // 非GET请求如果没有提供 data则将 params 视为 data
config.data = params; config.data = params
config.params = undefined; config.params = undefined
} }
if (joinParamsToUrl) { if (joinParamsToUrl) {
config.url = setObjToUrlParams( config.url = setObjToUrlParams(
config.url as string, config.url as string,
Object.assign({}, config.params, config.data) { ...config.params, ...config.data },
); )
} }
} else { }
else {
// 兼容restful风格 // 兼容restful风格
config.url = config.url + params; config.url = config.url + params
config.params = undefined; config.params = undefined
} }
} }
return config; return config
}, },
/** /**
@ -179,30 +186,30 @@ const transform: AxiosTransform = {
*/ */
requestInterceptors: (config, options) => { requestInterceptors: (config, options) => {
// 请求之前处理config // 请求之前处理config
const userStore = useUserStoreWidthOut(); const userStore = useUserStoreWidthOut()
const token = userStore.getToken; const token = userStore.getToken
if (token && (config as Recordable)?.requestOptions?.withToken !== false) { if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
// jwt token // jwt token
(config as Recordable).headers.Authorization = options.authenticationScheme (config as Recordable).headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}` ? `${options.authenticationScheme} ${token}`
: token; : token
} }
return config; return config
}, },
/** /**
* @description: * @description:
*/ */
responseInterceptorsCatch: (error: any) => { responseInterceptorsCatch: (error: any) => {
const { response, code, message } = error || {}; const { response, code, message } = error || {}
// TODO 此处要根据后端接口返回格式修改 // TODO 此处要根据后端接口返回格式修改
const msg: string = const msg: string
response && response.data && response.data.message ? response.data.message : ''; = response && response.data && response.data.message ? response.data.message : ''
const err: string = error.toString(); const err: string = error.toString()
try { try {
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { if (code === 'ECONNABORTED' && message.includes('timeout')) {
showFailToast('接口请求超时,请刷新页面重试!'); showFailToast('接口请求超时,请刷新页面重试!')
return; return
} }
if (err && err.includes('Network Error')) { if (err && err.includes('Network Error')) {
showDialog({ showDialog({
@ -210,23 +217,25 @@ const transform: AxiosTransform = {
message: '请检查您的网络连接是否正常', message: '请检查您的网络连接是否正常',
}) })
.then(() => {}) .then(() => {})
.catch(() => {}); .catch(() => {})
return Promise.reject(error); return Promise.reject(error)
} }
} catch (error) { }
throw new Error(error as any); catch (error) {
throw new Error(error as any)
} }
// 请求是否被取消 // 请求是否被取消
const isCancel = axios.isCancel(error); const isCancel = axios.isCancel(error)
if (!isCancel) { if (!isCancel) {
checkStatus(error.response && error.response.status, msg); checkStatus(error.response && error.response.status, msg)
} else {
console.warn(error, '请求被取消!');
} }
//return Promise.reject(error); else {
return Promise.reject(response?.data); console.warn(error, '请求被取消!')
}
// return Promise.reject(error);
return Promise.reject(response?.data)
}, },
}; }
function createAxios(opt?: Partial<CreateAxiosOptions>) { function createAxios(opt?: Partial<CreateAxiosOptions>) {
return new VAxios( return new VAxios(
@ -260,7 +269,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
// 接口地址 // 接口地址
apiUrl: globSetting.apiUrl, apiUrl: globSetting.apiUrl,
// 接口拼接地址 // 接口拼接地址
urlPrefix: urlPrefix, urlPrefix,
// 是否加入时间戳 // 是否加入时间戳
joinTime: true, joinTime: true,
// 忽略重复请求 // 忽略重复请求
@ -270,12 +279,12 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
}, },
withCredentials: false, withCredentials: false,
}, },
opt || {} opt || {},
) ),
); )
} }
export const http = createAxios(); export const http = createAxios()
// 项目,多个不同 api 地址,直接在这里导出多个 // 项目,多个不同 api 地址,直接在这里导出多个
// src/api ts 里面接口,就可以单独使用这个请求, // src/api ts 里面接口,就可以单独使用这个请求,

@ -1,65 +1,65 @@
import { AxiosRequestConfig } from 'axios'; import type { AxiosRequestConfig } from 'axios'
import { AxiosTransform } from './axiosTransform'; import type { AxiosTransform } from './axiosTransform'
export interface CreateAxiosOptions extends AxiosRequestConfig { export interface CreateAxiosOptions extends AxiosRequestConfig {
transform?: AxiosTransform; transform?: AxiosTransform
requestOptions?: RequestOptions; requestOptions?: RequestOptions
authenticationScheme?: string; authenticationScheme?: string
} }
// 上传文件 // 上传文件
export interface UploadFileParams { export interface UploadFileParams {
// 其他参数 // 其他参数
data?: Recordable; data?: Recordable
// 文件参数接口字段名 // 文件参数接口字段名
name?: string; name?: string
// 文件 // 文件
file: File | Blob; file: File | Blob
// 文件名称 // 文件名称
filename?: string; filename?: string
[key: string]: any; [key: string]: any
} }
export interface RequestOptions { export interface RequestOptions {
// 请求参数拼接到url // 请求参数拼接到url
joinParamsToUrl?: boolean; joinParamsToUrl?: boolean
// 格式化请求参数时间 // 格式化请求参数时间
formatDate?: boolean; formatDate?: boolean
// 是否显示提示信息 // 是否显示提示信息
isShowMessage?: boolean; isShowMessage?: boolean
// 是否解析成JSON // 是否解析成JSON
isParseToJson?: boolean; isParseToJson?: boolean
// 成功的文本信息 // 成功的文本信息
successMessageText?: string; successMessageText?: string
// 是否显示成功信息 // 是否显示成功信息
isShowSuccessMessage?: boolean; isShowSuccessMessage?: boolean
// 是否显示失败信息 // 是否显示失败信息
isShowErrorMessage?: boolean; isShowErrorMessage?: boolean
// 错误的文本信息 // 错误的文本信息
errorMessageText?: string; errorMessageText?: string
// 是否加入url // 是否加入url
joinPrefix?: boolean; joinPrefix?: boolean
// 接口地址, 不填则使用默认apiUrl // 接口地址, 不填则使用默认apiUrl
apiUrl?: string; apiUrl?: string
// 请求拼接路径 // 请求拼接路径
urlPrefix?: string; urlPrefix?: string
// 错误消息提示类型 // 错误消息提示类型
errorMessageMode?: 'none' | 'modal'; errorMessageMode?: 'none' | 'modal'
// 是否添加时间戳 // 是否添加时间戳
joinTime?: boolean; joinTime?: boolean
// 不进行任何处理,直接返回 // 不进行任何处理,直接返回
isTransformResponse?: boolean; isTransformResponse?: boolean
// 是否返回原生响应头 // 是否返回原生响应头
isReturnNativeResponse?: boolean; isReturnNativeResponse?: boolean
//忽略重复请求 // 忽略重复请求
ignoreCancelToken?: boolean; ignoreCancelToken?: boolean
// 是否携带token // 是否携带token
withToken?: boolean; withToken?: boolean
} }
export interface Result<T = any> { export interface Result<T = any> {
code: number; code: number
type?: 'success' | 'error' | 'warning'; type?: 'success' | 'error' | 'warning'
message: string; message: string
result?: T; result?: T
} }

@ -1,11 +1,11 @@
import { isObject } from './is/index'; import { isObject } from './is/index'
export function deepMerge<T = any>(src: any = {}, target: any = {}): T { export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
let key: string; let key: string
for (key in target) { for (key in target) {
src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]); src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key])
} }
return src; return src
} }
/** /**
@ -15,9 +15,9 @@ export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
* @returns {string} The processed part of the color * @returns {string} The processed part of the color
*/ */
function addLight(color: string, amount: number) { function addLight(color: string, amount: number) {
const cc = parseInt(color, 16) + amount; const cc = Number.parseInt(color, 16) + amount
const c = cc > 255 ? 255 : cc; const c = cc > 255 ? 255 : cc
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
} }
/** /**
@ -27,12 +27,12 @@ function addLight(color: string, amount: number) {
* @returns {string} The HEX representation of the processed color * @returns {string} The HEX representation of the processed color
*/ */
export function darken(color: string, amount: number) { export function darken(color: string, amount: number) {
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; color = color.includes('#') ? color.substring(1, color.length) : color
amount = Math.trunc((255 * amount) / 100); amount = Math.trunc((255 * amount) / 100)
return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight( return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight(
color.substring(2, 4), color.substring(2, 4),
amount amount,
)}${subtractLight(color.substring(4, 6), amount)}`; )}${subtractLight(color.substring(4, 6), amount)}`
} }
/** /**
@ -42,31 +42,31 @@ export function darken(color: string, amount: number) {
* @returns {string} The processed color represented as HEX * @returns {string} The processed color represented as HEX
*/ */
export function lighten(color: string, amount: number) { export function lighten(color: string, amount: number) {
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color; color = color.includes('#') ? color.substring(1, color.length) : color
amount = Math.trunc((255 * amount) / 100); amount = Math.trunc((255 * amount) / 100)
return `#${addLight(color.substring(0, 2), amount)}${addLight( return `#${addLight(color.substring(0, 2), amount)}${addLight(
color.substring(2, 4), color.substring(2, 4),
amount amount,
)}${addLight(color.substring(4, 6), amount)}`; )}${addLight(color.substring(4, 6), amount)}`
} }
/** /**
* url * url
* */ */
const RegExp = /^http(s)?:\/\//iu; const RegExp = /^http(s)?:\/\//iu
export function isUrl(url: string) { export function isUrl(url: string) {
return RegExp.test(url); return RegExp.test(url)
} }
/** /**
* *
* */ */
export function arrayTrans(arr: number[]): number[][] { export function arrayTrans(arr: number[]): number[][] {
const newArr: number[][] = []; const newArr: number[][] = []
while (arr.length > 0) { while (arr.length > 0) {
newArr.push(arr.splice(0, 2)); newArr.push(arr.splice(0, 2))
} }
return newArr; return newArr
} }
/** /**
@ -76,24 +76,24 @@ export function arrayTrans(arr: number[]): number[][] {
* @returns {string} The processed part of the color * @returns {string} The processed part of the color
*/ */
function subtractLight(color: string, amount: number) { function subtractLight(color: string, amount: number) {
const cc = parseInt(color, 16) - amount; const cc = Number.parseInt(color, 16) - amount
const c = cc < 0 ? 0 : cc; const c = cc < 0 ? 0 : cc
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`; return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
} }
export function hexToRgba(hex: string, opacity: number) { export function hexToRgba(hex: string, opacity: number) {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
hex = hex.replace(shorthandRegex, function (m, r, g, b) { hex = hex.replace(shorthandRegex, (m, r, g, b) => {
return r + r + g + g + b + b; return r + r + g + g + b + b
}); })
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
opacity = opacity >= 0 && opacity <= 1 ? Number(opacity) : 1; opacity = opacity >= 0 && opacity <= 1 ? Number(opacity) : 1
return result return result
? 'rgba(' + ? `rgba(${
[parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16), opacity].join( [Number.parseInt(result[1], 16), Number.parseInt(result[2], 16), Number.parseInt(result[3], 16), opacity].join(
',' ',',
) + )
')' })`
: hex; : hex
} }

@ -1,125 +1,125 @@
const toString = Object.prototype.toString; const toString = Object.prototype.toString
/** /**
* @description: * @description:
*/ */
export function is(val: unknown, type: string) { export function is(val: unknown, type: string) {
return toString.call(val) === `[object ${type}]`; return toString.call(val) === `[object ${type}]`
} }
/** /**
* @description: * @description:
*/ */
export function isFunction<T = Function>(val: unknown): val is T { export function isFunction<T = Function>(val: unknown): val is T {
return is(val, 'Function'); return is(val, 'Function')
} }
/** /**
* @description: * @description:
*/ */
export const isDef = <T = unknown>(val?: T): val is T => { export function isDef<T = unknown>(val?: T): val is T {
return typeof val !== 'undefined'; return typeof val !== 'undefined'
}; }
export const isUnDef = <T = unknown>(val?: T): val is T => { export function isUnDef<T = unknown>(val?: T): val is T {
return !isDef(val); return !isDef(val)
}; }
/** /**
* @description: * @description:
*/ */
export const isObject = (val: any): val is Record<any, any> => { export function isObject(val: any): val is Record<any, any> {
return val !== null && is(val, 'Object'); return val !== null && is(val, 'Object')
}; }
/** /**
* @description: * @description:
*/ */
export function isDate(val: unknown): val is Date { export function isDate(val: unknown): val is Date {
return is(val, 'Date'); return is(val, 'Date')
} }
/** /**
* @description: * @description:
*/ */
export function isNumber(val: unknown): val is number { export function isNumber(val: unknown): val is number {
return is(val, 'Number'); return is(val, 'Number')
} }
/** /**
* @description: AsyncFunction * @description: AsyncFunction
*/ */
export function isAsyncFunction<T = any>(val: unknown): val is () => Promise<T> { export function isAsyncFunction<T = any>(val: unknown): val is () => Promise<T> {
return is(val, 'AsyncFunction'); return is(val, 'AsyncFunction')
} }
/** /**
* @description: promise * @description: promise
*/ */
export function isPromise<T = any>(val: unknown): val is Promise<T> { export function isPromise<T = any>(val: unknown): val is Promise<T> {
return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch); return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch)
} }
/** /**
* @description: * @description:
*/ */
export function isString(val: unknown): val is string { export function isString(val: unknown): val is string {
return is(val, 'String'); return is(val, 'String')
} }
/** /**
* @description: boolean类型 * @description: boolean类型
*/ */
export function isBoolean(val: unknown): val is boolean { export function isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean'); return is(val, 'Boolean')
} }
/** /**
* @description: * @description:
*/ */
export function isArray(val: any): val is Array<any> { export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val); return val && Array.isArray(val)
} }
/** /**
* @description: * @description:
*/ */
export const isClient = () => { export function isClient() {
return typeof window !== 'undefined'; return typeof window !== 'undefined'
}; }
/** /**
* @description: * @description:
*/ */
export const isWindow = (val: any): val is Window => { export function isWindow(val: any): val is Window {
return typeof window !== 'undefined' && is(val, 'Window'); return typeof window !== 'undefined' && is(val, 'Window')
}; }
export const isElement = (val: unknown): val is Element => { export function isElement(val: unknown): val is Element {
return isObject(val) && !!val.tagName; return isObject(val) && !!val.tagName
}; }
export const isServer = typeof window === 'undefined'; export const isServer = typeof window === 'undefined'
// 是否为图片节点 // 是否为图片节点
export function isImageDom(o: Element) { export function isImageDom(o: Element) {
return o && ['IMAGE', 'IMG'].includes(o.tagName); return o && ['IMAGE', 'IMG'].includes(o.tagName)
} }
export function isNull(val: unknown): val is null { export function isNull(val: unknown): val is null {
return val === null; return val === null
} }
export function isNullAndUnDef(val: unknown): val is null | undefined { export function isNullAndUnDef(val: unknown): val is null | undefined {
return isUnDef(val) && isNull(val); return isUnDef(val) && isNull(val)
} }
export function isNullOrUnDef(val: unknown): val is null | undefined { export function isNullOrUnDef(val: unknown): val is null | undefined {
return isUnDef(val) || isNull(val); return isUnDef(val) || isNull(val)
} }
/** /**
* @description: https * @description: https
*/ */
export function isHttps(val: string): boolean { export function isHttps(val: string): boolean {
return /^https?:\/\//.test(val); return /^https?:\/\//.test(val)
} }

@ -1,33 +1,33 @@
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core'
import { import {
BarChart, BarChart,
GaugeChart,
LineChart, LineChart,
PieChart,
MapChart, MapChart,
PictorialBarChart, PictorialBarChart,
PieChart,
RadarChart, RadarChart,
GaugeChart, } from 'echarts/charts'
} from 'echarts/charts';
import { import {
TitleComponent,
TooltipComponent,
GridComponent,
PolarComponent,
AriaComponent, AriaComponent,
ParallelComponent,
LegendComponent,
RadarComponent,
ToolboxComponent,
DataZoomComponent,
VisualMapComponent,
TimelineComponent,
CalendarComponent, CalendarComponent,
DataZoomComponent,
GridComponent,
LegendComponent,
MarkLineComponent, MarkLineComponent,
} from 'echarts/components'; ParallelComponent,
PolarComponent,
RadarComponent,
TimelineComponent,
TitleComponent,
ToolboxComponent,
TooltipComponent,
VisualMapComponent,
} from 'echarts/components'
import { SVGRenderer } from 'echarts/renderers'; import { SVGRenderer } from 'echarts/renderers'
echarts.use([ echarts.use([
LegendComponent, LegendComponent,
@ -52,6 +52,6 @@ echarts.use([
TimelineComponent, TimelineComponent,
CalendarComponent, CalendarComponent,
MarkLineComponent, MarkLineComponent,
]); ])
export default echarts; export default echarts

@ -1,9 +1,9 @@
const projectName = import.meta.env.VITE_GLOB_APP_TITLE; const projectName = import.meta.env.VITE_GLOB_APP_TITLE
export function warn(message: string) { export function warn(message: string) {
console.warn(`[${projectName} warn]:${message}`); console.warn(`[${projectName} warn]:${message}`)
} }
export function error(message: string) { export function error(message: string) {
throw new Error(`[${projectName} error]:${message}`); throw new Error(`[${projectName} error]:${message}`)
} }

@ -9,16 +9,17 @@
* ==>www.baidu.com?a=3&b=4 * ==>www.baidu.com?a=3&b=4
*/ */
export function setObjToUrlParams(baseUrl: string, obj: object): string { export function setObjToUrlParams(baseUrl: string, obj: object): string {
let parameters = ''; let parameters = ''
let url = ''; let url = ''
for (const key in obj) { for (const key in obj) {
parameters += key + '=' + encodeURIComponent(obj[key]) + '&'; parameters += `${key}=${encodeURIComponent(obj[key])}&`
} }
parameters = parameters.replace(/&$/, ''); parameters = parameters.replace(/&$/, '')
if (/\?$/.test(baseUrl)) { if (/\?$/.test(baseUrl)) {
url = baseUrl + parameters; url = baseUrl + parameters
} else {
url = baseUrl.replace(/\/?$/, '?') + parameters;
} }
return url; else {
url = baseUrl.replace(/\/?$/, '?') + parameters
}
return url
} }

@ -1,19 +1,23 @@
<template> <template>
<div class="flex flex-col justify-center items-center h-screen p-60px"> <div class="h-screen flex flex-col items-center justify-center p-60px">
<div class="wel-box flex flex-col items-center justify-between w-full"> <div class="wel-box w-full flex flex-col items-center justify-between">
<SvgIcon class="logo" :size="130" name="logo" /> <SvgIcon class="logo" :size="130" name="logo" />
<div class="text-darkBlue dark:text-garyWhite text-2xl font-black mt-12 mb-4 text-center" <div class="text-darkBlue dark:text-garyWhite mb-4 mt-12 text-center text-2xl font-black">
>欢迎来到 {{ title }}</div {{ title }}
> </div>
<div class="w-full mt-4 mb-6"> <div class="mb-6 mt-4 w-full">
<van-swipe class="h-30" :autoplay="3000" :indicator-color="designStore.appTheme"> <van-swipe class="h-30" :autoplay="3000" :indicator-color="designStore.appTheme">
<van-swipe-item <van-swipe-item
class="text-gray-700 dark:text-gray-400 leading-relaxed text-center"
v-for="(text, index) in getSwipeText" v-for="(text, index) in getSwipeText"
:key="index" :key="index"
class="text-center text-gray-700 leading-relaxed dark:text-gray-400"
> >
<p class="text-lg">{{ text.title }}</p> <p class="text-lg">
<p class="text-sm">{{ text.details }}</p> {{ text.title }}
</p>
<p class="text-sm">
{{ text.details }}
</p>
</van-swipe-item> </van-swipe-item>
</van-swipe> </van-swipe>
</div> </div>
@ -22,48 +26,48 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue'
import { useDesignSettingStore } from '@/store/modules/designSetting'; import { useDesignSettingStore } from '@/store/modules/designSetting'
import SvgIcon from '@/components/SvgIcon.vue'; import SvgIcon from '@/components/SvgIcon.vue'
import { useGlobSetting } from '@/hooks/setting'; import { useGlobSetting } from '@/hooks/setting'
defineOptions({ defineOptions({
name: 'DashboardPage', name: 'DashboardPage',
}); })
const designStore = useDesignSettingStore(); const designStore = useDesignSettingStore()
const globSetting = useGlobSetting(); const globSetting = useGlobSetting()
const { title } = globSetting; const { title } = globSetting
const getSwipeText = computed(() => { const getSwipeText = computed(() => {
return [ return [
{ {
title: '💡 最新技术栈', title: '💡 最新技术栈',
details: '基于Vue3、Vant4、Vite、TypeScript、UnoCSS等最新技术栈开发', details: '基于Vue3、Vant4、Vite、TypeScript、UnoCSS等最新技术栈开发',
}, },
{ {
title: '⚡️ 轻量快速的热重载', title: '⚡️ 轻量快速的热重载',
details: '无论应用程序大小如何都始终极快的模块热重载HMR', details: '无论应用程序大小如何都始终极快的模块热重载HMR',
}, },
{ {
title: '🔩 主题配置', title: '🔩 主题配置',
details: '具备主题配置及黑暗主题适配,且持久化保存', details: '具备主题配置及黑暗主题适配,且持久化保存',
}, },
{ {
title: '🛠️ 丰富的 Vite 插件', title: '🛠️ 丰富的 Vite 插件',
details: '集成大部分 Vite 插件,无需繁琐配置,开箱即用', details: '集成大部分 Vite 插件,无需繁琐配置,开箱即用',
}, },
{ {
title: '📊 内置 useEcharts hooks', title: '📊 内置 useEcharts hooks',
details: '满足大部分图表展示,只需要写你的 options', details: '满足大部分图表展示,只需要写你的 options',
}, },
{ {
title: '🥳 完善的登录系统、路由、Axios配置', title: '🥳 完善的登录系统、路由、Axios配置',
details: '所有架构已搭建完毕,你可以直接开发你的业务需求', details: '所有架构已搭建完毕,你可以直接开发你的业务需求',
}, },
]; ]
}); })
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -1,40 +1,45 @@
<template> <template>
<div class="flex flex-col justify-center page-container"> <div class="page-container flex flex-col justify-center">
<div class="text-center"> <div class="text-center">
<img src="~@/assets/icons/exception/403.svg" alt="" /> <img src="~@/assets/icons/exception/403.svg" alt="">
</div> </div>
<div class="text-center"> <div class="text-center">
<h1 class="text-base text-gray-500">抱歉你无权访问该页面</h1> <h1 class="text-base text-gray-500">
<n-button type="info" @click="goHome">回到首页</n-button> 抱歉你无权访问该页面
</h1>
<n-button type="info" @click="goHome">
回到首页
</n-button>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router'
const router = useRouter();
function goHome() { const router = useRouter()
router.push('/'); function goHome() {
} router.push('/')
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.page-container { .page-container {
width: 100%; width: 100%;
border-radius: 4px; border-radius: 4px;
padding: 50px 0; padding: 50px 0;
height: 100vh; height: 100vh;
.text-center { .text-center {
h1 { h1 {
color: #666; color: #666;
padding: 20px 0; padding: 20px 0;
}
}
img {
width: 350px;
margin: 0 auto;
} }
} }
img {
width: 350px;
margin: 0 auto;
}
}
</style> </style>

@ -1,37 +1,42 @@
<template> <template>
<div class="flex flex-col justify-center page-container"> <div class="page-container flex flex-col justify-center">
<div class="text-center"> <div class="text-center">
<img src="~@/assets/icons/exception/404.svg" alt="" /> <img src="~@/assets/icons/exception/404.svg" alt="">
</div> </div>
<div class="text-center"> <div class="text-center">
<h1 class="text-base text-gray-500">抱歉你访问的页面不存在</h1> <h1 class="text-base text-gray-500">
<van-button type="primary" @click="goHome">回到首页</van-button> 抱歉你访问的页面不存在
</h1>
<van-button type="primary" @click="goHome">
回到首页
</van-button>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router'
const router = useRouter();
function goHome() { const router = useRouter()
router.push('/'); function goHome() {
} router.push('/')
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.page-container { .page-container {
height: 100%; height: 100%;
.text-center { .text-center {
h1 { h1 {
color: #666; color: #666;
padding: 20px 0; padding: 20px 0;
}
}
img {
width: 350px;
margin: 0 auto;
} }
} }
img {
width: 350px;
margin: 0 auto;
}
}
</style> </style>

@ -1,40 +1,45 @@
<template> <template>
<div class="flex flex-col justify-center page-container"> <div class="page-container flex flex-col justify-center">
<div class="text-center"> <div class="text-center">
<img src="~@/assets/icons/exception/500.svg" alt="" /> <img src="~@/assets/icons/exception/500.svg" alt="">
</div> </div>
<div class="text-center"> <div class="text-center">
<h1 class="text-base text-gray-500">抱歉服务器出错了</h1> <h1 class="text-base text-gray-500">
<n-button type="info" @click="goHome">回到首页</n-button> 抱歉服务器出错了
</h1>
<n-button type="info" @click="goHome">
回到首页
</n-button>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router'
const router = useRouter();
function goHome() { const router = useRouter()
router.push('/'); function goHome() {
} router.push('/')
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.page-container { .page-container {
width: 100%; width: 100%;
border-radius: 4px; border-radius: 4px;
padding: 50px 0; padding: 50px 0;
height: 100vh; height: 100vh;
.text-center { .text-center {
h1 { h1 {
color: #666; color: #666;
padding: 20px 0; padding: 20px 0;
}
}
img {
width: 350px;
margin: 0 auto;
} }
} }
img {
width: 350px;
margin: 0 auto;
}
}
</style> </style>

@ -1,8 +1,8 @@
<template> <template>
<van-form ref="formRef" v-if="getShow" class="flex flex-col items-center" @submit="handleReset"> <van-form v-if="getShow" ref="formRef" class="flex flex-col items-center" @submit="handleReset">
<van-field <van-field
class="enter-y items-center mb-25px !rounded-md"
v-model="formData.username" v-model="formData.username"
class="enter-y mb-25px items-center !rounded-md"
name="username" name="username"
placeholder="用户名" placeholder="用户名"
:rules="getFormRules.username" :rules="getFormRules.username"
@ -15,8 +15,8 @@
</van-field> </van-field>
<van-field <van-field
class="enter-y items-center mb-25px !rounded-md"
v-model="formData.mobile" v-model="formData.mobile"
class="enter-y mb-25px items-center !rounded-md"
name="password" name="password"
placeholder="手机号码" placeholder="手机号码"
:rules="getFormRules.mobile" :rules="getFormRules.mobile"
@ -29,8 +29,8 @@
</van-field> </van-field>
<van-field <van-field
class="enter-y items-center mb-70px !rounded-md"
v-model="formData.sms" v-model="formData.sms"
class="enter-y mb-70px items-center !rounded-md"
center center
clearable clearable
placeholder="请输入短信验证码" placeholder="请输入短信验证码"
@ -42,7 +42,9 @@
</Icon> </Icon>
</template> </template>
<template #button> <template #button>
<van-button size="small" type="primary">发送验证码</van-button> <van-button size="small" type="primary">
发送验证码
</van-button>
</template> </template>
</van-field> </van-field>
@ -69,39 +71,40 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, ref, unref } from 'vue'; import { computed, reactive, ref, unref } from 'vue'
import type { FormInstance } from 'vant'; import type { FormInstance } from 'vant'
import { Icon } from '@vicons/utils'; import { Icon } from '@vicons/utils'
import { UserOutlined, MobileOutlined, EditOutlined } from '@vicons/antd'; import { EditOutlined, MobileOutlined, UserOutlined } from '@vicons/antd'
import { LoginStateEnum, useLoginState, useFormRules } from './useLogin'; import { LoginStateEnum, useFormRules, useLoginState } from './useLogin'
const { handleBackLogin, getLoginState } = useLoginState(); const { handleBackLogin, getLoginState } = useLoginState()
const { getFormRules } = useFormRules(); const { getFormRules } = useFormRules()
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD); const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
const loading = ref(false); const loading = ref(false)
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>()
const formData = reactive({ const formData = reactive({
username: '', username: '',
mobile: '', mobile: '',
sms: '', sms: '',
}); })
function handleReset() { function handleReset() {
formRef.value formRef.value
?.validate() ?.validate()
.then(async () => { .then(async () => {
try { try {
loading.value = true; loading.value = true
// do something // do something
} finally { }
loading.value = false; finally {
} loading.value = false
}) }
.catch(() => { })
console.error('验证失败'); .catch(() => {
}); console.error('验证失败')
} })
}
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div class="flex justify-center h-screen p-8"> <div class="h-screen flex justify-center p-8">
<div class="flex flex-col w-full"> <div class="w-full flex flex-col">
<LoginTitle /> <LoginTitle />
<LoginForm /> <LoginForm />
<ForgetPasswordForm /> <ForgetPasswordForm />
@ -13,18 +13,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import LoginTitle from './LoginTitle.vue'; import LoginTitle from './LoginTitle.vue'
import LoginForm from './LoginForm.vue'; import LoginForm from './LoginForm.vue'
import ForgetPasswordForm from './ForgetPasswordForm.vue'; import ForgetPasswordForm from './ForgetPasswordForm.vue'
import RegisterForm from './RegisterForm.vue'; import RegisterForm from './RegisterForm.vue'
import LoginWave from './LoginWave.vue'; import LoginWave from './LoginWave.vue'
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
:deep(.van-field__left-icon) { :deep(.van-field__left-icon) {
display: flex; display: flex;
} }
:deep(.van-field__right-icon) { :deep(.van-field__right-icon) {
display: flex; display: flex;
} }
</style> </style>

@ -1,8 +1,8 @@
<template> <template>
<van-form ref="formRef" v-if="getShow" class="flex flex-col items-center" @submit="handleSubmit"> <van-form v-if="getShow" ref="formRef" class="flex flex-col items-center" @submit="handleSubmit">
<van-field <van-field
class="enter-y items-center mb-25px !rounded-md"
v-model="formData.username" v-model="formData.username"
class="enter-y mb-25px items-center !rounded-md"
name="username" name="username"
placeholder="用户名" placeholder="用户名"
:rules="getFormRules.username" :rules="getFormRules.username"
@ -14,8 +14,8 @@
</template> </template>
</van-field> </van-field>
<van-field <van-field
class="enter-y items-center mb-25px !rounded-md"
v-model="formData.password" v-model="formData.password"
class="enter-y mb-25px items-center !rounded-md"
:type="switchPassType ? 'password' : 'text'" :type="switchPassType ? 'password' : 'text'"
name="password" name="password"
placeholder="密码" placeholder="密码"
@ -37,16 +37,16 @@
</template> </template>
</van-field> </van-field>
<div class="enter-y w-full px-5px flex justify-between mb-100px"> <div class="enter-y mb-100px w-full flex justify-between px-5px">
<div class="flex items-center"> <div class="flex items-center">
<van-switch class="mr-8px !text-30px" v-model="rememberMe" /> <van-switch v-model="rememberMe" class="mr-8px !text-30px" />
<span class="!text-25px">记住我</span> <span class="!text-25px">记住我</span>
</div> </div>
<a class="!text-25px" @click="setLoginState(LoginStateEnum.RESET_PASSWORD)">忘记密码?</a> <a class="!text-25px" @click="setLoginState(LoginStateEnum.RESET_PASSWORD)">忘记密码?</a>
</div> </div>
<van-button <van-button
class="enter-y !rounded-md !mb-25px" class="enter-y !mb-25px !rounded-md"
type="primary" type="primary"
block block
native-type="submit" native-type="submit"
@ -67,64 +67,69 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, reactive, ref, unref } from 'vue'; import { computed, onMounted, reactive, ref, unref } from 'vue'
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router'
import { showFailToast, showLoadingToast, showSuccessToast } from 'vant'; import { showFailToast, showLoadingToast, showSuccessToast } from 'vant'
import type { FormInstance } from 'vant'; import type { FormInstance } from 'vant'
import { Icon } from '@vicons/utils'; import { Icon } from '@vicons/utils'
import { UserOutlined, LockOutlined, EyeOutlined, EyeInvisibleOutlined } from '@vicons/antd'; import { EyeInvisibleOutlined, EyeOutlined, LockOutlined, UserOutlined } from '@vicons/antd'
import { useUserStore } from '@/store/modules/user'; import { LoginStateEnum, useFormRules, useLoginState } from './useLogin'
import { ResultEnum } from '@/enums/httpEnum'; import { useUserStore } from '@/store/modules/user'
import { PageEnum } from '@/enums/pageEnum'; import { ResultEnum } from '@/enums/httpEnum'
import { LoginStateEnum, useLoginState, useFormRules } from './useLogin'; import { PageEnum } from '@/enums/pageEnum'
const { setLoginState, getLoginState } = useLoginState(); const { setLoginState, getLoginState } = useLoginState()
const { getFormRules } = useFormRules(); const { getFormRules } = useFormRules()
const userStore = useUserStore(); const userStore = useUserStore()
const router = useRouter(); const router = useRouter()
const route = useRoute(); const route = useRoute()
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>()
const loading = ref(false); const loading = ref(false)
const rememberMe = ref(false); const rememberMe = ref(false)
const switchPassType = ref(true); const switchPassType = ref(true)
const formData = reactive({ const formData = reactive({
username: 'admin', username: 'admin',
password: '123456', password: '123456',
}); })
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN); const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
function handleSubmit() { function handleSubmit() {
formRef.value formRef.value
?.validate() ?.validate()
.then(async () => { .then(async () => {
try { try {
loading.value = true; loading.value = true
showLoadingToast('登录中...'); showLoadingToast('登录中...')
const { code, message: msg } = await userStore.Login({ const { code, message: msg } = await userStore.Login({
username: formData.username, username: formData.username,
password: formData.password, password: formData.password,
}); })
if (code == ResultEnum.SUCCESS) { if (code === ResultEnum.SUCCESS) {
const toPath = decodeURIComponent((route.query?.redirect || '/') as string); const toPath = decodeURIComponent((route.query?.redirect || '/') as string)
showSuccessToast('登录成功,即将进入系统'); showSuccessToast('登录成功,即将进入系统')
if (route.name === PageEnum.BASE_LOGIN_NAME) { if (route.name === PageEnum.BASE_LOGIN_NAME) {
router.replace('/'); router.replace('/')
} else router.replace(toPath); }
} else { else {
showFailToast(msg || '登录失败'); router.replace(toPath)
} }
} finally {
loading.value = false;
} }
}) else {
.catch(() => { showFailToast(msg || '登录失败')
console.error('验证失败'); }
}); }
} finally {
loading.value = false
}
})
.catch(() => {
console.error('验证失败')
})
}
onMounted(() => {}); onMounted(() => {})
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -1,20 +1,20 @@
<template> <template>
<div class="flex flex-col items-center justify-center"> <div class="flex flex-col items-center justify-center">
<div class="logo my-35px enter-y"> <div class="logo enter-y my-35px">
<SvgIcon class="!h-250px !w-250px" name="logo" /> <SvgIcon class="!h-250px !w-250px" name="logo" />
</div> </div>
<div class="mb-80px text-darkBlue dark:text-garyWhite text-45px font-black enter-y"> <div class="text-darkBlue dark:text-garyWhite enter-y mb-80px text-45px font-black">
{{ title }} {{ title }}
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useGlobSetting } from '@/hooks/setting'; import { useGlobSetting } from '@/hooks/setting'
const globSetting = useGlobSetting(); const globSetting = useGlobSetting()
const { title } = globSetting; const { title } = globSetting
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -1,5 +1,5 @@
<template> <template>
<div class="enter-y fixed bottom-0 w-full !-z-5 wave-wrapper"> <div class="enter-y wave-wrapper fixed bottom-0 w-full !-z-5">
<svg <svg
class="ignore-waves" class="ignore-waves"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -45,61 +45,61 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useDesignSettingStore } from '@/store/modules/designSetting'; import { useDesignSettingStore } from '@/store/modules/designSetting'
import { hexToRgba } from '@/utils/index'; import { hexToRgba } from '@/utils/index'
const designStore = useDesignSettingStore(); const designStore = useDesignSettingStore()
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.wave-wrapper { .wave-wrapper {
position: fixed; position: fixed;
width: 100%; width: 100%;
left: 0; left: 0;
bottom: 0; bottom: 0;
} }
.ignore-waves { .ignore-waves {
position: relative; position: relative;
display: block; display: block;
width: 100%; width: 100%;
height: 50px; height: 50px;
min-height: 40px; min-height: 40px;
max-height: 80px; max-height: 80px;
}
/* Animation */
.parallax > use {
animation: move-forever 12s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
animation: move-forever 12s linear infinite;
}
.parallax > use:nth-child(1) {
animation-delay: -2s;
animation-duration: 7s;
}
.parallax > use:nth-child(2) {
animation-delay: -3s;
animation-duration: 10s;
}
.parallax > use:nth-child(3) {
animation-delay: -4s;
animation-duration: 13s;
}
.parallax > use:nth-child(4) {
animation-delay: -5s;
animation-duration: 16s;
}
@keyframes move-forever {
0% {
transform: translate3d(-90px, 0, 0);
} }
/* Animation */ 100% {
.parallax > use { transform: translate3d(85px, 0, 0);
animation: move-forever 12s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
animation: move-forever 12s linear infinite;
}
.parallax > use:nth-child(1) {
animation-delay: -2s;
animation-duration: 7s;
}
.parallax > use:nth-child(2) {
animation-delay: -3s;
animation-duration: 10s;
}
.parallax > use:nth-child(3) {
animation-delay: -4s;
animation-duration: 13s;
}
.parallax > use:nth-child(4) {
animation-delay: -5s;
animation-duration: 16s;
}
@keyframes move-forever {
0% {
transform: translate3d(-90px, 0, 0);
}
100% {
transform: translate3d(85px, 0, 0);
}
} }
}
</style> </style>

@ -1,9 +1,9 @@
<template> <template>
<van-form ref="formRef" v-if="getShow" class="flex flex-col" @submit="handleRegister"> <van-form v-if="getShow" ref="formRef" class="flex flex-col" @submit="handleRegister">
<van-cell-group inset class="enter-y !mx-0 !mb-60px"> <van-cell-group inset class="enter-y !mx-0 !mb-60px">
<van-field <van-field
class="enter-y items-center !rounded-md"
v-model="formData.username" v-model="formData.username"
class="enter-y items-center !rounded-md"
name="username" name="username"
placeholder="用户名" placeholder="用户名"
:rules="getFormRules.username" :rules="getFormRules.username"
@ -16,8 +16,8 @@
</van-field> </van-field>
<van-field <van-field
class="enter-y items-center !rounded-md"
v-model="formData.mobile" v-model="formData.mobile"
class="enter-y items-center !rounded-md"
name="password" name="password"
placeholder="手机号码" placeholder="手机号码"
:rules="getFormRules.mobile" :rules="getFormRules.mobile"
@ -30,8 +30,8 @@
</van-field> </van-field>
<van-field <van-field
class="enter-y items-center !rounded-md"
v-model="formData.sms" v-model="formData.sms"
class="enter-y items-center !rounded-md"
center center
clearable clearable
placeholder="请输入短信验证码" placeholder="请输入短信验证码"
@ -43,13 +43,15 @@
</Icon> </Icon>
</template> </template>
<template #button> <template #button>
<van-button size="small" type="primary">发送验证码</van-button> <van-button size="small" type="primary">
发送验证码
</van-button>
</template> </template>
</van-field> </van-field>
<van-field <van-field
class="enter-y items-center !rounded-md"
v-model="formData.password" v-model="formData.password"
class="enter-y items-center !rounded-md"
:type="switchPassType ? 'password' : 'text'" :type="switchPassType ? 'password' : 'text'"
name="password" name="password"
placeholder="密码" placeholder="密码"
@ -72,8 +74,8 @@
</van-field> </van-field>
<van-field <van-field
class="enter-y items-center !rounded-md"
v-model="formData.confirmPassword" v-model="formData.confirmPassword"
class="enter-y items-center !rounded-md"
:type="switchConfirmPassType ? 'password' : 'text'" :type="switchConfirmPassType ? 'password' : 'text'"
name="confirmPassword" name="confirmPassword"
placeholder="确认密码" placeholder="确认密码"
@ -131,58 +133,59 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, ref, unref } from 'vue'; import { computed, reactive, ref, unref } from 'vue'
import type { FormInstance } from 'vant'; import type { FormInstance } from 'vant'
import { Icon } from '@vicons/utils'; import { Icon } from '@vicons/utils'
import { import {
UserOutlined, EditOutlined,
MobileOutlined, EyeInvisibleOutlined,
EditOutlined, EyeOutlined,
LockOutlined, LockOutlined,
EyeOutlined, MobileOutlined,
EyeInvisibleOutlined, UserOutlined,
} from '@vicons/antd'; } from '@vicons/antd'
import { LoginStateEnum, useLoginState, useFormRules } from './useLogin'; import { LoginStateEnum, useFormRules, useLoginState } from './useLogin'
const { handleBackLogin, getLoginState } = useLoginState(); const { handleBackLogin, getLoginState } = useLoginState()
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER); const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
const loading = ref(false); const loading = ref(false)
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>()
const formData = reactive({ const formData = reactive({
username: '', username: '',
mobile: '', mobile: '',
sms: '', sms: '',
password: '', password: '',
confirmPassword: '', confirmPassword: '',
policy: false, policy: false,
}); })
const { getFormRules } = useFormRules(formData); const { getFormRules } = useFormRules(formData)
const switchPassType = ref(true); const switchPassType = ref(true)
const switchConfirmPassType = ref(true); const switchConfirmPassType = ref(true)
function handleRegister() { function handleRegister() {
formRef.value formRef.value
?.validate() ?.validate()
.then(async () => { .then(async () => {
try { try {
loading.value = true; loading.value = true
// do something // do something
console.log('%c [ ]-167', 'font-size:13px; background:pink; color:#bf2c9f;'); console.log('%c [ ]-167', 'font-size:13px; background:pink; color:#bf2c9f;')
} finally { }
loading.value = false; finally {
loading.value = false
console.log('%c [ ]-171', 'font-size:13px; background:pink; color:#bf2c9f;'); console.log('%c [ ]-171', 'font-size:13px; background:pink; color:#bf2c9f;')
} }
}) })
.catch(() => { .catch(() => {
console.error('验证失败'); console.error('验证失败')
}); })
} }
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -1,5 +1,5 @@
import type { FieldRule } from 'vant'; import type { FieldRule } from 'vant'
import { computed, ref, unref } from 'vue'; import { computed, ref, unref } from 'vue'
export enum LoginStateEnum { export enum LoginStateEnum {
LOGIN, LOGIN,
@ -7,54 +7,54 @@ export enum LoginStateEnum {
RESET_PASSWORD, RESET_PASSWORD,
} }
const currentState = ref(LoginStateEnum.LOGIN); const currentState = ref(LoginStateEnum.LOGIN)
export function useLoginState() { export function useLoginState() {
function setLoginState(state: LoginStateEnum) { function setLoginState(state: LoginStateEnum) {
currentState.value = state; currentState.value = state
} }
const getLoginState = computed(() => currentState.value); const getLoginState = computed(() => currentState.value)
function handleBackLogin() { function handleBackLogin() {
setLoginState(LoginStateEnum.LOGIN); setLoginState(LoginStateEnum.LOGIN)
} }
return { setLoginState, getLoginState, handleBackLogin }; return { setLoginState, getLoginState, handleBackLogin }
} }
export function useFormRules(formData?: Recordable) { export function useFormRules(formData?: Recordable) {
const getUsernameFormRule = computed(() => createRule('请输入用户名')); const getUsernameFormRule = computed(() => createRule('请输入用户名'))
const getPasswordFormRule = computed(() => createRule('请输入密码')); const getPasswordFormRule = computed(() => createRule('请输入密码'))
const getSmsFormRule = computed(() => createRule('请输入短信验证码')); const getSmsFormRule = computed(() => createRule('请输入短信验证码'))
const getMobileFormRule = computed(() => createRule('请输入手机号码')); const getMobileFormRule = computed(() => createRule('请输入手机号码'))
const validatePolicy = async (value: any, _: FieldRule) => { const validatePolicy = async (value: any, _: FieldRule) => {
return !value ? Promise.resolve('勾选后才能注册') : Promise.resolve(true); return !value ? Promise.resolve('勾选后才能注册') : Promise.resolve(true)
}; }
const validateConfirmPassword = (password: string) => { const validateConfirmPassword = (password: string) => {
return async (value: string) => { return async (value: string) => {
if (!value) { if (!value) {
return Promise.resolve('请输入确认密码'); return Promise.resolve('请输入确认密码')
} }
if (value !== password) { if (value !== password) {
return Promise.resolve('两次输入密码不一致'); return Promise.resolve('两次输入密码不一致')
} }
return Promise.resolve(true); return Promise.resolve(true)
}; }
}; }
const getFormRules = computed((): { [k: string]: FieldRule[] } => { const getFormRules = computed((): { [k: string]: FieldRule[] } => {
const usernameFormRule = unref(getUsernameFormRule); const usernameFormRule = unref(getUsernameFormRule)
const passwordFormRule = unref(getPasswordFormRule); const passwordFormRule = unref(getPasswordFormRule)
const smsFormRule = unref(getSmsFormRule); const smsFormRule = unref(getSmsFormRule)
const mobileFormRule = unref(getMobileFormRule); const mobileFormRule = unref(getMobileFormRule)
const mobileRule = { const mobileRule = {
sms: smsFormRule, sms: smsFormRule,
mobile: mobileFormRule, mobile: mobileFormRule,
}; }
switch (unref(currentState)) { switch (unref(currentState)) {
// register form rules // register form rules
case LoginStateEnum.REGISTER: case LoginStateEnum.REGISTER:
@ -66,24 +66,24 @@ export function useFormRules(formData?: Recordable) {
], ],
policy: [{ validator: validatePolicy, trigger: 'onBlur' }], policy: [{ validator: validatePolicy, trigger: 'onBlur' }],
...mobileRule, ...mobileRule,
}; }
// reset password form rules // reset password form rules
case LoginStateEnum.RESET_PASSWORD: case LoginStateEnum.RESET_PASSWORD:
return { return {
username: usernameFormRule, username: usernameFormRule,
...mobileRule, ...mobileRule,
}; }
// login form rules // login form rules
default: default:
return { return {
username: usernameFormRule, username: usernameFormRule,
password: passwordFormRule, password: passwordFormRule,
}; }
} }
}); })
return { getFormRules }; return { getFormRules }
} }
function createRule(message: string): FieldRule[] { function createRule(message: string): FieldRule[] {
@ -93,5 +93,5 @@ function createRule(message: string): FieldRule[] {
message, message,
trigger: 'onBlur', trigger: 'onBlur',
}, },
]; ]
} }

@ -1,106 +1,107 @@
<template> <template>
<div class="my-card m-40px p-30px rounded-2xl shadow-xl"> <div class="my-card m-40px rounded-2xl p-30px shadow-xl">
<div ref="chartRef" :style="{ height: '350px' }"></div> <div ref="chartRef" :style="{ height: '350px' }" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useECharts } from '@/hooks/web/useECharts'; import type { Ref } from 'vue'
import { onMounted, ref, Ref } from 'vue'; import { onMounted, ref } from 'vue'
import type { EChartsOption } from 'echarts'; import type { EChartsOption } from 'echarts'
import { useECharts } from '@/hooks/web/useECharts'
const chartRef = ref<HTMLDivElement | null>(null); const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>); const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
const chartOptions: EChartsOption = { const chartOptions: EChartsOption = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
// Use axis to trigger tooltip // Use axis to trigger tooltip
type: 'shadow', // 'shadow' as default; can also be 'line' or 'shadow' type: 'shadow', // 'shadow' as default; can also be 'line' or 'shadow'
},
}, },
legend: {}, },
grid: { legend: {},
left: '1%', grid: {
right: '7%', left: '1%',
bottom: '3%', right: '7%',
containLabel: true, bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'value',
},
yAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
series: [
{
name: 'Direct',
type: 'bar',
stack: 'total',
label: {
show: true,
},
emphasis: {
focus: 'series',
},
data: [320, 302, 301, 334, 390, 330, 320],
}, },
xAxis: { {
type: 'value', name: 'Mail Ad',
type: 'bar',
stack: 'total',
label: {
show: true,
},
emphasis: {
focus: 'series',
},
data: [120, 132, 101, 134, 90, 230, 210],
}, },
yAxis: { {
type: 'category', name: 'Affiliate Ad',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], type: 'bar',
stack: 'total',
label: {
show: true,
},
emphasis: {
focus: 'series',
},
data: [220, 182, 191, 234, 290, 330, 310],
}, },
series: [ {
{ name: 'Video Ad',
name: 'Direct', type: 'bar',
type: 'bar', stack: 'total',
stack: 'total', label: {
label: { show: true,
show: true,
},
emphasis: {
focus: 'series',
},
data: [320, 302, 301, 334, 390, 330, 320],
}, },
{ emphasis: {
name: 'Mail Ad', focus: 'series',
type: 'bar',
stack: 'total',
label: {
show: true,
},
emphasis: {
focus: 'series',
},
data: [120, 132, 101, 134, 90, 230, 210],
}, },
{ data: [150, 212, 201, 154, 190, 330, 410],
name: 'Affiliate Ad', },
type: 'bar', {
stack: 'total', name: 'Search Engine',
label: { type: 'bar',
show: true, stack: 'total',
}, label: {
emphasis: { show: true,
focus: 'series',
},
data: [220, 182, 191, 234, 290, 330, 310],
}, },
{ emphasis: {
name: 'Video Ad', focus: 'series',
type: 'bar',
stack: 'total',
label: {
show: true,
},
emphasis: {
focus: 'series',
},
data: [150, 212, 201, 154, 190, 330, 410],
}, },
{ data: [820, 832, 901, 934, 1290, 1330, 1320],
name: 'Search Engine', },
type: 'bar', ],
stack: 'total', }
label: {
show: true,
},
emphasis: {
focus: 'series',
},
data: [820, 832, 901, 934, 1290, 1330, 1320],
},
],
};
onMounted(() => { onMounted(() => {
setOptions(chartOptions); setOptions(chartOptions)
}); })
</script> </script>
<style scoped></style> <style scoped></style>

@ -7,9 +7,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import lineChart from './lineChart.vue'; import lineChart from './lineChart.vue'
import barChart from './barChart.vue'; import barChart from './barChart.vue'
import pieChart from './pieChart.vue'; import pieChart from './pieChart.vue'
</script> </script>
<style scoped></style> <style scoped></style>

@ -1,119 +1,120 @@
<template> <template>
<div class="my-card m-40px p-30px rounded-2xl shadow-xl"> <div class="my-card m-40px rounded-2xl p-30px shadow-xl">
<div ref="chartRef" :style="{ height: '350px' }"></div> <div ref="chartRef" :style="{ height: '350px' }" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useECharts } from '@/hooks/web/useECharts'; import type { Ref } from 'vue'
import { onMounted, ref, Ref } from 'vue'; import { onMounted, ref } from 'vue'
import type { EChartsOption } from 'echarts'; import type { EChartsOption } from 'echarts'
import { useECharts } from '@/hooks/web/useECharts'
const chartRef = ref<HTMLDivElement | null>(null); const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>); const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
const chartOptions: EChartsOption = { const chartOptions: EChartsOption = {
title: { title: {
text: 'Stacked Area Chart', text: 'Stacked Area Chart',
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
axisPointer: { axisPointer: {
type: 'cross', type: 'cross',
label: { label: {
backgroundColor: '#6a7985', backgroundColor: '#6a7985',
},
}, },
}, },
legend: { },
data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine'], legend: {
top: '10%', data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine'],
top: '10%',
},
toolbox: {
feature: {
saveAsImage: {},
}, },
toolbox: { },
feature: { grid: {
saveAsImage: {}, top: '30%',
}, left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
}, },
grid: { ],
top: '30%', yAxis: [
left: '3%', {
right: '4%', type: 'value',
bottom: '3%',
containLabel: true,
}, },
xAxis: [ ],
{ series: [
type: 'category', {
boundaryGap: false, name: 'Email',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], type: 'line',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series',
}, },
], data: [120, 132, 101, 134, 90, 230, 210],
yAxis: [ },
{ {
type: 'value', name: 'Union Ads',
type: 'line',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series',
}, },
], data: [220, 182, 191, 234, 290, 330, 310],
series: [ },
{ {
name: 'Email', name: 'Video Ads',
type: 'line', type: 'line',
stack: 'Total', stack: 'Total',
areaStyle: {}, areaStyle: {},
emphasis: { emphasis: {
focus: 'series', focus: 'series',
},
data: [120, 132, 101, 134, 90, 230, 210],
}, },
{ data: [150, 232, 201, 154, 190, 330, 410],
name: 'Union Ads', },
type: 'line', {
stack: 'Total', name: 'Direct',
areaStyle: {}, type: 'line',
emphasis: { stack: 'Total',
focus: 'series', areaStyle: {},
}, emphasis: {
data: [220, 182, 191, 234, 290, 330, 310], focus: 'series',
}, },
{ data: [320, 332, 301, 334, 390, 330, 320],
name: 'Video Ads', },
type: 'line', {
stack: 'Total', name: 'Search Engine',
areaStyle: {}, type: 'line',
emphasis: { stack: 'Total',
focus: 'series', label: {
}, show: true,
data: [150, 232, 201, 154, 190, 330, 410], position: 'top',
}, },
{ areaStyle: {},
name: 'Direct', emphasis: {
type: 'line', focus: 'series',
stack: 'Total',
areaStyle: {},
emphasis: {
focus: 'series',
},
data: [320, 332, 301, 334, 390, 330, 320],
}, },
{ data: [820, 932, 901, 934, 1290, 1330, 1320],
name: 'Search Engine', },
type: 'line', ],
stack: 'Total', }
label: {
show: true,
position: 'top',
},
areaStyle: {},
emphasis: {
focus: 'series',
},
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
};
onMounted(() => { onMounted(() => {
setOptions(chartOptions); setOptions(chartOptions)
}); })
</script> </script>
<style scoped></style> <style scoped></style>

@ -1,65 +1,66 @@
<template> <template>
<div class="my-card m-40px p-30px rounded-2xl shadow-xl"> <div class="my-card m-40px rounded-2xl p-30px shadow-xl">
<div ref="chartRef" :style="{ height: '350px' }"></div> <div ref="chartRef" :style="{ height: '350px' }" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useECharts } from '@/hooks/web/useECharts'; import type { Ref } from 'vue'
import { onMounted, ref, Ref } from 'vue'; import { onMounted, ref } from 'vue'
import type { EChartsOption } from 'echarts'; import type { EChartsOption } from 'echarts'
import { useECharts } from '@/hooks/web/useECharts'
const chartRef = ref<HTMLDivElement | null>(null); const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>); const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
const chartOptions: EChartsOption = { const chartOptions: EChartsOption = {
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
}, },
legend: { legend: {
top: '5%', top: '5%',
left: 'center', left: 'center',
}, },
series: [ series: [
{ {
name: 'Access From', name: 'Access From',
type: 'pie', type: 'pie',
radius: ['40%', '70%'], radius: ['40%', '70%'],
center: ['50%', '60%'], center: ['50%', '60%'],
avoidLabelOverlap: false, avoidLabelOverlap: false,
itemStyle: { itemStyle: {
borderRadius: 10, borderRadius: 10,
borderColor: '#fff', borderColor: '#fff',
borderWidth: 2, borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '40',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' },
],
}, },
], label: {
}; show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '40',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' },
],
},
],
}
onMounted(() => { onMounted(() => {
setOptions(chartOptions); setOptions(chartOptions)
}); })
</script> </script>
<style scoped></style> <style scoped></style>

@ -2,15 +2,16 @@
<div> <div>
<NavBar /> <NavBar />
<van-field <van-field
v-model="username"
label="用户名" label="用户名"
readonly readonly
label-class="font-bold" label-class="font-bold"
input-align="right" input-align="right"
:center="true" :center="true"
:border="false" :border="false"
v-model="username"
/> />
<van-field <van-field
v-model="afterPhone"
label="手机号" label="手机号"
readonly readonly
is-link is-link
@ -18,7 +19,6 @@
input-align="right" input-align="right"
:center="true" :center="true"
:border="false" :border="false"
v-model="afterPhone"
/> />
<van-field <van-field
label="修改登录密码" label="修改登录密码"
@ -34,23 +34,23 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import NavBar from './components/NavBar.vue'; import { computed } from 'vue'
import { useUserStore } from '@/store/modules/user'; import NavBar from './components/NavBar.vue'
import { computed } from 'vue'; import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore(); const userStore = useUserStore()
const { username, phone } = userStore.getUserInfo; const { username, phone } = userStore.getUserInfo
const phoneDesensitize = (phone: string) => { function phoneDesensitize(phone: string) {
const reg = /(\d{3})\d{4}(\d{4})/; const reg = /(\d{3})\d{4}(\d{4})/
return phone.replace(reg, '$1****$2'); return phone.replace(reg, '$1****$2')
}; }
const afterPhone = computed(() => phoneDesensitize(phone)); const afterPhone = computed(() => phoneDesensitize(phone))
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
:deep(.van-field__control) { :deep(.van-field__control) {
color: var(--van-text-color-2); color: var(--van-text-color-2);
} }
</style> </style>

@ -1,12 +1,12 @@
<template> <template>
<div> <div>
<NavBar /> <NavBar />
修改登录密码页面 <p>修改登录密码页面</p>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import NavBar from './components/NavBar.vue'; import NavBar from './components/NavBar.vue'
</script> </script>
<style scoped></style> <style scoped></style>

@ -1,13 +1,15 @@
<template> <template>
<div> <div>
<NavBar> <NavBar>
<template #right><span class="text-32px" @click="handleNickname">保存</span></template> <template #right>
<span class="text-32px" @click="handleNickname">保存</span>
</template>
</NavBar> </NavBar>
<van-form ref="formRef"> <van-form ref="formRef">
<van-field <van-field
v-model="formValue.nickname"
class="mt-20px" class="mt-20px"
name="nickname" name="nickname"
v-model="formValue.nickname"
placeholder="请输入昵称2-12字" placeholder="请输入昵称2-12字"
:rules="[ :rules="[
{ {
@ -26,62 +28,63 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import NavBar from './components/NavBar.vue'; import { onMounted, reactive, ref } from 'vue'
import { useUserStore } from '@/store/modules/user'; import type { FormInstance } from 'vant'
import { onMounted, reactive, ref } from 'vue'; import { showToast } from 'vant'
import type { FormInstance } from 'vant'; import NavBar from './components/NavBar.vue'
import { showToast } from 'vant'; import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore(); const userStore = useUserStore()
const { nickname } = userStore.getUserInfo; const { nickname } = userStore.getUserInfo
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>()
const formValue = reactive({ const formValue = reactive({
nickname: '', nickname: '',
}); })
const validateNickname = () => { function validateNickname() {
return async (value: string) => { return async (value: string) => {
const pattern = /^[\u4E00-\u9FA5A-Za-z0-9-_.·]+$/; const pattern = /^[\u4E00-\u9FA5A-Za-z0-9-_.·]+$/
if (!pattern.test(value)) { if (!pattern.test(value)) {
return Promise.resolve('请输入正确内容'); return Promise.resolve('请输入正确内容')
} }
if (value.length < 2 || value.length > 12) { if (value.length < 2 || value.length > 12) {
return Promise.resolve('长度不符合'); return Promise.resolve('长度不符合')
} }
return Promise.resolve(true); return Promise.resolve(true)
};
};
function handleNickname() {
formRef.value
?.validate()
.then(async () => {
try {
const formValue = formRef.value?.getValues();
showToast({
message: `当前表单值:${JSON.stringify(formValue)}`,
});
// do something
} finally {
// after successful
}
})
.catch(() => {
console.error('验证失败');
});
} }
}
onMounted(() => { function handleNickname() {
formValue.nickname = nickname; formRef.value
}); ?.validate()
.then(async () => {
try {
const formValue = formRef.value?.getValues()
showToast({
message: `当前表单值:${JSON.stringify(formValue)}`,
})
// do something
}
finally {
// after successful
}
})
.catch(() => {
console.error('验证失败')
})
}
onMounted(() => {
formValue.nickname = nickname
})
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.note { .note {
margin-top: 15px; margin-top: 15px;
font-size: 25px; font-size: 25px;
color: var(--van-text-color-2); color: var(--van-text-color-2);
} }
</style> </style>

@ -1,14 +1,16 @@
<template> <template>
<div> <div>
<NavBar> <NavBar>
<template #right><span class="text-32px" @click="handleNickname">保存</span></template> <template #right>
<span class="text-32px" @click="handleNickname">保存</span>
</template>
</NavBar> </NavBar>
<van-form ref="formRef"> <van-form ref="formRef">
<van-field <van-field
v-model="formValue.sign"
class="mt-20px" class="mt-20px"
name="sign" name="sign"
clearable clearable
v-model="formValue.sign"
rows="4" rows="4"
autosize autosize
label="签名" label="签名"
@ -22,49 +24,50 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import NavBar from './components/NavBar.vue'; import { onMounted, reactive, ref } from 'vue'
import { useUserStore } from '@/store/modules/user'; import type { FormInstance } from 'vant'
import { onMounted, reactive, ref } from 'vue'; import { showToast } from 'vant'
import type { FormInstance } from 'vant'; import NavBar from './components/NavBar.vue'
import { showToast } from 'vant'; import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore(); const userStore = useUserStore()
const { sign } = userStore.getUserInfo; const { sign } = userStore.getUserInfo
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>()
const formValue = reactive({ const formValue = reactive({
sign: '', sign: '',
}); })
function handleNickname() { function handleNickname() {
formRef.value formRef.value
?.validate() ?.validate()
.then(async () => { .then(async () => {
try { try {
const formValue = formRef.value?.getValues(); const formValue = formRef.value?.getValues()
showToast({ showToast({
message: `当前表单值:${JSON.stringify(formValue)}`, message: `当前表单值:${JSON.stringify(formValue)}`,
}); })
// do something // do something
} finally { }
// after successful finally {
} // after successful
}) }
.catch(() => { })
console.error('验证失败'); .catch(() => {
}); console.error('验证失败')
} })
}
onMounted(() => { onMounted(() => {
formValue.sign = sign ?? ''; formValue.sign = sign ?? ''
}); })
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.note { .note {
margin-top: 15px; margin-top: 15px;
font-size: 25px; font-size: 25px;
color: var(--van-text-color-2); color: var(--van-text-color-2);
} }
</style> </style>

@ -19,37 +19,37 @@
</van-field> </van-field>
<van-field <van-field
v-model="state.nickname"
label="昵称" label="昵称"
readonly readonly
label-class="font-bold" label-class="font-bold"
input-align="right" input-align="right"
:center="true" :center="true"
:border="false" :border="false"
v-model="state.nickname"
is-link is-link
to="/editNickname" to="/editNickname"
/> />
<van-field <van-field
v-model="state.genderText"
label="性别" label="性别"
readonly readonly
label-class="font-bold" label-class="font-bold"
input-align="right" input-align="right"
:center="true" :center="true"
:border="false" :border="false"
v-model="state.genderText"
is-link is-link
@click="showGenderPicker = true" @click="showGenderPicker = true"
/> />
<van-field <van-field
v-model="state.sign"
label="签名" label="签名"
readonly readonly
label-class="font-bold" label-class="font-bold"
input-align="right" input-align="right"
:center="true" :center="true"
:border="false" :border="false"
v-model="state.sign"
is-link is-link
to="/editSign" to="/editSign"
/> />
@ -71,21 +71,21 @@
</van-field> </van-field>
<van-field <van-field
v-model="state.industryText"
label="行业" label="行业"
readonly readonly
label-class="font-bold" label-class="font-bold"
input-align="right" input-align="right"
:center="true" :center="true"
:border="false" :border="false"
v-model="state.industryText"
is-link is-link
@click="showIndustryPicker = true" @click="showIndustryPicker = true"
/> />
<van-popup v-model:show="showGenderPicker" position="bottom" round> <van-popup v-model:show="showGenderPicker" position="bottom" round>
<van-picker <van-picker
visible-option-num="3"
v-model="state.genderValues" v-model="state.genderValues"
visible-option-num="3"
:columns="genderColumns" :columns="genderColumns"
@confirm="handleGender" @confirm="handleGender"
@cancel="showGenderPicker = false" @cancel="showGenderPicker = false"
@ -104,78 +104,80 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue'
import NavBar from './components/NavBar.vue'; import { showToast } from 'vant'
import UploaderImage from './components/UploaderImage.vue'; import NavBar from './components/NavBar.vue'
import { useUserStore } from '@/store/modules/user'; import UploaderImage from './components/UploaderImage.vue'
import { FormColumns, genderColumns, industryColumns } from './pickColumns'; import type { FormColumns } from './pickColumns'
import { showToast } from 'vant'; import { genderColumns, industryColumns } from './pickColumns'
import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore(); const userStore = useUserStore()
const { avatar, gender, industry, cover } = userStore.getUserInfo; const { avatar, gender, industry, cover } = userStore.getUserInfo
const showGenderPicker = ref(false); const showGenderPicker = ref(false)
const showIndustryPicker = ref(false); const showIndustryPicker = ref(false)
const state = reactive({ const state = reactive({
nickname: '', nickname: '',
sign: '', sign: '',
// the field v-model // the field v-model
genderText: '', genderText: '',
industryText: '', industryText: '',
// the pick v-model // the pick v-model
industryValues: [0], industryValues: [0],
genderValues: [0], genderValues: [0],
}); })
const handleGender = ({ selectedOptions }) => { function handleGender({ selectedOptions }) {
state.genderText = selectedOptions[0].text; state.genderText = selectedOptions[0].text
showToast(JSON.stringify(selectedOptions)); showToast(JSON.stringify(selectedOptions))
// do something // do something
showGenderPicker.value = false; showGenderPicker.value = false
}; }
const handleIndustry = ({ selectedOptions }) => { function handleIndustry({ selectedOptions }) {
state.industryText = selectedOptions[0].text; state.industryText = selectedOptions[0].text
showToast(JSON.stringify(selectedOptions)); showToast(JSON.stringify(selectedOptions))
// do something // do something
showIndustryPicker.value = false; showIndustryPicker.value = false
}; }
const getFromText = (columns: FormColumns[], value = 0) => function getFromText(columns: FormColumns[], value = 0) {
columns.find((item) => item.value === value)?.text; return columns.find(item => item.value === value)?.text
}
function initState() { function initState() {
Object.keys(state).forEach((key) => { Object.keys(state).forEach((key) => {
state[key] = userStore.getUserInfo[key]; state[key] = userStore.getUserInfo[key]
}); })
// set field text value. // set field text value.
state.genderText = getFromText(genderColumns, gender) ?? ''; state.genderText = getFromText(genderColumns, gender) ?? ''
state.industryText = getFromText(industryColumns, industry) ?? ''; state.industryText = getFromText(industryColumns, industry) ?? ''
// set the pick selected value. // set the pick selected value.
state.industryValues = [industry ?? 0]; state.industryValues = [industry ?? 0]
state.genderValues = [gender]; state.genderValues = [gender]
} }
onMounted(() => { onMounted(() => {
initState(); initState()
}); })
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.avatar { .avatar {
width: 140px; width: 140px;
height: 140px; height: 140px;
} }
.cover { .cover {
width: 170px; width: 170px;
height: 100px; height: 100px;
:deep(.van-image__img) { :deep(.van-image__img) {
border-radius: 10px !important; border-radius: 10px !important;
}
} }
}
:deep(.van-field__control) { :deep(.van-field__control) {
color: var(--van-text-color-2); color: var(--van-text-color-2);
} }
</style> </style>

@ -12,14 +12,14 @@
<div flex="~" justify="center"> <div flex="~" justify="center">
<div grid="~ cols-8 gap-2"> <div grid="~ cols-8 gap-2">
<span <span
v-for="(item, index) in designStore.appThemeList"
:key="index"
h="70px" h="70px"
w="70px" w="70px"
items-center items-center
border="2 rounded-md border-white" border="2 rounded-md border-white"
flex="~" flex="~"
justify="center" justify="center"
v-for="(item, index) in designStore.appThemeList"
:key="index"
:style="{ 'background-color': item }" :style="{ 'background-color': item }"
@click="togTheme(item)" @click="togTheme(item)"
> >
@ -38,6 +38,7 @@
</van-cell> </van-cell>
<van-field <van-field
v-model="animateState.text"
label="动画类型" label="动画类型"
readonly readonly
:disabled="!designStore.isPageAnimate" :disabled="!designStore.isPageAnimate"
@ -46,7 +47,6 @@
input-align="right" input-align="right"
:center="true" :center="true"
:border="false" :border="false"
v-model="animateState.text"
@click="openAnimatePick" @click="openAnimatePick"
/> />
<van-popup v-model:show="animateState.showPicker" position="bottom" round> <van-popup v-model:show="animateState.showPicker" position="bottom" round>
@ -61,48 +61,50 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue'
import { Icon } from '@vicons/utils'; import { Icon } from '@vicons/utils'
import { updateDarkSign } from '@/theme'; import { CheckOutlined } from '@vicons/antd'
import { CheckOutlined } from '@vicons/antd'; import NavBar from './components/NavBar.vue'
import { useDesignSettingStore } from '@/store/modules/designSetting'; import { updateDarkSign } from '@/theme'
import { animates as animateOptions } from '@/settings/animateSetting'; import { useDesignSettingStore } from '@/store/modules/designSetting'
import NavBar from './components/NavBar.vue'; import { animates as animateOptions } from '@/settings/animateSetting'
const designStore = useDesignSettingStore(); const designStore = useDesignSettingStore()
const getDarkMode = computed({ const getDarkMode = computed({
get: () => designStore.getDarkMode === 'dark', get: () => designStore.getDarkMode === 'dark',
set: (value) => { set: (value) => {
const darkMode = value ? 'dark' : 'light'; const darkMode = value ? 'dark' : 'light'
updateDarkSign(darkMode); updateDarkSign(darkMode)
designStore.setDarkMode(darkMode); designStore.setDarkMode(darkMode)
}, },
}); })
function togTheme(color: string) { function togTheme(color: string) {
designStore.appTheme = color; designStore.appTheme = color
}
const findCurrentAnimateType = animateOptions.find(
item => item.value === designStore.pageAnimateType,
)
const animateState = reactive({
text: findCurrentAnimateType?.text,
value: [designStore.pageAnimateType],
showPicker: false,
})
function openAnimatePick() {
if (designStore.isPageAnimate) {
animateState.showPicker = true
} }
}
const findCurrentAnimateType = animateOptions.find( function handleSaveAnimateType({ selectedOptions }) {
(item) => item.value === designStore.pageAnimateType animateState.text = selectedOptions[0].text
); designStore.setPageAnimateType(selectedOptions[0].value)
animateState.showPicker = false
const animateState = reactive({ }
text: findCurrentAnimateType?.text,
value: [designStore.pageAnimateType],
showPicker: false,
});
const openAnimatePick = () => {
if (designStore.isPageAnimate) animateState.showPicker = true;
};
const handleSaveAnimateType = ({ selectedOptions }) => {
animateState.text = selectedOptions[0].text;
designStore.setPageAnimateType(selectedOptions[0].value);
animateState.showPicker = false;
};
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -9,21 +9,21 @@
</Icon> </Icon>
</template> </template>
<template #right> <template #right>
<slot name="right"></slot> <slot name="right" />
</template> </template>
</van-nav-bar> </van-nav-bar>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from '@vicons/utils'; import { Icon } from '@vicons/utils'
import { ChevronBack } from '@vicons/ionicons5'; import { ChevronBack } from '@vicons/ionicons5'
import { useRouter, useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router'
import { computed } from 'vue'; import { computed } from 'vue'
const router = useRouter(); const router = useRouter()
const currentRoute = useRoute(); const currentRoute = useRoute()
const getTitle = computed(() => currentRoute.meta.title as string); const getTitle = computed(() => currentRoute.meta.title as string)
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -7,26 +7,26 @@
accept="image/*" accept="image/*"
> >
<template #default> <template #default>
<slot name="default"></slot> <slot name="default" />
</template> </template>
</van-uploader> </van-uploader>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { showFailToast } from 'vant'; import { showFailToast } from 'vant'
function beforeRead(file) { function beforeRead(file) {
if (file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg') { if (file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg') {
return true; return true
}
showFailToast('请上传正确格式的图片');
return false;
} }
showFailToast('请上传正确格式的图片')
return false
}
function afterRead(file) { function afterRead(file) {
console.log('%c [ file ]-43', 'font-size:13px; background:pink; color:#bf2c9f;', file); console.log('%c [ file ]-43', 'font-size:13px; background:pink; color:#bf2c9f;', file)
// //
} }
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

@ -1,18 +1,22 @@
<template> <template>
<div> <div>
<div :style="getUserCoverBg" class="my-bg h-550px -z-19"> </div> <div :style="getUserCoverBg" class="my-bg h-550px -z-19" />
<div <div
class="my-card shadow-xl relative -top-150px mx-40px rounded-2xl flex flex-col items-center pb-20px" class="my-card relative mx-40px flex flex-col items-center rounded-2xl pb-20px shadow-xl -top-150px"
> >
<van-image <van-image
class="border-4 border-solid !absolute -top-90px h-170px w-170px" class="h-170px w-170px border-4 border-solid !absolute -top-90px"
round round
fit="cover" fit="cover"
:src="avatar" :src="avatar"
/> />
<div class="flex flex-col items-center mt-90px"> <div class="mt-90px flex flex-col items-center">
<p class="font-black text-40px mb-20px">{{ nickname }}</p> <p class="mb-20px text-40px font-black">
<p class="text-30px px-36px">{{ sign }}</p> {{ nickname }}
</p>
<p class="px-36px text-30px">
{{ sign }}
</p>
</div> </div>
<van-divider class="w-full" /> <van-divider class="w-full" />
@ -57,8 +61,8 @@
</van-cell> </van-cell>
<van-action-sheet <van-action-sheet
teleport="body"
v-model:show="showLogoutAction" v-model:show="showLogoutAction"
teleport="body"
:actions="logoutActions" :actions="logoutActions"
cancel-text="取消" cancel-text="取消"
description="确认退出登录吗" description="确认退出登录吗"
@ -69,60 +73,61 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue'
import { Icon } from '@vicons/utils'; import { Icon } from '@vicons/utils'
import { IdcardFilled } from '@vicons/antd'; import { IdcardFilled } from '@vicons/antd'
import { Person, ColorPalette, DocumentText, LogOut } from '@vicons/ionicons5'; import { ColorPalette, DocumentText, LogOut, Person } from '@vicons/ionicons5'
import { useUserStore } from '@/store/modules/user'; import { showToast } from 'vant'
import { showToast } from 'vant'; import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore(); const userStore = useUserStore()
const showLogoutAction = ref(false); const showLogoutAction = ref(false)
const { nickname, avatar, cover, sign } = userStore.getUserInfo; const { nickname, avatar, cover, sign } = userStore.getUserInfo
const logoutActions = [ const logoutActions = [
{ {
name: '退出登录', name: '退出登录',
color: '#ee0a24', color: '#ee0a24',
callback: () => { callback: () => {
userStore.Logout(); userStore.Logout()
showToast('退出成功'); showToast('退出成功')
},
}, },
]; },
]
const getUserCoverBg = computed(() => { const getUserCoverBg = computed(() => {
return { backgroundImage: `url(${cover ? cover : avatar})` }; return { backgroundImage: `url(${cover || avatar})` }
}); })
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.my-bg { .my-bg {
clip-path: inset(0 -55% 0 -55% round 0 0 100% 100%); clip-path: inset(0 -55% 0 -55% round 0 0 100% 100%);
background-size: cover; background-size: cover;
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-image: linear-gradient(180deg, rgba(0, 0, 0, 0), #000); background-image: linear-gradient(180deg, rgba(0, 0, 0, 0), #000);
opacity: 0.9; opacity: 0.9;
} }
}
.van-cell {
align-items: center;
background: transparent;
&:active {
background-color: var(--van-cell-active-color);
} }
.van-cell { .xicon {
align-items: center; margin-right: 10px;
background: transparent;
&:active {
background-color: var(--van-cell-active-color);
}
.xicon {
margin-right: 10px;
}
} }
}
</style> </style>

@ -1,12 +1,12 @@
export interface FormColumns { export interface FormColumns {
text: string; text: string
value: number; value: number
} }
export const genderColumns: FormColumns[] = [ export const genderColumns: FormColumns[] = [
{ text: '男', value: 0 }, { text: '男', value: 0 },
{ text: '女', value: 1 }, { text: '女', value: 1 },
]; ]
export const industryColumns: FormColumns[] = [ export const industryColumns: FormColumns[] = [
{ text: '不展示', value: 0 }, { text: '不展示', value: 0 },
@ -27,4 +27,4 @@ export const industryColumns: FormColumns[] = [
{ text: '媒体/广告/公关', value: 15 }, { text: '媒体/广告/公关', value: 15 },
{ text: '体育/健身', value: 16 }, { text: '体育/健身', value: 16 },
{ text: '企事业单位', value: 17 }, { text: '企事业单位', value: 17 },
]; ]

@ -1,28 +1,33 @@
<template> <template>
<div class="flex flex-col items-center justify-center h-screen p-8"> <div class="h-screen flex flex-col items-center justify-center p-8">
<!-- <van-cell center title="🌗 暗黑模式"> <!-- <van-cell center title="🌗 暗黑模式">
<template #right-icon> <template #right-icon>
<van-switch v-model="getDarkMode" size="18px" /> <van-switch v-model="getDarkMode" size="18px" />
</template> </template>
</van-cell> --> </van-cell> -->
<div class="wel-box flex flex-col justify-between w-full"> <div class="wel-box w-full flex flex-col justify-between">
<div class="wel-top"> <div class="wel-top">
<div class="logo enter-y"> <div class="logo enter-y">
<SvgIcon :size="130" name="logo" /> <SvgIcon :size="130" name="logo" />
</div> </div>
<div <div
class="text-darkBlue dark:text-garyWhite text-2xl font-black mt-12 mb-4 text-center enter-y" class="text-darkBlue dark:text-garyWhite enter-y mb-4 mt-12 text-center text-2xl font-black"
>欢迎来到 {{ title }}</div
> >
<div class="w-full mt-4 mb-6 enter-y"> 欢迎来到 {{ title }}
</div>
<div class="enter-y mb-6 mt-4 w-full">
<van-swipe class="h-30" :autoplay="3000" :indicator-color="designStore.appTheme"> <van-swipe class="h-30" :autoplay="3000" :indicator-color="designStore.appTheme">
<van-swipe-item <van-swipe-item
class="text-gray-700 dark:text-gray-400 leading-relaxed text-center"
v-for="(text, index) in getSwipeText" v-for="(text, index) in getSwipeText"
:key="index" :key="index"
class="text-center text-gray-700 leading-relaxed dark:text-gray-400"
> >
<p class="text-lg">{{ text.title }}</p> <p class="text-lg">
<p class="text-sm">{{ text.details }}</p> {{ text.title }}
</p>
<p class="text-sm">
{{ text.details }}
</p>
</van-swipe-item> </van-swipe-item>
</van-swipe> </van-swipe>
</div> </div>
@ -33,74 +38,75 @@
type="primary" type="primary"
block block
@click="router.push({ name: 'Login' })" @click="router.push({ name: 'Login' })"
>Let's Get Started</van-button
> >
<a class="enter-y text-sm mt-6">创建账户</a> Let's Get Started
</van-button>
<a class="enter-y mt-6 text-sm">创建账户</a>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue'
import { useDesignSettingStore } from '@/store/modules/designSetting'; import { useRouter } from 'vue-router'
import SvgIcon from '@/components/SvgIcon.vue'; import { useDesignSettingStore } from '@/store/modules/designSetting'
import { useGlobSetting } from '@/hooks/setting'; import SvgIcon from '@/components/SvgIcon.vue'
import { useRouter } from 'vue-router'; import { useGlobSetting } from '@/hooks/setting'
import { updateDarkSign } from '@/theme'; // import { updateDarkSign } from '@/theme'
const getDarkMode = computed({ // const getDarkMode = computed({
get: () => designStore.getDarkMode === 'dark', // get: () => designStore.getDarkMode === 'dark',
set: (value) => { // set: (value) => {
const darkMode = value ? 'dark' : 'light'; // const darkMode = value ? 'dark' : 'light'
updateDarkSign(darkMode); // updateDarkSign(darkMode)
designStore.setDarkMode(darkMode); // designStore.setDarkMode(darkMode)
// },
// })
const designStore = useDesignSettingStore()
const globSetting = useGlobSetting()
const router = useRouter()
const { title } = globSetting
const getSwipeText = computed(() => {
return [
{
title: '💡 最新技术栈',
details: '基于Vue3、Vant4、Vite、TypeScript等最新技术栈开发',
}, },
}); {
title: '⚡️ 轻量快速的热重载',
const designStore = useDesignSettingStore(); details: '无论应用程序大小如何都始终极快的模块热重载HMR',
const globSetting = useGlobSetting(); },
const router = useRouter(); {
title: '🔩 主题配置',
const { title } = globSetting; details: '具备主题配置及黑暗主题适配,且持久化保存',
},
const getSwipeText = computed(() => { {
return [ title: '🛠️ 丰富的 Vite 插件',
{ details: '集成大部分 Vite 插件,无需繁琐配置,开箱即用',
title: '💡 最新技术栈', },
details: '基于Vue3、Vant4、Vite、TypeScript等最新技术栈开发', ]
}, })
{
title: '⚡️ 轻量快速的热重载',
details: '无论应用程序大小如何都始终极快的模块热重载HMR',
},
{
title: '🔩 主题配置',
details: '具备主题配置及黑暗主题适配,且持久化保存',
},
{
title: '🛠️ 丰富的 Vite 插件',
details: '集成大部分 Vite 插件,无需繁琐配置,开箱即用',
},
];
});
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.wel-box { .wel-box {
min-height: 50vh; min-height: 50vh;
max-width: 45vh; max-width: 45vh;
min-width: 30vh; min-width: 30vh;
.wel-top { .wel-top {
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
}
.wel-bottom {
display: flex;
align-items: center;
flex-direction: column;
}
} }
.wel-bottom {
display: flex;
align-items: center;
flex-direction: column;
}
}
</style> </style>

@ -1,26 +1,26 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"jsx": "preserve",
"lib": ["ESNext", "DOM"],
"useDefineForClassFields": true, "useDefineForClassFields": true,
"allowSyntheticDefaultImports": true, "baseUrl": ".",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node",
"strict": true,
"sourceMap": true,
"jsx": "preserve",
"baseUrl": ".",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"types": ["vite/client"],
"typeRoots": ["./node_modules/@types/", "./types"],
"noImplicitAny": false,
"skipLibCheck": true,
"paths": { "paths": {
"@/*": ["src/*"], "@/*": ["src/*"],
"#/*": ["types/*"] "#/*": ["types/*"]
} },
"resolveJsonModule": true,
"typeRoots": ["./node_modules/@types/", "./types"],
"types": ["vite/client"],
"strict": true,
"noImplicitAny": false,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true
}, },
"include": [ "include": [
"src/**/*.ts", "src/**/*.ts",

32
types/config.d.ts vendored

@ -1,29 +1,29 @@
export interface GlobConfig { export interface GlobConfig {
title: string; title: string
titleCN: string; titleCN: string
apiUrl: string; apiUrl: string
shortName: string; shortName: string
urlPrefix?: string; urlPrefix?: string
uploadUrl?: string; uploadUrl?: string
prodMock: boolean; prodMock: boolean
imgUrl?: string; imgUrl?: string
} }
export interface GlobEnvConfig { export interface GlobEnvConfig {
// 标题 // 标题
VITE_GLOB_APP_TITLE: string; VITE_GLOB_APP_TITLE: string
// 中文标题 // 中文标题
VITE_GLOB_APP_TITLE_CN: string; VITE_GLOB_APP_TITLE_CN: string
// 接口地址 // 接口地址
VITE_GLOB_API_URL: string; VITE_GLOB_API_URL: string
// 接口前缀 // 接口前缀
VITE_GLOB_API_URL_PREFIX?: string; VITE_GLOB_API_URL_PREFIX?: string
// Project abbreviation // Project abbreviation
VITE_GLOB_APP_SHORT_NAME: string; VITE_GLOB_APP_SHORT_NAME: string
// 图片上传地址 // 图片上传地址
VITE_GLOB_UPLOAD_URL?: string; VITE_GLOB_UPLOAD_URL?: string
// 图片前缀地址 // 图片前缀地址
VITE_GLOB_IMG_URL?: string; VITE_GLOB_IMG_URL?: string
// 生产环境开启mock // 生产环境开启mock
VITE_GLOB_PROD_MOCK: boolean; VITE_GLOB_PROD_MOCK: boolean
} }

78
types/global.d.ts vendored

@ -1,9 +1,9 @@
import type { import type {
VNodeChild,
ComponentPublicInstance, ComponentPublicInstance,
FunctionalComponent, FunctionalComponent,
VNodeChild,
PropType as VuePropType, PropType as VuePropType,
} from 'vue'; } from 'vue'
// declare global 在具有 import 或 export 声明全局范围内的事物的文件中使用。 // declare global 在具有 import 或 export 声明全局范围内的事物的文件中使用。
// 这在包含 import 或 export 因为此类文件被视为模块的文件中是必需的,并且在模块中声明的任何内容都在模块范围内。 // 这在包含 import 或 export 因为此类文件被视为模块的文件中是必需的,并且在模块中声明的任何内容都在模块范围内。
@ -12,66 +12,66 @@ import type {
declare global { declare global {
const __APP_INFO__: { const __APP_INFO__: {
pkg: { pkg: {
name: string; name: string
version: string; version: string
dependencies: Recordable<string>; dependencies: Recordable<string>
devDependencies: Recordable<string>; devDependencies: Recordable<string>
}; }
lastBuildTime: string; lastBuildTime: string
}; }
// vue // vue
type PropType<T> = VuePropType<T>; type PropType<T> = VuePropType<T>
type VueNode = VNodeChild | JSX.Element; type VueNode = VNodeChild | JSX.Element
export type Writable<T> = { export type Writable<T> = {
-readonly [P in keyof T]: T[P]; -readonly [P in keyof T]: T[P];
}; }
type Nullable<T> = T | null; type Nullable<T> = T | null
type NonNullable<T> = T extends null | undefined ? never : T; type NonNullable<T> = T extends null | undefined ? never : T
type Recordable<T = any> = Record<string, T>; type Recordable<T = any> = Record<string, T>
type ReadonlyRecordable<T = any> = { interface ReadonlyRecordable<T = any> {
readonly [key: string]: T; readonly [key: string]: T
}; }
type Indexable<T = any> = { interface Indexable<T = any> {
[key: string]: T; [key: string]: T
}; }
type DeepPartial<T> = { type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>; [P in keyof T]?: DeepPartial<T[P]>;
}; }
type TimeoutHandle = ReturnType<typeof setTimeout>; type TimeoutHandle = ReturnType<typeof setTimeout>
type IntervalHandle = ReturnType<typeof setInterval>; type IntervalHandle = ReturnType<typeof setInterval>
interface ChangeEvent extends Event { interface ChangeEvent extends Event {
target: HTMLInputElement; target: HTMLInputElement
} }
interface WheelEvent { interface WheelEvent {
path?: EventTarget[]; path?: EventTarget[]
} }
interface ImportMetaEnv extends ViteEnv { interface ImportMetaEnv extends ViteEnv {
__: unknown; __: unknown
} }
interface ViteEnv { interface ViteEnv {
VITE_PORT: number; VITE_PORT: number
VITE_USE_MOCK: boolean; VITE_USE_MOCK: boolean
VITE_PUBLIC_PATH: string; VITE_PUBLIC_PATH: string
VITE_GLOB_APP_TITLE: string; VITE_GLOB_APP_TITLE: string
VITE_GLOB_APP_SHORT_NAME: string; VITE_GLOB_APP_SHORT_NAME: string
VITE_DROP_CONSOLE: boolean; VITE_DROP_CONSOLE: boolean
VITE_GLOB_PROD_MOCK: boolean; VITE_GLOB_PROD_MOCK: boolean
VITE_GLOB_IMG_URL: string; VITE_GLOB_IMG_URL: string
VITE_PROXY: [string, string][]; VITE_PROXY: [string, string][]
VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'; VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean; VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean
} }
} }
declare module 'vue' { declare module 'vue' {
export type JSXComponent<Props = any> = export type JSXComponent<Props = any> =
| { new (): ComponentPublicInstance<Props> } | { new (): ComponentPublicInstance<Props> }
| FunctionalComponent<Props>; | FunctionalComponent<Props>
} }

Some files were not shown because too many files have changed in this diff Show More