├── src ├── api │ ├── models │ │ ├── userModel.ts │ │ ├── authModel.ts │ │ └── login.ts │ ├── user.ts │ ├── auth-test.ts │ └── auth.ts ├── static │ ├── icon │ │ └── .gitkeep │ ├── logo.png │ ├── images │ │ ├── avatar.png │ │ └── tabBar │ │ │ ├── about.png │ │ │ ├── demo.png │ │ │ ├── home.png │ │ │ ├── category.png │ │ │ ├── selectedDemo.png │ │ │ ├── selectedHome.png │ │ │ ├── selectedAbout.png │ │ │ └── selectedCategory.png │ ├── fonts │ │ ├── fa-brands-400.ttf │ │ ├── fa-light-300.ttf │ │ ├── fa-solid-900.ttf │ │ ├── fa-thin-100.ttf │ │ ├── fa-thin-100.woff2 │ │ ├── fa-brands-400.woff2 │ │ ├── fa-duotone-900.ttf │ │ ├── fa-light-300.woff2 │ │ ├── fa-regular-400.ttf │ │ ├── fa-solid-900.woff2 │ │ ├── fa-duotone-900.woff2 │ │ ├── fa-regular-400.woff2 │ │ ├── fa-sharp-solid-900.ttf │ │ ├── fa-v4compatibility.ttf │ │ ├── fa-sharp-solid-900.woff2 │ │ └── fa-v4compatibility.woff2 │ ├── iconfont2 │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ └── svg │ │ ├── 404.svg │ │ └── weep.svg ├── uni_modules │ └── .gitkeep ├── wxcomponents │ └── .gitkeep ├── utils │ ├── types.ts │ ├── auth.ts │ ├── regExp.ts │ ├── interceptors │ │ └── index.ts │ ├── log.ts │ ├── promise.ts │ ├── number.ts │ ├── uniapi │ │ ├── uni-copy.ts │ │ ├── index.ts │ │ └── prompt.ts │ ├── version.ts │ ├── index.ts │ ├── cache │ │ ├── index.ts │ │ └── storageCache.ts │ ├── color.ts │ ├── router │ │ ├── constant.ts │ │ ├── routes.ts │ │ ├── interceptor.ts │ │ └── navigates.ts │ ├── api.ts │ ├── random.ts │ ├── object.ts │ ├── array.ts │ ├── cipher.ts │ ├── img.ts │ ├── http │ │ └── index.ts │ ├── env.ts │ ├── fonts.ts │ ├── platform.ts │ ├── misc.ts │ ├── date.ts │ ├── is.ts │ └── string.ts ├── assets │ └── style │ │ ├── main.scss │ │ ├── mixin.scss │ │ ├── flex.scss │ │ └── auxiliary.scss ├── config │ ├── app.ts │ └── encryptionConfig.ts ├── types │ ├── apiModel.d.ts │ ├── router │ │ └── route.d.ts │ └── env.d.ts ├── enums │ ├── cacheEnum.ts │ ├── appEnum.ts │ ├── httpEnum.ts │ ├── routerEnum.ts │ └── platformEnum.ts ├── components │ ├── BasicButton │ │ ├── prpos.ts │ │ └── index.vue │ ├── FontAwesomeIcon │ │ ├── scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _sizing.scss │ │ │ ├── v4-shims.scss │ │ │ ├── _screen-reader.scss │ │ │ ├── _icons.scss │ │ │ ├── _list.scss │ │ │ ├── fontawesome.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _stacked.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── thin.scss │ │ │ ├── light.scss │ │ │ ├── solid.scss │ │ │ ├── regular.scss │ │ │ ├── sharp-solid.scss │ │ │ ├── brands.scss │ │ │ ├── duotone.scss │ │ │ ├── _core.scss │ │ │ ├── _functions.scss │ │ │ └── _mixins.scss │ │ └── index.scss │ ├── UnoUI │ │ ├── UToast │ │ │ ├── types.d.ts │ │ │ └── UToast.vue │ │ ├── UNotify │ │ │ ├── types.d.ts │ │ │ └── UNotify.vue │ │ ├── UButton │ │ │ └── UButton.vue │ │ └── UBasePage │ │ │ └── UBasePage.vue │ ├── AppProvider │ │ └── inedx.vue │ ├── Test │ │ └── index.vue │ ├── CustomBar │ │ ├── navBar.vue │ │ └── tabBar.vue │ ├── composables │ │ ├── useLayer.ts │ │ └── useProps.ts │ ├── BasicInput │ │ └── index.vue │ ├── UImage │ │ └── UImage.vue │ └── UButton │ │ └── UButton.vue ├── pages │ ├── template │ │ └── index.vue │ ├── log │ │ └── index.vue │ ├── notFound │ │ └── 404.vue │ ├── demo │ │ └── index.vue │ ├── index │ │ └── index.vue │ ├── about │ │ └── index.vue │ └── login │ │ └── index.vue ├── stores │ ├── index.ts │ └── modules │ │ ├── user.ts │ │ ├── lang.ts │ │ ├── router.ts │ │ ├── app.ts │ │ ├── auth.ts │ │ └── page.ts ├── pagesC │ ├── demo │ │ └── index.vue │ ├── user │ │ └── index.vue │ ├── collection │ │ └── index.vue │ └── index │ │ └── index.vue ├── pagesB │ └── detail │ │ └── index.vue ├── language │ ├── lang │ │ ├── lang.cn.ts │ │ └── lang.en.ts │ └── index.ts ├── pagesA │ └── list │ │ ├── test1 │ │ └── index.vue │ │ └── test2 │ │ └── index.vue ├── theme.json ├── main.ts ├── hooks │ ├── countDown.ts │ └── router.ts ├── androidPrivacy.json ├── pagesUno │ ├── dashboard │ │ └── dashboard.vue │ └── splash │ │ └── splash.vue ├── uni.scss ├── App.vue └── manifest.json ├── favicon.ico ├── .husky ├── pre-commit └── commit-msg ├── static-dev └── images │ └── avatar.png ├── .env ├── .npmrc ├── .env.staging ├── .env.production ├── .prettierignore ├── .eslintignore ├── assets ├── svg │ ├── loading.svg │ └── loading2.svg └── test.json ├── .editorconfig ├── scripts ├── static-serve.js ├── deploy.sh └── verifyCommit.ts ├── .env.development ├── tsconfig.json ├── mock ├── index.ts ├── auth.ts └── _util.ts ├── windi.config.ts ├── .gitignore ├── index.html ├── commitlint.config.js ├── .cz-config.js ├── .prettierrc.js ├── .eslintrc.js ├── README.md ├── vite.config.ts └── package.json /src/api/models/userModel.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/static/icon/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/uni_modules/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/wxcomponents/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils/http'; 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/favicon.ico -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Function 3 | */ 4 | export type Fn = () => T; 5 | -------------------------------------------------------------------------------- /src/assets/style/main.scss: -------------------------------------------------------------------------------- 1 | @import 'auxiliary'; 2 | // @import 'mixin'; 3 | // @import 'flex'; 4 | -------------------------------------------------------------------------------- /src/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/logo.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | # npm run lint:staged 6 | -------------------------------------------------------------------------------- /src/assets/style/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin flex($direction: row) { 2 | display: flex; 3 | flex-direction: $direction; 4 | } 5 | -------------------------------------------------------------------------------- /src/static/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/images/avatar.png -------------------------------------------------------------------------------- /static-dev/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/static-dev/images/avatar.png -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /src/static/fonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /src/static/fonts/fa-light-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-light-300.ttf -------------------------------------------------------------------------------- /src/static/fonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /src/static/fonts/fa-thin-100.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-thin-100.ttf -------------------------------------------------------------------------------- /src/static/fonts/fa-thin-100.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-thin-100.woff2 -------------------------------------------------------------------------------- /src/static/iconfont2/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/iconfont2/iconfont.ttf -------------------------------------------------------------------------------- /src/static/iconfont2/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/iconfont2/iconfont.woff -------------------------------------------------------------------------------- /src/static/images/tabBar/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/images/tabBar/about.png -------------------------------------------------------------------------------- /src/static/images/tabBar/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/images/tabBar/demo.png -------------------------------------------------------------------------------- /src/static/images/tabBar/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/images/tabBar/home.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # title 2 | VITE_APP_TITLE = uni-app Vue3 3 | 4 | # public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可 5 | VITE_PUBLIC_PATH = 6 | -------------------------------------------------------------------------------- /src/static/fonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /src/static/fonts/fa-duotone-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-duotone-900.ttf -------------------------------------------------------------------------------- /src/static/fonts/fa-light-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-light-300.woff2 -------------------------------------------------------------------------------- /src/static/fonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /src/static/fonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /src/config/app.ts: -------------------------------------------------------------------------------- 1 | import { getImageUrl } from '@/utils/env'; 2 | 3 | /** 4 | * 远程图片地址 5 | */ 6 | export const IMAGE_URL = getImageUrl(); 7 | -------------------------------------------------------------------------------- /src/static/fonts/fa-duotone-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-duotone-900.woff2 -------------------------------------------------------------------------------- /src/static/fonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /src/static/fonts/fa-sharp-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-sharp-solid-900.ttf -------------------------------------------------------------------------------- /src/static/fonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /src/static/images/tabBar/category.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/images/tabBar/category.png -------------------------------------------------------------------------------- /src/static/fonts/fa-sharp-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-sharp-solid-900.woff2 -------------------------------------------------------------------------------- /src/static/fonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/fonts/fa-v4compatibility.woff2 -------------------------------------------------------------------------------- /src/static/images/tabBar/selectedDemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/images/tabBar/selectedDemo.png -------------------------------------------------------------------------------- /src/static/images/tabBar/selectedHome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/images/tabBar/selectedHome.png -------------------------------------------------------------------------------- /src/static/images/tabBar/selectedAbout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/images/tabBar/selectedAbout.png -------------------------------------------------------------------------------- /src/types/apiModel.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ApiResult { 2 | code: number; 3 | success: boolean; 4 | data?: T; 5 | msg: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | // token key 2 | export const TOKEN_KEY = 'TOKEN__'; 3 | 4 | // user info key 5 | export const USER_INFO_KEY = 'USER__INFO__'; 6 | -------------------------------------------------------------------------------- /src/static/images/tabBar/selectedCategory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xinlc/uniapp-vue3-ts-template/HEAD/src/static/images/tabBar/selectedCategory.png -------------------------------------------------------------------------------- /src/components/BasicButton/prpos.ts: -------------------------------------------------------------------------------- 1 | export const buttonProps = { 2 | disabled: { type: Boolean, default: false }, 3 | click: { type: Function }, 4 | }; 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 2 | registry=https://registry.npm.taobao.org 3 | 4 | # pnpm 5 | strict-peer-dependencies=false 6 | -------------------------------------------------------------------------------- /src/enums/appEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 客户端 api 返回结果设置 3 | */ 4 | export enum ClientApiResultEnum { 5 | CLIENT_SUCCESS = 1, 6 | CLIENT_ERROR = 0, 7 | } 8 | -------------------------------------------------------------------------------- /src/api/models/authModel.ts: -------------------------------------------------------------------------------- 1 | export interface LoginParams { 2 | email: string; 3 | password: string; 4 | } 5 | 6 | export interface LoginModel { 7 | token: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 请求结果设置 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 0, 6 | ERROR = 1, 7 | TIMEOUT = 401, 8 | TYPE = 'success', 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/auth.ts: -------------------------------------------------------------------------------- 1 | import { getCache } from '@/utils/cache'; 2 | import { TOKEN_KEY } from '@/enums/cacheEnum'; 3 | 4 | export const TOKEN = () => getCache(TOKEN_KEY) || undefined; 5 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // fixed-width icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-fw { 5 | text-align: center; 6 | width: $fa-fw-width; 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/template/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/api/auth-test.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils/http'; 2 | 3 | import { ILogin, ILoginParams } from '@/api/models/login'; 4 | 5 | export const signIn = (form: ILoginParams) => request.post('/auth/token', form); 6 | -------------------------------------------------------------------------------- /src/utils/regExp.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 转义字符串以在正则表达式中使用 3 | * @param string - 要转义的字符串 4 | * @returns 5 | */ 6 | export function escapeRegExp(string: string): string { 7 | return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 8 | } 9 | -------------------------------------------------------------------------------- /.env.staging: -------------------------------------------------------------------------------- 1 | # 测试环境 2 | ENV = 'staging' 3 | 4 | # 运行环境 5 | VITE_PROD = false 6 | VITE_DEV = true 7 | 8 | VITE_BASE = '/api' 9 | 10 | VITE_USE_CHUNK_MOCK = false 11 | 12 | # 线上环境接口地址 13 | VITE_BASE_URL = 'http://test.xxx.com' 14 | -------------------------------------------------------------------------------- /src/components/UnoUI/UToast/types.d.ts: -------------------------------------------------------------------------------- 1 | export type UToastType = 'default' | 'success' | 'error' | 'warning' | 'primary'; 2 | 3 | export interface UToastOptions { 4 | type?: UToastType; 5 | message: string; 6 | duration?: number; 7 | } 8 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 线上环境 2 | ENV = 'production' 3 | 4 | # 运行环境 5 | VITE_PROD = true 6 | VITE_DEV = false 7 | 8 | VITE_BASE = '/api' 9 | 10 | VITE_USE_CHUNK_MOCK = false 11 | 12 | # 线上环境接口地址 13 | VITE_BASE_URL = 'http://prod.xxx.com' 14 | -------------------------------------------------------------------------------- /src/components/UnoUI/UNotify/types.d.ts: -------------------------------------------------------------------------------- 1 | export type UNotifyType = 'default' | 'success' | 'error' | 'warning' | 'primary'; 2 | 3 | export interface UNotifyOptions { 4 | type?: UNotifyType; 5 | message: string; 6 | duration?: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | 4 | const store = createPinia(); 5 | 6 | export function setupStore(app: App) { 7 | app.use(store); 8 | } 9 | 10 | export { store }; 11 | -------------------------------------------------------------------------------- /src/pagesC/demo/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # 忽略格式化文件 2 | node_modules 3 | dist 4 | .idea 5 | .vscode 6 | .hbuilderx 7 | src/manifest.json 8 | src/pages.json 9 | *.sh 10 | node_modules 11 | *.md 12 | *.woff 13 | *.ttf 14 | *.yaml 15 | dist 16 | /public 17 | /docs 18 | .husky 19 | .local 20 | /bin -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/index.scss: -------------------------------------------------------------------------------- 1 | @import 'scss/fontawesome'; 2 | 3 | @import 'scss/solid'; 4 | @import 'scss/brands'; 5 | @import 'scss/regular'; 6 | @import 'scss/light'; 7 | @import 'scss/thin'; 8 | @import 'scss/duotone'; 9 | @import 'scss/sharp-solid'; 10 | -------------------------------------------------------------------------------- /src/stores/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | interface UserState { 4 | id?: string | number; 5 | } 6 | 7 | export const useUserStore = defineStore({ 8 | id: 'user', 9 | state: (): UserState => ({}), 10 | getters: {}, 11 | actions: {}, 12 | }); 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # eslint 忽略检查 2 | node_modules 3 | dist 4 | .idea 5 | .vscode 6 | .hbuilderx 7 | src/manifest.json 8 | src/pages.json 9 | src/tmui/ 10 | *.sh 11 | node_modules 12 | *.md 13 | *.woff 14 | *.ttf 15 | *.yaml 16 | dist 17 | /public 18 | /docs 19 | .husky 20 | .local 21 | /bin 22 | -------------------------------------------------------------------------------- /src/utils/interceptors/index.ts: -------------------------------------------------------------------------------- 1 | import { routerInterceptor, routerRemoveInterceptor } from '@/utils/router/interceptor'; 2 | 3 | export function setupInterceptors() { 4 | routerInterceptor(); 5 | } 6 | 7 | export function removeInterceptor() { 8 | routerRemoveInterceptor(); 9 | } 10 | -------------------------------------------------------------------------------- /src/components/AppProvider/inedx.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | -------------------------------------------------------------------------------- /assets/svg/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/pagesC/user/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/pagesB/detail/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/Test/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /src/pagesC/collection/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/pages/log/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | import { getEnvValue } from '@/utils/env'; 2 | 3 | const projectName = getEnvValue('VITE_APP_TITLE'); 4 | 5 | export function warn(message: string) { 6 | console.warn(`[${projectName} warn]:${message}`); 7 | } 8 | 9 | export function error(message: string) { 10 | throw new Error(`[${projectName} error]:${message}`); 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/promise.ts: -------------------------------------------------------------------------------- 1 | import type { Fn } from './types'; 2 | 3 | /** 4 | * 睡眠 5 | * @param ms - 毫秒数 6 | * @param callback - 回调函数 7 | */ 8 | export function sleep(ms: number, callback?: Fn): Promise { 9 | return new Promise(resolve => 10 | setTimeout(async () => { 11 | callback && (await callback()); 12 | resolve(); 13 | }, ms), 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/api/models/login.ts: -------------------------------------------------------------------------------- 1 | export interface ILogin { 2 | userName: string; 3 | photo: string; 4 | time: string; 5 | roles: Array; 6 | authBtnList: Array; 7 | } 8 | 9 | export interface ILoginParams { 10 | page: number; 11 | pageSize: number; 12 | [keys: string]: any; 13 | } 14 | 15 | export interface UserInfosStates { 16 | userInfos: ILogin; 17 | token: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_sizing.scss: -------------------------------------------------------------------------------- 1 | // sizing icons 2 | // ------------------------- 3 | 4 | // literal magnification scale 5 | @for $i from 1 through 10 { 6 | .#{$fa-css-prefix}-#{$i}x { 7 | font-size: $i * 1em; 8 | } 9 | } 10 | 11 | // step-based scale (with alignment) 12 | @each $size, $value in $fa-sizes { 13 | .#{$fa-css-prefix}-#{$size} { 14 | @include fa-size($value); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/v4-shims.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2022 Fonticons, Inc. 5 | */ 6 | // V4 shims compile (Web Fonts-based) 7 | // ------------------------- 8 | 9 | @import 'functions'; 10 | @import 'variables'; 11 | @import 'shims'; 12 | -------------------------------------------------------------------------------- /scripts/static-serve.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 图片静态服务器,方便本地开发,图片还为上传到 oss 情况 3 | */ 4 | const path = require('path'); 5 | const express = require('express'); 6 | // const serveStatic = require('serve-static'); 7 | const app = express(); 8 | 9 | // app.use(serveStatic('public/ftp', { index: ['default.html', 'default.htm'] })); 10 | app.use('/static/images', express.static(path.resolve(__dirname, '../static-dev/images'))); 11 | 12 | app.listen(3088); 13 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // screen-reader utilities 2 | // ------------------------- 3 | 4 | // only display content to screen readers 5 | .sr-only, 6 | .#{$fa-css-prefix}-sr-only { 7 | @include fa-sr-only; 8 | } 9 | 10 | // use in conjunction with .sr-only to only display content when it's focused 11 | .sr-only-focusable, 12 | .#{$fa-css-prefix}-sr-only-focusable { 13 | @include fa-sr-only-focusable; 14 | } 15 | -------------------------------------------------------------------------------- /src/pagesC/index/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/language/lang/lang.cn.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | homeTitle: '首页', 4 | tabbarHome: '首页', 5 | tabbarMine: '我的', 6 | tabbarItyw: '消息', 7 | tabbarExplosive: '每日推荐', 8 | }, 9 | common: { 10 | platform: 'xxx平台', 11 | wxLoginTip: '微信授权登陆', 12 | 13 | input_email: '邮件', 14 | input_password: '密码', 15 | total: '总数:', 16 | tocart: '返回', 17 | title_items: '你的项目:', 18 | quntity: '数量:', 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/number.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 转为数字 3 | * @param val - 值 4 | * @returns 5 | */ 6 | export const toNumber = (val: any): any => { 7 | const n = parseFloat(val); 8 | return isNaN(n) ? val : n; 9 | }; 10 | 11 | /** 12 | * 保留小数点后面n位数字,四舍五入 13 | * @param {string} value 14 | * @param {number} n 15 | * @returns {string} 16 | */ 17 | export const toFix = (value: string, n: number): string => { 18 | n = n || 2; 19 | return parseFloat(value).toFixed(n); 20 | }; 21 | -------------------------------------------------------------------------------- /src/pagesA/list/test1/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /src/pagesA/list/test2/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /src/enums/routerEnum.ts: -------------------------------------------------------------------------------- 1 | export enum NAVIGATE_TYPE { 2 | NAVIGATE_TO = 'navigateTo', 3 | REDIRECT_TO = 'redirectTo', 4 | RE_LAUNCH = 'reLaunch', 5 | SWITCH_TAB = 'switchTab', 6 | NAVIGATE_BACK = 'navigateBack', 7 | } 8 | 9 | export const NAVIGATE_TYPE_LIST = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab']; 10 | 11 | export const HOME_PAGE = '/pages/index/index'; 12 | export const LOGIN_PAGE = '/pages/login/index'; 13 | export const NOT_FOUND_PAGE = '/pages/notFound/404'; 14 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 本地环境 2 | ENV = 'development' 3 | 4 | # 运行环境 5 | VITE_PROD = false 6 | VITE_DEV = true 7 | 8 | # Whether to open mock 9 | VITE_USE_MOCK = true 10 | 11 | # open 运行 npm run dev 时自动打开浏览器 12 | VITE_OPEN = true 13 | 14 | # port 端口号 15 | VITE_PORT = 3000 16 | 17 | # API BASE 18 | VITE_BASE = '/api' 19 | 20 | # 本地环境接口地址 21 | VITE_BASE_URL = 'http://localhost:8000' 22 | 23 | # 上传域名 24 | VITE_UPLOAD_URL = '/upload' 25 | 26 | # 图片地址 27 | VITE_IMAGE_URL = 'http://localhost:3088' 28 | -------------------------------------------------------------------------------- /src/language/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n'; //引入vue-i18n组件 2 | import cn from '@/language/lang/lang.cn'; 3 | import en from '@/language/lang/lang.en'; 4 | import { getCache } from '@/utils/cache'; 5 | 6 | const locale = getCache('language') || 'cn'; 7 | const messages = { 8 | cn: cn, 9 | en: en, 10 | }; 11 | const i18n = createI18n({ 12 | legacy: false, 13 | globalInjection: true, 14 | locale: locale, 15 | messages: messages, 16 | }); 17 | 18 | export default i18n; 19 | -------------------------------------------------------------------------------- /src/components/CustomBar/navBar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | cd .. 7 | 8 | # 生成静态文件 9 | pnpm run build 10 | 11 | # 进入生成的文件夹 12 | cd dist/ 13 | 14 | # 如果是发布到自定义域名 15 | # echo 'www.example.com' > CNAME 16 | 17 | git init 18 | git add -A 19 | git commit -m 'deploy: 自动部署' 20 | 21 | # 如果发布到 https://.github.io 22 | # git push -f git@github.com:/.github.io.git main 23 | 24 | # 如果发布到 https://.github.io/ 25 | git push -f git@github.com:xinlc/xinlc/uniapp-vue3-ts-template.git main:gh-pages 26 | 27 | cd - 28 | -------------------------------------------------------------------------------- /src/language/lang/lang.en.ts: -------------------------------------------------------------------------------- 1 | // 英文 English, 阿拉伯语言从右到左,所以标点符号也要带上 2 | export default { 3 | home: { 4 | homeTitle: 'home', 5 | tabbarHome: 'home', 6 | tabbarMine: 'mine', 7 | tabbarItyw: 'message', 8 | tabbarExplosive: 'todayExplosive', 9 | }, 10 | common: { 11 | platform: 'xxx platform', 12 | wxLoginTip: 'wechat login', 13 | 14 | input_email: 'Email', 15 | input_password: 'Password', 16 | total: 'SUBTOTAL:', 17 | tocart: 'Return to cart', 18 | title_items: 'Your items:', 19 | quntity: 'Quntity:', 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "target": "esnext", 5 | "useDefineForClassFields": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "jsx": "preserve", 10 | "sourceMap": true, 11 | "resolveJsonModule": true, 12 | "esModuleInterop": true, 13 | "lib": ["esnext", "dom"], 14 | "types": ["@dcloudio/types"], 15 | "paths": { 16 | "@/*": ["src/*"] 17 | } 18 | }, 19 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 20 | } 21 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_icons.scss: -------------------------------------------------------------------------------- 1 | // specific icon class definition 2 | // ------------------------- 3 | 4 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 5 | readers do not read off random characters that represent icons */ 6 | 7 | @each $name, $icon in $fa-icons { 8 | .#{$fa-css-prefix}-#{$name}::before { 9 | content: unquote('"#{ $icon }"'); 10 | } 11 | } 12 | @each $name, $icon in $fa-icons { 13 | .#{$fa-css-prefix}-duotone.#{$fa-css-prefix}-#{$name}::after { 14 | content: unquote('"#{ $icon }#{ $icon }"'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // icons in a list 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | list-style-type: none; 6 | margin-left: var(--#{$fa-css-prefix}-li-margin, #{$fa-li-margin}); 7 | padding-left: 0; 8 | 9 | > li { 10 | position: relative; 11 | } 12 | } 13 | 14 | .#{$fa-css-prefix}-li { 15 | left: calc(var(--#{$fa-css-prefix}-li-width, #{$fa-li-width}) * -1); 16 | position: absolute; 17 | text-align: center; 18 | width: var(--#{$fa-css-prefix}-li-width, #{$fa-li-width}); 19 | line-height: inherit; 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/uniapi/uni-copy.ts: -------------------------------------------------------------------------------- 1 | export const uniCopy = (content: any) => { 2 | if (!content) console.log('内容为空'); 3 | console.log(content); 4 | content = typeof content === 'string' ? content : content.toString(); // 复制内容,必须字符串,数字需要转换为字符串 5 | /** 6 | * 小程序端 和 app端的复制逻辑 7 | */ 8 | 9 | uni.setClipboardData({ 10 | data: content, 11 | success: function () { 12 | uni.showToast({ 13 | title: '复制成功', 14 | }); 15 | }, 16 | fail: function () { 17 | uni.showToast({ 18 | title: '复制失败', 19 | }); 20 | }, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "dark": { 3 | "bgColor": "#222222", 4 | "bgColorBottom": "#222222", 5 | "bgColorTop": "#222222", 6 | "bgTxtStyle": "light", 7 | "navBgColor": "#222222", 8 | "navTxtStyle": "white", 9 | "tabBgColor": "#222222", 10 | "tabBorderStyle": "white" 11 | }, 12 | "light": { 13 | "bgColor": "#F3F4F6", 14 | "bgColorBottom": "#F3F4F6", 15 | "bgColorTop": "#F3F4F6", 16 | "bgTxtStyle": "dark", 17 | "navBgColor": "#F3F4F6", 18 | "navTxtStyle": "white", 19 | "tabBgColor": "#F3F4F6", 20 | "tabBorderStyle": "black" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mock/index.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock'; 2 | 3 | export default [ 4 | { 5 | url: '/api/auth/token', 6 | timeout: 100, 7 | method: 'post', 8 | response: ({ query }: any) => { 9 | return { 10 | code: 0, 11 | data: { 12 | userName: 'admin', 13 | photo: 'https://xxx.png', 14 | time: new Date().getTime(), 15 | roles: ['admin'], 16 | authBtnList: ['btn.add', 'btn.del', 'btn.edit', 'btn.link'], 17 | }, 18 | msg: null, 19 | type: 'success', 20 | }; 21 | }, 22 | }, 23 | ] as MockMethod[]; 24 | -------------------------------------------------------------------------------- /src/config/encryptionConfig.ts: -------------------------------------------------------------------------------- 1 | import { getEnvValue, getPkgVersion, isDevMode } from '@/utils/env'; 2 | 3 | // System default cache time, in seconds 4 | export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; 5 | const PREFIX = 6 | getEnvValue('VITE_APP_CACHE_PREFIX') || getEnvValue('VITE_APP_TITLE') || 'UNI_APP_VUE3_TS'; 7 | export const DEFAULT_PREFIX_KEY = `${PREFIX}${getPkgVersion()}`; 8 | 9 | // aes encryption key 10 | export const cacheCipher = { 11 | key: 'aQ0{gD1@c_0@oH5:', 12 | iv: 'aF0#gC_$hE1$eA1!', 13 | }; 14 | 15 | // Whether the system cache is encrypted using aes 16 | export const enableStorageEncryption = !isDevMode(); 17 | -------------------------------------------------------------------------------- /windi.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite-plugin-windicss'; 2 | 3 | export default defineConfig({ 4 | extend: { 5 | lineClamp: { 6 | sm: '3', 7 | lg: '10', 8 | }, 9 | }, 10 | preflight: false, 11 | prefixer: false, 12 | extract: { 13 | // 忽略部分文件夹 14 | exclude: ['node_modules', '.git', 'dist'], 15 | }, 16 | content: [ 17 | './src/**/*.vue', 18 | './src/**/*.{vue,js}', 19 | './src/pages/**/*.{vue,js}', 20 | './index.html', 21 | ], 22 | corePlugins: { 23 | // 禁用掉在小程序环境中不可能用到的 plugins 24 | container: false, 25 | }, 26 | // plugins: [require('windicss/plugin/line-clamp')], 27 | }); 28 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createSSRApp } from 'vue'; 2 | import App from './App.vue'; 3 | import { setupStore } from '@/stores'; 4 | import '@/components/FontAwesomeIcon/index.scss'; 5 | import i18n from '@/language/index'; 6 | 7 | // import 'virtual:windi.css'; 8 | 9 | // import 'virtual:windi-base.css'; 10 | // import 'virtual:windi-components.css'; 11 | // import 'virtual:windi-utilities.css'; 12 | 13 | import 'uno.css'; 14 | import '@/assets/style/main.scss'; 15 | 16 | export function createApp() { 17 | const app = createSSRApp(App); 18 | 19 | // Configure store 20 | setupStore(app); 21 | 22 | app.use(i18n); 23 | 24 | return { 25 | app, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 对比 version 的版本号 3 | * @param v1 - 版本号1 4 | * @param v2 - 版本号2 5 | * @returns -1: v1 < v2; 0: v1 = v2; 1: v1 > v2 6 | */ 7 | export function compareVersion(v1: string, v2: string): number { 8 | const v1Arr = v1.split('.'); 9 | const v2Arr = v2.split('.'); 10 | const len = Math.max(v1Arr.length, v2Arr.length); 11 | 12 | while (v1Arr.length < len) v1Arr.push('0'); 13 | 14 | while (v2Arr.length < len) v2Arr.push('0'); 15 | 16 | for (let i = 0; i < len; i++) { 17 | const num1 = +v1Arr[i]; 18 | const num2 = +v2Arr[i]; 19 | 20 | if (num1 > num2) return 1; 21 | else if (num1 < num2) return -1; 22 | } 23 | 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/composables/useLayer.ts: -------------------------------------------------------------------------------- 1 | import type { ComponentObjectPropsOptions } from 'vue'; 2 | import { ColorProp, CustomClassProp, CustomStyleProp } from './useProps'; 3 | 4 | export const useBaseProps = (propOverrides?: Partial) => { 5 | const props = { 6 | // 自定义 class prop 7 | cc: CustomClassProp, 8 | // 自定义子组件 class prop 9 | ccc: CustomClassProp, 10 | // 自定义 style prop 11 | cs: CustomStyleProp, 12 | // 自定义子组件 style prop 13 | ccs: CustomStyleProp, 14 | color: ColorProp, 15 | }; 16 | 17 | // 覆盖 props 18 | if (propOverrides) { 19 | Object.assign(props, propOverrides); 20 | } 21 | 22 | return props; 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/fontawesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2022 Fonticons, Inc. 5 | */ 6 | // Font Awesome core compile (Web Fonts-based) 7 | // ------------------------- 8 | 9 | @import 'functions'; 10 | @import 'variables'; 11 | @import 'mixins'; 12 | @import 'core'; 13 | @import 'sizing'; 14 | @import 'fixed-width'; 15 | //@import 'list'; 16 | @import 'bordered-pulled'; 17 | @import 'animated'; 18 | @import 'rotated-flipped'; 19 | @import 'stacked'; 20 | @import 'icons'; 21 | @import 'screen-reader'; 22 | -------------------------------------------------------------------------------- /mock/auth.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from 'vite-plugin-mock'; 2 | 3 | export default [ 4 | { 5 | url: '/api/login', 6 | timeout: 100, 7 | method: 'post', 8 | response: ({ query }: any) => { 9 | return { 10 | code: 0, 11 | data: { 12 | token: 'xxx', 13 | }, 14 | msg: null, 15 | type: 'success', 16 | }; 17 | }, 18 | }, 19 | 20 | { 21 | url: '/api/logout', 22 | timeout: 100, 23 | method: 'post', 24 | response: ({ query }: any) => { 25 | return { 26 | code: 0, 27 | data: {}, 28 | msg: null, 29 | type: 'success', 30 | }; 31 | }, 32 | }, 33 | ] as MockMethod[]; 34 | -------------------------------------------------------------------------------- /src/components/BasicInput/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | unpackage/ 4 | dist/ 5 | dist-ssr/ 6 | 7 | # local env files 8 | # *.local 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | # logs 14 | # *.log 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | pnpm-debug.log* 19 | lerna-debug.log* 20 | 21 | # Editor directories and files 22 | .project 23 | .idea 24 | .vscode 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw* 30 | .hbuilderx 31 | # 要提交 .env 来确保 jit v2 的开发 watch mode 32 | !.env 33 | 34 | # auto-generated 35 | 36 | src/types/auto-imports.d.ts 37 | src/types/components.d.ts 38 | src/types/vitepress-auto-import.d.ts 39 | src/types/vitepress-components.d.ts 40 | 41 | # Other 42 | 43 | src/ignore 44 | .eslintcache 45 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/stores/modules/lang.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { useI18n } from 'vue-i18n'; 3 | import { getCache, setCache } from '@/utils/cache'; 4 | 5 | export const useLangStore = defineStore({ 6 | id: 'lang', 7 | state: () => { 8 | return { 9 | lang: '', 10 | }; 11 | }, 12 | getters: { 13 | getLang: state => { 14 | return state.lang; 15 | }, 16 | }, 17 | actions: { 18 | initLang() { 19 | this.lang = getCache('lang') || 'cn'; 20 | const { locale } = useI18n(); 21 | locale.value = this.lang; 22 | }, 23 | switchLang(lang: string) { 24 | this.lang = lang; 25 | const { locale } = useI18n(); 26 | locale.value = lang; 27 | setCache('lang', lang); 28 | }, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '@/utils/is'; 2 | 3 | export * from './array'; 4 | export * from './object'; 5 | export * from './promise'; 6 | export * from './string'; 7 | export * from './version'; 8 | export * from './date'; 9 | export * from './regExp'; 10 | export * from './number'; 11 | export * from './is'; 12 | export * from './misc'; 13 | export * from './img'; 14 | export * from './color'; 15 | export * from './random'; 16 | 17 | /** 18 | * 深度合并 19 | * @param src 20 | * @param target 21 | */ 22 | export function deepMerge(src: any = {}, target: any = {}): T { 23 | let key: string; 24 | for (key in target) { 25 | src[key] = isObject(src[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]); 26 | } 27 | return src; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // rotating + flipping icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { 5 | transform: rotate(90deg); 6 | } 7 | 8 | .#{$fa-css-prefix}-rotate-180 { 9 | transform: rotate(180deg); 10 | } 11 | 12 | .#{$fa-css-prefix}-rotate-270 { 13 | transform: rotate(270deg); 14 | } 15 | 16 | .#{$fa-css-prefix}-flip-horizontal { 17 | transform: scale(-1, 1); 18 | } 19 | 20 | .#{$fa-css-prefix}-flip-vertical { 21 | transform: scale(1, -1); 22 | } 23 | 24 | .#{$fa-css-prefix}-flip-both, 25 | .#{$fa-css-prefix}-flip-horizontal.#{$fa-css-prefix}-flip-vertical { 26 | transform: scale(-1, -1); 27 | } 28 | 29 | .#{$fa-css-prefix}-rotate-by { 30 | transform: rotate(var(--#{$fa-css-prefix}-rotate-angle, none)); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // stacking icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | display: inline-block; 6 | height: 2em; 7 | line-height: 2em; 8 | position: relative; 9 | vertical-align: $fa-stack-vertical-align; 10 | width: $fa-stack-width; 11 | } 12 | 13 | .#{$fa-css-prefix}-stack-1x, 14 | .#{$fa-css-prefix}-stack-2x { 15 | left: 0; 16 | position: absolute; 17 | text-align: center; 18 | width: 100%; 19 | z-index: var(--#{$fa-css-prefix}-stack-z-index, #{$fa-stack-z-index}); 20 | } 21 | 22 | .#{$fa-css-prefix}-stack-1x { 23 | line-height: inherit; 24 | } 25 | 26 | .#{$fa-css-prefix}-stack-2x { 27 | font-size: 2em; 28 | } 29 | 30 | .#{$fa-css-prefix}-inverse { 31 | color: var(--#{$fa-css-prefix}-inverse, #{$fa-inverse}); 32 | } 33 | -------------------------------------------------------------------------------- /src/types/router/route.d.ts: -------------------------------------------------------------------------------- 1 | import { types } from 'sass'; 2 | import Boolean = types.Boolean; 3 | 4 | export interface Route extends Record { 5 | path: string; 6 | meta?: { 7 | ignoreAuth?: boolean; 8 | tabBar: boolean; 9 | }; 10 | style: { 11 | navigationBarTitleText: string; 12 | [key: string]: string | boolean; 13 | }; 14 | } 15 | 16 | export interface SubPackages { 17 | root: string; 18 | pages: Route[]; 19 | } 20 | 21 | export interface RouteLocationNormalized { 22 | /* 当前页面栈的实例 */ 23 | currentPages: Page.PageInstance[]; 24 | /* 当前页面的实例 */ 25 | currentPage: Page.PageInstance; 26 | /* 当前页面在pages.json中的配置 */ 27 | currentRoute?: Route; 28 | /* 当前页面的path */ 29 | path?: string; 30 | /* 当前页面的url参数 */ 31 | query: Record; 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/uniapi/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 设置系统剪贴板的内容 3 | * @param data 需要设置的内容 4 | * @param showToast 配置是否弹出提示,默认弹出提示 5 | * @constructor 6 | */ 7 | export const SetClipboardData = (data: string, showToast = true) => 8 | new Promise((resolve, reject) => { 9 | uni.setClipboardData({ 10 | data, 11 | showToast, 12 | success: res => { 13 | resolve(res); 14 | }, 15 | fail: err => { 16 | reject(err); 17 | }, 18 | }); 19 | }); 20 | 21 | /** 22 | * @description 获取系统剪贴板内容 23 | * @constructor 24 | */ 25 | export const GetClipboardData = () => 26 | new Promise((resolve, reject) => { 27 | uni.getClipboardData({ 28 | success: res => { 29 | resolve(res); 30 | }, 31 | fail: err => { 32 | reject(err); 33 | }, 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // bordered + pulled icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | border-color: var(--#{$fa-css-prefix}-border-color, #{$fa-border-color}); 6 | border-radius: var(--#{$fa-css-prefix}-border-radius, #{$fa-border-radius}); 7 | border-style: var(--#{$fa-css-prefix}-border-style, #{$fa-border-style}); 8 | border-width: var(--#{$fa-css-prefix}-border-width, #{$fa-border-width}); 9 | padding: var(--#{$fa-css-prefix}-border-padding, #{$fa-border-padding}); 10 | } 11 | 12 | .#{$fa-css-prefix}-pull-left { 13 | float: left; 14 | margin-right: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin}); 15 | } 16 | 17 | .#{$fa-css-prefix}-pull-right { 18 | float: right; 19 | margin-left: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin}); 20 | } 21 | -------------------------------------------------------------------------------- /src/types/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | declare module '*.vue' { 5 | import { DefineComponent } from 'vue'; 6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 7 | const component: DefineComponent<{}, {}, any>; 8 | export default component; 9 | } 10 | 11 | interface ImportMetaEnv { 12 | readonly VITE_ENV: string; 13 | readonly VITE_APP_TITLE: string; 14 | readonly VITE_BASE_URL: string; 15 | readonly VITE_UPLOAD_URL: string; 16 | readonly VITE_PROD: boolean; 17 | readonly VITE_DEV: boolean; 18 | readonly VITE_APP_CACHE_PREFIX: string; 19 | readonly VITE_PORT: number; 20 | readonly VITE_OPEN: boolean; 21 | readonly VITE_IMAGE_URL: string; 22 | } 23 | 24 | interface ImportMeta { 25 | readonly env: ImportMetaEnv; 26 | } 27 | -------------------------------------------------------------------------------- /src/assets/style/flex.scss: -------------------------------------------------------------------------------- 1 | // flex布局 2 | 3 | $flex-directions: row, row-reverse, column, column-reverse; 4 | @each $v in $flex-directions { 5 | .flex-#{$v} { 6 | @include flex($v); 7 | } 8 | } 9 | 10 | $justify-content: start, end, center, between, around, evenly; 11 | @each $v in $justify-content { 12 | .justify-#{$v} { 13 | @if ($v == 'start') { 14 | justify-content: flex- + $v; 15 | } @else if($v == 'start') { 16 | justify-content: flex- + $v; 17 | } @else { 18 | justify-content: $v; 19 | } 20 | } 21 | } 22 | 23 | $align-items: start, end, center, baseline, stretch; 24 | @each $v in $align-items { 25 | .items-#{$v} { 26 | @if ($v == 'start') { 27 | align-items: flex- + $v; 28 | } @else if($v == 'start') { 29 | align-items: flex- + $v; 30 | } @else { 31 | align-items: $v; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/hooks/countDown.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 倒计时 3 | */ 4 | const INTERVAL_TIME = 1000; 5 | export function useCountDown(timeCount = 60) { 6 | const countShow = ref(false); 7 | const countDown = ref(timeCount); 8 | let timer: number | undefined; 9 | 10 | const resetCountDown = () => { 11 | clearInterval(timer); 12 | countShow.value = false; 13 | countDown.value = timeCount; 14 | timer = undefined; 15 | }; 16 | 17 | const startCountDown = () => { 18 | if (timer != undefined) { 19 | return; 20 | } 21 | 22 | countShow.value = true; 23 | timer = setInterval(() => { 24 | if (countDown.value > 0 && countDown.value <= timeCount) { 25 | countDown.value--; 26 | } else { 27 | resetCountDown(); 28 | } 29 | }, INTERVAL_TIME); 30 | }; 31 | 32 | return { 33 | countShow, 34 | countDown, 35 | startCountDown, 36 | resetCountDown, 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/stores/modules/router.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { Route } from '@/types/router/route'; 3 | import { pagesMap } from '@/utils/router/routes'; 4 | 5 | interface routeStore { 6 | routes: Map | undefined; 7 | currentRouter: Route | undefined; 8 | } 9 | 10 | export const useRouterStore = defineStore({ 11 | id: 'routerStore', 12 | state: (): routeStore => ({ 13 | routes: undefined, 14 | currentRouter: undefined, 15 | }), 16 | getters: { 17 | getRoutes(state) { 18 | return state.routes; 19 | }, 20 | getCurrentRoute(state) { 21 | return state.currentRouter; 22 | }, 23 | }, 24 | actions: { 25 | initialize() { 26 | this.setRoutes(); 27 | }, 28 | setRoutes() { 29 | this.routes = pagesMap; 30 | }, 31 | setCurrentRoute(path: string) { 32 | this.currentRouter = this.routes?.get(path) || undefined; 33 | }, 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /src/components/BasicButton/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 33 | -------------------------------------------------------------------------------- /src/androidPrivacy.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "prompt": "template", 4 | "title": "服务协议和隐私政策", 5 | "message": "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。
  你可阅读《服务协议》《隐私政策》了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。", 6 | "buttonAccept": "同意并接受", 7 | "buttonRefuse": "暂不同意", 8 | // HX 3.4.13之后版本新增,system 使用系统webview 打开隐私协议链接,默认使用uni-app内置web组件 9 | "hrefLoader": "default", 10 | "second": { 11 | "title": "确认提示", 12 | "message": "  进入应用前,你需先同意《服务协议》《隐私政策》,否则将退出应用。", 13 | "buttonAccept": "同意并继续", 14 | "buttonRefuse": "退出应用" 15 | }, 16 | "styles": { 17 | "backgroundColor": "#00FF00", 18 | "borderRadius": "5px", 19 | "title": { 20 | "color": "#ff00ff" 21 | }, 22 | "buttonAccept": { 23 | "color": "#ffff00" 24 | }, 25 | "buttonRefuse": { 26 | "color": "#00ffff" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/stores/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { store } from '@/stores'; 2 | import { defineStore } from 'pinia'; 3 | 4 | interface MenuButtonBoundingClientRect { 5 | width: number; 6 | height: number; 7 | top: number; 8 | left: number; 9 | right: number; 10 | bottom: number; 11 | } 12 | 13 | export const useAppStore = defineStore('app', () => { 14 | const darkMode = ref(false); 15 | const statusBarHeight = ref(0); 16 | const menuButtonBounding = ref(); 17 | const customBarHeight = computed(() => 18 | !menuButtonBounding.value 19 | ? 0 20 | : menuButtonBounding.value.bottom + menuButtonBounding.value.top - statusBarHeight.value, 21 | ); 22 | 23 | return { 24 | darkMode, 25 | statusBarHeight, 26 | customBarHeight, 27 | menuButtonBounding, 28 | }; 29 | }); 30 | 31 | // Need to be used outside the setup 32 | export function useAppStoreWidthOut() { 33 | return useAppStore(store); 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/cache/index.ts: -------------------------------------------------------------------------------- 1 | import { createStorage, CreateStorageParams } from './storageCache'; 2 | import { 3 | cacheCipher, 4 | DEFAULT_CACHE_TIME, 5 | DEFAULT_PREFIX_KEY, 6 | enableStorageEncryption, 7 | } from '@/config/encryptionConfig'; 8 | 9 | const options: Partial = { 10 | prefixKey: DEFAULT_PREFIX_KEY, 11 | key: cacheCipher.key, 12 | iv: cacheCipher.iv, 13 | hasEncrypt: enableStorageEncryption, 14 | timeout: DEFAULT_CACHE_TIME, 15 | }; 16 | 17 | export const storage = createStorage(options); 18 | 19 | export function setCache(key: string, value: any, expire?: number | null): void { 20 | storage.set(key, value, expire); 21 | } 22 | 23 | export function getCache(key: string): T { 24 | return storage.get(key); 25 | } 26 | 27 | export function removeCache(key: string): void { 28 | return storage.remove(key); 29 | } 30 | 31 | export function clearCache(): void { 32 | return storage.clear(); 33 | } 34 | -------------------------------------------------------------------------------- /src/api/auth.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@/utils/http'; 2 | import { LoginParams, LoginModel } from '@/api/models/authModel'; 3 | 4 | const LOGIN = '/login'; 5 | const LOGIN_OUT = '/logout'; 6 | const REFRESH_TOKEN = '/refresh/token'; 7 | 8 | /** 9 | * 登录 10 | * @param params 11 | */ 12 | export function login(params: LoginParams) { 13 | return request.post(LOGIN, params, { 14 | custom: { 15 | auth: false, 16 | }, 17 | }); 18 | } 19 | 20 | // /** 21 | // * 登录 22 | // * @param params 23 | // */ 24 | // export function login(params: LoginParams) { 25 | // return request.post>(LOGIN, params, { 26 | // custom: { 27 | // auth: false, 28 | // }, 29 | // }); 30 | // } 31 | 32 | /** 33 | * 登出 34 | */ 35 | export function logout() { 36 | return request.post(LOGIN_OUT, {}); 37 | } 38 | 39 | /** 40 | * 刷新token 41 | */ 42 | export function refreshToken() { 43 | return request.post(REFRESH_TOKEN, {}); 44 | } 45 | -------------------------------------------------------------------------------- /src/pages/notFound/404.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/utils/color.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '.'; 2 | 3 | /** 4 | * rgb转hex 5 | * @param r - 红色 6 | * @param g - 绿色 7 | * @param b - 蓝色 8 | * @returns 9 | */ 10 | export function rgbToHex(r: string | number, g: string | number, b: string | number) { 11 | return `#${toHex(r)}${toHex(g)}${toHex(b)}`; 12 | } 13 | 14 | export function toHex(n: string | number) { 15 | if (isString(n)) n = parseInt(n, 10); 16 | if (isNaN(n)) return '00'; 17 | n = Math.max(0, Math.min(n, 255)); 18 | return '0123456789ABCDEF'.charAt((n - (n % 16)) / 16) + '0123456789ABCDEF'.charAt(n % 16); 19 | } 20 | 21 | /** 22 | * 十六进制颜色转RGB颜色 23 | * @param hex 颜色值 #333 或 #333333 24 | */ 25 | export function hexToRGB(hex: string) { 26 | if (hex.length === 4) { 27 | const text = hex.substring(1, 4); 28 | hex = `#${text}${text}`; 29 | } 30 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 31 | return result 32 | ? { 33 | r: parseInt(result[1], 16), 34 | g: parseInt(result[2], 16), 35 | b: parseInt(result[3], 16), 36 | } 37 | : null; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/thin.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2022 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | /* #ifndef MP-WEIXIN || MP-ALIPAY */ 9 | @import 'fonts/fa-thin-100'; 10 | 11 | :root, 12 | :host { 13 | --#{$fa-css-prefix}-style-family-classic: $fa-style-family-thin; 14 | --#{$fa-css-prefix}-font-thin: normal 100 1em/1 '#{ $fa-style-family-thin }'; 15 | } 16 | 17 | @font-face { 18 | font-family: $fa-style-family-thin; 19 | font-style: normal; 20 | font-weight: 100; 21 | font-display: $fa-font-display; 22 | /* #ifdef H5 */ 23 | src: url('#{$fa-font-path}/fa-thin-100.woff2') format('woff2'), 24 | url('#{$fa-font-path}/fa-thin-100.ttf') format('truetype'); 25 | /* #endif */ 26 | 27 | /* #ifndef H5 */ 28 | src: $fa-thin-100-woff2; 29 | // $fa-thin-100-ttf; 30 | /* #endif */ 31 | } 32 | /* #endif */ 33 | 34 | .far, 35 | .#{$fa-css-prefix}-thin { 36 | font-weight: 100; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/light.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2022 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | /* #ifndef MP-WEIXIN || MP-ALIPAY */ 9 | @import 'fonts/fa-light-300'; 10 | 11 | :root, 12 | :host { 13 | --#{$fa-css-prefix}-style-family-classic: $fa-style-family-light; 14 | --#{$fa-css-prefix}-font-light: normal 300 1em/1 '#{ $fa-style-family-light }'; 15 | } 16 | 17 | @font-face { 18 | font-family: $fa-style-family-light; 19 | font-style: normal; 20 | font-weight: 300; 21 | font-display: $fa-font-display; 22 | /* #ifdef H5 */ 23 | src: url('#{$fa-font-path}/fa-light-300.woff2') format('woff2'), 24 | url('#{$fa-font-path}/fa-light-300.ttf') format('truetype'); 25 | /* #endif */ 26 | 27 | /* #ifndef H5 */ 28 | src: $fa-light-300-woff2; 29 | // $fa-light-300-ttf; 30 | /* #endif */ 31 | } 32 | /* #endif */ 33 | 34 | .far, 35 | .#{$fa-css-prefix}-light { 36 | font-weight: 300; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/solid.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2022 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | /* #ifndef MP-WEIXIN || MP-ALIPAY */ 9 | @import 'fonts/fa-solid-900'; 10 | 11 | :root, 12 | :host { 13 | --#{$fa-css-prefix}-style-family-classic: $fa-style-family-solid; 14 | --#{$fa-css-prefix}-font-solid: normal 900 1em/1 '#{ $fa-style-family-solid }'; 15 | } 16 | 17 | @font-face { 18 | font-family: $fa-style-family-solid; 19 | font-style: normal; 20 | font-weight: 900; 21 | font-display: $fa-font-display; 22 | /* #ifdef H5 */ 23 | src: url('#{$fa-font-path}/fa-solid-900.woff2') format('woff2'), 24 | url('#{$fa-font-path}/fa-solid-900.ttf') format('truetype'); 25 | /* #endif */ 26 | 27 | /* #ifndef H5 */ 28 | src: $fa-solid-900-woff2; 29 | // $fa-solid-900-ttf; 30 | /* #endif */ 31 | } 32 | /* #endif */ 33 | 34 | .fas, 35 | .#{$fa-css-prefix}-solid { 36 | font-weight: 900; 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/router/constant.ts: -------------------------------------------------------------------------------- 1 | import { LOGIN_PAGE } from '@/enums/routerEnum'; 2 | import { useRouterStore } from '@/stores/modules/router'; 3 | import { useRouter } from '@/hooks/router'; 4 | 5 | /** 6 | * 是否忽略验证 7 | * @param path 8 | * @return boolean 9 | */ 10 | export function isIgnoreAuth(path: string): boolean { 11 | const _path = filterPath(path); 12 | const routerStore = useRouterStore(); 13 | const routes = routerStore.getRoutes; 14 | if (!routes) return false; 15 | const route = routes.get(_path); 16 | return route === undefined ? true : !!route?.meta?.ignoreAuth; 17 | } 18 | 19 | /** 20 | * 跳转登录 21 | * @param path 22 | */ 23 | export function jumpLogin(path: string) { 24 | const _path = path.startsWith('/') ? path : `/${path}`; 25 | const pathQuery = encodeURIComponent(_path); 26 | const router = useRouter(); 27 | router.push(`${LOGIN_PAGE}?redirect=${pathQuery}`); 28 | } 29 | 30 | /** 31 | * 过滤url,获取path 32 | * @param url 33 | * @param prefix 34 | */ 35 | export function filterPath(url: string, prefix = '') { 36 | const path = url.split('?')[0]; 37 | return prefix + (path.startsWith('/') ? path.substring(1) : path); 38 | } 39 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/regular.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2022 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | /* #ifndef MP-WEIXIN || MP-ALIPAY */ 9 | @import 'fonts/fa-regular-400'; 10 | 11 | :root, 12 | :host { 13 | --#{$fa-css-prefix}-style-family-classic: $fa-style-family-regular; 14 | --#{$fa-css-prefix}-font-regular: normal 400 1em/1 '#{ $fa-style-family-regular }'; 15 | } 16 | 17 | @font-face { 18 | font-family: $fa-style-family-regular; 19 | font-style: normal; 20 | font-weight: 400; 21 | font-display: $fa-font-display; 22 | /* #ifdef H5 */ 23 | src: url('#{$fa-font-path}/fa-regular-400.woff2') format('woff2'), 24 | url('#{$fa-font-path}/fa-regular-400.ttf') format('truetype'); 25 | /* #endif */ 26 | 27 | /* #ifndef H5 */ 28 | src: $fa-regular-400-woff2; 29 | // $fa-regular-400-ttf; 30 | 31 | /* #endif */ 32 | } 33 | /* #endif */ 34 | 35 | .far, 36 | .#{$fa-css-prefix}-regular { 37 | font-weight: 400; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/UnoUI/UButton/UButton.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/utils/api.ts: -------------------------------------------------------------------------------- 1 | import type { HttpResponse } from 'luch-request'; 2 | import to from 'await-to-js'; 3 | 4 | type HttpPromise = Promise>; 5 | 6 | /** 7 | * to api 8 | */ 9 | export default function toApi(promise: HttpPromise) { 10 | return to, U>(promise); 11 | } 12 | 13 | /** 14 | * to api store 15 | */ 16 | export function toApiStore(promise: Promise) { 17 | return to(promise); 18 | } 19 | 20 | // /** 21 | // * @param { Promise } promise 22 | // * @param { Object= } errorExt - Additional Information you can pass to the err object 23 | // * @return { Promise } 24 | // */ 25 | // export function to ( 26 | // promise: Promise, 27 | // errorExt?: object 28 | // ): Promise<[U, undefined] | [null, T]> { 29 | // return promise 30 | // .then<[null, T]>((data: T) => [null, data]) 31 | // .catch<[U, undefined]>((err: U) => { 32 | // if (errorExt) { 33 | // const parsedError = Object.assign({}, err, errorExt); 34 | // return [parsedError, undefined]; 35 | // } 36 | 37 | // return [err, undefined]; 38 | // }); 39 | // } 40 | 41 | // export default to; 42 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/sharp-solid.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2022 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | /* #ifndef MP-WEIXIN || MP-ALIPAY */ 9 | @import 'fonts/fa-sharp-solid-900'; 10 | 11 | :root, 12 | :host { 13 | --#{$fa-css-prefix}-style-family-sharp: $fa-style-family-sharp-solid; 14 | --#{$fa-css-prefix}-font-sharp-solid: normal 900 1em/1 '#{ $fa-style-family-sharp-solid }'; 15 | } 16 | 17 | @font-face { 18 | font-family: $fa-style-family-sharp-solid; 19 | font-style: normal; 20 | font-weight: 900; 21 | font-display: $fa-font-display; 22 | /* #ifdef H5 */ 23 | src: url('#{$fa-font-path}/fa-sharp-solid-900.woff2') format('woff2'), 24 | url('#{$fa-font-path}/fa-sharp-solid-900.ttf') format('truetype'); 25 | /* #endif */ 26 | 27 | /* #ifndef H5 */ 28 | src: $fa-sharp-solid-900-woff2; 29 | // $fa-sharp-solid-900-ttf; 30 | /* #endif */ 31 | } 32 | /* #endif */ 33 | 34 | .far, 35 | .#{$fa-css-prefix}-sharp-solid { 36 | font-weight: 900; 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/router/routes.ts: -------------------------------------------------------------------------------- 1 | import { assign } from 'lodash-es'; 2 | import pagesJson from '@/pages.json'; 3 | import { Route } from '@/types/router/route'; 4 | 5 | const { pages, subPackages, tabBar } = pagesJson; 6 | 7 | // 将pages.json转换成Map对象,path为key 8 | const pagesMap = new Map(); 9 | 10 | pages.forEach(page => { 11 | pagesMap.set(page.path, page as Route); 12 | }); 13 | 14 | if (Array.isArray(subPackages) && subPackages.length) { 15 | subPackages.forEach(el => { 16 | const rootPath = el.root; 17 | el.pages.forEach(page => { 18 | page.path = `${rootPath}/${page.path}`; 19 | pagesMap.set(page.path, page as Route); 20 | }); 21 | }); 22 | } 23 | 24 | if (tabBar) { 25 | const tabBarList = tabBar.list; 26 | if (Array.isArray(tabBarList)) { 27 | tabBarList.forEach(el => { 28 | if (pagesMap.has(el.pagePath)) { 29 | const page = pagesMap.get(el.pagePath); 30 | const meta = page?.meta || {}; 31 | // @ts-ignore 32 | meta.tabBar = true; 33 | // @ts-ignore 34 | page.meta = assign({}, meta); 35 | pagesMap.set(el.pagePath, page as Route); 36 | } 37 | }); 38 | } 39 | } 40 | 41 | export { pagesMap }; 42 | -------------------------------------------------------------------------------- /src/hooks/router.ts: -------------------------------------------------------------------------------- 1 | import { Navigates } from '@/utils/router/navigates'; 2 | import { useRouterStore } from '@/stores/modules/router'; 3 | import { RouteLocationNormalized } from '@/types/router/route'; 4 | 5 | const router = new Navigates(); 6 | 7 | /** 8 | * 路由hook 9 | */ 10 | export function useRouter() { 11 | return router; 12 | } 13 | 14 | /** 15 | * 获取当前Route信息 16 | * 推荐在onLoad中调用此hook 17 | * getCurrentPages方法在不同平台有差异 18 | * 在微信小程序中只有在onLoad中才能获取到query 19 | * @return RouteLocationNormalized 20 | */ 21 | export function useRoute(): RouteLocationNormalized { 22 | const currentPages = getCurrentPages(); 23 | const currentPage = currentPages[currentPages.length - 1]; 24 | const path = currentPage?.route || ''; 25 | const routerStore = useRouterStore(); 26 | const currentRoute = routerStore.getRoutes?.get(path as string); 27 | let query = {}; 28 | /* #ifndef MP-WEIXIN */ 29 | // @ts-ignore 30 | query = currentPage?.$page?.options || {}; 31 | /* #endif */ 32 | 33 | /* #ifdef MP-WEIXIN */ 34 | // @ts-ignore 35 | query = currentPage?.options || {}; 36 | /* #endif */ 37 | return { 38 | currentPages, 39 | currentPage, 40 | path, 41 | currentRoute, 42 | query, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/UImage/UImage.vue: -------------------------------------------------------------------------------- 1 | 30 | 42 | 43 | 48 | -------------------------------------------------------------------------------- /src/utils/random.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 随机十六进制颜色 3 | * @returns #e34155 4 | */ 5 | export function randomHexColorCode() { 6 | const n = (Math.random() * 0xfffff * 1000000).toString(16); 7 | return `#${n.slice(0, 6)}`; 8 | } 9 | 10 | /** 11 | * 随机 rgb 颜色 12 | * @param min - 最小值 13 | * @param max - 最大值 14 | * @returns 15 | */ 16 | export function randomRgbColor(min = 0, max = 255) { 17 | const r = randomIntegerInRange(min, max); 18 | const g = randomIntegerInRange(min, max); 19 | const b = randomIntegerInRange(min, max); 20 | return `rgb(${r},${g},${b})`; 21 | } 22 | 23 | /** 24 | * 随机布尔值 25 | * @returns true or false 26 | */ 27 | export function randomBoolean() { 28 | return Math.random() >= 0.5; 29 | } 30 | 31 | /** 32 | * 生成指定范围的随机整数 33 | * @param min - 最小值 34 | * @param max - 最大值 35 | * @returns (0,5) => 3 36 | */ 37 | export function randomIntegerInRange(min: number, max: number): number { 38 | return Math.floor(Math.random() * (max - min + 1)) + min; 39 | } 40 | 41 | /** 42 | * 生成指定范围的随机小数 43 | * @param min - 最小值 44 | * @param max - 最大值 45 | * @returns (0,5) => 3.0211363285087005 46 | */ 47 | export function randomNumberInRange(min: number, max: number) { 48 | return Math.random() * (max - min) + min; 49 | } 50 | -------------------------------------------------------------------------------- /assets/svg/loading2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignores: [commit => commit.includes('init')], 3 | extends: ['@commitlint/config-conventional'], 4 | // extends: ['@commitlint/config-conventional', 'cz'], 5 | rules: { 6 | 'type-enum': [ 7 | 2, 8 | 'always', 9 | [ 10 | 'feat', 11 | 'fix', 12 | 'docs', 13 | 'style', 14 | 'refactor', 15 | 'perf', 16 | 'test', 17 | 'build', 18 | 'ci', 19 | 'chore', 20 | 'revert', 21 | 'wip', 22 | 'mod', 23 | 'release', 24 | ], 25 | ], 26 | // // 格式 小写 27 | // 'type-case': [2, 'always', 'lower-case'], 28 | // // 不能为空 29 | // 'type-empty': [2, 'never'], 30 | // // 范围不能为空 31 | // 'scope-empty': [2, 'never'], 32 | // // 范围格式 33 | // 'scope-case': [0], 34 | // // 主要 message 不能为空 35 | // 'subject-empty': [2, 'never'], 36 | // // 以什么为结束标志,禁用 37 | // 'subject-full-stop': [0, 'never'], 38 | // // 格式,禁用 39 | // 'subject-case': [0, 'never'], 40 | // // 以空行开头 41 | // 'body-leading-blank': [1, 'always'], 42 | // 'header-max-length': [0, 'always', 72], 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /mock/_util.ts: -------------------------------------------------------------------------------- 1 | import { Recordable } from 'vite-plugin-mock'; 2 | 3 | export function resultSuccess( 4 | data: T, 5 | { message = 'ok' } = {}, 6 | ) { 7 | return { 8 | code: 0, 9 | data, 10 | message, 11 | type: 'success', 12 | }; 13 | } 14 | 15 | export function resultPageSuccess( 16 | page: number, 17 | pageSize: number, 18 | list: object[], 19 | { message = 'ok' } = {}, 20 | ) { 21 | const pageData = pagination(page, pageSize, list); 22 | 23 | return { 24 | ...resultSuccess({ 25 | current: page, 26 | size: pageSize, 27 | records: pageData, 28 | total: list.length, 29 | }), 30 | message, 31 | }; 32 | } 33 | 34 | export function resultError( 35 | message = 'Request failed', 36 | { code = -1, data = null } = {}, 37 | ) { 38 | return { 39 | code, 40 | data, 41 | message, 42 | type: 'error', 43 | }; 44 | } 45 | 46 | export function pagination( 47 | pageNo: number, 48 | pageSize: number, 49 | array: T[], 50 | ): T[] { 51 | const offset = (pageNo - 1) * Number(pageSize); 52 | const ret = 53 | offset + Number(pageSize) >= array.length 54 | ? array.slice(offset, array.length) 55 | : array.slice(offset, offset + Number(pageSize)); 56 | return ret; 57 | } 58 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/brands.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2022 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | /* #ifndef MP-WEIXIN || MP-ALIPAY */ 9 | @import 'fonts/fa-brands-400'; 10 | 11 | :root, 12 | :host { 13 | --#{$fa-css-prefix}-style-family-brands: $fa-style-family-brands; 14 | --#{$fa-css-prefix}-font-brands: normal 400 1em/1 '#{ $fa-style-family-brands }'; 15 | } 16 | 17 | @font-face { 18 | font-family: $fa-style-family-brands; 19 | font-style: normal; 20 | font-weight: 400; 21 | font-display: $fa-font-display; 22 | 23 | /* #ifdef H5 */ 24 | src: url('#{$fa-font-path}/fa-brands-400.woff2') format('woff2'), 25 | url('#{$fa-font-path}/fa-brands-400.ttf') format('truetype'); 26 | /* #endif */ 27 | 28 | /* #ifndef H5 */ 29 | src: $fa-brands-400-woff2; 30 | // $fa-brands-400-ttf; 31 | /* #endif */ 32 | } 33 | /* #endif */ 34 | 35 | .fab, 36 | .#{$fa-css-prefix}-brands { 37 | font-weight: 400; 38 | } 39 | 40 | @each $name, $icon in $fa-brand-icons { 41 | .#{$fa-css-prefix}-#{$name}:before { 42 | content: unquote('"#{ $icon }"'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/UnoUI/UNotify/UNotify.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/utils/object.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from '.'; 2 | 3 | /** 4 | * 深度拷贝对象 5 | * @param obj - 对象 6 | * @returns 拷贝的对象 7 | */ 8 | export function clone(obj: object): object { 9 | return JSON.parse(JSON.stringify(obj)); 10 | } 11 | 12 | /** 13 | * 清除对象中 undefined,null,[]空数组 14 | * @param obj - 对象 15 | * @returns 清除后的对象 16 | */ 17 | export function clearNull(obj: any): any { 18 | if (typeof obj === 'object') { 19 | const result = clone(obj); 20 | 21 | for (const key in result) { 22 | const current = result[key]; 23 | if ([null, ''].includes(current) || (isArray(current) && current.length === 0)) delete result[key]; 24 | else result[key] = clearNull(current); 25 | } 26 | return result; 27 | } 28 | 29 | if (isArray(obj)) return obj.map((item: any) => clearNull(item)); 30 | 31 | return obj; 32 | } 33 | 34 | /** 35 | * 是否为对象的属性 36 | * @param val - 对象 37 | * @param key - 键 38 | */ 39 | const hasOwnProperty = Object.prototype.hasOwnProperty; 40 | export const hasOwn = (val: object, key: string | symbol): key is keyof typeof val => hasOwnProperty.call(val, key); 41 | 42 | export const objectToString = Object.prototype.toString; 43 | /** 44 | * 对象类型 45 | * @param value - 对象 46 | */ 47 | export const toTypeString = (value: unknown): string => objectToString.call(value); 48 | 49 | /** 50 | * 比较一个值是否改变 51 | * @param value - 对象 52 | * @param oldValue - 对象 53 | * 54 | */ 55 | export const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue); 56 | -------------------------------------------------------------------------------- /src/assets/style/auxiliary.scss: -------------------------------------------------------------------------------- 1 | view, 2 | scroll-view, 3 | swiper, 4 | match-media, 5 | movable-area, 6 | movable-view, 7 | cover-view, 8 | cover-image, 9 | icon, 10 | text, 11 | rich-text, 12 | progress, 13 | button, 14 | checkboxe, 15 | ditor, 16 | form, 17 | input, 18 | label, 19 | picker, 20 | picker-view, 21 | radio, 22 | slider, 23 | switch, 24 | textarea, 25 | navigator, 26 | audio, 27 | camera, 28 | image, 29 | video, 30 | live-player, 31 | live-pusher, 32 | map, 33 | canvas, 34 | web-view, 35 | :before, 36 | :after { 37 | box-sizing: border-box; 38 | } 39 | 40 | /* 隐藏scroll-view的滚动条 */ 41 | ::-webkit-scrollbar { 42 | display: none; 43 | width: 0 !important; 44 | height: 0 !important; 45 | -webkit-appearance: none; 46 | background: transparent; 47 | } 48 | 49 | // 超出省略,最多5行 50 | @for $i from 1 through 5 { 51 | .text-ellipsis-#{$i} { 52 | // vue下,单行和多行显示省略号需要单独处理 53 | @if $i == '1' { 54 | overflow: hidden; 55 | white-space: nowrap; 56 | text-overflow: ellipsis; 57 | } @else { 58 | display: -webkit-box !important; 59 | overflow: hidden; 60 | text-overflow: ellipsis; 61 | word-break: break-all; 62 | -webkit-line-clamp: $i; 63 | -webkit-box-orient: vertical !important; 64 | } 65 | } 66 | } 67 | 68 | // 历遍生成4个方向的底部安全区 69 | @each $d in top, right, bottom, left { 70 | .safe-area-inset-#{$d} { 71 | padding-#{$d}: 0 !important; 72 | padding-#{$d}: constant(safe-area-inset-#{$d}) !important; 73 | padding-#{$d}: env(safe-area-inset-#{$d}) !important; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/utils/array.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 移除数组中的某个元素 3 | * @param arr - 数组 4 | * @param el - 元素 5 | */ 6 | export const removeAt = (arr: T[], el: T) => { 7 | const i = arr.indexOf(el); 8 | if (i > -1) arr.splice(i, 1); 9 | }; 10 | 11 | /** 12 | * 将值插入到指定索引之后 13 | * @param arr - 数组 14 | * @param index - 索引 15 | * @param v - 值 16 | * @example 17 | * let otherArray = [2, 10]; 18 | * insertAt(otherArray, 0, 4, 6, 8); // otherArray = [2, 4, 6, 8, 10] 19 | */ 20 | export const insertAt = (arr: T[], index: number, ...v: T[]) => { 21 | arr.splice(index + 1, 0, ...v); 22 | return arr; 23 | }; 24 | 25 | /** 26 | * 返回数组中的最后一个元素 27 | * @param arr - 数组 28 | * @example 29 | * last([1, 2, 3]); // 3 30 | * last([]); // undefined 31 | * last(null); // undefined 32 | * last(undefined); // undefined 33 | */ 34 | export const last = (arr: T[]) => (arr && arr.length ? arr[arr.length - 1] : undefined); 35 | 36 | /** 37 | * 返回数组中的最后 n 个元素 38 | * @param arr - 数组 39 | * @param n - 索引 40 | * @example lastN(['a', 'b', 'c', 'd'], 2); // ['c', 'd'] 41 | */ 42 | export const lastN = (arr: T[], n: number) => arr.slice(-n); 43 | 44 | /** 45 | * 布尔全等判断 46 | * @param arr - 数组 47 | * @param fn - 判断函数 48 | * @example all([4, 2, 3], x => x > 1) => true 49 | */ 50 | export function all(arr: unknown[], fn = Boolean) { 51 | return arr.every(fn); 52 | } 53 | 54 | /** 55 | * 检查数组各项相等 56 | * @param arr - 数组 57 | * @example allEqual([4, 2, 3]) => false 58 | * @example allEqual([4, 4, 4]) => true 59 | */ 60 | export function allEqual(arr: unknown[]) { 61 | return arr.every(val => val === arr[0]); 62 | } 63 | -------------------------------------------------------------------------------- /scripts/verifyCommit.ts: -------------------------------------------------------------------------------- 1 | // Invoked on the commit-msg git hook by simple-git-hooks. 2 | 3 | import colors from 'picocolors'; 4 | import { readFileSync } from 'fs'; 5 | 6 | // get $1 from commit-msg script 7 | const msgPath = process.argv[2]; 8 | const msg = readFileSync(msgPath, 'utf-8').trim(); 9 | 10 | const releaseRE = /^v\d/; 11 | const commitRE = 12 | /^(revert: )?(feat|bug|fix|ui|docs|style|perf|release|deploy|refactor|test|chore|revert|merge|build)(\(.+\))?: .{1,50}/; 13 | const msgObj: any = { 14 | feat: '新功能(feature)', 15 | bug: '此项特别针对bug号,用于向测试反馈bug列表的bug修改情况', 16 | ui: '更新 ui', 17 | fix: '修复bug(fix)', 18 | docs: '文档(documentation)', 19 | style: '格式,不影响代码运行的变动(style)', 20 | perf: '性能性能优化(performance)', 21 | release: '发布(release)', 22 | deploy: '部署(deploy)', 23 | refactor: '重构,即不是新增功能,也不是修改bug的代码变动(refactor)', 24 | test: '单元测试(test)', 25 | chore: '构建过程或辅助工具的变动(chore)', 26 | revert: '回滚(revert)', 27 | merge: '合并分支(merge)', 28 | build: '构建(build)', 29 | }; 30 | 31 | let msgStr = ''; 32 | for (const key in msgObj) { 33 | if (Object.prototype.hasOwnProperty.call(msgObj, key)) { 34 | const element = msgObj[key]; 35 | msgStr += ` ${key}: ` + element + '\n'; 36 | } 37 | } 38 | 39 | if (!releaseRE.test(msg) && !commitRE.test(msg)) { 40 | console.log(); 41 | console.error( 42 | ` ${colors.bgRed(colors.white(' ERROR '))} ${colors.red(`invalid commit message format.`)}\n\n` + 43 | colors.red(` Proper commit message format is required for automated changelog generation. Examples:\n\n`) + 44 | `${colors.green(`${msgStr}`)}\n\n`, 45 | ); 46 | process.exit(1); 47 | } 48 | -------------------------------------------------------------------------------- /src/components/UnoUI/UToast/UToast.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/pagesUno/dashboard/dashboard.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/utils/cipher.ts: -------------------------------------------------------------------------------- 1 | import { encrypt, decrypt } from 'crypto-js/aes'; 2 | import UTF8, { parse } from 'crypto-js/enc-utf8'; 3 | import pkcs7 from 'crypto-js/pad-pkcs7'; 4 | import ECB from 'crypto-js/mode-ecb'; 5 | import md5 from 'crypto-js/md5'; 6 | 7 | import Base64 from 'crypto-js/enc-base64'; 8 | 9 | export interface EncryptionParams { 10 | key: string; 11 | iv: string; 12 | } 13 | 14 | /** 15 | * AES 加密解密 16 | */ 17 | export class AesEncryption { 18 | private key; 19 | 20 | private iv; 21 | 22 | constructor(opt: Partial = {}) { 23 | const { key, iv } = opt; 24 | if (key) { 25 | this.key = parse(key); 26 | } 27 | if (iv) { 28 | this.iv = parse(iv); 29 | } 30 | } 31 | 32 | get getOptions() { 33 | return { 34 | mode: ECB, 35 | padding: pkcs7, 36 | iv: this.iv, 37 | }; 38 | } 39 | 40 | encryptByAES(cipherText: string) { 41 | return encrypt(cipherText, this.key!, this.getOptions).toString(); 42 | } 43 | 44 | decryptByAES(cipherText: string) { 45 | return decrypt(cipherText, this.key!, this.getOptions).toString(UTF8); 46 | } 47 | } 48 | 49 | /** 50 | * Base64加密 51 | * @param cipherText 52 | */ 53 | export function encryptByBase64(cipherText: string) { 54 | return UTF8.parse(cipherText).toString(Base64); 55 | } 56 | 57 | /** 58 | * Base64解密 59 | * @param cipherText 60 | */ 61 | export function decodeByBase64(cipherText: string) { 62 | return Base64.parse(cipherText).toString(UTF8); 63 | } 64 | 65 | /** 66 | * MD5加密 67 | * @param password 68 | */ 69 | export function encryptByMd5(password: string) { 70 | return md5(password).toString(); 71 | } 72 | -------------------------------------------------------------------------------- /src/pages/demo/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 46 | 47 | 53 | -------------------------------------------------------------------------------- /.cz-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | types: [ 3 | { value: 'feat', name: 'feat: 新增功能' }, 4 | { value: 'fix', name: 'fix: 修复bug' }, 5 | { value: 'docs', name: 'docs: 文档变更' }, 6 | { value: 'style', name: 'style: 代码格式(不影响功能,例如空格、分号等格式修正)' }, 7 | { value: 'refactor', name: 'refactor: 代码重构(不包括 bug 修复、功能新增)' }, 8 | { value: 'perf', name: 'perf: 性能优化' }, 9 | { value: 'test', name: 'test: 添加、修改测试用例' }, 10 | { value: 'build', name: 'build: 构建流程、外部依赖变更(如升级 npm 包、修改 脚手架 配置等)' }, 11 | { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, 12 | { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' }, 13 | { value: 'revert', name: 'revert: 回滚 commit' }, 14 | { value: 'wip', name: 'wip: 开发中' }, 15 | { value: 'mod', name: 'mod: 不确定分类的修改' }, 16 | { value: 'release', name: 'release: 发布' }, 17 | ], 18 | scopes: [ 19 | ['custom', '自定义'], 20 | ['projects', '项目搭建'], 21 | ['components', '组件相关'], 22 | ['utils', 'utils 相关'], 23 | ['styles', '样式相关'], 24 | ['deps', '项目依赖'], 25 | ['other', '其他修改'], 26 | ].map(([value, description]) => { 27 | return { 28 | value, 29 | name: `${value.padEnd(30)} (${description})`, 30 | }; 31 | }), 32 | messages: { 33 | type: '确保本次提交遵循 Angular 规范!选择你要提交的类型:\n', 34 | scope: '选择一个 scope(可选):', 35 | customScope: '请输入自定义的 scope:', 36 | subject: '填写简短精炼的变更描述:', 37 | body: '填写更加详细的变更描述(可选)。使用 "|" 换行:', 38 | breaking: '列举非兼容性重大的变更(可选):', 39 | footer: '列举出所有变更的 Issues Closed(可选)。 例如: #31, #34:', 40 | confirmCommit: '确认提交?', 41 | }, 42 | allowBreakingChanges: ['feat', 'fix'], 43 | subjectLimit: 100, 44 | breaklineChar: '|', 45 | }; 46 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/duotone.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | * Copyright 2022 Fonticons, Inc. 5 | */ 6 | @import 'functions'; 7 | @import 'variables'; 8 | /* #ifndef MP-WEIXIN || MP-ALIPAY */ 9 | @import 'fonts/fa-duotone-900'; 10 | 11 | :root, 12 | :host { 13 | --#{$fa-css-prefix}-style-family-classic: $fa-style-family-duotone; 14 | --#{$fa-css-prefix}-font-duotone: normal 900 1em/1 '#{ $fa-style-family-duotone }'; 15 | } 16 | 17 | @font-face { 18 | font-family: $fa-style-family-duotone; 19 | font-style: normal; 20 | font-weight: 900; 21 | font-display: $fa-font-display; 22 | /* #ifdef H5 */ 23 | src: url('#{$fa-font-path}/fa-duotone-900.woff2') format('woff2'), 24 | url('#{$fa-font-path}/fa-duotone-900.ttf') format('truetype'); 25 | /* #endif */ 26 | 27 | /* #ifndef H5 */ 28 | src: $fa-duotone-900-woff2; 29 | // $fa-duotone-900-ttf; 30 | /* #endif */ 31 | } 32 | /* #endif */ 33 | 34 | .far, 35 | .#{$fa-css-prefix}-duotone { 36 | font-weight: 900; 37 | } 38 | 39 | .fad:before, 40 | .#{$fa-css-prefix}-duotone:before { 41 | position: absolute; 42 | color: $fa-primary-color; 43 | opacity: $fa-primary-opacity; 44 | } 45 | 46 | .#{$fa-css-prefix}-duotone.#{$fa-css-prefix}-swap-opacity:before, 47 | .#{$fa-css-prefix}-duotone:after, 48 | .#{$fa-css-prefix}-swap-opacity .#{$fa-css-prefix}-duotone:before, 49 | .#{$fa-css-prefix}-swap-opacity .fad:before, 50 | .fad.#{$fa-css-prefix}-swap-opacity:before, 51 | .fad:after { 52 | opacity: $fa-secondary-opacity; 53 | } 54 | 55 | .#{$fa-css-prefix}-duotone:after, 56 | .fad:after { 57 | color: $fa-secondary-color; 58 | } 59 | -------------------------------------------------------------------------------- /assets/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefix": "test", 3 | "icons": { 4 | "github": { 5 | "body": "", 6 | "width": 24, 7 | "height": 24 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/uniapi/prompt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 交互反馈 3 | * https://uniapp.dcloud.io/api/ui/prompt.html 4 | */ 5 | 6 | /** 7 | * 显示消息提示框 8 | * @param title 9 | * @param options 10 | * @constructor 11 | */ 12 | export function Toast(title: string, options?: Partial) { 13 | uni.showToast({ 14 | title, 15 | duration: 1500, 16 | icon: 'none', 17 | mask: true, 18 | ...options, 19 | }); 20 | } 21 | 22 | /** 23 | * 隐藏消息提示框 24 | */ 25 | export function HideToast() { 26 | uni.hideToast(); 27 | } 28 | 29 | /** 30 | * 显示 loading 提示框 31 | * @param title 32 | * @param options 33 | * @constructor 34 | */ 35 | export function Loading(title: string, options?: Partial) { 36 | uni.showLoading({ 37 | title, 38 | mask: true, 39 | ...options, 40 | }); 41 | } 42 | 43 | /** 44 | * 隐藏 loading 提示框 45 | */ 46 | export function HideLoading() { 47 | uni.hideLoading(); 48 | } 49 | 50 | /** 51 | * 显示模态弹窗,可以只有一个确定按钮,也可以同时有确定和取消按钮 52 | * @param options 53 | * @constructor 54 | */ 55 | export function Modal(options: UniApp.ShowModalOptions) { 56 | return new Promise((resolve, reject) => { 57 | uni.showModal({ 58 | ...options, 59 | success: res => { 60 | resolve(res); 61 | }, 62 | fail: res => { 63 | reject(res); 64 | }, 65 | }); 66 | }); 67 | } 68 | 69 | /** 70 | * 从底部向上弹出操作菜单 71 | * @param options 72 | * @constructor 73 | */ 74 | export function ActionSheet(options: UniApp.ShowActionSheetOptions) { 75 | return new Promise((resolve, reject) => { 76 | uni.showActionSheet({ 77 | ...options, 78 | success: res => { 79 | resolve(res); 80 | }, 81 | fail: res => { 82 | reject(res); 83 | }, 84 | }); 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /src/utils/img.ts: -------------------------------------------------------------------------------- 1 | import { randomIntegerInRange, randomRgbColor } from '.'; 2 | 3 | interface IImageVerifyOptions { 4 | /** 5 | * canvas dom 对象 6 | */ 7 | dom: HTMLCanvasElement; 8 | /** 9 | * canvas宽度 10 | */ 11 | width: number; 12 | /** 13 | * canvas高度 14 | */ 15 | height: number; 16 | } 17 | 18 | /** 19 | * 绘制图形验证码 20 | * @returns 随机验证码 21 | */ 22 | export function drawImageVerify({ dom, width = 152, height = 40 }: IImageVerifyOptions) { 23 | let imgCode = ''; 24 | 25 | const NUMBER_STRING = '0123456789'; 26 | 27 | const ctx = dom.getContext('2d'); 28 | if (!ctx) return imgCode; 29 | 30 | ctx.fillStyle = randomRgbColor(180, 230); 31 | ctx.fillRect(0, 0, width, height); 32 | 33 | for (let i = 0; i < 4; i += 1) { 34 | const text = NUMBER_STRING[randomIntegerInRange(0, NUMBER_STRING.length)]; 35 | imgCode += text; 36 | const fontSize = randomIntegerInRange(18, 41); 37 | const deg = randomIntegerInRange(-30, 30); 38 | ctx.font = `${fontSize}px Simhei`; 39 | ctx.textBaseline = 'top'; 40 | ctx.fillStyle = randomRgbColor(80, 150); 41 | ctx.save(); 42 | ctx.translate(30 * i + 23, 15); 43 | ctx.rotate((deg * Math.PI) / 180); 44 | ctx.fillText(text, -15 + 5, -15); 45 | ctx.restore(); 46 | } 47 | for (let i = 0; i < 5; i += 1) { 48 | ctx.beginPath(); 49 | ctx.moveTo(randomIntegerInRange(0, width), randomIntegerInRange(0, height)); 50 | ctx.lineTo(randomIntegerInRange(0, width), randomIntegerInRange(0, height)); 51 | ctx.strokeStyle = randomRgbColor(180, 230); 52 | ctx.closePath(); 53 | ctx.stroke(); 54 | } 55 | for (let i = 0; i < 41; i += 1) { 56 | ctx.beginPath(); 57 | ctx.arc(randomIntegerInRange(0, width), randomIntegerInRange(0, height), 1, 0, 2 * Math.PI); 58 | ctx.closePath(); 59 | ctx.fillStyle = randomRgbColor(150, 200); 60 | ctx.fill(); 61 | } 62 | 63 | return imgCode; 64 | } 65 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // base icon class definition 2 | // ------------------------- 3 | @import 'variables'; 4 | 5 | .#{$fa-css-prefix} { 6 | font-family: var(--#{$fa-css-prefix}-style-family, '#{$fa-style-family}'); 7 | font-weight: var(--#{$fa-css-prefix}-style, #{$fa-style}); 8 | } 9 | 10 | .#{$fa-css-prefix}, 11 | .#{$fa-css-prefix}-classic, 12 | .#{$fa-css-prefix}-sharp, 13 | .fas, 14 | .#{$fa-css-prefix}-solid, 15 | .far, 16 | .#{$fa-css-prefix}-regular, 17 | .fab, 18 | .#{$fa-css-prefix}-brands, 19 | .fal, 20 | .#{$fa-css-prefix}-light, 21 | .fat, 22 | .#{$fa-css-prefix}-thin, 23 | .fad, 24 | .#{$fa-css-prefix}-duotone, 25 | .fass, 26 | .#{$fa-css-prefix}-sharp, 27 | .#{$fa-css-prefix}-sharp-solid { 28 | -moz-osx-font-smoothing: grayscale; 29 | -webkit-font-smoothing: antialiased; 30 | display: var(--#{$fa-css-prefix}-display, #{$fa-display}); 31 | font-style: normal; 32 | font-variant: normal; 33 | line-height: 1; 34 | text-rendering: auto; 35 | } 36 | 37 | .fas, 38 | .#{$fa-css-prefix}-classic, 39 | .#{$fa-css-prefix}-solid { 40 | font-family: $fa-style-family-solid; 41 | } 42 | .far, 43 | .#{$fa-css-prefix}-classic, 44 | .#{$fa-css-prefix}-regular { 45 | font-family: $fa-style-family-regular; 46 | } 47 | 48 | .fal, 49 | .#{$fa-css-prefix}-classic, 50 | .#{$fa-css-prefix}-light { 51 | font-family: $fa-style-family-light; 52 | } 53 | 54 | .fad, 55 | .#{$fa-css-prefix}-classic, 56 | .#{$fa-css-prefix}-duotone { 57 | font-family: $fa-style-family-duotone; 58 | } 59 | 60 | .fat, 61 | .#{$fa-css-prefix}-classic, 62 | .#{$fa-css-prefix}-thin { 63 | font-family: $fa-style-family-thin; 64 | } 65 | 66 | .fab, 67 | .#{$fa-css-prefix}-brands { 68 | font-family: $fa-style-family-brands; 69 | } 70 | 71 | .fass, 72 | .#{$fa-css-prefix}-harp-solid.#{$fa-css-prefix}-solid { 73 | font-family: $fa-style-family-sharp-solid; 74 | } 75 | 76 | %fa-icon { 77 | @include fa-icon; 78 | } 79 | -------------------------------------------------------------------------------- /src/stores/modules/auth.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { getCache, removeCache, setCache } from '@/utils/cache'; 3 | import { TOKEN_KEY } from '@/enums/cacheEnum'; 4 | import { login, logout, refreshToken } from '@/api/auth'; 5 | import { LoginParams, LoginModel } from '@/api/models/authModel'; 6 | 7 | interface AuthState { 8 | token?: string; 9 | } 10 | 11 | export const useAuthStore = defineStore({ 12 | id: 'auth', 13 | state: (): AuthState => ({ 14 | token: undefined, 15 | }), 16 | getters: { 17 | getToken: state => state.token, 18 | isLogin: (state): boolean => !!state.token, 19 | }, 20 | actions: { 21 | initToken() { 22 | this.token = getCache(TOKEN_KEY) || undefined; 23 | }, 24 | setToken(token: string | undefined) { 25 | setCache(TOKEN_KEY, token); 26 | this.token = token; 27 | }, 28 | /** 29 | * @description 登录 30 | */ 31 | async login(params: LoginParams): Promise { 32 | try { 33 | const { data } = await login(params); 34 | this.setToken(data.token); 35 | return Promise.resolve(data); 36 | } catch (err: any) { 37 | return Promise.reject(err ?? { msg: '请求异常' }); 38 | } 39 | }, 40 | /** 41 | * @description 登出 42 | */ 43 | async loginOut(): Promise { 44 | try { 45 | const res = await logout(); 46 | removeCache(TOKEN_KEY); 47 | this.setToken(undefined); 48 | return Promise.resolve(res); 49 | } catch (err: any) { 50 | return Promise.reject(err ?? { msg: '请求异常' }); 51 | } 52 | }, 53 | /** 54 | * @description 刷新token 55 | */ 56 | async refreshToken(): Promise { 57 | try { 58 | const { data } = await refreshToken(); 59 | this.setToken(data.token); 60 | return Promise.resolve(data); 61 | } catch (err: any) { 62 | return Promise.reject(err ?? { msg: '请求异常' }); 63 | } 64 | }, 65 | }, 66 | }); 67 | -------------------------------------------------------------------------------- /src/utils/http/index.ts: -------------------------------------------------------------------------------- 1 | import Request from 'luch-request'; 2 | import { assign } from 'lodash-es'; 3 | import { Toast } from '@/utils/uniapi/prompt'; 4 | import { getBaseUrl } from '@/utils/env'; 5 | import { useAuthStore } from '@/stores/modules/auth'; 6 | import { ResultEnum } from '@/enums/httpEnum'; 7 | 8 | const BASE_URL = getBaseUrl(); 9 | const HEADER = { 10 | 'Content-Type': 'application/json;charset=UTF-8;', 11 | Accept: 'application/json, text/plain, */*', 12 | }; 13 | 14 | function createRequest() { 15 | return new Request({ 16 | baseURL: BASE_URL, 17 | header: HEADER, 18 | custom: { 19 | auth: true, 20 | }, 21 | }); 22 | } 23 | 24 | const request = createRequest(); 25 | /** 26 | * 请求拦截器 27 | */ 28 | request.interceptors.request.use( 29 | options => { 30 | if (options.custom?.auth) { 31 | const authStore = useAuthStore(); 32 | if (!authStore.isLogin) { 33 | Toast('请先登录'); 34 | // token不存在跳转到登录页 35 | return Promise.reject(options); 36 | } 37 | options.header = assign(options.header, { 38 | authorization: `Bearer ${authStore.getToken}`, 39 | }); 40 | } 41 | return options; 42 | }, 43 | options => Promise.reject(options), 44 | ); 45 | 46 | /** 47 | * 响应拦截器 48 | */ 49 | request.interceptors.response.use( 50 | async response => { 51 | const { data: resData } = response; 52 | const { code, message } = resData; 53 | if (code === ResultEnum.SUCCESS) { 54 | return resData as any; 55 | } 56 | // Toast(message); 57 | return Promise.reject(resData); 58 | }, 59 | response => { 60 | // console.log(response); 61 | const { data: resData } = response; 62 | 63 | // 兼容 ApiResult 64 | if (resData && resData.message) { 65 | // resData.msg = resData.message; 66 | resData.msg = '请求异常'; 67 | } 68 | 69 | // 请求错误做点什么。可以使用async await 做异步操作 70 | // error('Request Error!'); 71 | return Promise.reject(resData); 72 | }, 73 | ); 74 | 75 | export { request }; 76 | -------------------------------------------------------------------------------- /src/utils/router/interceptor.ts: -------------------------------------------------------------------------------- 1 | import { HOME_PAGE, NAVIGATE_TYPE_LIST, NOT_FOUND_PAGE } from '@/enums/routerEnum'; 2 | import { useAuthStore } from '@/stores/modules/auth'; 3 | import { isIgnoreAuth, jumpLogin } from '@/utils/router/constant'; 4 | 5 | /** 6 | * 路由跳转前拦截 7 | * @param path 8 | * @return boolean 9 | */ 10 | 11 | export function routerBeforeEach(path: string): boolean { 12 | const isIgnore = isIgnoreAuth(path); 13 | if (isIgnore) return true; 14 | const authStore = useAuthStore(); 15 | if (authStore.isLogin) return true; 16 | jumpLogin(path); 17 | return false; 18 | } 19 | 20 | /** 21 | * 添加拦截器 22 | * 微信小程序端uni.switchTab拦截无效, 已在api中拦截 23 | * 微信小程序原生tabbar请使用onShow 24 | * 微信小程序端 拦截无效,请使用api 25 | * @param routerName 26 | * @export void 27 | */ 28 | function addInterceptor(routerName: string) { 29 | uni.addInterceptor(routerName, { 30 | // 跳转前拦截 31 | invoke: args => { 32 | const flag = routerBeforeEach(args.url); 33 | return flag ? args : false; 34 | }, 35 | // 成功回调拦截 36 | success: () => {}, 37 | // 失败回调拦截 38 | fail: (err: any) => { 39 | let reg: RegExp; 40 | /* #ifdef MP-WEIXIN */ 41 | reg = /(.*)?(fail page ")(.*)(" is not found$)/; 42 | /* #endif */ 43 | /* #ifndef MP-WEIXIN */ 44 | reg = /(.*)?(fail page `)(.*)(` is not found$)/; 45 | /* #endif */ 46 | if (reg.test(err.errMsg)) { 47 | const go = err.errMsg.replace(reg, '$3') || ''; 48 | uni.navigateTo({ 49 | url: `${NOT_FOUND_PAGE}?redirect=${HOME_PAGE}&go=${go}`, 50 | }); 51 | } 52 | return false; 53 | }, 54 | // 完成回调拦截 55 | complete: () => {}, 56 | }); 57 | } 58 | 59 | /** 60 | * 添加路由拦截器 61 | */ 62 | export function routerInterceptor() { 63 | NAVIGATE_TYPE_LIST.forEach(item => { 64 | addInterceptor(item); 65 | }); 66 | } 67 | 68 | /** 69 | * 移除路由拦截器 70 | */ 71 | export function routerRemoveInterceptor() { 72 | NAVIGATE_TYPE_LIST.forEach(item => { 73 | uni.removeInterceptor(item); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /src/stores/modules/page.ts: -------------------------------------------------------------------------------- 1 | import { store } from '@/stores'; 2 | import type { UNotifyOptions } from '@/components/UnoUI/UNotify/types'; 3 | import type { UToastOptions } from '@/components/UnoUI/UToast/types'; 4 | 5 | interface PageConfig { 6 | showNavBar?: boolean; 7 | showBackAction?: boolean; 8 | showCustomAction?: boolean; 9 | pageTitle?: string; 10 | } 11 | 12 | export const usePageStore = defineStore('page', () => { 13 | const showNavBar = ref(true); 14 | const showBackAction = ref(false); 15 | const showCustomAction = ref(false); 16 | const pageTitle = ref(''); 17 | const notifyRef = ref<{ handleShowNotify: (options: UNotifyOptions) => {} }>(); 18 | const toastRef = ref<{ handleShowToast: (options: UToastOptions) => {} }>(); 19 | 20 | const setPageConfig = (config: PageConfig) => { 21 | const { 22 | showNavBar: _showNavBar = true, 23 | showBackAction: _showBackAction = false, 24 | showCustomAction: _showCustomAction = false, 25 | pageTitle: _pageTitle = '', 26 | } = config; 27 | 28 | showNavBar.value = _showNavBar; 29 | showBackAction.value = _showBackAction; 30 | showCustomAction.value = _showCustomAction; 31 | pageTitle.value = _pageTitle; 32 | }; 33 | 34 | const showNotify = (options: UNotifyOptions) => notifyRef.value!.handleShowNotify(options); 35 | 36 | const showToast = (options: UToastOptions) => toastRef.value!.handleShowToast(options); 37 | 38 | const pageReset = () => { 39 | showNavBar.value = true; 40 | showBackAction.value = false; 41 | showCustomAction.value = false; 42 | pageTitle.value = ''; 43 | notifyRef.value = undefined; 44 | toastRef.value = undefined; 45 | }; 46 | 47 | return { 48 | setPageConfig, 49 | showNavBar, 50 | pageTitle, 51 | showBackAction, 52 | showCustomAction, 53 | notifyRef, 54 | toastRef, 55 | showNotify, 56 | showToast, 57 | pageReset, 58 | }; 59 | }); 60 | 61 | // Need to be used outside the setup 62 | export function usePageStoreWidthOut() { 63 | return usePageStore(store); 64 | } 65 | -------------------------------------------------------------------------------- /src/pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 43 | 69 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/prettier'], 3 | // 一行代码的最大字符数,默认是80(printWidth: ) 4 | printWidth: 120, 5 | // tab宽度为2空格(tabWidth: ) 6 | tabWidth: 2, 7 | // 是否使用tab来缩进,我们使用空格(useTabs: ) 8 | useTabs: false, 9 | // 结尾是否添加分号,false的情况下只会在一些导致ASI错误的其工况下在开头加分号(semi: ) 10 | semi: true, 11 | // 使用单引号(singleQuote: ) 12 | singleQuote: true, 13 | // object对象中key值是否加引号(quoteProps: "")as-needed只有在需求要的情况下加引号,consistent是有一个需要引号就统一加,preserve是保留用户输入的引号 14 | quoteProps: 'as-needed', 15 | // 在jsx文件中的引号需要单独设置(jsxSingleQuote: ) 16 | jsxSingleQuote: false, 17 | // 尾部逗号设置,es5是尾部逗号兼容es5,none就是没有尾部逗号,all是指所有可能的情况,需要node8和es2017以上的环境。(trailingComma: "") 18 | trailingComma: 'all', 19 | // object对象里面的key和value值和括号间的空格(bracketSpacing: ) 20 | bracketSpacing: true, 21 | // jsx标签多行属性写法时,尖括号是否另起一行(jsxBracketSameLine: ) 22 | jsxBracketSameLine: false, 23 | // 箭头函数单个参数的情况是否省略括号,默认always是总是带括号(arrowParens: "") 24 | arrowParens: 'avoid', 25 | // range是format执行的范围,可以选执行一个文件的一部分,默认的设置是整个文件(rangeStart: rangeEnd: ) 26 | rangeStart: 0, 27 | rangeEnd: Infinity, 28 | // 不需要写文件开头的 @prettier 29 | requirePragma: false, 30 | // 不需要自动在文件开头插入 @prettier 31 | insertPragma: false, 32 | // vue script和style标签中是否缩进,开启可能会破坏编辑器的代码折叠 33 | vueIndentScriptAndStyle: true, 34 | // endOfLine: "" 行尾换行符,默认是lf, 35 | endOfLine: 'auto', 36 | // ignore 对HTML全局空白不敏感, 根据显示样式决定 html 要不要折行 css 37 | htmlWhitespaceSensitivity: 'strict', 38 | // embeddedLanguageFormatting: "off",默认是auto,控制被引号包裹的代码是否进行格式化 39 | embeddedLanguageFormatting: 'auto', 40 | // 使用默认的折行标准 proseWrap: "" 41 | proseWrap: 'never', 42 | overrides: [ 43 | { 44 | files: ['*.json5'], 45 | options: { 46 | singleQuote: false, 47 | quoteProps: 'preserve', 48 | }, 49 | }, 50 | { 51 | files: ['*.yml'], 52 | options: { 53 | singleQuote: false, 54 | }, 55 | }, 56 | ], 57 | }; 58 | -------------------------------------------------------------------------------- /src/uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里是uni-app内置的常用样式变量 3 | * 4 | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 5 | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App 6 | * 7 | */ 8 | 9 | /** 10 | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 11 | * 12 | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 13 | */ 14 | 15 | /* 颜色变量 */ 16 | 17 | /* 行为相关颜色 */ 18 | $uni-color-primary: #007aff; 19 | $uni-color-success: #4cd964; 20 | $uni-color-warning: #f0ad4e; 21 | $uni-color-error: #dd524d; 22 | 23 | /* 文字基本颜色 */ 24 | $uni-text-color: #333; //基本色 25 | $uni-text-color-inverse: #fff; //反色 26 | $uni-text-color-grey: #999; //辅助灰色,如加载更多的提示信息 27 | $uni-text-color-placeholder: #808080; 28 | $uni-text-color-disable: #c0c0c0; 29 | 30 | /* 背景颜色 */ 31 | $uni-bg-color: #ffffff; 32 | $uni-bg-color-grey: #f8f8f8; 33 | $uni-bg-color-hover: #f1f1f1; //点击状态颜色 34 | $uni-bg-color-mask: rgba(0, 0, 0, 0.4); //遮罩颜色 35 | 36 | /* 边框颜色 */ 37 | $uni-border-color: #c8c7cc; 38 | 39 | /* 尺寸变量 */ 40 | 41 | /* 文字尺寸 */ 42 | $uni-font-size-sm: 24rpx; 43 | $uni-font-size-base: 28rpx; 44 | $uni-font-size-lg: 32rpx; 45 | 46 | /* 图片尺寸 */ 47 | $uni-img-size-sm: 40rpx; 48 | $uni-img-size-base: 52rpx; 49 | $uni-img-size-lg: 80rpx; 50 | 51 | /* Border Radius */ 52 | $uni-border-radius-sm: 4rpx; 53 | $uni-border-radius-base: 6rpx; 54 | $uni-border-radius-lg: 12rpx; 55 | $uni-border-radius-circle: 50%; 56 | 57 | /* 水平间距 */ 58 | $uni-spacing-row-sm: 10px; 59 | $uni-spacing-row-base: 20rpx; 60 | $uni-spacing-row-lg: 30rpx; 61 | 62 | /* 垂直间距 */ 63 | $uni-spacing-col-sm: 8rpx; 64 | $uni-spacing-col-base: 16rpx; 65 | $uni-spacing-col-lg: 24rpx; 66 | 67 | /* 透明度 */ 68 | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 69 | 70 | /* 文章场景相关 */ 71 | $uni-color-title: #2c405a; // 文章标题颜色 72 | $uni-font-size-title: 40rpx; 73 | $uni-color-subtitle: #555555; // 二级标题颜色 74 | $uni-font-size-subtitle: 36rpx; 75 | $uni-color-paragraph: #3f536e; // 文章段落颜色 76 | $uni-font-size-paragraph: 30rpx; 77 | 78 | /* 按钮相关 */ 79 | $nui-button-bg-color: #60a5fa; 80 | $nui-button-bg-hover-color: #3b82f6; 81 | $nui-button-border-color: #bfdbfe; 82 | -------------------------------------------------------------------------------- /src/utils/env.ts: -------------------------------------------------------------------------------- 1 | import pkg from '../../package.json'; 2 | import { judgePlatform } from '@/utils/platform'; 3 | import { PLATFORMS } from '@/enums/platformEnum'; 4 | 5 | /** 6 | * @description: Generate cache key according to version 7 | */ 8 | export function getPkgVersion() { 9 | return `${`__${pkg.version}`}__`.toUpperCase(); 10 | } 11 | 12 | /** 13 | * @description: Development mode 14 | */ 15 | export const devMode = 'development'; 16 | 17 | /** 18 | * @description: Production mode 19 | */ 20 | export const prodMode = 'production'; 21 | 22 | /** 23 | * @description: Get environment mode 24 | * @returns: 25 | * @example: 26 | */ 27 | export function getEnvMode(): string { 28 | return isDevMode() ? devMode : prodMode; 29 | } 30 | 31 | /** 32 | * @description: Get environment variables 33 | * @returns: 34 | * @example: 35 | */ 36 | export function getEnvValue(key: string): T { 37 | // 注意:拿到的都是 string 类型 38 | // @ts-ignore 39 | return import.meta.env[key]; 40 | } 41 | 42 | /** 43 | * @description: Is it a development mode 44 | * @returns: 45 | * @example: 46 | */ 47 | export function isDevMode(): boolean { 48 | return getEnvValue('VITE_DEV') === 'true'; 49 | } 50 | 51 | /** 52 | * @description: Is it a production mode 53 | * @returns: 54 | * @example: 55 | */ 56 | export function isProdMode(): boolean { 57 | return getEnvValue('VITE_PROD') === 'true'; 58 | } 59 | 60 | /** 61 | * @description: Get environment VITE_BASE_URL value 62 | * @returns: 63 | * @example: 64 | */ 65 | export function getBaseUrl(): string { 66 | if (judgePlatform(PLATFORMS.H5) && isDevMode()) return '/api'; 67 | return getEnvValue('VITE_BASE_URL'); 68 | } 69 | 70 | /** 71 | * @description: Get environment VITE_UPLOAD_URL value 72 | * @returns: 73 | * @example: 74 | */ 75 | export function getUploadUrl(): string { 76 | if (judgePlatform(PLATFORMS.H5) && isDevMode()) return '/upload'; 77 | return getEnvValue('VITE_UPLOAD_URL'); 78 | } 79 | 80 | /** 81 | * @description: Get environment VITE_IMAGE_URL value 82 | * @returns: 83 | * @example: 84 | */ 85 | export function getImageUrl(): string { 86 | return getEnvValue('VITE_IMAGE_URL'); 87 | } 88 | -------------------------------------------------------------------------------- /src/components/UButton/UButton.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 50 | 51 | 88 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_functions.scss: -------------------------------------------------------------------------------- 1 | // functions 2 | // -------------------------- 3 | 4 | // fa-content: convenience function used to set content property 5 | @function fa-content($fa-var) { 6 | @return unquote('"#{ $fa-var }"'); 7 | } 8 | 9 | // fa-divide: Originally obtained from the Bootstrap https://github.com/twbs/bootstrap 10 | // 11 | // Licensed under: The MIT License (MIT) 12 | // 13 | // Copyright (c) 2011-2021 Twitter, Inc. 14 | // Copyright (c) 2011-2021 The Bootstrap Authors 15 | // 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // 23 | // The above copyright notice and this permission notice shall be included in 24 | // all copies or substantial portions of the Software. 25 | // 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | // THE SOFTWARE. 33 | 34 | @function fa-divide($dividend, $divisor, $precision: 10) { 35 | $sign: if($dividend > 0 and $divisor > 0, 1, -1); 36 | $dividend: abs($dividend); 37 | $divisor: abs($divisor); 38 | $quotient: 0; 39 | $remainder: $dividend; 40 | @if $dividend == 0 { 41 | @return 0; 42 | } 43 | @if $divisor == 0 { 44 | @error "Cannot divide by 0"; 45 | } 46 | @if $divisor == 1 { 47 | @return $dividend; 48 | } 49 | @while $remainder >= $divisor { 50 | $quotient: $quotient + 1; 51 | $remainder: $remainder - $divisor; 52 | } 53 | @if $remainder > 0 and $precision > 0 { 54 | $remainder: fa-divide($remainder * 10, $divisor, $precision - 1) * 0.1; 55 | } 56 | @return ($quotient + $remainder) * $sign; 57 | } 58 | -------------------------------------------------------------------------------- /src/components/composables/useProps.ts: -------------------------------------------------------------------------------- 1 | import type { PropType, CSSProperties } from 'vue'; 2 | 3 | export const StringProp = { 4 | type: String as PropType, 5 | default: '', 6 | }; 7 | 8 | export const NumberProp = { 9 | type: Number as PropType, 10 | default: 0, 11 | }; 12 | 13 | export const BooleanProp = { 14 | type: Boolean as PropType, 15 | default: false, 16 | }; 17 | 18 | export const BooleanTrueProp = { 19 | type: Boolean as PropType, 20 | default: true, 21 | }; 22 | 23 | export const ArrayProp = { 24 | type: Array as PropType, 25 | default: () => [], 26 | }; 27 | 28 | export type ClassType = String | Object | Array; 29 | 30 | export const CustomClassProp = { 31 | type: [String, Object, Array] as PropType, 32 | default: '', 33 | }; 34 | 35 | export type StyleValue = string | CSSProperties; 36 | 37 | export const CustomStyleProp = { 38 | type: [String, Object, Array] as PropType, 39 | default: '', 40 | }; 41 | 42 | export type ColorType = 'primary' | 'success' | 'info' | 'warning' | 'danger'; 43 | 44 | export const ColorProp = { 45 | type: String as PropType, 46 | validator: (value: string) => ['primary', 'success', 'info', 'warning', 'danger'].includes(value), 47 | default: 'primary', 48 | }; 49 | 50 | export type VariantType = 'solid' | 'outline' | 'ghost' | 'light' | 'text'; 51 | 52 | export const VariantProp = { 53 | type: String as PropType, 54 | default: 'solid', 55 | }; 56 | 57 | export type SizeType = 'xs' | 'sm' | 'md' | 'lg'; 58 | 59 | export const SizeProp = { 60 | type: String as PropType, 61 | validator: (value: string) => ['xs', 'sm', 'md', 'lg'].includes(value), 62 | default: 'md', 63 | }; 64 | 65 | export type AlignType = 'start' | 'center' | 'end'; 66 | 67 | export const AlignProp = { 68 | type: String as PropType, 69 | validator: (value: string) => ['start', 'center', 'end'].includes(value), 70 | default: 'start', 71 | }; 72 | 73 | export type InputType = 'text' | 'number' | 'digit' | 'password' | 'textarea'; 74 | 75 | export const InputTypeProp = { 76 | type: String as PropType, 77 | default: 'text', 78 | }; 79 | 80 | export type Numberish = string | number; 81 | export const NumberishProp = { 82 | type: [String, Number] as PropType, 83 | default: '', 84 | }; 85 | 86 | export type PopupType = 'top' | 'center' | 'bottom' | 'left' | 'right'; 87 | 88 | export const PopupTypeProp = { 89 | type: String as PropType, 90 | default: 'bottom', 91 | }; 92 | -------------------------------------------------------------------------------- /src/components/UnoUI/UBasePage/UBasePage.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 72 | -------------------------------------------------------------------------------- /src/static/svg/404.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/about/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 49 | 50 | 80 | -------------------------------------------------------------------------------- /src/components/CustomBar/tabBar.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 86 | 87 | 98 | -------------------------------------------------------------------------------- /src/enums/platformEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 平台 3 | */ 4 | export enum PLATFORMS { 5 | DEFAULT = 'DEFAULT' /* 默认 */, 6 | VUE3 = 'VUE3' /* HBuilderX 3.2.0+ */, 7 | APP_PLUS = 'APP-PLUS' /* App */, 8 | APP_PLUS_NVUE = 'APP-PLUS-NVUE' /* App nvue 页面 */, 9 | APP_NVUE = 'APP-NVUE' /* App nvue 页面 */, 10 | H5 = 'H5' /* H5 */, 11 | MP_WEIXIN = 'MP-WEIXIN' /* 微信小程序 */, 12 | MP_ALIPAY = 'MP-ALIPAY' /* 支付宝小程序 */, 13 | MP_BAIDU = 'MP_BAIDU' /* 百度小程序 */, 14 | MP_TOUTIAO = 'MP-TOUTIAO' /* 字节跳动小程序 */, 15 | MP_LARK = 'MP-LARK' /* 飞书小程序 */, 16 | MP_QQ = 'MP-QQ' /* QQ小程序 */, 17 | MP_KUAISHOU = 'MP-KUAISHOU' /* 快手小程序 */, 18 | MP_JD = 'MP-JD' /* 京东小程序 */, 19 | MP_360 = 'MP-360' /* 360小程序 */, 20 | MP = 'MP' /* 微信小程序/支付宝小程序/百度小程序/字节跳动小程序/飞书小程序/QQ小程序/360小程序 */, 21 | QUICKAPP_WEBVIEW = 'QUICKAPP-WEBVIEW' /* 快应用通用(包含联盟、华为) */, 22 | QUICKAPP_WEBVIEW_UNION = 'QUICKAPP-WEBVIEW-UNION' /* 快应用联盟 */, 23 | QUICKAPP_WEBVIEW_HUAWEI = 'QUICKAPP-WEBVIEW-HUAWEI' /* 快应用华为 */, 24 | } 25 | 26 | /** 27 | * 平台环境 28 | * @constructor 29 | */ 30 | function PLATFORM_ENV() { 31 | let platform = PLATFORMS.DEFAULT; 32 | 33 | /* #ifdef VUE3 */ 34 | platform = PLATFORMS.VUE3; 35 | /* #endif */ 36 | 37 | /* #ifdef APP-PLUS */ 38 | platform = PLATFORMS.APP_PLUS; 39 | /* #endif */ 40 | 41 | /* #ifdef APP-PLUS-NVUE */ 42 | platform = PLATFORMS.APP_PLUS_NVUE; 43 | /* #endif */ 44 | 45 | /* #ifdef APP-NVUE */ 46 | platform = PLATFORMS.APP_NVUE; 47 | /* #endif */ 48 | 49 | /* #ifdef H5 */ 50 | platform = PLATFORMS.H5; 51 | /* #endif */ 52 | 53 | /* #ifdef MP */ 54 | platform = PLATFORMS.MP; 55 | /* #endif */ 56 | 57 | /* #ifdef MP-WEIXIN */ 58 | platform = PLATFORMS.MP_WEIXIN; 59 | /* #endif */ 60 | 61 | /* #ifdef MP-ALIPAY */ 62 | platform = PLATFORMS.MP_ALIPAY; 63 | /* #endif */ 64 | 65 | /* #ifdef MP_BAIDU */ 66 | platform = PLATFORMS.MP_BAIDU; 67 | /* #endif */ 68 | 69 | /* #ifdef MP-TOUTIAO */ 70 | platform = PLATFORMS.MP_TOUTIAO; 71 | /* #endif */ 72 | 73 | /* #ifdef MP-LARK */ 74 | platform = PLATFORMS.MP_LARK; 75 | /* #endif */ 76 | 77 | /* #ifdef MP-QQ */ 78 | platform = PLATFORMS.MP_QQ; 79 | /* #endif */ 80 | 81 | /* #ifdef MP-KUAISHOU */ 82 | platform = PLATFORMS.MP_KUAISHOU; 83 | /* #endif */ 84 | 85 | /* #ifdef MP-JD */ 86 | platform = PLATFORMS.MP_JD; 87 | /* #endif */ 88 | 89 | /* #ifdef MP-360 */ 90 | platform = PLATFORMS.MP_360; 91 | /* #endif */ 92 | 93 | /* #ifdef QUICKAPP-WEBVIEW */ 94 | platform = PLATFORMS.QUICKAPP_WEBVIEW; 95 | /* #endif */ 96 | 97 | /* #ifdef QUICKAPP-WEBVIEW-UNION */ 98 | platform = PLATFORMS.QUICKAPP_WEBVIEW_UNION; 99 | /* #endif */ 100 | 101 | /* #ifdef QUICKAPP-WEBVIEW-HUAWEI */ 102 | platform = PLATFORMS.QUICKAPP_WEBVIEW_HUAWEI; 103 | /* #endif */ 104 | 105 | return platform; 106 | } 107 | 108 | /* 当前平台 */ 109 | export const CURRENT_PLATFORM = PLATFORM_ENV(); 110 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | parser: 'vue-eslint-parser', 7 | extends: ['plugin:@typescript-eslint/recommended', 'plugin:vue/vue3-essential', 'prettier'], 8 | parserOptions: { 9 | ecmaVersion: 'latest', 10 | parser: '@typescript-eslint/parser', 11 | sourceType: 'module', 12 | }, 13 | settings: { 14 | 'import/resolver': { 15 | alias: { 16 | map: [['@', './src']], 17 | extensions: ['.ts', '.js', '.jsx', '.json', '.vue'], 18 | }, 19 | }, 20 | }, 21 | plugins: ['vue', '@typescript-eslint', 'prettier'], 22 | rules: { 23 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 24 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 25 | 'no-var': 'error', 26 | 'prettier/prettier': 'error', 27 | 'vue/no-multiple-template-root': 'off', 28 | 'no-mutating-props': 'off', 29 | 'vue/no-v-html': 'off', 30 | // @fixable 必须使用单引号,禁止使用双引号 31 | quotes: [ 32 | 'error', 33 | 'single', 34 | { 35 | avoidEscape: true, 36 | allowTemplateLiterals: true, 37 | }, 38 | ], 39 | // 结尾必须有分号; 40 | semi: [ 41 | 'error', 42 | 'always', 43 | { 44 | omitLastInOneLineBlock: true, 45 | }, 46 | ], 47 | 'vue/script-setup-uses-vars': 'error', 48 | '@typescript-eslint/ban-ts-ignore': 'off', 49 | '@typescript-eslint/explicit-function-return-type': 'off', 50 | '@typescript-eslint/no-explicit-any': 'off', 51 | '@typescript-eslint/no-var-requires': 'off', 52 | '@typescript-eslint/no-empty-function': 'off', 53 | 'vue/custom-event-name-casing': 'off', 54 | 'no-use-before-define': 'off', 55 | '@typescript-eslint/no-use-before-define': 'off', 56 | '@typescript-eslint/ban-ts-comment': 'off', 57 | '@typescript-eslint/ban-types': 'off', 58 | '@typescript-eslint/no-non-null-assertion': 'off', 59 | '@typescript-eslint/explicit-module-boundary-types': 'off', 60 | '@typescript-eslint/no-unused-vars': [ 61 | 'warn', 62 | { 63 | argsIgnorePattern: '^_', 64 | varsIgnorePattern: '^_', 65 | }, 66 | ], 67 | 'no-unused-vars': [ 68 | 'warn', 69 | { 70 | argsIgnorePattern: '^_', 71 | varsIgnorePattern: '^_', 72 | }, 73 | ], 74 | 'space-before-function-paren': 'off', 75 | 'vue/attributes-order': 'off', 76 | 'vue/v-on-event-hyphenation': 'off', 77 | 'vue/multi-word-component-names': 'off', 78 | 'vue/one-component-per-file': 'off', 79 | 'vue/html-closing-bracket-newline': 'off', 80 | 'vue/max-attributes-per-line': 'off', 81 | 'vue/multiline-html-element-content-newline': 'off', 82 | 'vue/singleline-html-element-content-newline': 'off', 83 | 'vue/attribute-hyphenation': 'off', 84 | 'vue/require-default-prop': 'off', 85 | }, 86 | globals: { 87 | defineProps: 'readonly', 88 | defineEmits: 'readonly', 89 | defineExpose: 'readonly', 90 | withDefaults: 'readonly', 91 | uni: 'readonly', 92 | }, 93 | }; 94 | -------------------------------------------------------------------------------- /src/components/FontAwesomeIcon/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // mixins 2 | // -------------------------- 3 | 4 | // base rendering for an icon 5 | @mixin fa-icon { 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | display: inline-block; 9 | font-style: normal; 10 | font-variant: normal; 11 | font-weight: normal; 12 | line-height: 1; 13 | } 14 | 15 | // sets relative font-sizing and alignment (in _sizing) 16 | @mixin fa-size($font-size) { 17 | font-size: fa-divide($font-size, $fa-size-scale-base) * 1em; // converts step in sizing scale into an em-based value that's relative to the scale's base 18 | line-height: fa-divide(1, $font-size) * 1em; // sets the line-height of the icon back to that of it's parent 19 | vertical-align: (fa-divide(6, $font-size) - fa-divide(3, 8)) * 1em; // vertically centers the icon taking into account the surrounding text's descender 20 | } 21 | 22 | // only display content to screen readers 23 | // see: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ 24 | // see: https://hugogiraudel.com/2016/10/13/css-hide-and-seek/ 25 | @mixin fa-sr-only() { 26 | position: absolute; 27 | width: 1px; 28 | height: 1px; 29 | padding: 0; 30 | margin: -1px; 31 | overflow: hidden; 32 | clip: rect(0, 0, 0, 0); 33 | white-space: nowrap; 34 | border-width: 0; 35 | } 36 | 37 | // use in conjunction with .sr-only to only display content when it's focused 38 | @mixin fa-sr-only-focusable() { 39 | &:not(:focus) { 40 | @include fa-sr-only(); 41 | } 42 | } 43 | 44 | // sets a specific icon family to use alongside style + icon mixins 45 | 46 | // convenience mixins for declaring pseudo-elements by CSS variable, 47 | // including all style-specific font properties, and both the ::before 48 | // and ::after elements in the duotone case. 49 | @mixin fa-icon-solid($fa-var) { 50 | @extend %fa-icon; 51 | @extend .fa-solid; 52 | 53 | &::before { 54 | content: unquote('"#{ $fa-var }"'); 55 | } 56 | } 57 | 58 | @mixin fa-icon-regular($fa-var) { 59 | @extend %fa-icon; 60 | @extend .fa-regular; 61 | 62 | &::before { 63 | content: unquote('"#{ $fa-var }"'); 64 | } 65 | } 66 | 67 | @mixin fa-icon-light($fa-var) { 68 | @extend %fa-icon; 69 | @extend .fa-light; 70 | 71 | &::before { 72 | content: unquote('"#{ $fa-var }"'); 73 | } 74 | } 75 | 76 | @mixin fa-icon-thin($fa-var) { 77 | @extend %fa-icon; 78 | @extend .fa-thin; 79 | 80 | &::before { 81 | content: unquote('"#{ $fa-var }"'); 82 | } 83 | } 84 | 85 | @mixin fa-icon-sharp-solid($fa-var) { 86 | @extend %fa-icon; 87 | @extend .fa-sharp-solid; 88 | 89 | &::before { 90 | content: unquote('"#{ $fa-var }"'); 91 | } 92 | } 93 | 94 | @mixin fa-icon-duotone($fa-var) { 95 | @extend %fa-icon; 96 | @extend .fa-duotone; 97 | 98 | &::before { 99 | content: unquote('"#{ $fa-var }"'); 100 | } 101 | } 102 | 103 | @mixin fa-icon-brands($fa-var) { 104 | @extend %fa-icon; 105 | @extend .fa-brands; 106 | 107 | &::before { 108 | content: unquote('"#{ $fa-var }"'); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 56 | 66 | 67 | 68 | 69 | 100 | -------------------------------------------------------------------------------- /src/utils/fonts.ts: -------------------------------------------------------------------------------- 1 | import { getBaseUrl } from '@/utils/env'; 2 | 3 | const fa_style_family = 'Font Awesome 6'; 4 | 5 | const fa_style_family_solid = `${fa_style_family} Solid`; 6 | const fa_style_family_regular = `${fa_style_family} Regular`; 7 | const fa_style_family_light = `${fa_style_family} Light`; 8 | const fa_style_family_thin = `${fa_style_family} Thin`; 9 | const fa_style_family_sharp_solid = `${fa_style_family} Sharp Solid`; 10 | const fa_style_family_brands = `${fa_style_family} Brands Regular`; 11 | const fa_style_family_duotone = `${fa_style_family} Duotone Solid`; 12 | 13 | const BASE_URL = getBaseUrl(); 14 | 15 | const fontFamilyList = [ 16 | { 17 | family: fa_style_family_solid, 18 | source: `/fa-solid-900.woff2`, 19 | weight: '900', 20 | }, 21 | { 22 | family: fa_style_family_regular, 23 | source: `/fa-regular-400.woff2`, 24 | weight: '400', 25 | }, 26 | { 27 | family: fa_style_family_light, 28 | source: `/fa-light-300.woff2`, 29 | weight: '300', 30 | }, 31 | { 32 | family: fa_style_family_thin, 33 | source: `/fa-thin-100.woff2`, 34 | weight: '100', 35 | }, 36 | { 37 | family: fa_style_family_sharp_solid, 38 | source: `/fa-sharp-solid-900.woff2`, 39 | weight: '900', 40 | }, 41 | { 42 | family: fa_style_family_duotone, 43 | source: `/fa-duotone-900.woff2`, 44 | weight: '900', 45 | }, 46 | { 47 | family: fa_style_family_brands, 48 | source: `/fa-brands-400.woff2`, 49 | weight: '400', 50 | }, 51 | ]; 52 | 53 | function LoadFontFace(data: UniApp.LoadFontFaceOptions & { weight?: string; global?: boolean }) { 54 | return new Promise((resolve, reject) => { 55 | uni.loadFontFace({ 56 | global: true, 57 | ...data, 58 | desc: { 59 | style: 'normal', 60 | weight: data.weight ?? 'normal', 61 | variant: 'normal', 62 | }, 63 | success: res => { 64 | resolve(res); 65 | }, 66 | fail: err => { 67 | reject(err); 68 | }, 69 | }); 70 | }); 71 | } 72 | 73 | /** 74 | * 动态加载字体 75 | */ 76 | export function dynamicLoadFontFace() { 77 | /* 实际项目中替换为自己服务器的静态资源地址 78 | * 微信小程序端字体链接必须是同源下的,或开启了cors支持,微信小程序的域名是servicewechat.com 79 | * */ 80 | const DYNAMIC_LOAD_FONT_FACE_URL = `${BASE_URL}/fonts`; 81 | fontFamilyList.forEach(fontFamily => { 82 | LoadFontFace({ 83 | ...fontFamily, 84 | source: `url(${DYNAMIC_LOAD_FONT_FACE_URL}${fontFamily.source})`, 85 | }) 86 | .then((res: any) => { 87 | console.log(fontFamily.family, res.errMsg); 88 | }) 89 | .catch(err => { 90 | console.error(fontFamily.family, err.errMsg); 91 | }); 92 | }); 93 | } 94 | 95 | /** 96 | * APP本地加载字体 97 | */ 98 | export function loadFontFaceFromLocal() { 99 | fontFamilyList.forEach(fontFamily => { 100 | const source = `url(${plus.io.convertLocalFileSystemURL(`_www/static/fonts${fontFamily.source}`)})`; 101 | LoadFontFace({ 102 | ...fontFamily, 103 | source, 104 | }) 105 | .then((res: any) => { 106 | console.log(fontFamily.family, res.errMsg); 107 | }) 108 | .catch(err => { 109 | console.error(fontFamily.family, err); 110 | }); 111 | }); 112 | } 113 | -------------------------------------------------------------------------------- /src/utils/cache/storageCache.ts: -------------------------------------------------------------------------------- 1 | import { cacheCipher } from '@/config/encryptionConfig'; 2 | import type { EncryptionParams } from '@/utils/cipher'; 3 | import { AesEncryption } from '@/utils/cipher'; 4 | import { isNullOrUnDef } from '@/utils/is'; 5 | 6 | export interface CreateStorageParams extends EncryptionParams { 7 | prefixKey: string; 8 | hasEncrypt: boolean; 9 | timeout?: number | null; 10 | } 11 | export const createStorage = ({ 12 | prefixKey = '', 13 | key = cacheCipher.key, 14 | iv = cacheCipher.iv, 15 | timeout = null, 16 | hasEncrypt = true, 17 | }: Partial = {}) => { 18 | if (hasEncrypt && [key.length, iv.length].some(item => item !== 16)) { 19 | throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!'); 20 | } 21 | 22 | const encryption = new AesEncryption({ key, iv }); 23 | 24 | /** 25 | * Cache class 26 | * Construction parameters can be passed into sessionStorage, localStorage, 27 | * @class Cache 28 | * @example 29 | */ 30 | class Storage { 31 | private prefixKey?: string; 32 | 33 | private encryption: AesEncryption; 34 | 35 | private hasEncrypt: boolean; 36 | 37 | /** 38 | * 39 | * @param {*} storage 40 | */ 41 | constructor() { 42 | this.prefixKey = prefixKey; 43 | this.encryption = encryption; 44 | this.hasEncrypt = hasEncrypt; 45 | } 46 | 47 | private getKey(key: string) { 48 | return `${this.prefixKey}${key}`.toUpperCase(); 49 | } 50 | 51 | /** 52 | * Set cache 53 | * @param {string} key 54 | * @param {*} value 55 | * @param {*} expire Expiration time in seconds 56 | * @memberof Cache 57 | */ 58 | set(key: string, value: any, expire: number | null = timeout) { 59 | try { 60 | const stringData = JSON.stringify({ 61 | value, 62 | time: Date.now(), 63 | expire: !isNullOrUnDef(expire) ? new Date().getTime() + expire * 1000 : null, 64 | }); 65 | const stringifyValue = this.hasEncrypt ? this.encryption.encryptByAES(stringData) : stringData; 66 | uni.setStorageSync(this.getKey(key), stringifyValue); 67 | } catch (err) { 68 | throw new Error(`setStorageSync error: ${err}`); 69 | } 70 | } 71 | 72 | /** 73 | * Read cache 74 | * @param {string} key 75 | * @param {*} def 76 | * @memberof Cache 77 | */ 78 | get(key: string, def: any = null): T { 79 | const val = uni.getStorageSync(this.getKey(key)); 80 | if (!val) return def; 81 | 82 | try { 83 | const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val; 84 | const data = JSON.parse(decVal); 85 | const { value, expire } = data; 86 | if (isNullOrUnDef(expire) || expire < new Date().getTime()) { 87 | this.remove(key); 88 | return def; 89 | } 90 | return value; 91 | } catch (e) { 92 | return def; 93 | } 94 | } 95 | 96 | /** 97 | * Delete cache based on key 98 | * @param {string} key 99 | * @memberof Cache 100 | */ 101 | remove(key: string) { 102 | uni.removeStorageSync(this.getKey(key)); 103 | } 104 | 105 | /** 106 | * Delete all caches of this instance 107 | */ 108 | clear(): void { 109 | uni.clearStorageSync(); 110 | } 111 | } 112 | return new Storage(); 113 | }; 114 | -------------------------------------------------------------------------------- /src/pagesUno/splash/splash.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | 22 | 128 | -------------------------------------------------------------------------------- /src/utils/router/navigates.ts: -------------------------------------------------------------------------------- 1 | import { warn } from 'vue'; 2 | import { cloneDeep } from 'lodash-es'; 3 | import { NAVIGATE_TYPE } from '@/enums/routerEnum'; 4 | import { deepMerge } from '@/utils'; 5 | import { routerBeforeEach } from '@/utils/router/interceptor'; 6 | import { useRouterStore } from '@/stores/modules/router'; 7 | import { filterPath } from '@/utils/router/constant'; 8 | 9 | export type NavigateOptions = Partial> & { 10 | delta?: number; 11 | }; 12 | 13 | export class Navigates { 14 | private type: string; 15 | 16 | private readonly options: NavigateOptions; 17 | 18 | constructor(type?: string, options?: NavigateOptions) { 19 | this.type = type || NAVIGATE_TYPE.NAVIGATE_TO; 20 | this.options = options || {}; 21 | } 22 | 23 | navigate(url: string, options?: NavigateOptions) { 24 | const navigateOptions = deepMerge(cloneDeep(this.options), options); 25 | const _options = deepMerge({ url }, navigateOptions); 26 | switch (this.type) { 27 | case NAVIGATE_TYPE.NAVIGATE_TO: 28 | uni.navigateTo(_options); 29 | break; 30 | case NAVIGATE_TYPE.REDIRECT_TO: 31 | uni.redirectTo(_options); 32 | break; 33 | case NAVIGATE_TYPE.RE_LAUNCH: 34 | uni.reLaunch(_options); 35 | break; 36 | case NAVIGATE_TYPE.SWITCH_TAB: 37 | uni.switchTab(_options); 38 | break; 39 | case NAVIGATE_TYPE.NAVIGATE_BACK: 40 | uni.navigateBack(navigateOptions); 41 | break; 42 | default: 43 | warn('navigate Error'); 44 | break; 45 | } 46 | } 47 | 48 | /** 49 | * uni.navigateTo 50 | * @param url 51 | * @param options 52 | */ 53 | push(url: string, options?: NavigateOptions) { 54 | this.type = NAVIGATE_TYPE.NAVIGATE_TO; 55 | this.navigate(url, options); 56 | } 57 | 58 | /** 59 | * uni.redirectTo 60 | * @param url 61 | * @param options 62 | */ 63 | replace(url: string, options?: NavigateOptions) { 64 | this.type = NAVIGATE_TYPE.REDIRECT_TO; 65 | this.navigate(url, options); 66 | } 67 | 68 | /** 69 | * uni.reLaunch 70 | * @param url 71 | * @param options 72 | */ 73 | replaceAll(url: string, options?: NavigateOptions) { 74 | this.type = NAVIGATE_TYPE.RE_LAUNCH; 75 | this.navigate(url, options); 76 | } 77 | 78 | /** 79 | * uni.switchTab 80 | * @param url 81 | * @param options 82 | */ 83 | pushTab(url: string, options?: NavigateOptions) { 84 | // 微信小程序端uni.switchTab拦截无效处理 85 | /* #ifdef MP-WEIXIN */ 86 | if (!routerBeforeEach(url)) { 87 | return; 88 | } 89 | /* #endif */ 90 | this.type = NAVIGATE_TYPE.SWITCH_TAB; 91 | this.navigate(url, options); 92 | } 93 | 94 | /** 95 | * uni.navigateBack 96 | * @param options 97 | */ 98 | back(options?: NavigateOptions) { 99 | this.type = NAVIGATE_TYPE.NAVIGATE_BACK; 100 | this.navigate('', options); 101 | } 102 | 103 | /** 104 | * 自动判断跳转页面 (navigateTo|switchTab) 105 | * @param url 106 | * @param options 107 | */ 108 | go(url: string, options?: NavigateOptions & { replace?: boolean }) { 109 | const path = filterPath(url); 110 | const routerStore = useRouterStore(); 111 | const routes = routerStore.getRoutes; 112 | const route = routes?.get(path); 113 | if (route?.meta?.tabBar) { 114 | this.pushTab(url, options); 115 | return; 116 | } 117 | if (options?.replace) { 118 | this.replace(url, options); 119 | return; 120 | } 121 | this.push(url, options); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/pages/login/index.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 68 | 69 | 108 | -------------------------------------------------------------------------------- /src/utils/platform.ts: -------------------------------------------------------------------------------- 1 | import { PLATFORMS } from '@/enums/platformEnum'; 2 | 3 | /** 4 | * 判断是否是某个平台环境 5 | * @param target 目标平台 6 | * 7 | * @return boolean 8 | */ 9 | export function judgePlatform(target: PLATFORMS): boolean { 10 | let isVue3 = false; 11 | let isAppPlus = false; 12 | let isAppPlusNvue = false; 13 | let isAppNvue = false; 14 | let isH5 = false; 15 | let isMp = false; 16 | let isMpWeinxin = false; 17 | let isMpAlipay = false; 18 | let isMpBaidu = false; 19 | let isMpToutiao = false; 20 | let isMpLark = false; 21 | let isMpQq = false; 22 | let isMpKuaishou = false; 23 | let isMpJd = false; 24 | let isMp360 = false; 25 | let isQuickAppWebView = false; 26 | let isQuickAppWebViewUnion = false; 27 | let isQuickAppWebViewHuawei = false; 28 | 29 | switch (target) { 30 | case PLATFORMS.VUE3: 31 | /* #ifdef VUE3 */ 32 | isVue3 = true; 33 | /* #endif */ 34 | return isVue3; 35 | break; 36 | case PLATFORMS.APP_PLUS: 37 | /* #ifdef APP-PLUS */ 38 | isAppPlus = true; 39 | /* #endif */ 40 | return isAppPlus; 41 | break; 42 | case PLATFORMS.APP_PLUS_NVUE: 43 | /* #ifdef APP-PLUS-NVUE */ 44 | isAppPlusNvue = true; 45 | /* #endif */ 46 | return isAppPlusNvue; 47 | break; 48 | case PLATFORMS.APP_NVUE: 49 | /* #ifdef APP-NVUE */ 50 | isAppNvue = true; 51 | /* #endif */ 52 | return isAppNvue; 53 | break; 54 | case PLATFORMS.H5: 55 | /* #ifdef H5 */ 56 | isH5 = true; 57 | /* #endif */ 58 | return isH5; 59 | break; 60 | case PLATFORMS.MP: 61 | /* #ifdef MP */ 62 | isMp = true; 63 | /* #endif */ 64 | return isMp; 65 | break; 66 | case PLATFORMS.MP_WEIXIN: 67 | /* #ifdef MP-WEIXIN */ 68 | isMpWeinxin = true; 69 | /* #endif */ 70 | return isMpWeinxin; 71 | break; 72 | case PLATFORMS.MP_ALIPAY: 73 | /* #ifdef MP-ALIPAY */ 74 | isMpAlipay = true; 75 | /* #endif */ 76 | return isMpAlipay; 77 | break; 78 | case PLATFORMS.MP_BAIDU: 79 | /* #ifdef MP_BAIDU */ 80 | isMpBaidu = true; 81 | /* #endif */ 82 | return isMpBaidu; 83 | break; 84 | case PLATFORMS.MP_TOUTIAO: 85 | /* #ifdef MP-TOUTIAO */ 86 | isMpToutiao = true; 87 | /* #endif */ 88 | return isMpToutiao; 89 | break; 90 | case PLATFORMS.MP_LARK: 91 | /* #ifdef MP-LARK */ 92 | isMpLark = true; 93 | /* #endif */ 94 | return isMpLark; 95 | break; 96 | case PLATFORMS.MP_QQ: 97 | /* #ifdef MP-QQ */ 98 | isMpQq = true; 99 | /* #endif */ 100 | return isMpQq; 101 | break; 102 | case PLATFORMS.MP_KUAISHOU: 103 | /* #ifdef MP-KUAISHOU */ 104 | isMpKuaishou = true; 105 | /* #endif */ 106 | return isMpKuaishou; 107 | break; 108 | case PLATFORMS.MP_JD: 109 | /* #ifdef MP-JD */ 110 | isMpJd = true; 111 | /* #endif */ 112 | return (isMpJd = true); 113 | break; 114 | case PLATFORMS.MP_360: 115 | /* #ifdef MP-360 */ 116 | isMp360 = true; 117 | /* #endif */ 118 | return isMp360; 119 | break; 120 | case PLATFORMS.QUICKAPP_WEBVIEW: 121 | /* #ifdef QUICKAPP-WEBVIEW */ 122 | isQuickAppWebView = true; 123 | /* #endif */ 124 | return isQuickAppWebView; 125 | break; 126 | case PLATFORMS.QUICKAPP_WEBVIEW_UNION: 127 | /* #ifdef QUICKAPP-WEBVIEW-UNION */ 128 | isQuickAppWebViewUnion = true; 129 | /* #endif */ 130 | return isQuickAppWebViewUnion; 131 | break; 132 | case PLATFORMS.QUICKAPP_WEBVIEW_HUAWEI: 133 | /* #ifdef QUICKAPP-WEBVIEW-HUAWEI */ 134 | isQuickAppWebViewHuawei = true; 135 | /* #endif */ 136 | return isQuickAppWebViewHuawei; 137 | break; 138 | default: 139 | return false; 140 | } 141 | return false; 142 | } 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | uni-app Vue3 Vite3 Pinia TypeScript 基础框架 2 | === 3 | 4 | ## 特性 5 | 6 | - **最新技术栈**:使用 Vue3/Vite3/Pinia/TypeScript/Windicss/Unocss 等前端前沿技术开发; 7 | - **Eslint/Prettier**:规范代码格式,统一编码; 8 | - **路由拦截**:基于 uni.addInterceptor 进行路由拦截; 9 | - **请求拦截**:核心使用 [luch-request](https://github.com/lei-mu/luch-request),支持请求和响应拦截等; 10 | - **缓存加密**:使用AES加密缓存,可设置区分在开发或生成环境中是否加密; 11 | - **自动导入**:自动导入 vue3 的 hooks 等。 12 | 13 | ## 目录结构 14 | 15 | ```shell 16 | ├── mock # mockjs 17 | ├── src 18 | │ ├── api # 接口相关 19 | │ │ ├── models # 数据模型 20 | │ │ ├── auth.ts 21 | │ │ └── ... 22 | │ ├── assets # 静态资源目录,css、less/scss 等资源不要放在 static 目录下 23 | │ ├── components # 组件目录 24 | │ │ ├── AppProvider 25 | │ │ └── ... 26 | │ ├── config # 配置相关 27 | │ │ ├── app.ts 28 | │ │ └── encryptionConfig.ts 29 | │ ├── enums # 枚举/常量 30 | │ │ ├── appEnum.ts 31 | │ │ └── ... 32 | │ ├── language # i18n 33 | │ │ ├── lang 34 | │ │ └── index.ts 35 | │ ├── pages # 页面 36 | │ │ ├── about 37 | │ │ └── ... 38 | │ ├── pagesA # 子页面(拆包) 39 | │ │ └── list 40 | │ ├── stores # state 状态管理模式(pinia) 41 | │ │ ├── modules 42 | │ │ └── index.ts 43 | │ ├── static # 静态文件静态公共文件 44 | │ │ ├── images 45 | │ │ └── ... 46 | │ ├── types # 类型文件 47 | │ ├── uni_modules # uni 组件库 48 | │ ├── utils # 工具类 49 | │ │ ├── cache # 缓存相关目录 50 | │ │ ├── http # request相关目录 51 | │ │ ├── interceptors # 拦截器相关目录 52 | │ │ └── ... 53 | │ ├── wxcomponents # 微信小程序组件 54 | │ ├── App.vue 55 | │ ├── env.d.ts 56 | │ ├── main.ts 57 | │ ├── manifest.json 58 | │ ├── pages.json 59 | │ └── uni.scss 60 | ├── .editorconfig 61 | ├── .env 62 | ├── .env.development 63 | ├── .env.production 64 | ├── .env.staging 65 | ├── .eslintignore 66 | ├── .eslintrc.js 67 | ├── .gitignore 68 | ├── .npmrc 69 | ├── .prettierignore 70 | ├── .prettierrc.js 71 | ├── LICENSE 72 | ├── README.md 73 | ├── favicon.ico 74 | ├── index.html 75 | ├── package-lock.json 76 | ├── package.json 77 | ├── tsconfig.json 78 | └── vite.config.ts 79 | 80 | ``` 81 | 82 | ## 安装使用 83 | 84 | - 安装依赖 85 | 86 | ```bash 87 | # nodejs v18.12.x 88 | npx degit xinlc/uniapp-vue3-ts-template my-project 89 | 90 | cd my-project 91 | 92 | pnpm install 93 | ``` 94 | 95 | - 运行 96 | 97 | ```bash 98 | # 其他端请查看 package.json script 99 | 100 | # h5 or npm run xxx 101 | pnpm dev:h5 102 | 103 | # 微信小程序, 生成的包在 dist/dev/mp-weixin 下,用微信开发者工具打开 104 | pnpm dev:mp-weixin 105 | ``` 106 | 107 | - 打包 108 | 109 | ```bash 110 | # 其他端请查看 package.json script 111 | pnpm build:h5 112 | ``` 113 | 114 | ## 开发工具 115 | 116 | ### VsCode 插件 117 | 118 | - volar 119 | - ESLint 120 | - prettier 121 | - UnoCSS 122 | 123 | ## Git 提交规范 124 | 125 | - 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) 126 | 127 | - `feat` 增加新功能 128 | - `fix` 修复问题/BUG 129 | - `style` 代码风格相关无影响运行结果的 130 | - `perf` 优化/性能提升 131 | - `refactor` 重构 132 | - `revert` 撤销修改 133 | - `test` 测试相关 134 | - `docs` 文档/注释 135 | - `chore` 依赖更新/脚手架配置修改等 136 | - `workflow` 工作流改进 137 | - `ci` 持续集成 138 | - `types` 类型定义文件更改 139 | - `wip` 开发中 140 | 141 | ## 参考资料 142 | 143 | - [uni-app Vue3 Vite3 TypeScript 基础框架](https://gitee.com/h_mo/uniapp-vue3-vite3-ts-template) - 基于该项目进行修改 144 | - [uni-app](https://github.com/dcloudio/uni-app) - 跨平台框架 145 | - [Vue3](https://github.com/vuejs/) - Vue3 146 | - [Vite](https://github.com/vitejs/vite) - 构建工具 147 | - [Pinia](https://github.com/vuejs/pinia) - 状态管理(代替 Vuex) 148 | - [TypeScript](https://github.com/microsoft/TypeScript) - TS 149 | - [luch-request](https://github.com/lei-mu/luch-request) - API 请求库 150 | - [Windi CSS](https://github.com/windicss/windicss) - ACSS 框架 151 | - [unplugin-auto-import](https://github.com/antfu/unplugin-auto-import) - 自动导入 152 | - [UnoCSS](https://github.com/unocss/unocss) - 原子 CSS 引擎。 153 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "uniapp-vue3-ts-template", 3 | "appid" : "", 4 | "description" : "", 5 | "versionName" : "1.0.0", 6 | "versionCode" : "100", 7 | "transformPx" : false, 8 | /* 5+App特有相关 */ 9 | "app-plus" : { 10 | "usingComponents" : true, 11 | "nvueStyleCompiler" : "uni-app", 12 | "compilerVersion" : 3, 13 | "splashscreen" : { 14 | "alwaysShowBeforeRender" : true, 15 | "waiting" : true, 16 | "autoclose" : true, 17 | "delay" : 0 18 | }, 19 | /* 模块配置 */ 20 | "modules" : {}, 21 | "optimization" : { 22 | "subPackages" : true 23 | }, 24 | /* 应用发布信息 */ 25 | "distribute" : { 26 | /* android打包配置 */ 27 | "android" : { 28 | "permissions" : [ 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | "", 39 | "", 40 | "", 41 | "", 42 | "", 43 | "" 44 | ], 45 | "minSdkVersion" : 21 46 | }, 47 | /* ios打包配置 */ 48 | "ios" : { 49 | "dSYMs" : false 50 | }, 51 | /* SDK配置 */ 52 | "sdkConfigs" : { 53 | "ad" : {} 54 | } 55 | } 56 | }, 57 | /* 快应用特有相关 */ 58 | "quickapp" : {}, 59 | /* 小程序特有相关 */ 60 | "mp-weixin" : { 61 | "appid" : "", 62 | "setting" : { 63 | "urlCheck" : false, 64 | "es6" : true, 65 | "postcss" : true, 66 | "minified" : true 67 | }, 68 | "usingComponents" : true, 69 | "permission" : {}, 70 | "optimization" : { 71 | "subPackages" : true 72 | }, 73 | "lazyCodeLoading" : "requiredComponents", 74 | "darkmode": true, 75 | "themeLocation": "theme.json" 76 | }, 77 | "mp-alipay" : { 78 | "usingComponents" : true, 79 | "optimization" : { 80 | "subPackages" : true 81 | } 82 | }, 83 | "mp-baidu" : { 84 | "usingComponents" : true 85 | }, 86 | "mp-toutiao" : { 87 | "usingComponents" : true 88 | }, 89 | "uniStatistics": { 90 | "enable": false 91 | }, 92 | "vueVersion" : "3", 93 | "h5" : { 94 | "router" : { 95 | "mode" : "hash", 96 | "base" : "./" 97 | }, 98 | "devServer" : { 99 | "https" : false 100 | }, 101 | "title" : "uniapp-vue3-ts-template", 102 | "unipush" : { 103 | "enable" : false 104 | }, 105 | "sdkConfigs" : { 106 | "maps" : {} 107 | }, 108 | "optimization" : { 109 | "treeShaking" : { 110 | "enable" : true 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/utils/misc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 执行数组里的函数 3 | */ 4 | export const invokeArrayFns = (fns: Function[], arg?: any) => { 5 | for (let i = 0; i < fns.length; i++) fns[i](arg); 6 | }; 7 | 8 | /** 9 | * Nano version of string hash 10 | * @param str - 字符串 11 | * @returns foo => 193420387 12 | */ 13 | export const stringHash = (str: string): number => { 14 | let hash = 5381; 15 | let i = str.length; 16 | 17 | while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); 18 | return hash >>> 0; 19 | }; 20 | 21 | /** 22 | * uuid 23 | */ 24 | export const uuid = (): string => { 25 | return Array.from({ length: 16 }, () => 26 | Math.floor(Math.random() * 256) 27 | .toString(16) 28 | .padStart(2, '0'), 29 | ).join(''); 30 | }; 31 | 32 | // https://github.com/ai/nanoid/blob/main/non-secure/index.js 33 | const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'; 34 | 35 | /** 36 | * nanoid 37 | * @param alphabet - 字符串 38 | * @param defaultSize - 长度 39 | */ 40 | export const nanoid = (defaultSize = 21, alphabet = urlAlphabet) => { 41 | let id = ''; 42 | // A compact alternative for `for (var i = 0; i < step; i++)`. 43 | let i = defaultSize; 44 | while (i--) { 45 | // `| 0` is more compact and faster than `Math.floor()`. 46 | id += alphabet[(Math.random() * 64) | 0]; 47 | } 48 | return id; 49 | }; 50 | 51 | /** 52 | * 手机号码中间4位隐藏星号 53 | * @param mobile - 手机号 54 | * @returns 138****8888 55 | */ 56 | export function hideMobile(mobile: string) { 57 | return mobile.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2'); 58 | } 59 | 60 | /** 61 | * 键值对拼接成URL参数 62 | * @param obj - 键值对 63 | * @returns a=1&b=2 64 | */ 65 | export const params2Url = (obj: Object) => { 66 | const params = []; 67 | for (const key in obj) params.push(`${key}=${obj[key]}`); 68 | 69 | return encodeURIComponent(params.join('&')); 70 | }; 71 | 72 | /** 73 | * 将总秒数转换成 时:分:秒 74 | * @param seconds - 秒 75 | */ 76 | export const seconds2Time = (seconds: number) => { 77 | const hour = Math.floor(seconds / 3600); 78 | const minute = Math.floor((seconds - hour * 3600) / 60); 79 | const second = seconds - hour * 3600 - minute * 60; 80 | return `${fillZero(hour)}:${fillZero(minute)}:${fillZero(second)}`; 81 | }; 82 | 83 | /** 84 | * 将总秒数转换成 日:时:分:秒 85 | * @param seconds - 秒 86 | */ 87 | export const seconds2DayTime = (seconds: number) => { 88 | const day = Math.floor(seconds / 86400); 89 | const hour = Math.floor((seconds - day * 86400) / 3600); 90 | const minute = Math.floor((seconds - day * 86400 - hour * 3600) / 60); 91 | const second = seconds - day * 86400 - hour * 3600 - minute * 60; 92 | return `${fillZero(day)}:${fillZero(hour)}:${fillZero(minute)}:${fillZero(second)}`; 93 | }; 94 | 95 | /** 96 | * 填充0 97 | * @param num - 数字 98 | */ 99 | function fillZero(num: number) { 100 | /** 101 | * ES6 字符串补全 102 | * padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。 103 | * padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。 104 | * 以上两个方法接受两个参数, 105 | * 第一个参数是指定生成的字符串的最小长度, 106 | * 第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充。 107 | * @link https://www.runoob.com/w3cnote/es6-string.html 108 | */ 109 | return num.toString().padStart(2, '0'); 110 | } 111 | 112 | /** 113 | * 下载文件 114 | * @param link - 文件链接 115 | * @param name - 文件名 116 | * @example downloadFile('http://www.baidu.com/img/bd_logo1.png', 'logo.png') 117 | */ 118 | export function download(link: string, name?: string) { 119 | if (!name) name = link.slice(link.lastIndexOf('/') + 1); 120 | 121 | const eleLink = document.createElement('a'); 122 | eleLink.download = name; 123 | eleLink.style.display = 'none'; 124 | eleLink.href = link; 125 | document.body.appendChild(eleLink); 126 | eleLink.click(); 127 | document.body.removeChild(eleLink); 128 | } 129 | 130 | /** 131 | * 浏览器下载静态文件 132 | * @param name - 文件名 133 | * @param content - 文件内容 blob 134 | * @example downloadFile('1.json',JSON.stringify({name:'hahahha'})) 135 | * @example downloadFile('1.json',new Blob([ data ])) 136 | */ 137 | export function downloadFile(name: string, content: any) { 138 | if (typeof name == 'undefined') throw new Error('The first parameter name is a must'); 139 | 140 | if (typeof content == 'undefined') throw new Error('The second parameter content is a must'); 141 | 142 | if (!(content instanceof Blob)) content = new Blob([content]); 143 | 144 | const link = URL.createObjectURL(content); 145 | download(link, name); 146 | } 147 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | export enum FormatType { 4 | toMinute = 'YYYY-MM-DD HH:mm', 5 | toHour = 'YYYY-MM-DD HH', 6 | toDay = 'YYYY-MM-DD', 7 | toMonth = 'YYYY-MM', 8 | year = 'YYYY', 9 | toSecond = 'YYYY-MM-DD HH:mm:ss', 10 | } 11 | 12 | type DateFormat = FormatType | string; 13 | 14 | type IDate = Date | string; 15 | 16 | /** 17 | * 格式化日期 18 | * @param date - 日期 默认为当天 19 | * @param format - 格式 默认为YYYY-MM-DD HH:mm 20 | */ 21 | export function formatDate(date?: IDate, format: DateFormat = FormatType.toSecond): string { 22 | if (!date) date = new Date(); 23 | 24 | return dayjs(date).format(format); 25 | } 26 | 27 | /** 28 | * 获取当前时间 29 | * @param format - 格式 默认为YYYY-MM-DD 30 | */ 31 | export function getNow(format: DateFormat = FormatType.toDay): string { 32 | return dayjs().format(format); 33 | } 34 | 35 | /** 36 | * 获取月第一天 37 | * @param date - 日期 默认为当天 38 | * @param format - 格式 默认为YYYY-MM-DD 39 | */ 40 | export function getFirstDayOfMonth(date?: IDate, format: DateFormat = FormatType.toDay): string { 41 | if (!date) date = new Date(); 42 | 43 | return dayjs(date).startOf('month').format(format); 44 | } 45 | 46 | /** 47 | * 获取月最后一天 48 | * @param date - 日期 默认为当天 49 | * @param format - 格式 默认为YYYY-MM-DD 50 | */ 51 | export function getLastDayOfMonth(date?: IDate, format: DateFormat = FormatType.toDay): string { 52 | if (!date) date = new Date(); 53 | 54 | return dayjs(date).endOf('month').format(format); 55 | } 56 | 57 | /** 58 | * 获取整月 59 | * @param date - 日期 默认为当天 60 | * @param format - 格式 默认为YYYY-MM-DD 61 | */ 62 | export function getDaysOfMonth(date?: IDate, format: DateFormat = FormatType.toDay): string[] { 63 | return [getFirstDayOfMonth(date, format), getLastDayOfMonth(date, format)]; 64 | } 65 | 66 | /** 67 | * 获取上个月 68 | * @param format - 格式 默认为YYYY-MM-DD 69 | */ 70 | export function getDaysOfLastMonth(format = FormatType.toDay): string[] { 71 | const month = dayjs().subtract(1, 'month').toDate(); 72 | return [getFirstDayOfMonth(month, format), getLastDayOfMonth(month, format)]; 73 | } 74 | 75 | /** 76 | * 获取月第一天 到 现在 77 | * @param format - 格式 默认为YYYY-MM-DD 78 | */ 79 | export function getDaysToNowOfMonth(date?: IDate, format = FormatType.toDay): string[] { 80 | return [getFirstDayOfMonth(date, format), getNow(format)]; 81 | } 82 | 83 | /** 84 | * 获取年第一天 85 | * @param date - 日期 默认为当年 86 | * @param format - 格式 默认为YYYY-MM-DD 87 | * @returns 88 | */ 89 | export function getFirstDayOfYear(date?: IDate, format = FormatType.toDay): string { 90 | if (!date) date = new Date(); 91 | 92 | return dayjs(date).startOf('year').format(format); 93 | } 94 | 95 | /** 96 | * 本周 97 | */ 98 | export function getDaysOfWeek(format = FormatType.toDay): string[] { 99 | return [dayjs().startOf('week').format(format), dayjs().endOf('week').format(format)]; 100 | } 101 | 102 | /** 103 | * d2是否在d1之后 104 | * @param d1 - 日期1 105 | * @param d2 - 日期2 默认为当前时间 106 | */ 107 | export function isAfter(d1: IDate, d2: IDate = new Date()): boolean { 108 | return dayjs(d2).isAfter(d1); 109 | } 110 | 111 | /** 112 | * d2是否在d1之前 113 | * @param d1 - 日期1 114 | * @param d2 - 日期2 默认为当前时间 115 | */ 116 | export function isBefore(d1: IDate, d2: IDate = new Date()): boolean { 117 | return dayjs(d2).isBefore(d1); 118 | } 119 | 120 | /** 121 | * d3是否在d1与d2之间 122 | * @param d1 - 日期1 123 | * @param d2 - 日期2 124 | * @param d3 - 日期3 默认为当前时间 125 | * @returns 126 | */ 127 | export function isBetween(d1: IDate, d2: IDate, d3: IDate = new Date()): boolean { 128 | return isAfter(d1, d3) && isBefore(d2, d3); 129 | } 130 | 131 | /** 132 | * 加几天 133 | * @param days - 天数 默认为1 134 | * @param d - 日期 默认为当天 135 | * @param format - 格式 默认为YYYY-MM-DD 136 | * @returns 137 | */ 138 | export function addDays(days = 1, d: IDate = new Date(), format = FormatType.toDay): IDate { 139 | return dayjs(d).add(days, 'day').format(format); 140 | } 141 | 142 | /** 143 | * 减几天 144 | * @param days - 天数 默认为1 145 | * @param d - 日期 默认为当天 146 | * @param format - 格式 默认为YYYY-MM-DD 147 | * @returns 148 | */ 149 | export function subDays(days = 1, d: IDate = new Date(), format = FormatType.toDay): IDate { 150 | return dayjs(d).subtract(days, 'day').format(format); 151 | } 152 | 153 | /** 154 | * 转换成 Date 155 | */ 156 | export function toDate(date: string | string[]) { 157 | if (typeof date === 'string') return dayjs(date).toDate(); 158 | 159 | return date.map(item => dayjs(item).toDate()); 160 | } 161 | -------------------------------------------------------------------------------- /src/utils/is.ts: -------------------------------------------------------------------------------- 1 | const { toString } = Object.prototype; 2 | 3 | import { toTypeString } from './object'; 4 | 5 | export function is(val: unknown, type: string) { 6 | return toString.call(val) === `[object ${type}]`; 7 | } 8 | 9 | export function isDef(val?: T): val is T { 10 | return typeof val !== 'undefined'; 11 | } 12 | 13 | export function isUnDef(val?: T): val is T { 14 | return !isDef(val); 15 | } 16 | 17 | export function isObject(val: any): val is Record { 18 | return val !== null && is(val, 'Object'); 19 | } 20 | 21 | export function isEmpty(val: T): val is T { 22 | if (isArray(val) || isString(val)) { 23 | return val.length === 0; 24 | } 25 | 26 | if (val instanceof Map || val instanceof Set) { 27 | return val.size === 0; 28 | } 29 | 30 | if (isObject(val)) { 31 | return Object.keys(val).length === 0; 32 | } 33 | 34 | return false; 35 | } 36 | 37 | export function isDate(val: unknown): val is Date { 38 | return is(val, 'Date'); 39 | } 40 | 41 | export function isNull(val: unknown): val is null { 42 | return val === null; 43 | } 44 | export const isUndefined = (val: unknown): val is undefined => toTypeString(val) === '[object Undefined]'; 45 | 46 | export function isNullAndUnDef(val: unknown): val is null | undefined { 47 | return isUnDef(val) && isNull(val); 48 | } 49 | 50 | export function isNullOrUnDef(val: unknown): val is null | undefined { 51 | return isUnDef(val) || isNull(val); 52 | } 53 | 54 | export function isNumber(val: unknown): val is number { 55 | return is(val, 'Number'); 56 | } 57 | 58 | export function isPromise(val: unknown): val is Promise { 59 | return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch); 60 | } 61 | export const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol'; 62 | 63 | export function isString(val: unknown): val is string { 64 | return is(val, 'String'); 65 | } 66 | // export const isString = (val: unknown): val is string => typeof val === 'string' 67 | 68 | export function isFunction(val: unknown): val is Function { 69 | return typeof val === 'function'; 70 | } 71 | 72 | export function isBoolean(val: unknown): val is boolean { 73 | return is(val, 'Boolean'); 74 | } 75 | 76 | export function isRegExp(val: unknown): val is RegExp { 77 | return is(val, 'RegExp'); 78 | } 79 | export const isFile = (val: unknown): val is File => toTypeString(val) === '[object File]'; 80 | 81 | export function isArray(val: any): val is Array { 82 | return val && Array.isArray(val); 83 | } 84 | 85 | export function isWindow(val: any): val is Window { 86 | return typeof window !== 'undefined' && is(val, 'Window'); 87 | } 88 | 89 | export function isElement(val: unknown): val is Element { 90 | return isObject(val) && !!val.tagName; 91 | } 92 | 93 | export function isMap(val: unknown): val is Map { 94 | return is(val, 'Map'); 95 | } 96 | 97 | // export const isMap = (val: unknown): val is Map => toTypeString(val) === '[object Map]'; 98 | 99 | export const isSet = (val: unknown): val is Set => toTypeString(val) === '[object Set]'; 100 | 101 | export const isServer = typeof window === 'undefined'; 102 | 103 | export const isClient = !isServer; 104 | 105 | export function isUrl(path: string): boolean { 106 | // @ts-ignore 107 | const reg = 108 | /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/; 109 | return reg.test(path); 110 | } 111 | 112 | /** 113 | * 是否为纯粹的对象 114 | * isObject([]) 是 true ,因为 type [] 为 'object' 115 | * isPlainObject([]) 则是false 116 | */ 117 | export const isPlainObject = (val: unknown): val is object => toTypeString(val) === '[object Object]'; 118 | /** 119 | * 是否为空字符串 120 | */ 121 | export function isEmptyString(v: unknown) { 122 | return isString(v) && v.trim().length === 0; 123 | } 124 | 125 | // /** 126 | // * 是否为空 127 | // * @example isEmpty(null) // true 128 | // * @example isEmpty(undefined) // true 129 | // * @example isEmpty('') // true 130 | // * @example isEmpty([]) // true 131 | // * @example isEmpty({}) // true 132 | // * @example isEmpty(' ') // false 133 | // * @example isEmpty(123) // true 134 | // */ 135 | // export function isEmpty(val: any) { 136 | // return val == null || !(Object.keys(val) || val).length; 137 | // } 138 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * https://vitejs.cn/config/ 3 | */ 4 | import { ConfigEnv, defineConfig, loadEnv } from 'vite'; 5 | import { resolve } from 'path'; 6 | import uni from '@dcloudio/vite-plugin-uni'; 7 | import eslintPlugin from 'vite-plugin-eslint'; 8 | import { viteMockServe } from 'vite-plugin-mock'; 9 | // import Components from 'unplugin-vue-components/vite'; 10 | import AutoImport from 'unplugin-auto-import/vite'; 11 | import UnoCSS from 'unocss/vite'; 12 | 13 | // 使用 UnoCSS 代替 WindiCSS 14 | // import WindiCSS from 'vite-plugin-windicss'; 15 | // import MiniProgramTailwind from '@dcasia/mini-program-tailwind-webpack-plugin/rollup'; 16 | 17 | const pathResolve = (dir: string): any => { 18 | return resolve(__dirname, '.', dir); 19 | }; 20 | 21 | const alias: Record = { 22 | '@': pathResolve('./src/'), 23 | }; 24 | 25 | const viteConfig = defineConfig((mode: ConfigEnv) => { 26 | const root = process.cwd(); 27 | const env = loadEnv(mode.mode, root); 28 | 29 | const localEnabled: boolean = (env.VITE_USE_MOCK as unknown as boolean) || false; 30 | const prodEnabled: boolean = (env.VITE_USE_CHUNK_MOCK as unknown as boolean) || false; 31 | 32 | return { 33 | base: mode.command === 'serve' ? './' : env.VITE_PUBLIC_PATH, 34 | root: root, 35 | resolve: { alias }, 36 | define: { 37 | 'process.env': {}, 38 | }, 39 | 40 | server: { 41 | host: true, 42 | // host: '0.0.0.0', 43 | port: env.VITE_PORT as unknown as number, 44 | open: env.VITE_OPEN === 'true', 45 | proxy: { 46 | [env.VITE_BASE]: { 47 | target: env.VITE_BASE_URL, 48 | ws: true, 49 | changeOrigin: true, 50 | rewrite: path => path.replace(new RegExp(env.VITE_BASE, 'g'), '/'), 51 | }, 52 | }, 53 | }, 54 | // build: { 55 | // outDir: 'dist', 56 | // sourcemap: false, 57 | // chunkSizeWarningLimit: 1500, 58 | // rollupOptions: { 59 | // output: { 60 | // entryFileNames: `assets/[name].${new Date().getTime()}.js`, 61 | // chunkFileNames: `assets/[name].${new Date().getTime()}.js`, 62 | // assetFileNames: `assets/[name].${new Date().getTime()}.[ext]`, 63 | // compact: true, 64 | // manualChunks: { 65 | // vue: ['vue', 'vue-router', 'vuex'], 66 | // echarts: ['echarts'], 67 | // }, 68 | // }, 69 | // }, 70 | // terserOptions: { 71 | // compress: { 72 | // drop_console: true, 73 | // drop_debugger: true, 74 | // }, 75 | // ie8: true, 76 | // output: { 77 | // comments: true, 78 | // }, 79 | // }, 80 | // }, 81 | plugins: [ 82 | uni(), 83 | viteMockServe({ 84 | // 解析根目录下的mock文件夹 85 | mockPath: 'mock', 86 | localEnabled: localEnabled, // 开发打包开关 87 | prodEnabled: prodEnabled, // 生产打包开关 88 | supportTs: true, // 打开后,可以读取 ts 文件模块。 请注意,打开后将无法监视.js 文件。 89 | watchFiles: true, // 监视文件更改 90 | }), 91 | 92 | // https://github.com/unocss/unocss 93 | // see unocss.config.ts for config 94 | UnoCSS(), 95 | 96 | // 建议用 easycom 97 | // https://github.com/antfu/unplugin-vue-components 98 | // Components({ 99 | // dirs: ['src/components'], 100 | // extensions: ['vue', 'ts', 'tsx'], 101 | // dts: 'src/types/components.d.ts', 102 | // deep: true, 103 | // }), 104 | 105 | // https://github.com/antfu/unplugin-auto-import 106 | AutoImport({ 107 | imports: ['vue', 'pinia', 'uni-app'], 108 | dts: 'src/types/auto-imports.d.ts', 109 | dirs: ['src/hooks', 'src/stores/modules'], 110 | vueTemplate: true, 111 | }), 112 | // WindiCSS({ 113 | // scan: { 114 | // dirs: ['.'], // 当前目录下所有文件 115 | // fileExtensions: ['vue', 'js', 'ts'], // 同时启用扫描vue/js/ts 116 | // }, 117 | // }), 118 | // MiniProgramTailwind(), 119 | // eslintPlugin({ 120 | // include: ['src/**/*.js', 'src/**/*.vue', 'src/**/*.ts'], 121 | // exclude: ['./node_modules/**'], 122 | // cache: false, 123 | // }), 124 | ], 125 | css: { 126 | preprocessorOptions: { 127 | scss: { 128 | // additionalData: '@import "@/assets/style/main.scss";', 129 | additionalData: `$image-path: '${env.VITE_IMAGE_URL}';`, 130 | }, 131 | }, 132 | }, 133 | }; 134 | }); 135 | 136 | export default viteConfig; 137 | -------------------------------------------------------------------------------- /src/utils/string.ts: -------------------------------------------------------------------------------- 1 | const camelizeRE = /-(\w)/g; 2 | /** 3 | * 驼峰化 4 | * @param str - 字符串 5 | * @example user-info => userInfo 6 | */ 7 | export const camelize = (str: string): string => { 8 | return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : '')); 9 | }; 10 | 11 | /** 12 | * 将字符串转换为 pascal 13 | * @param str - 字符串 14 | * @example 15 | * user-info => UserInfo 16 | * some_database_field_name => SomeDatabaseFieldName 17 | * Some label that needs to be pascalized => SomeLabelThatNeedsToBePascalized 18 | * some-mixed_string with spaces_underscores-and-hyphens => SomeMixedStringWithSpacesUnderscoresAndHyphens 19 | */ 20 | export const toPascalCase = (str: string): string => { 21 | return str 22 | .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)! 23 | .map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase()) 24 | .join(''); 25 | }; 26 | 27 | /** 28 | * 将字符串转换为 camel 29 | * @param str 30 | * @example hello-world => helloWorld 31 | * @example hello_world => helloWorld 32 | * @example hello world => helloWorld 33 | */ 34 | export const toCamelCase = (str: string) => { 35 | const s = 36 | str && 37 | str 38 | .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)! 39 | .map(x => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase()) 40 | .join(''); 41 | return s.slice(0, 1).toLowerCase() + s.slice(1); 42 | }; 43 | 44 | /** 45 | * 将字符串转换为 kebab 46 | * @param str 47 | * @example helloWorld => hello-world 48 | * @example hello_world => hello-world 49 | * @example hello world => hello-world 50 | */ 51 | export const toKebabCase = (str: string) => 52 | str && 53 | str 54 | .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)! 55 | .map(x => x.toLowerCase()) 56 | .join('-'); 57 | 58 | /** 59 | * 将字符串转换为 snake 60 | * @param str 61 | * @example helloWorld => hello_world 62 | * @example hello_world => hello_world 63 | * @example hello world => hello_world 64 | */ 65 | export const toSnakeCase = (str: string) => 66 | str && 67 | str 68 | .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)! 69 | .map(x => x.toLowerCase()) 70 | .join('_'); 71 | 72 | /** 73 | * 字符数组 74 | * @param s 75 | * @example hello => ['h', 'e', 'l', 'l', 'o'] 76 | */ 77 | export const toCharArray = (s: string) => [...s]; 78 | 79 | /** 80 | * 首字母大写 81 | * @param str - 字符串 82 | * @example userInfo => UserInfo 83 | */ 84 | export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); 85 | 86 | const hyphenateRE = /\B([A-Z])/g; 87 | /** 88 | * 大写字母 转为 小写-连接 89 | * @param str - 字符串 90 | * @example UserInfo => user-info 91 | */ 92 | export const hyphenate = (str: string) => str.replace(hyphenateRE, '-$1').toLowerCase(); 93 | 94 | /** 95 | * 替换所有相同字符串 96 | * @param text - 需要处理的字符串 97 | * @param repstr - 被替换的字符 98 | * @param newstr - 替换后的字符 99 | */ 100 | export function replaceAll(text: string, repstr: string, newstr: string) { 101 | return text.replace(new RegExp(repstr, 'gm'), newstr); 102 | } 103 | 104 | /** 105 | * @desc 去左右空格 106 | * @param value - 需要处理的字符串 107 | */ 108 | export function trim(value: string) { 109 | return value.replace(/(^\s*)|(\s*$)/g, ''); 110 | } 111 | 112 | /** 113 | * @desc 去所有空格 114 | * @param value - 需要处理的字符串 115 | */ 116 | export function trimAll(value: string) { 117 | return value.replace(/\s+/g, ''); 118 | } 119 | 120 | /** 121 | * 根据数字获取对应的汉字 122 | * @param num - 数字(0-10) 123 | */ 124 | export function getHanByNumber(num: number) { 125 | const HAN_STR = '零一二三四五六七八九十'; 126 | return HAN_STR.charAt(num); 127 | } 128 | 129 | /** 130 | * 插入字符串 131 | * @param str - 原字符串 132 | * @param start - 插入位置 133 | * @param insertStr - 插入字符串 134 | */ 135 | export function insertStr(str: string, start: number, insertStr: string): string { 136 | return str.slice(0, start) + insertStr + str.slice(start); 137 | } 138 | 139 | /** 140 | * 转义HTML字符 141 | * @param str - 字符串 142 | * @example 'Me & you' => '<a href="#">Me & you</a>' 143 | */ 144 | export function escapeHTML(str: string) { 145 | return str.replace( 146 | /[&<>'"]/g, 147 | tag => 148 | ({ 149 | '&': '&', 150 | '<': '<', 151 | '>': '>', 152 | "'": ''', 153 | '"': '"', 154 | }[tag] || tag), 155 | ); 156 | } 157 | 158 | /** 159 | * 移除空格 160 | * @param str - 字符串 161 | * @example ' Hello \nWorld ' => 'Hello World' 162 | */ 163 | export const removeWhitespace = (str: string) => str.replace(/\s+/g, ''); 164 | 165 | /** 166 | * 消息格式化 167 | * @param {string} message 字符串模板,如'format {0}', 168 | * @param {any[]} arr 模板数据,如['data'], 169 | * @returns {string} 消息格式化后的字符串 170 | * 171 | * @example 172 | * format('format {0}', ['123']); // 'format 123' 173 | * format('format {0} {1}', ['123']); // 'format 123 undefined' 174 | */ 175 | export function format(message: string, arr: any[]): string { 176 | return message.replace(/{(\d+)}/g, (matchStr, group1) => { 177 | return arr[group1]; 178 | }); 179 | } 180 | -------------------------------------------------------------------------------- /src/static/svg/weep.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uniapp-vue3-ts-template", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev:app": "uni -p app", 6 | "dev:app-android": "uni -p app-android", 7 | "dev:app-ios": "uni -p app-ios", 8 | "dev:custom": "uni -p", 9 | "dev:h5": "uni", 10 | "dev:h5:ssr": "uni --ssr", 11 | "dev:mp-alipay": "uni -p mp-alipay", 12 | "dev:mp-baidu": "uni -p mp-baidu", 13 | "dev:mp-kuaishou": "uni -p mp-kuaishou", 14 | "dev:mp-lark": "uni -p mp-lark", 15 | "dev:mp-qq": "uni -p mp-qq", 16 | "dev:mp-toutiao": "uni -p mp-toutiao", 17 | "dev:mp-weixin": "uni -p mp-weixin & npm run static", 18 | "dev:quickapp-webview": "uni -p quickapp-webview", 19 | "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei", 20 | "dev:quickapp-webview-union": "uni -p quickapp-webview-union", 21 | "build:app": "uni build -p app", 22 | "build:app-android": "uni build -p app-android", 23 | "build:app-ios": "uni build -p app-ios", 24 | "build:custom": "uni build -p", 25 | "build:h5": "uni build", 26 | "build:h5:ssr": "uni build --ssr", 27 | "build:mp-alipay": "uni build -p mp-alipay", 28 | "build:mp-baidu": "uni build -p mp-baidu", 29 | "build:mp-kuaishou": "uni build -p mp-kuaishou", 30 | "build:mp-lark": "uni build -p mp-lark", 31 | "build:mp-qq": "uni build -p mp-qq", 32 | "build:mp-toutiao": "uni build -p mp-toutiao", 33 | "build:mp-weixin": "uni build -p mp-weixin", 34 | "build:quickapp-webview": "uni build -p quickapp-webview", 35 | "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei", 36 | "build:quickapp-webview-union": "uni build -p quickapp-webview-union", 37 | "build:h5:staging": "uni build --mode staging", 38 | "build:app:staging": "uni build --mode staging -p app", 39 | "build:mp-weixin:staging": "uni build --mode staging -p mp-weixin", 40 | "prepare": "husky install", 41 | "lint": "eslint --cache --max-warnings 0 \"src/**/*.{vue,ts}\" --fix", 42 | "lint:fix": "eslint --ext .ts,tsx,vue src/** --no-error-on-unmatched-pattern --fix", 43 | "lint:staged": "lint-staged", 44 | "prettier": "prettier --write \"src/**/*.{js,ts,json,css,scss,vue}\"", 45 | "mock": "cd mock && ts-node-dev mock.ts", 46 | "cz": "cz", 47 | "commit": "git-cz", 48 | "static": "node scripts/static-serve.js" 49 | }, 50 | "dependencies": { 51 | "@dcloudio/uni-app": "3.0.0-3061520221228001", 52 | "@dcloudio/uni-app-plus": "3.0.0-3061520221228001", 53 | "@dcloudio/uni-components": "3.0.0-3061520221228001", 54 | "@dcloudio/uni-h5": "3.0.0-3061520221228001", 55 | "@dcloudio/uni-i18n": "^3.0.0-3061520221228001", 56 | "@dcloudio/uni-mp-alipay": "3.0.0-3061520221228001", 57 | "@dcloudio/uni-mp-baidu": "3.0.0-3061520221228001", 58 | "@dcloudio/uni-mp-kuaishou": "3.0.0-3061520221228001", 59 | "@dcloudio/uni-mp-lark": "3.0.0-3061520221228001", 60 | "@dcloudio/uni-mp-qq": "3.0.0-3061520221228001", 61 | "@dcloudio/uni-mp-toutiao": "3.0.0-3061520221228001", 62 | "@dcloudio/uni-mp-weixin": "3.0.0-3061520221228001", 63 | "@dcloudio/uni-quickapp-webview": "3.0.0-3061520221228001", 64 | "@dcloudio/uni-ui": "^1.4.22", 65 | "@fortawesome/fontawesome-free": "^6.2.0", 66 | "await-to-js": "^3.0.0", 67 | "crypto-js": "^4.1.1", 68 | "dayjs": "^1.11.6", 69 | "lodash-es": "^4.17.21", 70 | "luch-request": "^3.0.8", 71 | "pinia": "^2.0.25", 72 | "qs": "^6.11.0", 73 | "vue": "^3.2.45", 74 | "vue-demi": "^0.13.11", 75 | "vue-i18n": "^9.2.2" 76 | }, 77 | "devDependencies": { 78 | "@commitlint/cli": "^17.1.2", 79 | "@commitlint/config-conventional": "^17.1.0", 80 | "@dcasia/mini-program-tailwind-webpack-plugin": "^1.5.6", 81 | "@dcloudio/types": "^3.0.17", 82 | "@dcloudio/uni-automator": "3.0.0-3061520221228001", 83 | "@dcloudio/uni-cli-shared": "3.0.0-3061520221228001", 84 | "@dcloudio/uni-stacktracey": "3.0.0-3061520221228001", 85 | "@dcloudio/vite-plugin-uni": "3.0.0-3061520221228001", 86 | "@iconify-json/carbon": "^1.1.9", 87 | "@iconify-json/mdi": "^1.1.33", 88 | "@iconify/tools": "^2.1.2", 89 | "@iconify/utils": "^2.0.3", 90 | "@types/crypto-js": "^4.1.1", 91 | "@types/lodash-es": "^4.17.6", 92 | "@types/node": "^17.0.45", 93 | "@types/qs": "^6.9.7", 94 | "@typescript-eslint/eslint-plugin": "^5.40.0", 95 | "@typescript-eslint/parser": "^5.40.0", 96 | "@unocss/preset-mini": "^0.46.5", 97 | "autoprefixer": "^10.4.12", 98 | "commitizen": "^4.2.5", 99 | "commitlint-config-cz": "^0.13.3", 100 | "cz-conventional-changelog": "^3.3.0", 101 | "cz-customizable": "^7.0.0", 102 | "eslint": "^8.25.0", 103 | "eslint-config-prettier": "^8.5.0", 104 | "eslint-plugin-prettier": "^4.2.1", 105 | "eslint-plugin-vue": "^9.6.0", 106 | "express": "^4.18.2", 107 | "husky": "^8.0.1", 108 | "lint-staged": "^13.0.3", 109 | "mockjs": "^1.1.0", 110 | "mrm": "^4.1.6", 111 | "postcss": "^8.4.17", 112 | "prettier": "^2.7.1", 113 | "sass": "^1.55.0", 114 | "terser": "^5.15.1", 115 | "typescript": "^4.8.4", 116 | "unocss": "^0.46.5", 117 | "unocss-applet": "^0.2.19", 118 | "unplugin-auto-import": "^0.11.2", 119 | "unplugin-vue-components": "^0.22.8", 120 | "vite": "^3.2.4", 121 | "vite-plugin-eslint": "^1.8.1", 122 | "vite-plugin-mock": "^2.9.6", 123 | "vite-plugin-windicss": "^1.8.8", 124 | "windicss": "^3.5.6" 125 | }, 126 | "config": { 127 | "commitizen": { 128 | "path": "node_modules/cz-customizable" 129 | } 130 | }, 131 | "lint-staged": { 132 | "src/**/*.{js,jsx,vue,ts,tsx}": [ 133 | "eslint" 134 | ] 135 | } 136 | } 137 | --------------------------------------------------------------------------------