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

View File

@ -1,6 +1,6 @@
/**
* 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'

View File

@ -2,8 +2,8 @@
* Get the configuration file variable name
* @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__`
.toUpperCase()
.replace(/\s/g, '');
};
.replace(/\s/g, '')
}

View File

@ -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
*/
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
import fs from 'fs-extra';
import colors from 'picocolors';
import fs from 'fs-extra'
import colors from 'picocolors'
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant'
import { getRootPath, getEnvConfig } from '../utils';
import { getConfigFileName } from '../getConfigFileName';
import { getEnvConfig, getRootPath } from '../utils'
import { getConfigFileName } from '../getConfigFileName'
import pkg from '../../package.json';
import pkg from '../../package.json'
function createConfig(
{
configName,
config,
configFileName = GLOB_CONFIG_FILE_NAME,
}: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} }
}: { configName: string, config: any, configFileName?: string } = { configName: '', config: {} },
) {
try {
const windowConf = `window.${configName}`;
const windowConf = `window.${configName}`
// Ensure that the variable will not be modified
const configStr = `${windowConf}=${JSON.stringify(config)};
Object.freeze(${windowConf});
@ -26,19 +26,20 @@ function createConfig(
configurable: false,
writable: false,
});
`.replace(/\s/g, '');
fs.mkdirp(getRootPath(OUTPUT_DIR));
fs.writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
`.replace(/\s/g, '')
fs.mkdirp(getRootPath(OUTPUT_DIR))
fs.writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr)
console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
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));
console.log(`${colors.cyan(`✨ [${pkg.name}]`)} - configuration file is build successfully:`)
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}`))
}
}
export function runBuildConfig() {
const config = getEnvConfig();
const configFileName = getConfigFileName(config);
createConfig({ config, configName: configFileName });
const config = getEnvConfig()
const configFileName = getConfigFileName(config)
createConfig({ config, configName: configFileName })
}

View File

@ -1,23 +1,24 @@
// #!/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 {
const argvList = process.argv.splice(2);
const argvList = process.argv.splice(2)
// Generate configuration file
if (!argvList.includes('disabled-config')) {
await runBuildConfig();
await runBuildConfig()
}
console.log(`${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
} catch (error) {
console.log(colors.red('vite build error:\n' + error));
process.exit(1);
console.log(`${colors.cyan(`[${pkg.name}]`)} - build successfully!`)
}
};
runBuild();
catch (error) {
console.log(colors.red(`vite build error:\n${error}`))
process.exit(1)
}
}
runBuild()

View File

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

View File

@ -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
* 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(
compress: 'gzip' | 'brotli' | 'none',
deleteOriginFile = false
deleteOriginFile = false,
): PluginOption | PluginOption[] {
const compressList = compress.split(',');
const compressList = compress.split(',')
const plugins: PluginOption[] = [];
const plugins: PluginOption[] = []
if (compressList.includes('gzip')) {
plugins.push(
compressPlugin({
ext: '.gz',
deleteOriginFile,
})
);
}),
)
}
if (compressList.includes('brotli')) {
plugins.push(
@ -28,8 +28,8 @@ export function configCompressPlugin(
ext: '.br',
algorithm: 'brotliCompress',
deleteOriginFile,
})
);
}),
)
}
return plugins;
return plugins
}

View File

@ -2,19 +2,19 @@
* Plugin to minimize and use ejs template syntax in index.html.
* https://github.com/anncwb/vite-plugin-html
*/
import type { PluginOption } from 'vite';
import { createHtmlPlugin } from 'vite-plugin-html';
import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
import type { PluginOption } from 'vite'
import { createHtmlPlugin } from 'vite-plugin-html'
import pkg from '../../../package.json'
import { GLOB_CONFIG_FILE_NAME } from '../../constant'
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 = () => {
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
// _app.config.js 用于项目在打包后,需要动态修改配置的需求,如接口地址
@ -41,6 +41,6 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
]
: [],
},
});
return htmlPlugin;
})
return htmlPlugin
}

View File

@ -1,14 +1,14 @@
import type { PluginOption } from 'vite';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
import vue from '@vitejs/plugin-vue';
import UnoCSS from 'unocss/vite';
import type { PluginOption } from 'vite'
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'
import vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'
import { configHtmlPlugin } from './html';
import { configMockPlugin } from './mock';
import { configCompressPlugin } from './compress';
import { configVisualizerConfig } from './visualizer';
import { configSvgIconsPlugin } from './svgSprite';
import { configHtmlPlugin } from './html'
import { configMockPlugin } from './mock'
import { configCompressPlugin } from './compress'
import { configVisualizerConfig } from './visualizer'
import { configSvgIconsPlugin } from './svgSprite'
/**
* vite
@ -22,7 +22,7 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock:
// 如果你需要多种形式,你可以用','来分隔
// 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[])[] = [
// have to
@ -33,30 +33,30 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock:
resolvers: [VantResolver()],
types: [],
}),
];
]
// UnoCSS
vitePlugins.push(UnoCSS());
vitePlugins.push(UnoCSS())
// 加载 html 插件 vite-plugin-html
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild))
// rollup-plugin-visualizer
vitePlugins.push(configVisualizerConfig());
vitePlugins.push(configVisualizerConfig())
// vite-plugin-mock
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock));
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock))
// vite-plugin-svg-icons
vitePlugins.push(configSvgIconsPlugin(isBuild));
vitePlugins.push(configSvgIconsPlugin(isBuild))
if (isBuild) {
// rollup-plugin-gzip
// 加载 gzip 打包
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
}

View File

@ -2,7 +2,7 @@
* Mock plugin for development and production.
* 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) {
return viteMockServe({
@ -15,5 +15,5 @@ export function configMockPlugin(isBuild: boolean, prodMock: boolean) {
setupProdMockServer();
`,
});
})
}

View File

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

View File

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

View File

@ -1,38 +1,38 @@
/**
* 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
* @param list
*/
export function createProxy(list: ProxyList = []) {
const ret: ProxyTargetList = {};
const ret: ProxyTargetList = {}
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
ret[prefix] = {
target: target,
target,
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),
rewrite: path => path.replace(new RegExp(`^${prefix}`), ''),
// https is require secure=false
// 如果您secure="true"只允许来自 HTTPS 的请求则secure="false"意味着允许来自 HTTP 和 HTTPS 的请求。
...(isHttps ? { secure: false } : {}),
};
}
}
return ret;
return ret
// ret
// {

View File

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

View File

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

View File

@ -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) => {
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
*/
export function setupProdMockServer() {
createProdMockServer(mockModules);
createProdMockServer(mockModules)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,44 +5,45 @@
</template>
<script lang="ts">
import type { CSSProperties } from 'vue';
import { defineComponent, computed } from 'vue';
import type { CSSProperties } from 'vue'
import { computed, defineComponent } from 'vue'
export default defineComponent({
name: 'SvgIcon',
props: {
prefix: {
type: String,
default: 'icon',
},
name: {
type: String,
required: true,
},
size: {
type: [Number, String],
default: 16,
},
color: {
type: String,
default: '#333',
},
export default defineComponent({
name: 'SvgIcon',
props: {
prefix: {
type: String,
default: 'icon',
},
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 };
name: {
type: String,
required: true,
},
});
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>
<style scoped lang="less"></style>

View File

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

View File

@ -1,14 +1,14 @@
// token key
export const TOKEN_KEY = 'TOKEN';
export const TOKEN_KEY = 'TOKEN'
// user info key
export const USER_INFO_KEY = 'USER__INFO__';
export const USER_INFO_KEY = 'USER__INFO__'
// role info key
export const ROLES_KEY = 'ROLES__KEY__';
export const ROLES_KEY = 'ROLES__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
export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__';
export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import { warn } from '@/utils/log';
import { getAppEnvConfig } from '@/utils/env';
import { GlobConfig } from '#/config';
import { warn } from '@/utils/log'
import { getAppEnvConfig } from '@/utils/env'
import type { GlobConfig } from '#/config'
export const useGlobSetting = (): Readonly<GlobConfig> => {
export function useGlobSetting(): Readonly<GlobConfig> {
const {
VITE_GLOB_APP_TITLE,
VITE_GLOB_APP_TITLE_CN,
@ -12,12 +12,12 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL,
} = getAppEnvConfig();
} = getAppEnvConfig()
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
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
@ -30,6 +30,6 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
uploadUrl: VITE_GLOB_UPLOAD_URL,
prodMock: VITE_GLOB_PROD_MOCK,
imgUrl: VITE_GLOB_IMG_URL,
};
return glob as Readonly<GlobConfig>;
};
}
return glob as Readonly<GlobConfig>
}

View File

@ -1,18 +1,18 @@
import { computed } from 'vue';
import { useDesignSettingStore } from '@/store/modules/designSetting';
import { computed } from 'vue'
import { useDesignSettingStore } from '@/store/modules/designSetting'
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 {
getDarkMode,
@ -20,5 +20,5 @@ export function useDesignSetting() {
getAppThemeList,
getIsPageAnimate,
getPageAnimateType,
};
}
}

View File

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

View File

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

View File

@ -1,30 +1,30 @@
import { ref, onMounted, onUnmounted } from 'vue';
import { onMounted, onUnmounted, ref } from 'vue'
/**
* @description
* */
*/
export function useOnline() {
const online = ref(true);
const online = ref(true)
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(() => {
// 开始监听网络状态的变化
window.addEventListener('online', showStatus);
window.addEventListener('online', showStatus)
window.addEventListener('offline', showStatus);
});
window.addEventListener('offline', showStatus)
})
onUnmounted(() => {
// 移除监听网络状态的变化
window.removeEventListener('online', showStatus);
window.removeEventListener('online', showStatus)
window.removeEventListener('offline', showStatus);
});
window.removeEventListener('offline', showStatus)
})
return { online };
return { online }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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> = [
{
@ -121,6 +121,6 @@ const routeModuleList: Array<RouteRecordRaw> = [
},
component: () => import('@/views/my/ThemeSetting.vue'),
},
];
]
export default routeModuleList;
export default routeModuleList

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import { defineStore } from 'pinia';
import { store } from '@/store';
import designSetting from '@/settings/designSetting';
import type { DesignSettingState } from '@/settings/designSetting';
import { defineStore } from 'pinia'
import { store } from '@/store'
import designSetting 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({
id: 'app-design-setting',
@ -16,27 +16,27 @@ export const useDesignSettingStore = defineStore({
}),
getters: {
getDarkMode(): 'light' | 'dark' {
return this.darkMode;
return this.darkMode
},
getAppTheme(): string {
return this.appTheme;
return this.appTheme
},
getAppThemeList(): string[] {
return this.appThemeList;
return this.appThemeList
},
getIsPageAnimate(): boolean {
return this.isPageAnimate;
return this.isPageAnimate
},
getPageAnimateType(): string {
return this.pageAnimateType;
return this.pageAnimateType
},
},
actions: {
setDarkMode(mode: 'light' | 'dark'): void {
this.darkMode = mode;
this.darkMode = mode
},
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
export function useDesignSettingWithOut() {
return useDesignSettingStore(store);
return useDesignSettingStore(store)
}

View File

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

View File

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

View File

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

View File

@ -11,7 +11,6 @@ html {
}
[data-theme='dark'] {
&,
* {
color-scheme: dark !important;
@ -24,7 +23,6 @@ html {
}
[data-theme='light'] {
&,
* {
color-scheme: light !important;
@ -80,7 +78,9 @@ a:hover {
.zoom-fade-enter-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 {

View File

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

View File

@ -6,5 +6,8 @@
@import './zoom.less';
.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;
}

View File

@ -1,7 +1,9 @@
// zoom-out
.zoom-out-enter-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,
@ -13,7 +15,9 @@
// zoom-fade
.zoom-fade-enter-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 {

View File

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

View File

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

View File

@ -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_FORMAT = 'YYYY-MM-DD ';
const DATE_TIME_FORMAT = 'yyyy-MM-dd HH:mm:ss'
const DATE_FORMAT = 'YYYY-MM-DD '
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 {
return format(date, formatStr);
return format(date, formatStr)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>(
join: boolean,
restful: T
): T extends true ? string : object;
): T extends true ? string : object
export function joinTimestamp(join: boolean, restful = false): string | object {
if (!join) {
return restful ? '' : {};
return restful ? '' : {}
}
const now = new Date().getTime();
const now = new Date().getTime()
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) {
if (Object.prototype.toString.call(params) !== '[object Object]') {
return;
return
}
for (const key in params) {
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)) {
const value = params[key];
const value = params[key]
if (value) {
try {
params[key] = isString(value) ? value.trim() : value;
} catch (error) {
throw new Error(error as any);
params[key] = isString(value) ? value.trim() : value
}
catch (error) {
throw new Error(error as any)
}
}
}
if (isObject(params[key])) {
formatRequestDate(params[key]);
formatRequestDate(params[key])
}
}
}

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import { isObject } from './is/index';
import { isObject } from './is/index'
export function deepMerge<T = any>(src: any = {}, target: any = {}): T {
let key: string;
let key: string
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
*/
function addLight(color: string, amount: number) {
const cc = parseInt(color, 16) + amount;
const c = cc > 255 ? 255 : cc;
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`;
const cc = Number.parseInt(color, 16) + amount
const c = cc > 255 ? 255 : cc
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
*/
export function darken(color: string, amount: number) {
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
amount = Math.trunc((255 * amount) / 100);
color = color.includes('#') ? color.substring(1, color.length) : color
amount = Math.trunc((255 * amount) / 100)
return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight(
color.substring(2, 4),
amount
)}${subtractLight(color.substring(4, 6), amount)}`;
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
*/
export function lighten(color: string, amount: number) {
color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color;
amount = Math.trunc((255 * amount) / 100);
color = color.includes('#') ? color.substring(1, color.length) : color
amount = Math.trunc((255 * amount) / 100)
return `#${addLight(color.substring(0, 2), amount)}${addLight(
color.substring(2, 4),
amount
)}${addLight(color.substring(4, 6), amount)}`;
amount,
)}${addLight(color.substring(4, 6), amount)}`
}
/**
* url
* */
const RegExp = /^http(s)?:\/\//iu;
*/
const RegExp = /^http(s)?:\/\//iu
export function isUrl(url: string) {
return RegExp.test(url);
return RegExp.test(url)
}
/**
*
* */
*/
export function arrayTrans(arr: number[]): number[][] {
const newArr: number[][] = [];
const newArr: number[][] = []
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
*/
function subtractLight(color: string, amount: number) {
const cc = parseInt(color, 16) - amount;
const c = cc < 0 ? 0 : cc;
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`;
const cc = Number.parseInt(color, 16) - amount
const c = cc < 0 ? 0 : cc
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
}
export function hexToRgba(hex: string, opacity: number) {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
hex = hex.replace(shorthandRegex, (m, r, g, 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);
opacity = opacity >= 0 && opacity <= 1 ? Number(opacity) : 1;
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
return result
? 'rgba(' +
[parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16), opacity].join(
','
) +
')'
: hex;
? `rgba(${
[Number.parseInt(result[1], 16), Number.parseInt(result[2], 16), Number.parseInt(result[3], 16), opacity].join(
',',
)
})`
: hex
}

View File

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

View File

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

View File

@ -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) {
console.warn(`[${projectName} warn]:${message}`);
console.warn(`[${projectName} warn]:${message}`)
}
export function error(message: string) {
throw new Error(`[${projectName} error]:${message}`);
throw new Error(`[${projectName} error]:${message}`)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,20 @@
<template>
<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" />
</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 }}
</div>
</div>
</template>
<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>
<style scoped lang="less"></style>

View File

@ -1,5 +1,5 @@
<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
class="ignore-waves"
xmlns="http://www.w3.org/2000/svg"
@ -45,61 +45,61 @@
</template>
<script setup lang="ts">
import { useDesignSettingStore } from '@/store/modules/designSetting';
import { hexToRgba } from '@/utils/index';
import { useDesignSettingStore } from '@/store/modules/designSetting'
import { hexToRgba } from '@/utils/index'
const designStore = useDesignSettingStore();
const designStore = useDesignSettingStore()
</script>
<style scoped lang="less">
.wave-wrapper {
position: fixed;
width: 100%;
left: 0;
bottom: 0;
}
.ignore-waves {
position: relative;
display: block;
width: 100%;
height: 50px;
min-height: 40px;
max-height: 80px;
position: fixed;
width: 100%;
left: 0;
bottom: 0;
}
.ignore-waves {
position: relative;
display: block;
width: 100%;
height: 50px;
min-height: 40px;
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 */
.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);
}
100% {
transform: translate3d(85px, 0, 0);
}
100% {
transform: translate3d(85px, 0, 0);
}
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,65 +1,66 @@
<template>
<div class="my-card m-40px p-30px rounded-2xl shadow-xl">
<div ref="chartRef" :style="{ height: '350px' }"></div>
<div class="my-card m-40px rounded-2xl p-30px shadow-xl">
<div ref="chartRef" :style="{ height: '350px' }" />
</div>
</template>
<script setup lang="ts">
import { useECharts } from '@/hooks/web/useECharts';
import { onMounted, ref, Ref } from 'vue';
import type { EChartsOption } from 'echarts';
import type { Ref } from 'vue'
import { onMounted, ref } from 'vue'
import type { EChartsOption } from 'echarts'
import { useECharts } from '@/hooks/web/useECharts'
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
const chartRef = ref<HTMLDivElement | null>(null)
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
const chartOptions: EChartsOption = {
tooltip: {
trigger: 'item',
},
legend: {
top: '5%',
left: 'center',
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '60%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
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' },
],
const chartOptions: EChartsOption = {
tooltip: {
trigger: 'item',
},
legend: {
top: '5%',
left: 'center',
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '60%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
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' },
],
},
],
}
onMounted(() => {
setOptions(chartOptions);
});
onMounted(() => {
setOptions(chartOptions)
})
</script>
<style scoped></style>

View File

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

View File

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

View File

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

View File

@ -1,14 +1,16 @@
<template>
<div>
<NavBar>
<template #right><span class="text-32px" @click="handleNickname">保存</span></template>
<template #right>
<span class="text-32px" @click="handleNickname">保存</span>
</template>
</NavBar>
<van-form ref="formRef">
<van-field
v-model="formValue.sign"
class="mt-20px"
name="sign"
clearable
v-model="formValue.sign"
rows="4"
autosize
label="签名"
@ -22,49 +24,50 @@
</template>
<script setup lang="ts">
import NavBar from './components/NavBar.vue';
import { useUserStore } from '@/store/modules/user';
import { onMounted, reactive, ref } from 'vue';
import type { FormInstance } from 'vant';
import { showToast } from 'vant';
import { onMounted, reactive, ref } from 'vue'
import type { FormInstance } from 'vant'
import { showToast } from 'vant'
import NavBar from './components/NavBar.vue'
import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore();
const userStore = useUserStore()
const { sign } = userStore.getUserInfo;
const formRef = ref<FormInstance>();
const { sign } = userStore.getUserInfo
const formRef = ref<FormInstance>()
const formValue = reactive({
sign: '',
});
const formValue = reactive({
sign: '',
})
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('验证失败');
});
}
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(() => {
formValue.sign = sign ?? '';
});
onMounted(() => {
formValue.sign = sign ?? ''
})
</script>
<style scoped lang="less">
.note {
margin-top: 15px;
font-size: 25px;
color: var(--van-text-color-2);
}
margin-top: 15px;
font-size: 25px;
color: var(--van-text-color-2);
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,26 +1,26 @@
{
"compilerOptions": {
"target": "ESNext",
"jsx": "preserve",
"lib": ["ESNext", "DOM"],
"useDefineForClassFields": true,
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"module": "ESNext",
"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": {
"@/*": ["src/*"],
"#/*": ["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": [
"src/**/*.ts",

32
types/config.d.ts vendored
View File

@ -1,29 +1,29 @@
export interface GlobConfig {
title: string;
titleCN: string;
apiUrl: string;
shortName: string;
urlPrefix?: string;
uploadUrl?: string;
prodMock: boolean;
imgUrl?: string;
title: string
titleCN: string
apiUrl: string
shortName: string
urlPrefix?: string
uploadUrl?: string
prodMock: boolean
imgUrl?: string
}
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
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
VITE_GLOB_PROD_MOCK: boolean;
VITE_GLOB_PROD_MOCK: boolean
}

78
types/global.d.ts vendored
View File

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

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