版本更新:

1. 修复:移动端头像剪切功能
2. 修复:系统展示头像展示时从固定静态文件改为接口数据
3. 更新:升级PC端vue-element-plus-admin版本到1.9.2
4. 更新:文件上传接口
5. 更新:依赖库更新,使用python10版本
6. 新增:kinit-api 命令创建app目录
This commit is contained in:
ktianc 2023-01-30 15:34:24 +08:00
parent a7283b70f8
commit 1426e118f2
16 changed files with 56 additions and 71 deletions

View File

@ -42,7 +42,7 @@
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.0.29", "pinia": "2.0.29",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",
"qs": "^6.11.0", "qs": "^6.11.0",
"url": "^0.11.0", "url": "^0.11.0",
@ -96,7 +96,6 @@
"stylelint-order": "^6.0.1", "stylelint-order": "^6.0.1",
"terser": "^5.16.1", "terser": "^5.16.1",
"typescript": "4.9.4", "typescript": "4.9.4",
"unplugin-vue-define-options": "^1.1.4",
"vite": "4.0.4", "vite": "4.0.4",
"vite-plugin-ejs": "^1.6.4", "vite-plugin-ejs": "^1.6.4",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",

View File

@ -50,7 +50,6 @@ watch(
) )
const dialogStyle = computed(() => { const dialogStyle = computed(() => {
console.log(unref(dialogHeight))
return { return {
height: unref(dialogHeight) height: unref(dialogHeight)
} }

View File

@ -4,6 +4,7 @@ import { useI18n } from '@/hooks/web/useI18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { useAuthStoreWithOut } from '@/store/modules/auth' import { useAuthStoreWithOut } from '@/store/modules/auth'
import avatar from '@/assets/imgs/avatar.jpg'
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
@ -46,7 +47,7 @@ const user = authStore.getUser
<ElDropdown :class="prefixCls" trigger="click"> <ElDropdown :class="prefixCls" trigger="click">
<div class="flex items-center"> <div class="flex items-center">
<img <img
src="@/assets/imgs/avatar.jpg" :src="user.avatar ? user.avatar : avatar"
alt="" alt=""
class="w-[calc(var(--logo-height)-25px)] rounded-[50%]" class="w-[calc(var(--logo-height)-25px)] rounded-[50%]"
/> />

View File

@ -3,6 +3,7 @@ const config: {
unauthorized_code: number | string unauthorized_code: number | string
default_headers: AxiosHeaders default_headers: AxiosHeaders
request_timeout: number request_timeout: number
token: string
} = { } = {
/** /**
* *
@ -22,7 +23,13 @@ const config: {
* *
* application/x-www-form-urlencoded multipart/form-data * application/x-www-form-urlencoded multipart/form-data
*/ */
default_headers: 'application/json' default_headers: 'application/json',
/**
* Token字段
* config/axios/service/service.interceptors
*/
token: 'Token'
} }
export { config } export { config }

View File

@ -1,15 +1,12 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { useCache } from '@/hooks/web/useCache' import { useCache } from '@/hooks/web/useCache'
import { useAppStore } from '@/store/modules/app'
import { useAuthStore } from '@/store/modules/auth'
import qs from 'qs' import qs from 'qs'
import { config } from './config' import { config } from './config'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useAuthStore } from '@/store/modules/auth'
const { result_code, unauthorized_code, request_timeout } = config const { result_code, unauthorized_code, request_timeout, token } = config
const appStore = useAppStore()
const authStore = useAuthStore()
const { wsCache } = useCache() const { wsCache } = useCache()
// 创建axios实例 // 创建axios实例
@ -21,10 +18,10 @@ const service: AxiosInstance = axios.create({
// request拦截器 // request拦截器
service.interceptors.request.use( service.interceptors.request.use(
(config: AxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
const token = wsCache.get(appStore.getToken) const _token = wsCache.get(token)
if (token !== '') { if (_token !== '') {
;(config.headers as any)['Authorization'] = token // 让每个请求携带自定义token 请根据实际情况自行修改 ;(config.headers as any)['Authorization'] = _token // 让每个请求携带自定义token 请根据实际情况自行修改
} }
if ( if (
config.method === 'post' && config.method === 'post' &&
@ -66,6 +63,7 @@ service.interceptors.response.use(
} else if (response.data.code === unauthorized_code) { } else if (response.data.code === unauthorized_code) {
// 请重新登录 // 请重新登录
ElMessage.error(response.data.message) ElMessage.error(response.data.message)
const authStore = useAuthStore()
authStore.logout() authStore.logout()
} else { } else {
ElMessage.error(response.data.message) ElMessage.error(response.data.message)
@ -75,11 +73,11 @@ service.interceptors.response.use(
console.log('err' + error) console.log('err' + error)
let { message } = error let { message } = error
if (message == 'Network Error') { if (message == 'Network Error') {
message = '后端接口连接异常' message = '系统接口连接异常'
} else if (message.includes('timeout')) { } else if (message.includes('timeout')) {
message = '系统接口请求超时' message = '系统接口请求超时'
} else if (message.includes('Request failed with status code')) { } else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常' message = '系统接口状态码异常'
} }
ElMessage.error(message) ElMessage.error(message)
return Promise.reject(error) return Promise.reject(error)

View File

@ -3,16 +3,16 @@ import { useI18n } from '@/hooks/web/useI18n'
import { useCache } from '@/hooks/web/useCache' import { useCache } from '@/hooks/web/useCache'
import { intersection } from 'lodash-es' import { intersection } from 'lodash-es'
import { isArray } from '@/utils/is' import { isArray } from '@/utils/is'
import { useAppStoreWithOut } from '@/store/modules/app' import { useAuthStoreWithOut } from '@/store/modules/auth'
const { t } = useI18n() const { t } = useI18n()
const { wsCache } = useCache() const { wsCache } = useCache()
const appStore = useAppStoreWithOut() const authStore = useAuthStoreWithOut()
// 全部权限 // 全部权限
const all_permission = ['*.*.*'] const all_permission = ['*.*.*']
const hasPermission = (value: string | string[]): boolean => { const hasPermission = (value: string | string[]): boolean => {
const permissions = wsCache.get(appStore.getUserInfo).permissions as string[] const permissions = wsCache.get(authStore.getUserInfo).permissions as string[]
if (!value) { if (!value) {
throw new Error(t('permission.hasPermission')) throw new Error(t('permission.hasPermission'))
} }

View File

@ -1,5 +1,4 @@
import router from './router' import router from './router'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useCache } from '@/hooks/web/useCache' import { useCache } from '@/hooks/web/useCache'
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import { useTitle } from '@/hooks/web/useTitle' import { useTitle } from '@/hooks/web/useTitle'
@ -11,7 +10,6 @@ import { useAuthStoreWithOut } from '@/store/modules/auth'
const permissionStore = usePermissionStoreWithOut() const permissionStore = usePermissionStoreWithOut()
const appStore = useAppStoreWithOut()
const authStore = useAuthStoreWithOut() const authStore = useAuthStoreWithOut()
const { wsCache } = useCache() const { wsCache } = useCache()
@ -25,14 +23,14 @@ const whiteList = ['/login', '/docs/privacy', '/docs/agreement'] // 不重定向
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
start() start()
loadStart() loadStart()
if (wsCache.get(appStore.getUserInfo)) { if (wsCache.get(authStore.getUserInfo)) {
if (to.path === '/login') { if (to.path === '/login') {
next({ path: '/' }) next({ path: '/' })
} else if (to.path === '/reset/password') { } else if (to.path === '/reset/password') {
next() next()
} else { } else {
if (!authStore.getIsUser) { if (!authStore.getIsUser) {
await authStore.getUserInfo() await authStore.getUserInfoAction()
} }
if (permissionStore.getIsAddRouters) { if (permissionStore.getIsAddRouters) {
next() next()

View File

@ -27,7 +27,6 @@ interface AppState {
pageLoading: boolean pageLoading: boolean
layout: LayoutType layout: LayoutType
title: string title: string
userInfo: string
isDark: boolean isDark: boolean
currentSize: ElementPlusSize currentSize: ElementPlusSize
sizeMap: ElementPlusSize[] sizeMap: ElementPlusSize[]
@ -38,14 +37,11 @@ interface AppState {
logoImage: string logoImage: string
footerContent: string footerContent: string
icpNumber: string icpNumber: string
token: string
} }
export const useAppStore = defineStore('app', { export const useAppStore = defineStore('app', {
state: (): AppState => { state: (): AppState => {
return { return {
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
token: 'Token', // 存储Token字段
sizeMap: ['default', 'large', 'small'], sizeMap: ['default', 'large', 'small'],
mobile: false, // 是否是移动端 mobile: false, // 是否是移动端
title: import.meta.env.VITE_APP_TITLE, // 标题 title: import.meta.env.VITE_APP_TITLE, // 标题
@ -164,12 +160,6 @@ export const useAppStore = defineStore('app', {
getTitle(): string { getTitle(): string {
return this.title return this.title
}, },
getUserInfo(): string {
return this.userInfo
},
getToken(): string {
return this.token
},
getIsDark(): boolean { getIsDark(): boolean {
return this.isDark return this.isDark
}, },

View File

@ -2,15 +2,15 @@ import { defineStore } from 'pinia'
import { store } from '../index' import { store } from '../index'
import { UserLoginType } from '@/api/login/types' import { UserLoginType } from '@/api/login/types'
import { loginApi } from '@/api/login' import { loginApi } from '@/api/login'
import { useAppStore } from '@/store/modules/app'
import { useCache } from '@/hooks/web/useCache' import { useCache } from '@/hooks/web/useCache'
import { getCurrentUserInfo } from '@/api/vadmin/auth/user' import { getCurrentUserInfo } from '@/api/vadmin/auth/user'
import { resetRouter } from '@/router' import { resetRouter } from '@/router'
import { config } from '@/config/axios/config'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
const appStore = useAppStore() const { token } = config
const { wsCache } = useCache() const { wsCache } = useCache()
const tagsViewStore = useTagsViewStore()
export interface UserState { export interface UserState {
id?: number id?: number
@ -24,6 +24,7 @@ export interface UserState {
} }
export interface AuthState { export interface AuthState {
userInfo: string
user: UserState user: UserState
isUser: boolean isUser: boolean
} }
@ -31,6 +32,7 @@ export interface AuthState {
export const useAuthStore = defineStore('auth', { export const useAuthStore = defineStore('auth', {
state: (): AuthState => { state: (): AuthState => {
return { return {
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
user: {}, user: {},
isUser: false isUser: false
} }
@ -41,6 +43,9 @@ export const useAuthStore = defineStore('auth', {
}, },
getIsUser(): boolean { getIsUser(): boolean {
return this.isUser return this.isUser
},
getUserInfo(): string {
return this.userInfo
} }
}, },
actions: { actions: {
@ -48,10 +53,9 @@ export const useAuthStore = defineStore('auth', {
formData.platform = '0' formData.platform = '0'
const res = await loginApi(formData) const res = await loginApi(formData)
if (res) { if (res) {
wsCache.set(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`) wsCache.set(token, `${res.data.token_type} ${res.data.access_token}`)
// 存储用户信息 // 存储用户信息
const auth = useAuthStore() await this.getUserInfoAction()
await auth.getUserInfo()
} }
return res return res
}, },
@ -59,6 +63,7 @@ export const useAuthStore = defineStore('auth', {
wsCache.clear() wsCache.clear()
this.user = {} this.user = {}
this.isUser = false this.isUser = false
const tagsViewStore = useTagsViewStore()
tagsViewStore.delAllViews() tagsViewStore.delAllViews()
resetRouter() resetRouter()
window.location.href = '/login' window.location.href = '/login'
@ -68,11 +73,11 @@ export const useAuthStore = defineStore('auth', {
this.user.name = data.name this.user.name = data.name
this.user.nickname = data.nickname this.user.nickname = data.nickname
this.user.telephone = data.telephone this.user.telephone = data.telephone
wsCache.set(appStore.getUserInfo, this.user) wsCache.set(this.userInfo, this.user)
}, },
async getUserInfo() { async getUserInfoAction() {
const res = await getCurrentUserInfo() const res = await getCurrentUserInfo()
wsCache.set(appStore.getUserInfo, res.data) wsCache.set(this.userInfo, res.data)
this.isUser = true this.isUser = true
this.user = res.data this.user = res.data
} }

View File

@ -6,6 +6,7 @@ import { ref, reactive } from 'vue'
import { CountTo } from '@/components/CountTo' import { CountTo } from '@/components/CountTo'
import { formatTime } from '@/utils' import { formatTime } from '@/utils'
import { Highlight } from '@/components/Highlight' import { Highlight } from '@/components/Highlight'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { import {
getCountApi, getCountApi,
getProjectApi, getProjectApi,
@ -21,7 +22,7 @@ import type {
Shortcuts Shortcuts
} from '@/api/dashboard/workplace/types' } from '@/api/dashboard/workplace/types'
import { useCache } from '@/hooks/web/useCache' import { useCache } from '@/hooks/web/useCache'
import { useAppStoreWithOut } from '@/store/modules/app' import avatar from '@/assets/imgs/avatar.jpg'
const { wsCache } = useCache() const { wsCache } = useCache()
@ -96,9 +97,9 @@ getAllApi()
const { t } = useI18n() const { t } = useI18n()
const appStore = useAppStoreWithOut() const authStore = useAuthStoreWithOut()
const user = wsCache.get(appStore.getUserInfo) const user = wsCache.get(authStore.getUserInfo)
</script> </script>
<template> <template>
@ -109,7 +110,7 @@ const user = wsCache.get(appStore.getUserInfo)
<ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24"> <ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center"> <div class="flex items-center">
<img <img
src="@/assets/imgs/avatar.jpg" :src="user.avatar ? user.avatar : avatar"
alt="" alt=""
class="w-70px h-70px rounded-[50%] mr-20px" class="w-70px h-70px rounded-[50%] mr-20px"
/> />

View File

@ -137,7 +137,7 @@ const signIn = async () => {
// //
push({ path: '/reset/password' }) push({ path: '/reset/password' })
} else { } else {
// 使 //
getMenu() getMenu()
} }
} else { } else {

View File

@ -10,6 +10,7 @@ import type { UploadProps } from 'element-plus'
import { useCache } from '@/hooks/web/useCache' import { useCache } from '@/hooks/web/useCache'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { config } from '@/config/axios/config'
const props = defineProps({ const props = defineProps({
tabId: propTypes.number tabId: propTypes.number
@ -79,8 +80,9 @@ const getData = async () => {
const appStore = useAppStore() const appStore = useAppStore()
const { wsCache } = useCache() const { wsCache } = useCache()
const loading = ref(false) const loading = ref(false)
const { token } = config
const token = wsCache.get(appStore.getToken) const _token = wsCache.get(token)
const save = async () => { const save = async () => {
const formRef = unref(elFormRef) const formRef = unref(elFormRef)
@ -120,7 +122,7 @@ getData()
:on-success="handleLogoUploadSuccess" :on-success="handleLogoUploadSuccess"
accept="image/jpeg,image/gif,image/png" accept="image/jpeg,image/gif,image/png"
name="file" name="file"
:headers="{ Authorization: token }" :headers="{ Authorization: _token }"
> >
<img v-if="form.web_logo" :src="form.web_logo" class="logo-image" /> <img v-if="form.web_logo" :src="form.web_logo" class="logo-image" />
<ElIcon v-else class="logo-image-uploader-icon" <ElIcon v-else class="logo-image-uploader-icon"
@ -139,7 +141,7 @@ getData()
:on-success="handleICOUploadSuccess" :on-success="handleICOUploadSuccess"
accept="image/x-icon" accept="image/x-icon"
name="file" name="file"
:headers="{ Authorization: token }" :headers="{ Authorization: _token }"
> >
<img v-if="form.web_ico" :src="form.web_ico" class="ico-image" /> <img v-if="form.web_ico" :src="form.web_ico" class="ico-image" />
<ElIcon v-else class="ico-image-uploader-icon" <ElIcon v-else class="ico-image-uploader-icon"

View File

@ -29,8 +29,7 @@
"element-plus/global", "element-plus/global",
"@types/intro.js", "@types/intro.js",
"@types/qrcode", "@types/qrcode",
"vite-plugin-svg-icons/client", "vite-plugin-svg-icons/client"
"unplugin-vue-define-options/macros-global"
], ],
"typeRoots": ["./node_modules/@types/", "./types"] "typeRoots": ["./node_modules/@types/", "./types"]
}, },

View File

@ -9,7 +9,6 @@ import EslintPlugin from 'vite-plugin-eslint'
import PurgeIcons from 'vite-plugin-purge-icons' import PurgeIcons from 'vite-plugin-purge-icons'
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite" import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import DefineOptions from "unplugin-vue-define-options/vite"
import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import' import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
import { ViteEjsPlugin } from "vite-plugin-ejs" import { ViteEjsPlugin } from "vite-plugin-ejs"
@ -60,18 +59,6 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
svgoOptions: true svgoOptions: true
}), }),
PurgeIcons(), PurgeIcons(),
// viteMockServe({
// ignore: /^\_/,
// mockPath: 'mock',
// localEnabled: !isBuild,
// prodEnabled: isBuild,
// injectCode: `
// import { setupProdMockServer } from '../mock/_createProductionServer'
// setupProdMockServer()
// `
// }),
DefineOptions(),
ViteEjsPlugin({ ViteEjsPlugin({
title: env.VITE_APP_TITLE title: env.VITE_APP_TITLE
}) })
@ -102,7 +89,6 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
minify: 'terser', minify: 'terser',
outDir: env.VITE_OUT_DIR || 'dist', outDir: env.VITE_OUT_DIR || 'dist',
sourcemap: env.VITE_SOURCEMAP === 'true' ? 'inline' : false, sourcemap: env.VITE_SOURCEMAP === 'true' ? 'inline' : false,
// brotliSize: false,
terserOptions: { terserOptions: {
compress: { compress: {
drop_debugger: env.VITE_DROP_DEBUGGER === 'true', drop_debugger: env.VITE_DROP_DEBUGGER === 'true',

View File

@ -11,13 +11,13 @@ from fastapi.security import OAuth2PasswordBearer
""" """
系统版本 系统版本
""" """
VERSION = "1.4.3" VERSION = "1.5.0"
"""安全警告: 不要在生产中打开调试运行!""" """安全警告: 不要在生产中打开调试运行!"""
DEBUG = True DEBUG = True
"""是否开启演示功能取消所有POST,DELETE,PUT操作权限""" """是否开启演示功能取消所有POST,DELETE,PUT操作权限"""
DEMO = False DEMO = not DEBUG
"""演示功能白名单""" """演示功能白名单"""
DEMO_WHITE_LIST_PATH = [ DEMO_WHITE_LIST_PATH = [
"/auth/login/", "/auth/login/",
@ -105,7 +105,7 @@ EVENTS = [
# 默认密码,"0" 默认为手机号后六位 # 默认密码,"0" 默认为手机号后六位
DEFAULT_PASSWORD = "0" DEFAULT_PASSWORD = "0"
# 是否开启保存登录日志 # 是否开启保存登录日志
LOGIN_LOG_RECORD = True LOGIN_LOG_RECORD = not DEBUG
# 是否开启保存每次请求日志到本地 # 是否开启保存每次请求日志到本地
REQUEST_LOG_RECORD = True REQUEST_LOG_RECORD = True
# 是否开启每次操作日志记录到MongoDB数据库 # 是否开启每次操作日志记录到MongoDB数据库