feat: add a new feature

This commit is contained in:
KuroSago 2024-06-21 09:18:53 +08:00
parent 2b80695c2d
commit 1ffc4237ca
16 changed files with 358 additions and 177 deletions

View File

@ -4,7 +4,23 @@
{
"recommendations": [
"Vue.volar", // Vue
"afzalsayed96.icones",
"antfu.iconify",
"antfu.unocss",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"formulahendry.auto-close-tag",
"formulahendry.auto-complete-tag",
"formulahendry.auto-rename-tag",
"lokalise.i18n-ally",
"mhutchie.git-graph",
"mikestead.dotenv",
"naumovs.color-highlight",
"pkief.material-icon-theme",
"sdras.vue-vscode-snippets",
"whtouche.vscode-js-console-utils",
"zhuangtongfa.material-theme",
"Vue.vscode-typescript-vue-plugin", // TS使TS*.vue
"esbenp.prettier-vscode", //
"dbaeumer.vscode-eslint" //

242
.vscode/settings.json vendored
View File

@ -1,73 +1,185 @@
////////////////////////////////////////////////////////////////////////////
// vscode ////////////////////////////////////////////////
// https://blog.csdn.net/weixin_46238462/article/details/125867532 //
//////////////////////////////////////////////////////////////////////////
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.detectIndentation": false,
"editor.tabSize": 4,
"files.eol": "auto",
"eslint.enable": true,
//////////////////////////////////////////////////////////////////
// https://prettier.io/docs/en/options.html#tab-width //
////////////////////////////////////////////////////////////////
"prettier.semi": true,
"prettier.tabWidth": 4,
"prettier.printWidth": 160,
"prettier.endOfLine": "auto",
"prettier.singleQuote": true,
"prettier.ignorePath": ".prettierignore",
// Enable the ESlint flat config support
// (remove this if your ESLint extension above v3.0.5)
"eslint.experimental.useFlatConfig": true,
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
// "editor.codeActionsOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off" },
{ "rule": "format/*", "severity": "off" },
{ "rule": "*-indent", "severity": "off" },
{ "rule": "*-spacing", "severity": "off" },
{ "rule": "*-spaces", "severity": "off" },
{ "rule": "*-order", "severity": "off" },
{ "rule": "*-dangle", "severity": "off" },
{ "rule": "*-newline", "severity": "off" },
{ "rule": "*quotes", "severity": "off" },
{ "rule": "*semi", "severity": "off" }
"eslint.experimental.useFlatConfig": true,
"editor.formatOnSave": false,
"prettier.enable": false,
"unocss.root": ["./"],
"typescript.tsdk": "node_modules/typescript/lib",
"vue.server.hybridMode": true,
"eslint.validate": ["html", "css", "scss", "json", "jsonc"],
"i18n-ally.displayLanguage": "zh-cn",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.enabledFrameworks": ["vue"],
"i18n-ally.editor.preferEditor": true,
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": ["src/locales/langs"],
"editor.fontLigatures": true,
"editor.quickSuggestions": {
"strings": true
},
"editor.tabSize": 2,
"files.associations": {
"*.env.*": "dotenv",
"*.svg": "html"
},
// "files.refactoring.autoSave": false,
"files.eol": "\n",
"path-intellisense.mappings": {
"@": "${workspaceFolder}/src",
"~@": "${workspaceFolder}/src"
},
"terminal.integrated.fontSize": 14,
"terminal.integrated.fontWeight": 500,
"terminal.integrated.tabs.enabled": true,
// "workbench.iconTheme": "material-icon-theme",
"workbench.colorTheme": "Visual Studio Light",
"css.lint.unknownAtRules": "ignore",
"psi-header.config": {
"forceToTop": true,
"blankLinesAfter": 3,
"license": "Custom"
},
"psi-header.changes-tracking": {
"isActive": true,
"modAuthor": "Modified By: ",
"modDate": "Last Modified: ",
"modDateFormat": "date",
"include": [],
"exclude": ["markdown", "json"],
"excludeGlob": ["out/**", "src/**/*.xyz", "components/**/*.mk"],
"autoHeader": "manualSave"
},
"psi-header.license-text": ["May the force be with you."],
"psi-header.variables": [
["company", "self."]
// ["author", "你的名字 - 默认主机名"],
// ["authoremail", "xxxxx@gmail.com"],
// ["initials", "S.L"]
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"xml",
"gql",
"graphql",
"astro",
"css",
"less",
"scss",
"pcss",
"postcss"
"psi-header.lang-config": [
{
"language": "html",
"begin": "<!--",
"end": "-->",
"prefix": " "
},
{
"language": "vue",
"begin": "<!--",
"end": "-->",
"prefix": " "
},
{
"language": "lua",
"begin": "--[[",
"prefix": "--",
"end": "--]]",
"blankLinesAfter": 0
},
{
"language": "python",
"begin": "###",
"prefix": "# ",
"end": "###",
"blankLinesAfter": 0,
"beforeHeader": ["#!/usr/bin/env python", "# -*- coding:utf-8 -*-"]
},
{
"language": "javascript",
"begin": "/**",
"prefix": " * ",
"end": " */",
"blankLinesAfter": 0,
"forceToTop": true
},
{
"language": "typescript",
"mapTo": "javascript",
"begin": "/**",
"prefix": " * ",
"end": " */",
"blankLinesAfter": 0,
"forceToTop": true
}
],
"psi-header.templates": [
{
"language": "javascript",
"template": [
"## File: <<filerelativepath>>",
"",
"Project: <<projectname>>",
"",
"Created Date: <<filecreated('YYYY-MM-DD HH:mm:SS')>>",
"",
"Author: <<author>>",
"",
"## Last Modified: <<filecreated('YYYY-MM-DD HH:mm:SS')>>",
"",
"Modified By: ",
"",
"## Copyright (c) <<year>> <<company>>",
"",
"Use To: "
]
},
{
"language": "typescript",
"template": [
"## File: <<filerelativepath>>",
"",
"Project: <<projectname>>",
"",
"Created Date: <<filecreated('YYYY-MM-DD HH:mm:SS')>>",
"",
"Author: <<author>>",
"",
"## Last Modified: <<filecreated('YYYY-MM-DD HH:mm:SS')>>",
"",
"Modified By: ",
"",
"## Copyright (c) <<year>> <<company>>",
"",
"Use To: "
]
},
{
"language": "html",
"begin": "<!--",
"end": "-->",
"template": [
"## File: <<filerelativepath>>",
"",
"Project: <<projectname>>",
"",
"Created Date: <<filecreated('YYYY-MM-DD HH:mm:SS')>>",
"",
"Author: <<author>>",
"",
"## Last Modified: <<filecreated('YYYY-MM-DD HH:mm:SS')>>",
"",
"Modified By: ",
"",
"## Copyright (c) <<year>> <<company>>",
"",
"Use To: "
]
},
{
"language": "typescript",
"mapTo": "javascript"
},
{
"language": "tsx",
"mapTo": "javascript"
},
{
"language": "vue",
"mapTo": "html"
}
]
}

View File

@ -33,6 +33,19 @@
</a>
</p>
## appProvider 插件
- ./vite/plugins/appProvider.ts
- 本配置功能为扫描 @/pages/xx/index.vue 文件,通过覆盖一层 view 的方式注入 style 变量
- 相关文件 `./src/layout/AppProvider.vue`
## vite/plugins/build.unocss.config.ts - 构建 unocss 相关配置
- 本配置通过读取 ./src/theme/theme.ts 文件中相关配置构建 **theme 变量 , **theme 即 AppProvider.vue 文件的 style 变量
- 相关文件 `./src/theme/theme.ts`
`./src/stores/modules/system.ts`
`./src/mixins/theme.ts`
## 简介
- **uni-app Vue3 Vite4 pinia2 TypeScript 基础框架**

View File

@ -51,10 +51,14 @@
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-4010920240607001",
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-4010920240607001",
"@multiavatar/multiavatar": "^1.0.7",
"@unocss/transformer-directives": "^0.61.0",
"alova": "^2.21.3",
"crypto-js": "^4.2.0",
"lodash-es": "^4.17.21",
"normalize-path": "^3.0.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"unplugin-vue-components": "^0.27.0",
"vue": "^3.4.29"
},
"devDependencies": {
@ -68,6 +72,7 @@
"@types/crypto-js": "^4.2.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.14.2",
"@types/normalize-path": "^3.0.0",
"@unocss/eslint-plugin": "^0.61.0",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/runtime-core": "^3.4.29",

View File

@ -1,13 +1,13 @@
<script setup lang="ts">
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app';
import { useUserStore } from '@/stores/modules/user';
// import { useUserStore } from '@/stores/modules/user';
onLaunch(() => {
console.log('App Launch');
});
onShow(() => {
const userStore = useUserStore();
userStore.initUserInfo();
// const userStore = useUserStore();
// userStore.initUserInfo();
console.log('App Show');
});
onHide(() => {

View File

@ -2,14 +2,14 @@
"version": "1",
"prompt": "template",
"title": "服务协议和隐私政策",
"message": "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>了解详细信息。如果你同意请点击下面按钮开始接受我们的服务。",
"message": "__请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款, 包括但不限于:为了更好的向你提供服务, 我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>__你可阅读<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>了解详细信息。如果你同意, 请点击下面按钮开始接受我们的服务。",
"buttonAccept": "同意并接受",
"buttonRefuse": "暂不同意",
// HX 3.4.13system 使webview 使uni-appweb
// HX 3.4.13, system 使webview , 使uni-appweb
"hrefLoader": "default",
"second": {
"title": "确认提示",
"message": "  进入应用前,你需先同意<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>否则将退出应用。",
"message": "__进入应用前, 你需先同意<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>, 否则将退出应用。",
"buttonAccept": "同意并继续",
"buttonRefuse": "退出应用"
},

View File

@ -0,0 +1,7 @@
/**
* @description: api
*/
export enum ClientApiResultEnum {
CLIENT_SUCCESS = 1,
CLIENT_ERROR = 0,
}

View File

@ -2,7 +2,7 @@
* @description:
*/
export enum ResultEnum {
SUCCESS = 10000,
SUCCESS = 200,
FAIL = 10001,
ERROR = 1,
TIMEOUT = 401,

View File

@ -3,12 +3,16 @@ import App from './App.vue';
import 'virtual:uno.css';
import { setupRouter } from './router';
import { setupStore } from './stores';
import { themeMixin } from '@/mixins/theme';
export function createApp() {
const app = createSSRApp(App);
setupStore(app);
//
app.mixin(themeMixin);
setupRouter(app);
return {

View File

@ -4,7 +4,7 @@ import { useUserStore } from '@/stores/modules/user';
const router = useRouter();
const userStore = useUserStore();
const { loggedIn, userInfo } = storeToRefs(userStore);
const { loginOrNot, userInfo } = storeToRefs(userStore);
function handleJump(url: string) {
router.push(url);
@ -17,8 +17,8 @@ function handleLoginOut() {
</script>
<template>
<view class="text-md pt-36">
<view v-if="loggedIn" class="text-center">
<view class="text-md pt-36 bg-primary">
<view v-if="loginOrNot" class="text-center">
<image class="h-56 w-56" :src="userInfo?.avatar" />
<view class="mt-2">
{{ userInfo?.nickname }}
@ -28,7 +28,7 @@ function handleLoginOut() {
<BasicButton @click="handleJump('/pages/log/index?id=4345&title=log&word=关键词')">
log
</BasicButton>
<BasicButton v-if="loggedIn" @click="handleLoginOut">
<BasicButton v-if="loginOrNot" @click="handleLoginOut">
登出
</BasicButton>
<BasicButton v-else @click="handleJump('/pages/login/index')">

View File

@ -1,5 +1,4 @@
import type { Router } from 'uni-mini-router/lib/interfaces';
import { isLogin } from '@/utils/auth';
export function createRouterGuard(router: Router) {
createBeforeEachGuard(router);
@ -9,7 +8,7 @@ export function createRouterGuard(router: Router) {
function createBeforeEachGuard(router: Router) {
router.beforeEach((to, _, next) => {
console.log('beforeEach', to);
const _isLogin = isLogin();
const _isLogin = true;
if (to && to?.meta?.ignoreAuth) {
// 如果目标路由忽略验证直接跳转
next();
@ -31,7 +30,7 @@ function createAfterEachGuard(router: Router) {
router.afterEach((to) => {
if (to && to?.meta?.ignoreAuth)
return;
const _isLogin = isLogin();
const _isLogin = true;
if (!_isLogin && to && to.name !== 'Login') {
// 如果没有登录且目标路由不是登录页面则跳转到登录页面
router.push({ name: 'Login', params: { ...to.query } });

View File

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

View File

@ -3,67 +3,75 @@ import { useRequest } from 'alova';
import { getUserInfoApi } from '@/services/api/user';
import type { UserInfoModel } from '@/services/model/userModel';
import { login as loginApi } from '@/services/api/auth';
import { getToken, isLogin, setToken } from '@/utils/auth';
import { removeCache } from '@/utils/cache';
import { TOKEN_KEY } from '@/enums/cacheEnum';
export const useUserStore = defineStore('UserStore', () => {
const token = ref<string | null>(null);
const userInfo = ref<UserInfoModel | null>(null);
interface UserStore {
token: string | null
userInfo: UserInfoModel | null
}
// 初始化
function initUserInfo() {
if (isLogin()) {
token.value = getToken();
getUserInfo();
}
}
export const useUserStore = defineStore({
id: 'UserStore',
state: (): UserStore => ({
token: null,
userInfo: null,
}),
getters: {
loginOrNot: state => !!state.token,
},
actions: {
// 是否登录
const loggedIn = computed(() => !!token.value);
// setToken
setToken(token: string) {
this.token = token;
},
// 登录
const { send: sendLogin } = useRequest(loginApi, { immediate: false });
async function login(params: LoginParams) {
try {
const res = await sendLogin(params);
token.value = res.token;
setToken(res.token);
await getUserInfo();
} catch (error) {
throw error;
}
}
// 登录
async login(params: LoginParams) {
const { send: sendLogin } = useRequest(loginApi, { immediate: false });
try {
const res = await sendLogin(params);
this.token = res.token;
this.setToken(res.token);
await this.getUserInfo();
} catch (error) {
throw error;
}
},
// 获取用户信息
const { send: _getUserInfo } = useRequest(getUserInfoApi, { initialData: null, immediate: false });
async function getUserInfo() {
try {
userInfo.value = await _getUserInfo();
} catch (error) {
throw error;
}
}
// 获取用户信息
async getUserInfo() {
const { send: _getUserInfo } = useRequest(getUserInfoApi, { initialData: null, immediate: false });
try {
this.userInfo = await _getUserInfo();
} catch (error) {
throw error;
}
},
// 登出
// const { send: sendLogout } = useRequest(logoutApi, { immediate: false });
async function logout() {
try {
// 初始化
async initUserInfo() {
//
},
// 登出
async logout() {
// const { send: sendLogout } = useRequest(logoutApi, { immediate: false });
try {
// await sendLogout();
removeCache(TOKEN_KEY);
userInfo.value = null;
token.value = null;
} catch (err: any) {
throw err;
}
}
this.userInfo = null;
this.token = null;
} catch (err: any) {
throw err;
}
},
},
persist: {
storage: {
setItem: uni.setStorageSync,
getItem: uni.getStorageSync,
},
paths: ['token', 'userInfo'],
},
return {
userInfo,
loggedIn,
login,
logout,
getUserInfo,
initUserInfo,
};
});

View File

@ -6,7 +6,10 @@ import { getBaseUrl, isUseMock } from '@/utils/env';
import { mockAdapter } from '@/mock';
import { ContentTypeEnum, ResultEnum } from '@/enums/httpEnum';
import type { API } from '@/services/model/baseModel';
import { getAuthorization } from '@/utils/auth';
function getAuthorization() {
return '1';
}
const BASE_URL = getBaseUrl();

View File

@ -1,59 +1,67 @@
{ //
"compilerOptions": { //
"target": "ESNext", // export=import from
"lib": ["ESNext", "DOM", "ScriptHost"], // JavaScript
"emitDecoratorMetadata": true, // " . “(obj.key) 语法访问字段和"( obj[key])
"compilerOptions": {
"target": "ESNext", //
"jsx": "preserve",
"jsxImportSource": "vue",
"lib": ["ESNext", "DOM", "ScriptHost"], // TS
"emitDecoratorMetadata": true, //
/* */
"experimentalDecorators": true,
"baseUrl": "./", //
"rootDir": "./src", //
"experimentalDecorators": true, // JavaScript
"baseUrl": ".", //
"module": "ESNext", //
"moduleResolution": "node", //
"paths": { "@/*": ["./src/*"] }, //
"resolveJsonModule": true, // TS
"types": ["@types/node"], //
/* */
"strict": true, //
"moduleResolution": "node", // TypeScript
//
"paths": {
"~/*": ["./*"],
"@/*": ["./src/*"]
},
"resolveJsonModule": true, // JSON
"types": ["vite/client", "node"], //
// "sourceMap": true, //JavaScript
// "declaration": true, // TypeScriptJavaScript.d.ts
// "declarationMap": true, // d.ts
/* */
"strict": true, //
"strictBindCallApply": true, // bindcallapply
"strictFunctionTypes": true, // thisany
"strictNullChecks": true, //
"strictPropertyInitialization": true, //
"alwaysStrict": true, //
"noFallthroughCasesInSwitch": true, // 'use strict'
"noImplicitAny": true, //
"noImplicitOverride": true, // 使
"noImplicitReturns": true, // any
"noImplicitThis": true, //
"noPropertyAccessFromIndexSignature": false, // switchcase使break
"noUncheckedIndexedAccess": true, //
"strictFunctionTypes": true, //
"strictNullChecks": true, // nullundefined
"strictPropertyInitialization": true, //
"alwaysStrict": true, // 'use strict'
"noFallthroughCasesInSwitch": true, // switchcase使break
"noImplicitAny": false, // any
"noImplicitOverride": true, //
"noImplicitReturns": true, //
"noImplicitThis": true, // thisany
"noPropertyAccessFromIndexSignature": false, // " . “(obj.key) 语法访问字段和"( obj[key])
"noUncheckedIndexedAccess": true, //
/* */
"noUnusedLocals": true, // 使
"noUnusedParameters": true, // 使
"newLine": "crlf", // TS
"noEmitOnError": true, // nullundefined
"outDir": "./dist", //
"removeComments": true, // TypeScript
"esModuleInterop": true, //
"pretty": true, //
"noUnusedParameters": true, // 使
"newLine": "crlf", //
"noEmitOnError": true, //
"removeComments": true, //
// "incremental": true //
"allowSyntheticDefaultImports": true,
"esModuleInterop": true, // export=import from
/* */
"forceConsistentCasingInFileNames": true, //
"extendedDiagnostics": false // JSON
// "incremental": true //
"isolatedModules": true,
// "rootDir": "./src", //
// "outDir": "./dist", //
// "extendedDiagnostics": true, // TS
"pretty": true // 使
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"vite.config.*",
"typings/*.d.ts"
"typings/*.ts",
"typings/*.d.ts",
"./**/*.ts",
"./**/**/*.ts",
"./**/*.tsx",
"./**/*.vue"
], //
"exclude": []
"exclude": ["node_modules", "dist"]
}

View File

@ -14,6 +14,7 @@ import transformClass from 'unplugin-transform-class/vite';
import { visualizer } from 'rollup-plugin-visualizer';
import ViteRestart from 'vite-plugin-restart';
import AutoImport from 'unplugin-auto-import/vite';
import { appProvider, buildThemeConfig, createComponents } from './vite/plugins/index';
export default defineConfig(async ({ mode }) => {
const root = process.cwd();
@ -47,6 +48,9 @@ export default defineConfig(async ({ mode }) => {
},
},
plugins: [
appProvider(),
buildThemeConfig(),
createComponents(),
// @ts-expect-error TODO uni() 会报错uni is not a function,暂时使用此方式解决
uni?.default(),
AutoImport({