版本升级:

1. 修复(kinit-admin):页面缓存问题修复
2. 更新(kinit-api,kinit-admin):菜单管理新增是否缓存字段
3. 更新(kinit-admin):将缓存默认存储在localStorage中
4. 更新(kinit-api):将python-jose库更换为pyjwt库
5. 优化(kinit-admin,kinit-uni):退出登录方法优化
6. 优化(kinit-admin,kinit-uni):response拦截优化
7. 新增(kinit-api,kinit-admin,kinit-uni):jwt到期时间缩短,加入刷新token功能
8. (kinit-uni)切换到 vscode 开发 uniapp 项目
This commit is contained in:
ktianc 2023-03-13 14:34:26 +08:00
parent 06012fda6e
commit ff56a184ca
81 changed files with 2950 additions and 2508 deletions

View File

@ -5,6 +5,7 @@ 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 request from '@/config/axios'
const { result_code, unauthorized_code, request_timeout } = config const { result_code, unauthorized_code, request_timeout } = config
@ -62,33 +63,99 @@ service.interceptors.request.use(
// response 拦截器 // response 拦截器
service.interceptors.response.use( service.interceptors.response.use(
(response: AxiosResponse<any>) => { (response: AxiosResponse<any>) => {
// 这个状态码是和后端约定好的
const code = response.data.code || unauthorized_code
const message = response.data.message || '后端接口无返回内容'
const refresh = response.data.refresh || false
if (response.config.responseType === 'blob') { if (response.config.responseType === 'blob') {
// 如果是文件流,直接过 // 如果是文件流,直接过
return response return response
} else if (response.data.code === result_code) { } else if (code === result_code) {
if (refresh) {
// 因token快过期刷新token
refreshToken().then((res) => {
const appStore = useAppStore()
wsCache.set(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
wsCache.set(appStore.getRefreshToken, res.data.refresh_token)
})
// .catch(() => {
// const authStore = useAuthStore()
// authStore.logout()
// ElMessage.error('未认证,请登录')
// })
}
return response.data return response.data
} else if (response.data.code === unauthorized_code) { } else if (code === unauthorized_code) {
// 请重新登录 // 因token无效token过期导致
ElMessage.error(response.data.message) refreshToken().then((res) => {
const authStore = useAuthStore() const appStore = useAppStore()
authStore.logout() wsCache.set(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
wsCache.set(appStore.getRefreshToken, res.data.refresh_token)
ElMessage.error('操作失败,请重试')
})
// .catch(() => {
// const authStore = useAuthStore()
// authStore.logout()
// ElMessage.error('未认证,请登录')
// })
} else { } else {
ElMessage.error(response.data.message) ElMessage.error(message)
} }
}, },
(error: AxiosError) => { (error: AxiosError) => {
console.log('err' + error)
let { message } = error let { message } = error
if (message == 'Network Error') { const status = error.response?.status
message = '后端接口连接异常' switch (status) {
} else if (message.includes('timeout')) { case 400:
message = '系统接口请求超时' message = '请求错误'
} else if (message.includes('Request failed with status code')) { break
message = '系统接口' + message.substr(message.length - 3) + '异常' case 401:
// 强制要求重新登录因账号已冻结账号已过期手机号码错误刷新token无效等问题导致
const authStore = useAuthStore()
authStore.logout()
message = '未认证,请登录'
break
case 403:
message = '拒绝访问'
break
case 404:
message = `请求地址出错: ${error.response?.config.url}`
break
case 408:
message = '请求超时'
break
case 500:
message = '服务器内部错误'
break
case 501:
message = '服务未实现'
break
case 502:
message = '网关错误'
break
case 503:
message = '服务不可用'
break
case 504:
message = '网关超时'
break
case 505:
message = 'HTTP版本不受支持'
break
default:
break
} }
ElMessage.error(message) ElMessage.error(message)
return Promise.reject(error) return Promise.reject(error)
} }
) )
// 刷新Token
const refreshToken = (): Promise<IResponse> => {
const appStore = useAppStore()
const data = wsCache.get(appStore.getRefreshToken)
return request.post({ url: '/auth/token/refresh/', data })
}
export { service } export { service }

View File

@ -1,12 +1,14 @@
/** /**
* *
* sessionStorage
* localStorage
*/ */
import WebStorageCache from 'web-storage-cache' import WebStorageCache from 'web-storage-cache'
type CacheType = 'sessionStorage' | 'localStorage' type CacheType = 'sessionStorage' | 'localStorage'
export const useCache = (type: CacheType = 'sessionStorage') => { export const useCache = (type: CacheType = 'localStorage') => {
const wsCache: WebStorageCache = new WebStorageCache({ const wsCache: WebStorageCache = new WebStorageCache({
storage: type storage: type
}) })

View File

@ -39,6 +39,7 @@ interface AppState {
footerContent: string footerContent: string
icpNumber: string icpNumber: string
token: string token: string
refreshToken: string
} }
export const useAppStore = defineStore('app', { export const useAppStore = defineStore('app', {
@ -46,6 +47,7 @@ export const useAppStore = defineStore('app', {
return { return {
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突 userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
token: 'Token', // 存储Token字段 token: 'Token', // 存储Token字段
refreshToken: 'RefreshToken', // 存储刷新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, // 标题
@ -170,6 +172,9 @@ export const useAppStore = defineStore('app', {
getToken(): string { getToken(): string {
return this.token return this.token
}, },
getRefreshToken(): string {
return this.refreshToken
},
getIsDark(): boolean { getIsDark(): boolean {
return this.isDark return this.isDark
}, },

View File

@ -7,6 +7,7 @@ import { useCache } from '@/hooks/web/useCache'
import { getCurrentAdminUserInfo } from '@/api/vadmin/auth/user' import { getCurrentAdminUserInfo } from '@/api/vadmin/auth/user'
import { resetRouter } from '@/router' import { resetRouter } from '@/router'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import router from '@/router'
const { wsCache } = useCache() const { wsCache } = useCache()
@ -48,6 +49,7 @@ export const useAuthStore = defineStore('auth', {
if (res) { if (res) {
const appStore = useAppStore() const appStore = useAppStore()
wsCache.set(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`) wsCache.set(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
wsCache.set(appStore.getRefreshToken, res.data.refresh_token)
// 存储用户信息 // 存储用户信息
const auth = useAuthStore() const auth = useAuthStore()
await auth.getUserInfo() await auth.getUserInfo()
@ -61,7 +63,7 @@ export const useAuthStore = defineStore('auth', {
const tagsViewStore = useTagsViewStore() const tagsViewStore = useTagsViewStore()
tagsViewStore.delAllViews() tagsViewStore.delAllViews()
resetRouter() resetRouter()
window.location.href = '/login' router.push('/login')
}, },
updateUser(data: UserState) { updateUser(data: UserState) {
this.user.gender = data.gender this.user.gender = data.gender

View File

@ -24,6 +24,7 @@ const rules = reactive({
disabled: [required()], disabled: [required()],
hidden: [required()], hidden: [required()],
path: [required()], path: [required()],
noCache: [required()],
order: [required()] order: [required()]
}) })

View File

@ -44,6 +44,12 @@ export const columns = reactive<TableColumn[]>([
label: '组件路径', label: '组件路径',
show: true show: true
}, },
{
field: 'noCache',
label: '页面缓存',
width: '120px',
show: true
},
{ {
field: 'hidden', field: 'hidden',
label: '显示状态', label: '显示状态',
@ -224,5 +230,31 @@ export const schema = reactive<FormSchema[]>([
span: 12 span: 12
}, },
ifshow: (values) => values.menu_type === '2' ifshow: (values) => values.menu_type === '2'
},
{
field: 'noCache',
label: '页面缓存',
colProps: {
span: 12
},
component: 'Radio',
componentProps: {
style: {
width: '100%'
},
options: [
{
label: '缓存',
value: false
},
{
label: '不缓存',
value: true
}
]
},
value: false,
ifshow: (values) => values.menu_type === '1',
labelMessage: '开启页面缓存,需要组件名称必须与xx.vue页面的name一致'
} }
]) ])

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'AuthMenu'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { RightToolbar } from '@/components/RightToolbar' import { RightToolbar } from '@/components/RightToolbar'
@ -213,12 +219,16 @@ watch(
<ElSwitch :value="!row.hidden" disabled /> <ElSwitch :value="!row.hidden" disabled />
</template> </template>
<template #noCache="{ row }">
<ElSwitch :value="!row.noCache" disabled />
</template>
<template #disabled="{ row }"> <template #disabled="{ row }">
<ElSwitch :value="!row.disabled" disabled /> <ElSwitch :value="!row.disabled" disabled />
</template> </template>
</Table> </Table>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="700px"> <Dialog v-model="dialogVisible" :title="dialogTitle" width="800px">
<Write ref="writeRef" :current-row="tableObject.currentRow" :parent-id="parentId" /> <Write ref="writeRef" :current-row="tableObject.currentRow" :parent-id="parentId" />
<template #footer> <template #footer>

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'AuthRole'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'AuthUser'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'HelpIssueForm'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { Form } from '@/components/Form' import { Form } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm' import { useForm } from '@/hooks/web/useForm'

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'HelpIssue'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'

View File

@ -118,7 +118,7 @@ export const searchSchema = reactive<FormSchema[]>([
}, },
{ {
field: 'platform', field: 'platform',
label: '展示平台', label: '登录平台',
component: 'Select', component: 'Select',
componentProps: { componentProps: {
style: { style: {

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'HelpIssue'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'SystemDictDetail'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'SystemDict'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'SystemRecordLogin'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'SystemRecordOperation'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import { Table } from '@/components/Table' import { Table } from '@/components/Table'

View File

@ -1,3 +1,9 @@
<script lang="ts">
export default {
name: 'SystemSettings'
}
</script>
<script setup lang="ts"> <script setup lang="ts">
import { ElTabs, ElTabPane } from 'element-plus' import { ElTabs, ElTabPane } from 'element-plus'
import { ref } from 'vue' import { ref } from 'vue'

View File

@ -11,7 +11,7 @@ from fastapi.security import OAuth2PasswordBearer
""" """
系统版本 系统版本
""" """
VERSION = "1.6.1" VERSION = "1.6.2"
"""安全警告: 不要在生产中打开调试运行!""" """安全警告: 不要在生产中打开调试运行!"""
DEBUG = True DEBUG = True
@ -21,6 +21,7 @@ DEMO = not DEBUG
"""演示功能白名单""" """演示功能白名单"""
DEMO_WHITE_LIST_PATH = [ DEMO_WHITE_LIST_PATH = [
"/auth/login/", "/auth/login/",
"/auth/token/refresh/",
"/auth/wx/login/", "/auth/wx/login/",
"/vadmin/system/dict/types/details/", "/vadmin/system/dict/types/details/",
"/vadmin/auth/user/export/query/list/to/excel/" "/vadmin/auth/user/export/query/list/to/excel/"
@ -49,8 +50,12 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login/", auto_error=False)
SECRET_KEY = 'vgb0tnl9d58+6n-6h-ea&u^1#s0ccp!794=kbvqacjq75vzps$' SECRET_KEY = 'vgb0tnl9d58+6n-6h-ea&u^1#s0ccp!794=kbvqacjq75vzps$'
"""用于设定 JWT 令牌签名算法""" """用于设定 JWT 令牌签名算法"""
ALGORITHM = "HS256" ALGORITHM = "HS256"
"""令牌过期时间9999分钟""" """access_token 过期时间,一天"""
ACCESS_TOKEN_EXPIRE_MINUTES = 9999 ACCESS_TOKEN_EXPIRE_MINUTES = 1440
"""refresh_token 过期时间用于刷新token使用两天"""
REFRESH_TOKEN_EXPIRE_MINUTES = 1440 * 2
"""access_token 缓存时间用于刷新token使用30分钟"""
ACCESS_TOKEN_CACHE_MINUTES = 30
""" """
挂载临时文件目录并添加路由访问此路由不会在接口文档中显示 挂载临时文件目录并添加路由访问此路由不会在接口文档中显示

View File

@ -30,11 +30,11 @@ async def get_banners(auth: Auth = Depends(AllUserAuth())):
"id": 3, "image": "https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-09/banner3.png" "id": 3, "image": "https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-09/banner3.png"
}, },
] ]
return SuccessResponse(data) return SuccessResponse(data, refresh=auth.refresh)
@app.get("/user/access/source/", summary="用户来源") @app.get("/user/access/source/", summary="用户来源")
async def get_user_access_source(): async def get_user_access_source(auth: Auth = Depends(AllUserAuth())):
data = [ data = [
{"value": 1000, "name": 'analysis.directAccess'}, {"value": 1000, "name": 'analysis.directAccess'},
{"value": 310, "name": 'analysis.mailMarketing'}, {"value": 310, "name": 'analysis.mailMarketing'},
@ -42,11 +42,11 @@ async def get_user_access_source():
{"value": 135, "name": 'analysis.videoAdvertising'}, {"value": 135, "name": 'analysis.videoAdvertising'},
{"value": 1548, "name": 'analysis.searchEngines'} {"value": 1548, "name": 'analysis.searchEngines'}
] ]
return SuccessResponse(data) return SuccessResponse(data, refresh=auth.refresh)
@app.get("/weekly/user/activity/", summary="每周用户活跃量") @app.get("/weekly/user/activity/", summary="每周用户活跃量")
async def get_weekly_user_activity(): async def get_weekly_user_activity(auth: Auth = Depends(AllUserAuth())):
data = [ data = [
{"value": 13253, "name": 'analysis.monday'}, {"value": 13253, "name": 'analysis.monday'},
{"value": 34235, "name": 'analysis.tuesday'}, {"value": 34235, "name": 'analysis.tuesday'},
@ -56,11 +56,11 @@ async def get_weekly_user_activity():
{"value": 1322, "name": 'analysis.saturday'}, {"value": 1322, "name": 'analysis.saturday'},
{"value": 1324, "name": 'analysis.sunday'} {"value": 1324, "name": 'analysis.sunday'}
] ]
return SuccessResponse(data) return SuccessResponse(data, refresh=auth.refresh)
@app.get("/monthly/sales/", summary="每月销售额") @app.get("/monthly/sales/", summary="每月销售额")
async def get_monthly_sales(): async def get_monthly_sales(auth: Auth = Depends(AllUserAuth())):
data = [ data = [
{"estimate": 100, "actual": 120, "name": 'analysis.january'}, {"estimate": 100, "actual": 120, "name": 'analysis.january'},
{"estimate": 120, "actual": 82, "name": 'analysis.february'}, {"estimate": 120, "actual": 82, "name": 'analysis.february'},
@ -75,4 +75,4 @@ async def get_monthly_sales():
{"estimate": 118, "actual": 99, "name": 'analysis.november'}, {"estimate": 118, "actual": 99, "name": 'analysis.november'},
{"estimate": 123, "actual": 123, "name": 'analysis.december'} {"estimate": 123, "actual": 123, "name": 'analysis.december'}
] ]
return SuccessResponse(data) return SuccessResponse(data, refresh=auth.refresh)

View File

@ -4,10 +4,9 @@
# @File : current.py # @File : current.py
# @IDE : PyCharm # @IDE : PyCharm
# @desc : 获取认证后的信息工具 # @desc : 获取认证后的信息工具
from datetime import datetime, timedelta
from typing import List, Optional from typing import List, Optional
import jwt
from jose import jwt, JWTError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from apps.vadmin.auth.crud import UserDal from apps.vadmin.auth.crud import UserDal
@ -43,7 +42,7 @@ class OpenAuth(AuthValidation):
""" """
@classmethod @classmethod
def validate_token(cls, token: str | None, db: AsyncSession) -> str | None: def validate_token(cls, request: Request, token: str | None) -> str | None:
""" """
验证用户 token没有则返回 None 验证用户 token没有则返回 None
""" """
@ -52,9 +51,19 @@ class OpenAuth(AuthValidation):
try: try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
telephone: str = payload.get("sub") telephone: str = payload.get("sub")
if telephone is None: exp: int = payload.get("exp")
is_refresh: bool = payload.get("is_refresh")
if telephone is None or is_refresh:
return None return None
except JWTError: # 计算当前时间 + 缓冲时间是否大于等于 JWT 过期时间
buffer_time = (datetime.now() + timedelta(minutes=settings.ACCESS_TOKEN_CACHE_MINUTES)).timestamp()
if buffer_time >= exp:
request.scope["refresh"] = True
else:
request.scope["refresh"] = False
except jwt.exceptions.InvalidSignatureError:
return None
except jwt.exceptions.ExpiredSignatureError:
return None return None
return telephone return telephone
@ -83,7 +92,7 @@ class OpenAuth(AuthValidation):
""" """
每次调用依赖此类的接口会执行该方法 每次调用依赖此类的接口会执行该方法
""" """
telephone = self.validate_token(token, db) telephone = self.validate_token(request, token)
if telephone: if telephone:
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True) user = await UserDal(db).get_data(telephone=telephone, v_return_none=True)
return await self.validate_user(request, user, db) return await self.validate_user(request, user, db)
@ -106,7 +115,9 @@ class AllUserAuth(AuthValidation):
""" """
每次调用依赖此类的接口会执行该方法 每次调用依赖此类的接口会执行该方法
""" """
telephone = self.validate_token(token, db) if not settings.OAUTH_ENABLE:
return Auth(db=db)
telephone = self.validate_token(request, token)
if isinstance(telephone, Auth): if isinstance(telephone, Auth):
return telephone return telephone
user = await UserDal(db).get_data(telephone=telephone, v_return_none=True) user = await UserDal(db).get_data(telephone=telephone, v_return_none=True)
@ -136,7 +147,9 @@ class FullAdminAuth(AuthValidation):
""" """
每次调用依赖此类的接口会执行该方法 每次调用依赖此类的接口会执行该方法
""" """
telephone = self.validate_token(token, db) if not settings.OAUTH_ENABLE:
return Auth(db=db)
telephone = self.validate_token(request, token)
if isinstance(telephone, Auth): if isinstance(telephone, Auth):
return telephone return telephone
options = [joinedload(VadminUser.roles), joinedload("roles.menus")] options = [joinedload(VadminUser.roles), joinedload("roles.menus")]

View File

@ -20,21 +20,20 @@ PassLib 是一个用于处理哈希密码的很棒的 Python 包。它支持许
""" """
from datetime import timedelta from datetime import timedelta
from fastapi import APIRouter, Depends, Request import jwt
from fastapi import APIRouter, Depends, Request, Body
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from core.database import db_getter from core.database import db_getter
from utils import status
from utils.response import SuccessResponse, ErrorResponse from utils.response import SuccessResponse, ErrorResponse
from application import settings from application import settings
from utils.tools import generate_string
from .login_manage import LoginManage from .login_manage import LoginManage
from .validation import LoginForm, WXLoginForm from .validation import LoginForm, WXLoginForm
from apps.vadmin.record.models import VadminLoginRecord from apps.vadmin.record.models import VadminLoginRecord
from apps.vadmin.auth.crud import MenuDal, UserDal from apps.vadmin.auth.crud import MenuDal, UserDal
from apps.vadmin.auth.schemas import UserIn
from .current import FullAdminAuth from .current import FullAdminAuth
from .validation.auth import Auth from .validation.auth import Auth
from utils.wx.oauth import WXOAuth from utils.wx.oauth import WXOAuth
from core.data_types import Telephone
app = APIRouter() app = APIRouter()
@ -57,10 +56,12 @@ async def login_for_access_token(
if not result.status: if not result.status:
raise ValueError(result.msg) raise ValueError(result.msg)
token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = LoginManage.create_token({"sub": result.user.telephone, "is_refresh": False})
token = LoginManage.create_access_token(data={"sub": result.user.telephone}, expires_delta=token_expires) expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
refresh_token = LoginManage.create_token({"sub": result.user.telephone, "is_refresh": True}, expires=expires)
resp = { resp = {
"access_token": token, "access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer", "token_type": "bearer",
"is_reset_password": result.user.is_reset_password, "is_reset_password": result.user.is_reset_password,
"is_wx_server_openid": result.user.is_wx_server_openid "is_wx_server_openid": result.user.is_wx_server_openid
@ -95,10 +96,12 @@ async def wx_login_for_access_token(request: Request, data: WXLoginForm, db: Asy
await user.update_login_info(db, request.client.host) await user.update_login_info(db, request.client.host)
# 登录成功创建 token # 登录成功创建 token
token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = LoginManage.create_token({"sub": user.telephone, "is_refresh": False})
token = LoginManage.create_access_token(data={"sub": user.telephone}, expires_delta=token_expires) expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
refresh_token = LoginManage.create_token({"sub": user.telephone, "is_refresh": True}, expires=expires)
resp = { resp = {
"access_token": token, "access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer", "token_type": "bearer",
"is_reset_password": user.is_reset_password, "is_reset_password": user.is_reset_password,
"is_wx_server_openid": user.is_wx_server_openid "is_wx_server_openid": user.is_wx_server_openid
@ -110,3 +113,28 @@ async def wx_login_for_access_token(request: Request, data: WXLoginForm, db: Asy
@app.get("/getMenuList/", summary="获取当前用户菜单树") @app.get("/getMenuList/", summary="获取当前用户菜单树")
async def get_menu_list(auth: Auth = Depends(FullAdminAuth())): async def get_menu_list(auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await MenuDal(auth.db).get_routers(auth.user)) return SuccessResponse(await MenuDal(auth.db).get_routers(auth.user))
@app.post("/token/refresh/", summary="刷新Token")
async def token_refresh(refresh: str = Body(..., title="刷新Token")):
error_code = status.HTTP_401_UNAUTHORIZED
try:
payload = jwt.decode(refresh, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
telephone: str = payload.get("sub")
is_refresh: bool = payload.get("is_refresh")
if telephone is None or not is_refresh:
return ErrorResponse("未认证,请您重新登录", code=error_code, status=error_code)
except jwt.exceptions.InvalidSignatureError:
return ErrorResponse("无效认证,请您重新登录", code=error_code, status=error_code)
except jwt.exceptions.ExpiredSignatureError:
return ErrorResponse("登录已超时,请您重新登录", code=error_code, status=error_code)
access_token = LoginManage.create_token({"sub": telephone, "is_refresh": False})
expires = timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)
refresh_token = LoginManage.create_token({"sub": telephone, "is_refresh": True}, expires=expires)
resp = {
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer"
}
return SuccessResponse(resp)

View File

@ -7,10 +7,9 @@
# @desc : 简要说明 # @desc : 简要说明
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Optional
from fastapi import Request from fastapi import Request
from application import settings from application import settings
from jose import jwt import jwt
from apps.vadmin.auth import models from apps.vadmin.auth import models
from .validation import LoginValidation, LoginForm, LoginResult from .validation import LoginValidation, LoginForm, LoginResult
from utils.aliyun_sms import AliyunSMS from utils.aliyun_sms import AliyunSMS
@ -44,15 +43,19 @@ class LoginManage:
return LoginResult(status=False, msg="验证码错误") return LoginResult(status=False, msg="验证码错误")
@staticmethod @staticmethod
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): def create_token(payload: dict, expires: timedelta = None):
""" """
创建一个生成新的访问令牌的工具函数 创建一个生成新的访问令牌的工具函数
pyjwthttps://github.com/jpadilla/pyjwt/blob/master/docs/usage.rst
jwt 博客https://geek-docs.com/python/python-tutorial/j_python-jwt.html
#TODO 传入的时间为UTC时间datetime.datetime类型但是在解码时获取到的是本机时间的时间戳
""" """
to_encode = data.copy() if expires:
if expires_delta: expire = datetime.utcnow() + expires
expire = datetime.utcnow() + expires_delta
else: else:
expire = datetime.utcnow() + timedelta(minutes=60) expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire}) payload.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) encoded_jwt = jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt return encoded_jwt

View File

@ -4,9 +4,9 @@
# @File : auth.py # @File : auth.py
# @IDE : PyCharm # @IDE : PyCharm
# @desc : 用户凭证验证装饰器 # @desc : 用户凭证验证装饰器
from datetime import datetime, timedelta
from fastapi import Request from fastapi import Request
from jose import jwt, JWTError import jwt
from pydantic import BaseModel from pydantic import BaseModel
from application import settings from application import settings
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -17,6 +17,7 @@ from utils import status
class Auth(BaseModel): class Auth(BaseModel):
user: models.VadminUser = None user: models.VadminUser = None
refresh: bool = False
db: AsyncSession db: AsyncSession
class Config: class Config:
@ -29,22 +30,35 @@ class AuthValidation:
用于用户每次调用接口时验证用户提交的token是否正确并从token中获取用户信息 用于用户每次调用接口时验证用户提交的token是否正确并从token中获取用户信息
""" """
error_code = status.HTTP_401_UNAUTHORIZED
@classmethod @classmethod
def validate_token(cls, token: str, db: AsyncSession) -> str | Auth: def validate_token(cls, request: Request, token: str) -> str:
""" """
验证用户 token 验证用户 token
""" """
if not settings.OAUTH_ENABLE:
return Auth(db=db)
if not token: if not token:
raise CustomException(msg="请您先登录!", code=status.HTTP_ERROR) raise CustomException(msg="请您先登录!", code=status.HTTP_ERROR)
try: try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
telephone: str = payload.get("sub") telephone: str = payload.get("sub")
if telephone is None: exp: int = payload.get("exp")
raise CustomException(msg="认证已过期,请您重新登陆", code=status.HTTP_401_UNAUTHORIZED) is_refresh: bool = payload.get("is_refresh")
except JWTError: if telephone is None or is_refresh:
raise CustomException(msg="认证已过期,请您重新登陆", code=status.HTTP_401_UNAUTHORIZED) raise CustomException(msg="未认证,请您重新登录", code=cls.error_code)
# 计算当前时间 + 缓冲时间是否大于等于 JWT 过期时间
buffer_time = (datetime.now() + timedelta(minutes=settings.ACCESS_TOKEN_CACHE_MINUTES)).timestamp()
# print("过期时间", exp, datetime.fromtimestamp(exp))
# print("当前时间", buffer_time, datetime.fromtimestamp(buffer_time))
# print("剩余时间", exp - buffer_time)
if buffer_time >= exp:
request.scope["refresh"] = True
else:
request.scope["refresh"] = False
except jwt.exceptions.InvalidSignatureError:
raise CustomException(msg="无效认证,请您重新登录", code=cls.error_code)
except jwt.exceptions.ExpiredSignatureError:
raise CustomException(msg="认证已过期,请您重新登录", code=cls.error_code)
return telephone return telephone
@classmethod @classmethod
@ -53,12 +67,13 @@ class AuthValidation:
验证用户信息 验证用户信息
""" """
if user is None: if user is None:
raise CustomException(msg="认证已过期,请您重新登陆", code=status.HTTP_401_UNAUTHORIZED) raise CustomException(msg="认证,请您重新登陆", code=cls.error_code, status_code=cls.error_code)
elif not user.is_active: elif not user.is_active:
raise CustomException(msg="用户已被冻结!", code=status.HTTP_403_FORBIDDEN) raise CustomException(msg="用户已被冻结!", code=cls.error_code, status_code=cls.error_code)
request.scope["telephone"] = user.telephone request.scope["telephone"] = user.telephone
try: try:
request.scope["body"] = await request.body() request.scope["body"] = await request.body()
except RuntimeError: except RuntimeError:
request.scope["body"] = "获取失败" request.scope["body"] = "获取失败"
return Auth(user=user, db=db) refresh = request.scope.get("refresh", False)
return Auth(user=user, db=db, refresh=refresh)

View File

@ -31,12 +31,12 @@ async def get_users(
schema = schemas.UserOut schema = schemas.UserOut
datas = await crud.UserDal(auth.db).get_datas(**params.dict(), v_options=options, v_schema=schema) datas = await crud.UserDal(auth.db).get_datas(**params.dict(), v_options=options, v_schema=schema)
count = await crud.UserDal(auth.db).get_count(**params.to_count()) count = await crud.UserDal(auth.db).get_count(**params.to_count())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count, refresh=auth.refresh)
@app.post("/users/", summary="创建用户") @app.post("/users/", summary="创建用户")
async def create_user(data: schemas.UserIn, auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.create"]))): async def create_user(data: schemas.UserIn, auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.create"]))):
return SuccessResponse(await crud.UserDal(auth.db).create_data(data=data)) return SuccessResponse(await crud.UserDal(auth.db).create_data(data=data), refresh=auth.refresh)
@app.delete("/users/", summary="批量删除用户", description="软删除,删除后清空所关联的角色") @app.delete("/users/", summary="批量删除用户", description="软删除,删除后清空所关联的角色")
@ -46,7 +46,7 @@ async def delete_users(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAu
elif 1 in ids.ids: elif 1 in ids.ids:
return ErrorResponse("不能删除超级管理员用户") return ErrorResponse("不能删除超级管理员用户")
await crud.UserDal(auth.db).delete_datas(ids=ids.ids, v_soft=True, is_active=False) await crud.UserDal(auth.db).delete_datas(ids=ids.ids, v_soft=True, is_active=False)
return SuccessResponse("删除成功") return SuccessResponse("删除成功", refresh=auth.refresh)
@app.put("/users/{data_id}/", summary="更新用户信息") @app.put("/users/{data_id}/", summary="更新用户信息")
@ -55,7 +55,7 @@ async def put_user(
data: schemas.UserUpdate, data: schemas.UserUpdate,
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.update"])) auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.update"]))
): ):
return SuccessResponse(await crud.UserDal(auth.db).put_data(data_id, data)) return SuccessResponse(await crud.UserDal(auth.db).put_data(data_id, data), refresh=auth.refresh)
@app.get("/users/{data_id}/", summary="获取用户信息") @app.get("/users/{data_id}/", summary="获取用户信息")
@ -66,29 +66,32 @@ async def get_user(
model = models.VadminUser model = models.VadminUser
options = [joinedload(model.roles)] options = [joinedload(model.roles)]
schema = schemas.UserOut schema = schemas.UserOut
return SuccessResponse(await crud.UserDal(auth.db).get_data(data_id, options, v_schema=schema)) return SuccessResponse(
await crud.UserDal(auth.db).get_data(data_id, options, v_schema=schema),
refresh=auth.refresh
)
@app.post("/user/current/reset/password/", summary="重置当前用户密码") @app.post("/user/current/reset/password/", summary="重置当前用户密码")
async def user_current_reset_password(data: schemas.ResetPwd, auth: Auth = Depends(AllUserAuth())): async def user_current_reset_password(data: schemas.ResetPwd, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.UserDal(auth.db).reset_current_password(auth.user, data)) return SuccessResponse(await crud.UserDal(auth.db).reset_current_password(auth.user, data), refresh=auth.refresh)
@app.post("/user/current/update/info/", summary="更新当前用户基本信息") @app.post("/user/current/update/info/", summary="更新当前用户基本信息")
async def post_user_current_update_info(data: schemas.UserUpdateBaseInfo, auth: Auth = Depends(AllUserAuth())): async def post_user_current_update_info(data: schemas.UserUpdateBaseInfo, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.UserDal(auth.db).update_current_info(auth.user, data)) return SuccessResponse(await crud.UserDal(auth.db).update_current_info(auth.user, data), refresh=auth.refresh)
@app.post("/user/current/update/avatar/", summary="更新当前用户头像") @app.post("/user/current/update/avatar/", summary="更新当前用户头像")
async def post_user_current_update_avatar(file: UploadFile, auth: Auth = Depends(AllUserAuth())): async def post_user_current_update_avatar(file: UploadFile, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.UserDal(auth.db).update_current_avatar(auth.user, file)) return SuccessResponse(await crud.UserDal(auth.db).update_current_avatar(auth.user, file), refresh=auth.refresh)
@app.get("/user/admin/current/info/", summary="获取当前管理员信息") @app.get("/user/admin/current/info/", summary="获取当前管理员信息")
async def get_user_admin_current_info(auth: Auth = Depends(FullAdminAuth())): async def get_user_admin_current_info(auth: Auth = Depends(FullAdminAuth())):
result = schemas.UserOut.from_orm(auth.user).dict() result = schemas.UserOut.from_orm(auth.user).dict()
result["permissions"] = list(get_user_permissions(auth.user)) result["permissions"] = list(get_user_permissions(auth.user))
return SuccessResponse(result) return SuccessResponse(result, refresh=auth.refresh)
@app.post("/user/export/query/list/to/excel/", summary="导出用户查询列表为excel") @app.post("/user/export/query/list/to/excel/", summary="导出用户查询列表为excel")
@ -97,17 +100,17 @@ async def post_user_export_query_list(
params: UserParams = Depends(), params: UserParams = Depends(),
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.export"])) auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.export"]))
): ):
return SuccessResponse(await crud.UserDal(auth.db).export_query_list(header, params)) return SuccessResponse(await crud.UserDal(auth.db).export_query_list(header, params), refresh=auth.refresh)
@app.get("/user/download/import/template/", summary="下载最新批量导入用户模板") @app.get("/user/download/import/template/", summary="下载最新批量导入用户模板")
async def get_user_download_new_import_template(auth: Auth = Depends(AllUserAuth())): async def get_user_download_new_import_template(auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.UserDal(auth.db).download_import_template()) return SuccessResponse(await crud.UserDal(auth.db).download_import_template(), refresh=auth.refresh)
@app.post("/import/users/", summary="批量导入用户") @app.post("/import/users/", summary="批量导入用户")
async def post_import_users(file: UploadFile, auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.import"]))): async def post_import_users(file: UploadFile, auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.import"]))):
return SuccessResponse(await crud.UserDal(auth.db).import_users(file)) return SuccessResponse(await crud.UserDal(auth.db).import_users(file), refresh=auth.refresh)
@app.post("/users/init/password/send/sms/", summary="初始化所选用户密码并发送通知短信") @app.post("/users/init/password/send/sms/", summary="初始化所选用户密码并发送通知短信")
@ -116,13 +119,16 @@ async def post_users_init_password(
ids: IdList = Depends(), ids: IdList = Depends(),
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.reset"])) auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.reset"]))
): ):
return SuccessResponse(await crud.UserDal(auth.db).init_password_send_sms(ids.ids, request.app.state.redis)) return SuccessResponse(
await crud.UserDal(auth.db).init_password_send_sms(ids.ids, request.app.state.redis),
refresh=auth.refresh
)
@app.put("/users/wx/server/openid/", summary="更新当前用户服务端微信平台openid") @app.put("/users/wx/server/openid/", summary="更新当前用户服务端微信平台openid")
async def put_user_wx_server_openid(request: Request, code: str, auth: Auth = Depends(AllUserAuth())): async def put_user_wx_server_openid(request: Request, code: str, auth: Auth = Depends(AllUserAuth())):
result = await crud.UserDal(auth.db).update_wx_server_openid(code, auth.user, request.app.state.redis) result = await crud.UserDal(auth.db).update_wx_server_openid(code, auth.user, request.app.state.redis)
return SuccessResponse(result) return SuccessResponse(result, refresh=auth.refresh)
########################################################### ###########################################################
@ -135,12 +141,12 @@ async def get_roles(
): ):
datas = await crud.RoleDal(auth.db).get_datas(**params.dict()) datas = await crud.RoleDal(auth.db).get_datas(**params.dict())
count = await crud.RoleDal(auth.db).get_count(**params.to_count()) count = await crud.RoleDal(auth.db).get_count(**params.to_count())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count, refresh=auth.refresh)
@app.post("/roles/", summary="创建角色信息") @app.post("/roles/", summary="创建角色信息")
async def create_role(role: schemas.RoleIn, auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create"]))): async def create_role(role: schemas.RoleIn, auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create"]))):
return SuccessResponse(await crud.RoleDal(auth.db).create_data(data=role)) return SuccessResponse(await crud.RoleDal(auth.db).create_data(data=role), refresh=auth.refresh)
@app.delete("/roles/", summary="批量删除角色", description="硬删除, 如果存在用户关联则无法删除") @app.delete("/roles/", summary="批量删除角色", description="硬删除, 如果存在用户关联则无法删除")
@ -148,7 +154,7 @@ async def delete_roles(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAu
if 1 in ids.ids: if 1 in ids.ids:
return ErrorResponse("不能删除管理员角色") return ErrorResponse("不能删除管理员角色")
await crud.RoleDal(auth.db).delete_datas(ids.ids, v_soft=False) await crud.RoleDal(auth.db).delete_datas(ids.ids, v_soft=False)
return SuccessResponse("删除成功") return SuccessResponse("删除成功", refresh=auth.refresh)
@app.put("/roles/{data_id}/", summary="更新角色信息") @app.put("/roles/{data_id}/", summary="更新角色信息")
@ -159,12 +165,12 @@ async def put_role(
): ):
if 1 == data_id: if 1 == data_id:
return ErrorResponse("不能修改管理员角色") return ErrorResponse("不能修改管理员角色")
return SuccessResponse(await crud.RoleDal(auth.db).put_data(data_id, data)) return SuccessResponse(await crud.RoleDal(auth.db).put_data(data_id, data), refresh=auth.refresh)
@app.get("/roles/options/", summary="获取角色选择项") @app.get("/roles/options/", summary="获取角色选择项")
async def get_role_options(auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.create", "auth.user.update"]))): async def get_role_options(auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.create", "auth.user.update"]))):
return SuccessResponse(await crud.RoleDal(auth.db).get_select_datas()) return SuccessResponse(await crud.RoleDal(auth.db).get_select_datas(), refresh=auth.refresh)
@app.get("/roles/{data_id}/", summary="获取角色信息") @app.get("/roles/{data_id}/", summary="获取角色信息")
@ -175,7 +181,10 @@ async def get_role(
model = models.VadminRole model = models.VadminRole
options = [joinedload(model.menus)] options = [joinedload(model.menus)]
schema = schemas.RoleOut schema = schemas.RoleOut
return SuccessResponse(await crud.RoleDal(auth.db).get_data(data_id, options, v_schema=schema)) return SuccessResponse(
await crud.RoleDal(auth.db).get_data(data_id, options, v_schema=schema),
refresh=auth.refresh
)
########################################################### ###########################################################
@ -184,33 +193,33 @@ async def get_role(
@app.get("/menus/", summary="获取菜单列表") @app.get("/menus/", summary="获取菜单列表")
async def get_menus(auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.list"]))): async def get_menus(auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.list"]))):
datas = await crud.MenuDal(auth.db).get_tree_list(mode=1) datas = await crud.MenuDal(auth.db).get_tree_list(mode=1)
return SuccessResponse(datas) return SuccessResponse(datas, refresh=auth.refresh)
@app.get("/menus/tree/options/", summary="获取菜单树选择项,添加/修改菜单时使用") @app.get("/menus/tree/options/", summary="获取菜单树选择项,添加/修改菜单时使用")
async def get_menus_options(auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.create", "auth.menu.update"]))): async def get_menus_options(auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.create", "auth.menu.update"]))):
datas = await crud.MenuDal(auth.db).get_tree_list(mode=2) datas = await crud.MenuDal(auth.db).get_tree_list(mode=2)
return SuccessResponse(datas) return SuccessResponse(datas, refresh=auth.refresh)
@app.get("/menus/role/tree/options/", summary="获取菜单列表树信息,角色权限使用") @app.get("/menus/role/tree/options/", summary="获取菜单列表树信息,角色权限使用")
async def get_menus_treeselect( async def get_menus_treeselect(
auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create", "auth.role.update"])) auth: Auth = Depends(FullAdminAuth(permissions=["auth.role.create", "auth.role.update"]))
): ):
return SuccessResponse(await crud.MenuDal(auth.db).get_tree_list(mode=3)) return SuccessResponse(await crud.MenuDal(auth.db).get_tree_list(mode=3), refresh=auth.refresh)
@app.post("/menus/", summary="创建菜单信息") @app.post("/menus/", summary="创建菜单信息")
async def create_menu(menu: schemas.Menu, auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.create"]))): async def create_menu(menu: schemas.Menu, auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.create"]))):
if menu.parent_id: if menu.parent_id:
menu.alwaysShow = False menu.alwaysShow = False
return SuccessResponse(await crud.MenuDal(auth.db).create_data(data=menu)) return SuccessResponse(await crud.MenuDal(auth.db).create_data(data=menu), refresh=auth.refresh)
@app.delete("/menus/", summary="批量删除菜单", description="硬删除, 如果存在角色关联则无法删除") @app.delete("/menus/", summary="批量删除菜单", description="硬删除, 如果存在角色关联则无法删除")
async def delete_menus(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.delete"]))): async def delete_menus(ids: IdList = Depends(), auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.delete"]))):
await crud.MenuDal(auth.db).delete_datas(ids.ids, v_soft=False) await crud.MenuDal(auth.db).delete_datas(ids.ids, v_soft=False)
return SuccessResponse("删除成功") return SuccessResponse("删除成功", refresh=auth.refresh)
@app.put("/menus/{data_id}/", summary="更新菜单信息") @app.put("/menus/{data_id}/", summary="更新菜单信息")
@ -218,7 +227,7 @@ async def put_menus(
data_id: int, data_id: int,
data: schemas.Menu, auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.update"])) data: schemas.Menu, auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.update"]))
): ):
return SuccessResponse(await crud.MenuDal(auth.db).put_data(data_id, data)) return SuccessResponse(await crud.MenuDal(auth.db).put_data(data_id, data), refresh=auth.refresh)
@app.get("/menus/{data_id}/", summary="获取菜单信息") @app.get("/menus/{data_id}/", summary="获取菜单信息")
@ -227,7 +236,7 @@ async def put_menus(
auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.view", "auth.menu.update"])) auth: Auth = Depends(FullAdminAuth(permissions=["auth.menu.view", "auth.menu.update"]))
): ):
schema = schemas.MenuSimpleOut schema = schemas.MenuSimpleOut
return SuccessResponse(await crud.MenuDal(auth.db).get_data(data_id, None, v_schema=schema)) return SuccessResponse(await crud.MenuDal(auth.db).get_data(data_id, None, v_schema=schema), refresh=auth.refresh)
@app.get("/role/menus/tree/{role_id}/", summary="获取菜单列表树信息以及角色菜单权限ID角色权限使用") @app.get("/role/menus/tree/{role_id}/", summary="获取菜单列表树信息以及角色菜单权限ID角色权限使用")
@ -237,4 +246,4 @@ async def get_role_menu_tree(
): ):
treeselect = await crud.MenuDal(auth.db).get_tree_list(mode=3) treeselect = await crud.MenuDal(auth.db).get_tree_list(mode=3)
role_menu_tree = await crud.RoleDal(auth.db).get_role_menu_tree(role_id) role_menu_tree = await crud.RoleDal(auth.db).get_role_menu_tree(role_id)
return SuccessResponse({"role_menu_tree": role_menu_tree, "menus": treeselect}) return SuccessResponse({"role_menu_tree": role_menu_tree, "menus": treeselect}, refresh=auth.refresh)

View File

@ -29,36 +29,42 @@ async def get_issue_categorys(p: params.IssueCategoryParams = Depends(), auth: A
schema = schemas.IssueCategoryListOut schema = schemas.IssueCategoryListOut
datas = await crud.IssueCategoryDal(auth.db).get_datas(**p.dict(), v_options=options, v_schema=schema) datas = await crud.IssueCategoryDal(auth.db).get_datas(**p.dict(), v_options=options, v_schema=schema)
count = await crud.IssueCategoryDal(auth.db).get_count(**p.to_count()) count = await crud.IssueCategoryDal(auth.db).get_count(**p.to_count())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count, refresh=auth.refresh)
@app.get("/issue/categorys/options/", summary="获取类别选择项") @app.get("/issue/categorys/options/", summary="获取类别选择项")
async def get_issue_categorys_options(auth: Auth = Depends(AllUserAuth())): async def get_issue_categorys_options(auth: Auth = Depends(AllUserAuth())):
schema = schemas.IssueCategoryOptionsOut schema = schemas.IssueCategoryOptionsOut
return SuccessResponse(await crud.IssueCategoryDal(auth.db).get_datas(limit=0, is_active=True, v_schema=schema)) return SuccessResponse(
await crud.IssueCategoryDal(auth.db).get_datas(limit=0, is_active=True, v_schema=schema),
refresh=auth.refresh
)
@app.post("/issue/categorys/", summary="创建类别") @app.post("/issue/categorys/", summary="创建类别")
async def create_issue_category(data: schemas.IssueCategory, auth: Auth = Depends(AllUserAuth())): async def create_issue_category(data: schemas.IssueCategory, auth: Auth = Depends(AllUserAuth())):
data.user_id = auth.user.id data.user_id = auth.user.id
return SuccessResponse(await crud.IssueCategoryDal(auth.db).create_data(data=data)) return SuccessResponse(await crud.IssueCategoryDal(auth.db).create_data(data=data), refresh=auth.refresh)
@app.delete("/issue/categorys/", summary="批量删除类别", description="硬删除") @app.delete("/issue/categorys/", summary="批量删除类别", description="硬删除")
async def delete_issue_categorys(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())): async def delete_issue_categorys(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.IssueCategoryDal(auth.db).delete_datas(ids=ids.ids, v_soft=False) await crud.IssueCategoryDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
return SuccessResponse("删除成功") return SuccessResponse("删除成功", refresh=auth.refresh)
@app.put("/issue/categorys/{data_id}/", summary="更新类别信息") @app.put("/issue/categorys/{data_id}/", summary="更新类别信息")
async def put_issue_category(data_id: int, data: schemas.IssueCategory, auth: Auth = Depends(AllUserAuth())): async def put_issue_category(data_id: int, data: schemas.IssueCategory, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.IssueCategoryDal(auth.db).put_data(data_id, data)) return SuccessResponse(await crud.IssueCategoryDal(auth.db).put_data(data_id, data), refresh=auth.refresh)
@app.get("/issue/categorys/{data_id}/", summary="获取类别信息") @app.get("/issue/categorys/{data_id}/", summary="获取类别信息")
async def get_issue_category(data_id: int, auth: Auth = Depends(AllUserAuth())): async def get_issue_category(data_id: int, auth: Auth = Depends(AllUserAuth())):
schema = schemas.IssueCategorySimpleOut schema = schemas.IssueCategorySimpleOut
return SuccessResponse(await crud.IssueCategoryDal(auth.db).get_data(data_id, v_schema=schema)) return SuccessResponse(
await crud.IssueCategoryDal(auth.db).get_data(data_id, v_schema=schema),
refresh=auth.refresh
)
@app.get("/issue/categorys/platform/{platform}/", summary="获取平台中的常见问题类别列表") @app.get("/issue/categorys/platform/{platform}/", summary="获取平台中的常见问题类别列表")
@ -81,24 +87,24 @@ async def get_issues(p: params.IssueParams = Depends(), auth: Auth = Depends(All
schema = schemas.IssueListOut schema = schemas.IssueListOut
datas = await crud.IssueDal(auth.db).get_datas(**p.dict(), v_options=options, v_schema=schema) datas = await crud.IssueDal(auth.db).get_datas(**p.dict(), v_options=options, v_schema=schema)
count = await crud.IssueDal(auth.db).get_count(**p.to_count()) count = await crud.IssueDal(auth.db).get_count(**p.to_count())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count, refresh=auth.refresh)
@app.post("/issues/", summary="创建问题") @app.post("/issues/", summary="创建问题")
async def create_issue(data: schemas.Issue, auth: Auth = Depends(AllUserAuth())): async def create_issue(data: schemas.Issue, auth: Auth = Depends(AllUserAuth())):
data.user_id = auth.user.id data.user_id = auth.user.id
return SuccessResponse(await crud.IssueDal(auth.db).create_data(data=data)) return SuccessResponse(await crud.IssueDal(auth.db).create_data(data=data), refresh=auth.refresh)
@app.delete("/issues/", summary="批量删除问题", description="硬删除") @app.delete("/issues/", summary="批量删除问题", description="硬删除")
async def delete_issues(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())): async def delete_issues(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.IssueDal(auth.db).delete_datas(ids=ids.ids, v_soft=False) await crud.IssueDal(auth.db).delete_datas(ids=ids.ids, v_soft=False)
return SuccessResponse("删除成功") return SuccessResponse("删除成功", refresh=auth.refresh)
@app.put("/issues/{data_id}/", summary="更新问题信息") @app.put("/issues/{data_id}/", summary="更新问题信息")
async def put_issue(data_id: int, data: schemas.Issue, auth: Auth = Depends(AllUserAuth())): async def put_issue(data_id: int, data: schemas.Issue, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.IssueDal(auth.db).put_data(data_id, data)) return SuccessResponse(await crud.IssueDal(auth.db).put_data(data_id, data), refresh=auth.refresh)
@app.get("/issues/{data_id}/", summary="获取问题信息") @app.get("/issues/{data_id}/", summary="获取问题信息")

View File

@ -23,7 +23,7 @@ app = APIRouter()
async def get_record_login(p: LoginParams = Depends(), auth: Auth = Depends(AllUserAuth())): async def get_record_login(p: LoginParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas = await crud.LoginRecordDal(auth.db).get_datas(**p.dict()) datas = await crud.LoginRecordDal(auth.db).get_datas(**p.dict())
count = await crud.LoginRecordDal(auth.db).get_count(**p.to_count()) count = await crud.LoginRecordDal(auth.db).get_count(**p.to_count())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count, refresh=auth.refresh)
@app.get("/operations/", summary="获取操作日志列表") @app.get("/operations/", summary="获取操作日志列表")
@ -31,14 +31,14 @@ async def get_record_operation(p: OperationParams = Depends(), db: DatabaseManag
auth: Auth = Depends(AllUserAuth())): auth: Auth = Depends(AllUserAuth())):
count = await db.get_count("operation_record", **p.to_count()) count = await db.get_count("operation_record", **p.to_count())
datas = await db.get_datas("operation_record", v_schema=schemas.OpertionRecordSimpleOut, **p.dict()) datas = await db.get_datas("operation_record", v_schema=schemas.OpertionRecordSimpleOut, **p.dict())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count, refresh=auth.refresh)
@app.get("/sms/send/list/", summary="获取短信发送列表") @app.get("/sms/send/list/", summary="获取短信发送列表")
async def get_sms_send_list(p: SMSParams = Depends(), auth: Auth = Depends(AllUserAuth())): async def get_sms_send_list(p: SMSParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas = await crud.SMSSendRecordDal(auth.db).get_datas(**p.dict()) datas = await crud.SMSSendRecordDal(auth.db).get_datas(**p.dict())
count = await crud.SMSSendRecordDal(auth.db).get_count(**p.to_count()) count = await crud.SMSSendRecordDal(auth.db).get_count(**p.to_count())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count, refresh=auth.refresh)
########################################################### ###########################################################
@ -46,4 +46,4 @@ async def get_sms_send_list(p: SMSParams = Depends(), auth: Auth = Depends(AllUs
########################################################### ###########################################################
@app.get("/analysis/user/login/distribute/", summary="获取用户登录分布情况列表") @app.get("/analysis/user/login/distribute/", summary="获取用户登录分布情况列表")
async def get_user_login_distribute(auth: Auth = Depends(AllUserAuth())): async def get_user_login_distribute(auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.LoginRecordDal(auth.db).get_user_distribute()) return SuccessResponse(await crud.LoginRecordDal(auth.db).get_user_distribute(), refresh=auth.refresh)

View File

@ -31,18 +31,18 @@ app = APIRouter()
async def get_dict_types(p: DictTypeParams = Depends(), auth: Auth = Depends(AllUserAuth())): async def get_dict_types(p: DictTypeParams = Depends(), auth: Auth = Depends(AllUserAuth())):
datas = await crud.DictTypeDal(auth.db).get_datas(**p.dict()) datas = await crud.DictTypeDal(auth.db).get_datas(**p.dict())
count = await crud.DictTypeDal(auth.db).get_count(**p.to_count()) count = await crud.DictTypeDal(auth.db).get_count(**p.to_count())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count, refresh=auth.refresh)
@app.post("/dict/types/", summary="创建字典类型") @app.post("/dict/types/", summary="创建字典类型")
async def create_dict_types(data: schemas.DictType, auth: Auth = Depends(AllUserAuth())): async def create_dict_types(data: schemas.DictType, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictTypeDal(auth.db).create_data(data=data)) return SuccessResponse(await crud.DictTypeDal(auth.db).create_data(data=data), refresh=auth.refresh)
@app.delete("/dict/types/", summary="批量删除字典类型") @app.delete("/dict/types/", summary="批量删除字典类型")
async def delete_dict_types(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())): async def delete_dict_types(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.DictTypeDal(auth.db).delete_datas(ids=ids.ids) await crud.DictTypeDal(auth.db).delete_datas(ids=ids.ids)
return SuccessResponse("删除成功") return SuccessResponse("删除成功", refresh=auth.refresh)
@app.post("/dict/types/details/", summary="获取多个字典类型下的字典元素列表") @app.post("/dict/types/details/", summary="获取多个字典类型下的字典元素列表")
@ -51,23 +51,26 @@ async def post_dicts_details(
dict_types: List[str] = Body(None, title="字典元素列表", description="查询字典元素列表") dict_types: List[str] = Body(None, title="字典元素列表", description="查询字典元素列表")
): ):
datas = await crud.DictTypeDal(auth.db).get_dicts_details(dict_types) datas = await crud.DictTypeDal(auth.db).get_dicts_details(dict_types)
return SuccessResponse(datas) return SuccessResponse(datas, refresh=auth.refresh)
@app.get("/dict/types/options/", summary="获取字典类型选择项") @app.get("/dict/types/options/", summary="获取字典类型选择项")
async def get_dicts_options(auth: Auth = Depends(AllUserAuth())): async def get_dicts_options(auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictTypeDal(auth.db).get_select_datas()) return SuccessResponse(await crud.DictTypeDal(auth.db).get_select_datas(), refresh=auth.refresh)
@app.put("/dict/types/{data_id}/", summary="更新字典类型") @app.put("/dict/types/{data_id}/", summary="更新字典类型")
async def put_dict_types(data_id: int, data: schemas.DictType, auth: Auth = Depends(AllUserAuth())): async def put_dict_types(data_id: int, data: schemas.DictType, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictTypeDal(auth.db).put_data(data_id, data)) return SuccessResponse(await crud.DictTypeDal(auth.db).put_data(data_id, data), refresh=auth.refresh)
@app.get("/dict/types/{data_id}/", summary="获取字典类型详细") @app.get("/dict/types/{data_id}/", summary="获取字典类型详细")
async def get_dict_type(data_id: int, auth: Auth = Depends(AllUserAuth())): async def get_dict_type(data_id: int, auth: Auth = Depends(AllUserAuth())):
schema = schemas.DictTypeSimpleOut schema = schemas.DictTypeSimpleOut
return SuccessResponse(await crud.DictTypeDal(auth.db).get_data(data_id, None, v_schema=schema)) return SuccessResponse(
await crud.DictTypeDal(auth.db).get_data(data_id, None, v_schema=schema),
refresh=auth.refresh
)
########################################################### ###########################################################
@ -75,7 +78,7 @@ async def get_dict_type(data_id: int, auth: Auth = Depends(AllUserAuth())):
########################################################### ###########################################################
@app.post("/dict/details/", summary="创建字典元素") @app.post("/dict/details/", summary="创建字典元素")
async def create_dict_details(data: schemas.DictDetails, auth: Auth = Depends(AllUserAuth())): async def create_dict_details(data: schemas.DictDetails, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictDetailsDal(auth.db).create_data(data=data)) return SuccessResponse(await crud.DictDetailsDal(auth.db).create_data(data=data), refresh=auth.refresh)
@app.get("/dict/details/", summary="获取单个字典类型下的字典元素列表,分页") @app.get("/dict/details/", summary="获取单个字典类型下的字典元素列表,分页")
@ -84,24 +87,27 @@ async def get_dict_details(params: DictDetailParams = Depends(), auth: Auth = De
return ErrorResponse(msg="未获取到字典类型!") return ErrorResponse(msg="未获取到字典类型!")
datas = await crud.DictDetailsDal(auth.db).get_datas(**params.dict()) datas = await crud.DictDetailsDal(auth.db).get_datas(**params.dict())
count = await crud.DictDetailsDal(auth.db).get_count(**params.to_count()) count = await crud.DictDetailsDal(auth.db).get_count(**params.to_count())
return SuccessResponse(datas, count=count) return SuccessResponse(datas, count=count, refresh=auth.refresh)
@app.delete("/dict/details/", summary="批量删除字典元素", description="硬删除") @app.delete("/dict/details/", summary="批量删除字典元素", description="硬删除")
async def delete_dict_details(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())): async def delete_dict_details(ids: IdList = Depends(), auth: Auth = Depends(AllUserAuth())):
await crud.DictDetailsDal(auth.db).delete_datas(ids.ids, v_soft=False) await crud.DictDetailsDal(auth.db).delete_datas(ids.ids, v_soft=False)
return SuccessResponse("删除成功") return SuccessResponse("删除成功", refresh=auth.refresh)
@app.put("/dict/details/{data_id}/", summary="更新字典元素") @app.put("/dict/details/{data_id}/", summary="更新字典元素")
async def put_dict_details(data_id: int, data: schemas.DictDetails, auth: Auth = Depends(AllUserAuth())): async def put_dict_details(data_id: int, data: schemas.DictDetails, auth: Auth = Depends(AllUserAuth())):
return SuccessResponse(await crud.DictDetailsDal(auth.db).put_data(data_id, data)) return SuccessResponse(await crud.DictDetailsDal(auth.db).put_data(data_id, data), refresh=auth.refresh)
@app.get("/dict/details/{data_id}/", summary="获取字典元素详情") @app.get("/dict/details/{data_id}/", summary="获取字典元素详情")
async def get_dict_detail(data_id: int, auth: Auth = Depends(AllUserAuth())): async def get_dict_detail(data_id: int, auth: Auth = Depends(AllUserAuth())):
schema = schemas.DictDetailsSimpleOut schema = schemas.DictDetailsSimpleOut
return SuccessResponse(await crud.DictDetailsDal(auth.db).get_data(data_id, None, v_schema=schema)) return SuccessResponse(
await crud.DictDetailsDal(auth.db).get_data(data_id, None, v_schema=schema),
refresh=auth.refresh
)
########################################################### ###########################################################
@ -136,17 +142,23 @@ async def sms_send(request: Request, telephone: str):
########################################################### ###########################################################
@app.get("/settings/tabs/", summary="获取系统配置标签列表") @app.get("/settings/tabs/", summary="获取系统配置标签列表")
async def get_settings_tabs(classify: str, auth: Auth = Depends(FullAdminAuth())): async def get_settings_tabs(classify: str, auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.SettingsTabDal(auth.db).get_datas(limit=0, classify=classify)) return SuccessResponse(
await crud.SettingsTabDal(auth.db).get_datas(limit=0, classify=classify),
refresh=auth.refresh
)
@app.get("/settings/tabs/values/", summary="获取系统配置标签下的信息") @app.get("/settings/tabs/values/", summary="获取系统配置标签下的信息")
async def get_settings_tabs_values(tab_id: int, auth: Auth = Depends(FullAdminAuth())): async def get_settings_tabs_values(tab_id: int, auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.SettingsDal(auth.db).get_tab_values(tab_id=tab_id)) return SuccessResponse(await crud.SettingsDal(auth.db).get_tab_values(tab_id=tab_id), refresh=auth.refresh)
@app.put("/settings/tabs/values/", summary="更新系统配置信息") @app.put("/settings/tabs/values/", summary="更新系统配置信息")
async def put_settings_tabs_values(request: Request, datas: dict = Body(...), auth: Auth = Depends(FullAdminAuth())): async def put_settings_tabs_values(request: Request, datas: dict = Body(...), auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse(await crud.SettingsDal(auth.db).update_datas(datas, request.app.state.redis)) return SuccessResponse(
await crud.SettingsDal(auth.db).update_datas(datas, request.app.state.redis),
refresh=auth.refresh
)
@app.get("/settings/base/config/", summary="获取系统基础配置", description="每次进入系统中时使用") @app.get("/settings/base/config/", summary="获取系统基础配置", description="每次进入系统中时使用")
@ -156,9 +168,15 @@ async def get_setting_base_config(db: AsyncSession = Depends(db_getter)):
@app.get("/settings/privacy/", summary="获取隐私协议") @app.get("/settings/privacy/", summary="获取隐私协议")
async def get_settings_privacy(auth: Auth = Depends(FullAdminAuth())): async def get_settings_privacy(auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse((await crud.SettingsDal(auth.db).get_data(config_key="web_privacy")).config_value) return SuccessResponse(
(await crud.SettingsDal(auth.db).get_data(config_key="web_privacy")).config_value,
refresh=auth.refresh
)
@app.get("/settings/agreement/", summary="获取用户协议") @app.get("/settings/agreement/", summary="获取用户协议")
async def get_settings_agreement(auth: Auth = Depends(FullAdminAuth())): async def get_settings_agreement(auth: Auth = Depends(FullAdminAuth())):
return SuccessResponse((await crud.SettingsDal(auth.db).get_data(config_key="web_agreement")).config_value) return SuccessResponse(
(await crud.SettingsDal(auth.db).get_data(config_key="web_agreement")).config_value,
refresh=auth.refresh
)

View File

@ -21,6 +21,30 @@ def register_redis(app: FastAPI) -> None:
博客https://blog.csdn.net/wgPython/article/details/107668521 博客https://blog.csdn.net/wgPython/article/details/107668521
博客https://www.cnblogs.com/emunshe/p/15761597.html 博客https://www.cnblogs.com/emunshe/p/15761597.html
官网https://aioredis.readthedocs.io/en/latest/getting-started/ 官网https://aioredis.readthedocs.io/en/latest/getting-started/
Github: https://github.com/aio-libs/aioredis-py
aioredis.from_url(url, *, encoding=None, parser=None, decode_responses=False, db=None, password=None, ssl=None,
connection_cls=None, loop=None, **kwargs) 方法是 aioredis 库中用于从 Redis 连接 URL 创建 Redis 连接对象的方法
以下是该方法的参数说明
urlRedis 连接 URL例如 redis://localhost:6379/0
encoding可选参数Redis 编码格式默认为 utf-8
parser可选参数Redis 数据解析器默认为 None表示使用默认解析器
decode_responses可选参数是否将 Redis 响应解码为 Python 字符串默认为 False
db可选参数Redis 数据库编号默认为 None
password可选参数Redis 认证密码默认为 None表示无需认证
ssl可选参数是否使用 SSL/TLS 加密连接默认为 None
connection_cls可选参数Redis 连接类默认为 None表示使用默认连接类
loop可选参数用于创建连接对象的事件循环默认为 None表示使用默认事件循环
**kwargs可选参数其他连接参数用于传递给 Redis 连接类的构造函数
aioredis.from_url() 方法的主要作用是将 Redis 连接 URL 转换为 Redis 连接对象
除了 URL 参数外其他参数用于指定 Redis 连接的各种选项例如 Redis 数据库编号密码SSL/TLS 加密等等可以根据需要选择使用这些选项
health_check_interval aioredis.from_url() 方法中的一个可选参数用于设置 Redis 连接的健康检查间隔时间
健康检查是指在 Redis 连接池中使用的连接对象会定期向 Redis 服务器发送 PING 命令来检查连接是否仍然有效
该参数的默认值是 0表示不进行健康检查如果需要启用健康检查则可以将该参数设置为一个正整数表示检查间隔的秒数
例如如果需要每隔 5 秒对 Redis 连接进行一次健康检查则可以将 health_check_interval 设置为 5
:param app: :param app:
:return: :return:
""" """

View File

@ -16,9 +16,10 @@ from core.logger import logger
class CustomException(Exception): class CustomException(Exception):
def __init__(self, msg: str, code: int): def __init__(self, msg: str, code: int, status_code: int = status.HTTP_200_OK):
self.msg = msg self.msg = msg
self.code = code self.code = code
self.status_code = status_code
def register_exception(app: FastAPI): def register_exception(app: FastAPI):

Binary file not shown.

37
kinit-uni/.eslintrc.js Normal file
View File

@ -0,0 +1,37 @@
module.exports = {
env: {
browser: true,
es6: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:vue/recommended',
'prettier',
'plugin:prettier/recommended'
],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module'
},
globals: {
uni: true,
wx: true,
ROUTES: true
},
plugins: ['vue', 'prettier'],
rules: {
'vue/multi-word-component-names': 'off',
'prettier/prettier': 'error',
'vue/html-indent': ['error', 2],
'vue/max-attributes-per-line': 'off',
'no-console': 'off',
'no-unused-vars': ['warn', { args: 'none' }],
'arrow-parens': ['error', 'always'],
'comma-dangle': ['error', 'never'],
quotes: ['error', 'single'],
semi: ['error', 'never'],
'object-curly-spacing': ['error', 'always'],
indent: ['error', 2, { SwitchCase: 1 }]
}
}

View File

@ -1,21 +1,18 @@
<script> <script>
import config from './config' export default {
import { getToken } from '@/common/utils/auth' onLaunch: function () {
this.initApp()
export default { },
onLaunch: function() { methods: {
this.initApp() //
}, initApp() {
methods: { //
// this.$store.dispatch('app/InitConfig')
initApp() {
//
this.$store.dispatch('app/InitConfig')
}
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
@import '@/static/scss/index.scss'; @import '@/static/scss/index.scss';
</style> </style>

View File

@ -47,3 +47,23 @@ RuoYi App 移动解决方案采用uniapp框架一份代码多终端适配
- 文档地址https://uviewui.com - 文档地址https://uviewui.com
uView UI是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架全面的组件和便捷的工具会让您信手拈来如鱼得水 uView UI是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架全面的组件和便捷的工具会让您信手拈来如鱼得水
## 开发工具
在此项目中我将开发`uni-app`的开发工具从 Hbuilder X 换到了 VSCode没有谁好谁坏只是本人更习惯使用 VSCode但是在运行项目时依然使用的是 Hbuilder XVSCode只是用来编写代码。当然使用 Hbuilder X 也是支持的,只做一个分享。
以下是我在VSCode中安装的几个插件
1. 名称: Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code
2. 名称: ESLint
3. 名称: Image preview
4. 名称: Markdown Preview Enhanced
5. 名称: Path Intellisense
6. 名称: Prettier - Code formatter
7. 名称: Sass (.sass only)
8. 名称: SCSS IntelliSense
9. 名称: Stylelint
10. 名称: uni-app-schemas
11. 名称: uni-app-snippets
12. 名称: uni-create-view
13. 名称: Vetur

View File

@ -8,58 +8,57 @@ import { setUserOpenid } from '@/common/request/api/login.js'
import { toast } from '@/common/utils/common' import { toast } from '@/common/utils/common'
export const wxLoginMixins = { export const wxLoginMixins = {
computed: { computed: {
...mapGetters([ ...mapGetters(['isUserOpenid'])
'isUserOpenid', },
]) data() {
}, return {}
data () { },
return { methods: {
} onGetPhoneNumber(e) {
}, return new Promise((resolve, reject) => {
methods: { // 获取手机号官方文档https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html
onGetPhoneNumber(e) { if (e.detail.errMsg === 'getPhoneNumber:fail user deny') {
return new Promise((resolve, reject) => { // 用户拒绝授权
// 获取手机号官方文档https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html toast('已取消授权')
if (e.detail.errMsg === "getPhoneNumber:fail user deny") { reject('已取消授权')
// 用户拒绝授权 } else if (e.detail.errMsg === 'getPhoneNumber:fail no permission') {
toast("已取消授权") // 微信公众平台未认证或未使用企业认证
reject("已取消授权") toast('微信公众平台未认证或未使用企业认证')
} else if (e.detail.errMsg === "getPhoneNumber:fail no permission") { reject('微信公众平台未认证或未使用企业认证')
// 微信公众平台未认证或未使用企业认证 } else if (e.detail.errMsg === 'getPhoneNumber:ok') {
toast("微信公众平台未认证或未使用企业认证") // code换取用户手机号 每个code只能使用一次code的有效期为5min
reject("微信公众平台未认证或未使用企业认证") this.$store.dispatch('auth/wxLogin', e.detail.code).then((res) => {
} else if (e.detail.errMsg === "getPhoneNumber:ok") { this.setOpenid()
// code换取用户手机号 每个code只能使用一次code的有效期为5min this.$store.dispatch('auth/GetInfo').then((result) => {
this.$store.dispatch('auth/wxLogin', e.detail.code).then(res => { resolve(result)
this.setOpenid(); })
this.$store.dispatch('auth/GetInfo').then(result => { })
resolve(result) } else {
}) toast('授权失败')
}) reject('授权失败')
} else { }
toast("授权失败") })
reject("授权失败") },
} setOpenid() {
}) let self = this
}, // uniapp 官方文档https://uniapp.dcloud.io/api/plugins/login.html#login
setOpenid() { if (self.isUserOpenid) {
let self = this; return
// uniapp 官方文档https://uniapp.dcloud.io/api/plugins/login.html#login }
if (self.isUserOpenid) { return; }; uni.login({
uni.login({ provider: 'weixin',
provider: 'weixin', success: function (loginRes) {
success: function (loginRes) { if (loginRes.code) {
if (loginRes.code) { setUserOpenid(loginRes.code).then(() => {
setUserOpenid(loginRes.code).then(res => { // console.log("更新openid成功", res)
// console.log("更新openid成功", res) self.$store.commit('auth/SET_IS_USER_OPENID', true)
self.$store.commit("auth/SET_IS_USER_OPENID", true); })
}) } else {
} else { console.log('登录失败获取code失败' + loginRes.errMsg)
console.log('登录失败获取code失败' + res.errMsg) }
} }
} })
}); }
} }
} }
}

View File

@ -1,46 +1,46 @@
export const wxShareMixins = { export const wxShareMixins = {
data() { data() {
return { return {
share: { share: {
title: "", title: '',
path: "", path: '',
imageUrl: "" imageUrl: ''
} }
} }
}, },
onLoad: function() { onLoad: function () {
wx.showShareMenu({ wx.showShareMenu({
withShareTicket: true, withShareTicket: true,
menus: ["shareAppMessage", "shareTimeline"] menus: ['shareAppMessage', 'shareTimeline']
}) })
}, },
onShareAppMessage(res) { onShareAppMessage(res) {
let that = this; let that = this
let imageUrl = that.share.imageUrl || ''; let imageUrl = that.share.imageUrl || ''
if (res.from === 'button') { if (res.from === 'button') {
//这块需要传参不然链接地址进去获取不到数据 //这块需要传参不然链接地址进去获取不到数据
let path = `/` + that.$scope.route + `?item=` + that.$scope.options.item; let path = '/' + that.$scope.route + '?item=' + that.$scope.options.item
return { return {
title: '商品分享~', title: '商品分享~',
path: path, path: path,
imageUrl: imageUrl imageUrl: imageUrl
}; }
} }
if (res.from === 'menu') { if (res.from === 'menu') {
return { return {
title: that.share.title, title: that.share.title,
path: that.share.path, path: that.share.path,
imageUrl: that.share.imageUrl imageUrl: that.share.imageUrl
}; }
} }
}, },
// 分享到朋友圈 // 分享到朋友圈
onShareTimeline() { onShareTimeline() {
return { return {
title: this.share.title, title: this.share.title,
path: this.share.path, path: this.share.path,
imageUrl: this.share.imageUrl imageUrl: this.share.imageUrl
}; }
}, },
methods: {} methods: {}
} }

View File

@ -5,29 +5,29 @@ export function login(telephone, password) {
const data = { const data = {
telephone, telephone,
password, password,
method: '0', method: '0',
platform: '1' platform: '1'
} }
return request.post(`/auth/login/`, data) return request.post('/auth/login/', data)
} }
// 获取用户详细信息 // 获取用户详细信息
export function getInfo() { export function getInfo() {
return request.get(`/vadmin/auth/user/admin/current/info/`) return request.get('/vadmin/auth/user/admin/current/info/')
} }
// 更新用户openid // 更新用户openid
export function setUserOpenid(code) { export function setUserOpenid(code) {
const params = {code} const params = { code }
return request.put(`/vadmin/auth/users/wx/server/openid/`, {}, {params: params}) return request.put('/vadmin/auth/users/wx/server/openid/', {}, { params: params })
} }
// 使用微信一键登录 // 使用微信一键登录
export function wxCodeLogin(code) { export function wxCodeLogin(code) {
const data = { const data = {
code, code,
method: '2', method: '2',
platform: '1' platform: '1'
} }
return request.post(`/auth/wx/login/`, data) return request.post('/auth/wx/login/', data)
} }

View File

@ -2,15 +2,18 @@ import request from '@/common/request/request'
// 更新当前用户基本信息 // 更新当前用户基本信息
export function updateCurrentUser(data) { export function updateCurrentUser(data) {
return request.post(`/vadmin/auth/user/current/update/info/`, data) return request.post('/vadmin/auth/user/current/update/info/', data)
} }
// 重置当前用户密码 // 重置当前用户密码
export function postCurrentUserResetPassword(data) { export function postCurrentUserResetPassword(data) {
return request.post(`/vadmin/auth/user/current/reset/password/`, data) return request.post('/vadmin/auth/user/current/reset/password/', data)
} }
// 更新当前用户头像 // 更新当前用户头像
export function postCurrentUserUploadAvatar(filePath) { export function postCurrentUserUploadAvatar(filePath) {
return request.upload(`/vadmin/auth/user/current/update/avatar/`, {filePath: filePath, name: 'file'}) return request.upload('/vadmin/auth/user/current/update/avatar/', {
} filePath: filePath,
name: 'file'
})
}

View File

@ -2,15 +2,15 @@ import request from '@/common/request/request.js'
// 获取平台中的常见问题类别列表 // 获取平台中的常见问题类别列表
export function getIssueCategoryList() { export function getIssueCategoryList() {
return request.get(`/vadmin/help/issue/categorys/platform/1/`) return request.get('/vadmin/help/issue/categorys/platform/1/')
} }
// 获取问题详情 // 获取问题详情
export function getIssue(dataId) { export function getIssue(dataId) {
return request.get(`/vadmin/help/issues/${dataId}/`) return request.get(`/vadmin/help/issues/${dataId}/`)
} }
// 更新常见问题查看次数+1 // 更新常见问题查看次数+1
export function updateIssueAddViewNumber(dataId) { export function updateIssueAddViewNumber(dataId) {
return request.get(`/vadmin/help/issues/add/view/number/${dataId}/`) return request.get(`/vadmin/help/issues/add/view/number/${dataId}/`)
} }

View File

@ -2,5 +2,5 @@ import request from '@/common/request/request.js'
// 获取多个字典类型下的字典元素列表 // 获取多个字典类型下的字典元素列表
export function getDictTypeDetailsApi(data) { export function getDictTypeDetailsApi(data) {
return request.post(`/vadmin/system/dict/types/details/`, data) return request.post('/vadmin/system/dict/types/details/', data)
} }

View File

@ -2,5 +2,5 @@ import request from '@/common/request/request'
// 获取系统基本配置 // 获取系统基本配置
export function getSystemBaseConfigApi() { export function getSystemBaseConfigApi() {
return request.get(`/vadmin/system/settings/base/config/`) return request.get('/vadmin/system/settings/base/config/')
} }

View File

@ -1,6 +1,6 @@
export default { export default {
"401": "认证失败,无法访问系统资源", 401: '认证失败,无法访问系统资源',
"403": "当前操作没有权限", 403: '当前操作没有权限',
"404": "访问资源不存在", 404: '访问资源不存在',
"default": "系统未知错误,请反馈给管理员" default: '系统未知错误,请反馈给管理员'
}; }

View File

@ -1,78 +1,127 @@
import luchRequest from '@/components/luch-request' // 使用npm import luchRequest from '@/components/luch-request' // 使用npm
import config from '@/config.js'; import config from '@/config.js'
import errorCode from "@/common/request/errorCode"; import errorCode from '@/common/request/errorCode'
import { getToken } from '@/common/utils/auth' import { getToken, getRefreshToken, setToken, setRefreshToken } from '@/common/utils/auth'
import { toast, showConfirm } from '@/common/utils/common' import { toast } from '@/common/utils/common'
import store from '@/store' import store from '@/store'
import request from '@/common/request/request.js'
// luch-request插件官网https://www.quanzhan.co/luch-request/guide/3.x/#%E5%85%A8%E5%B1%80%E8%AF%B7%E6%B1%82%E9%85%8D%E7%BD%AE // luch-request插件官网https://www.quanzhan.co/luch-request/guide/3.x/#%E5%85%A8%E5%B1%80%E8%AF%B7%E6%B1%82%E9%85%8D%E7%BD%AE
// 创建luchRequest实例 // 创建luchRequest实例
console.log(config.baseUrl)
const http = new luchRequest({ const http = new luchRequest({
baseURL: config.baseUrl, baseURL: config.baseUrl,
timeout: 20000, // 请求超时时间 timeout: 20000, // 请求超时时间
dataType: 'json', dataType: 'json',
custom: { custom: {
loading: true loading: true
}, }
sslVerify: true,
header: {}
}) })
// 请求拦截器 // 请求拦截器
http.interceptors.request.use( http.interceptors.request.use(
config => { (config) => {
// 在发送请求之前 // 在发送请求之前
let token = getToken() let token = getToken()
if (token) { if (token) {
// 添加头信息token验证 // 添加头信息token验证
config.header["Authorization"] = token config.header['Authorization'] = token
} }
return config return config
}, },
error => { (error) => {
return Promise.reject(error) return Promise.reject(error)
} }
) )
// 响应拦截器 // 响应拦截器
http.interceptors.response.use(res => { http.interceptors.response.use(
// console.log("响应拦截器:", res) (res) => {
// 未设置状态码则默认成功状态 // console.log("响应拦截器:", res)
const code = res.data.code || 200; // 未设置状态码则默认401状态
// 获取错误信息 const code = res.data.code || 200
const msg = res.data.message || errorCode[code] || errorCode["default"]; // 获取错误信息
if (code === 500) { const msg = res.data.message || errorCode[code] || errorCode['default']
toast(msg) // 是否刷新token
return Promise.reject(new Error(msg)); const refresh = res.data.refresh || false
} else if (code === 401) { if (code === 500) {
showConfirm("登录状态已过期,您可以继续留在该页面,或者重新登录?").then(res => { toast(msg)
if (res.confirm) { return Promise.reject(new Error(msg))
store.dispatch('auth/LogOut') } else if (code === 401) {
} // 因token快过期刷新token
}) refreshToken().then((res) => {
return Promise.reject("error"); setToken(`${res.data.token_type} ${res.data.access_token}`)
} else if (code !== 200) { setRefreshToken(res.data.refresh_token)
toast(msg) })
return Promise.reject("error"); toast('操作失败,请重试')
} else { return Promise.reject('error')
return res.data; } else if (code !== 200) {
} toast(msg)
}, return Promise.reject('error')
error => { } else if (code === 200) {
console.log("请求状态码服务器直接报错", error); if (refresh) {
let { errMsg } = error; // 因token快过期刷新token
if (errMsg == "request:fail") { refreshToken().then((res) => {
errMsg = "接口连接异常"; setToken(`${res.data.token_type} ${res.data.access_token}`)
} else if (errMsg == "request:fail timeout") { setRefreshToken(res.data.refresh_token)
errMsg = "接口连接超时"; })
} else { }
errMsg = error.data.message; return res.data
} } else {
toast(errMsg) return res.data
return Promise.reject(error); }
} },
); (error) => {
console.log('err', error)
let message = error.data.message || error.errMsg
const status = error.statusCode
switch (status) {
case 400:
message = '请求错误'
break
case 401:
// 强制要求重新登录因账号已冻结账号已过期手机号码错误刷新token无效等问题导致
store.dispatch('auth/LogOut')
message = '未认证,请登录'
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求地址出错'
break
case 408:
message = '请求超时'
break
case 500:
message = '服务器内部错误'
break
case 501:
message = '服务未实现'
break
case 502:
message = '网关错误'
break
case 503:
message = '服务不可用'
break
case 504:
message = '网关超时'
break
case 505:
message = 'HTTP版本不受支持'
break
default:
break
}
toast(message)
return Promise.reject(error)
}
)
export default http // 刷新 token
function refreshToken() {
const data = JSON.stringify(getRefreshToken())
return request.post('/auth/token/refresh/', data)
}
export default http

View File

@ -11,3 +11,17 @@ export function setToken(token) {
export function removeToken() { export function removeToken() {
return uni.removeStorageSync(TokenKey) return uni.removeStorageSync(TokenKey)
} }
const RefreshTokenKey = 'Refresh-Token'
export function getRefreshToken() {
return uni.getStorageSync(RefreshTokenKey)
}
export function setRefreshToken(token) {
return uni.setStorageSync(RefreshTokenKey, token)
}
export function removeRefreshToken() {
return uni.removeStorageSync(RefreshTokenKey)
}

View File

@ -1,7 +1,7 @@
/** /**
* 显示消息提示框 * 显示消息提示框
* @param content 提示的标题 * @param content 提示的标题
*/ */
export function toast(content) { export function toast(content) {
uni.showToast({ uni.showToast({
icon: 'none', icon: 'none',
@ -10,9 +10,9 @@ export function toast(content) {
} }
/** /**
* 显示模态弹窗 * 显示模态弹窗
* @param content 提示的标题 * @param content 提示的标题
*/ */
export function showConfirm(content) { export function showConfirm(content) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.showModal({ uni.showModal({
@ -20,7 +20,7 @@ export function showConfirm(content) {
content: content, content: content,
cancelText: '取消', cancelText: '取消',
confirmText: '确定', confirmText: '确定',
success: function(res) { success: function (res) {
resolve(res) resolve(res)
} }
}) })
@ -28,27 +28,27 @@ export function showConfirm(content) {
} }
/** /**
* 参数处理 * 参数处理
* @param params 参数 * @param params 参数
*/ */
export function tansParams(params) { export function tansParams(params) {
let result = '' let result = ''
for (const propName of Object.keys(params)) { for (const propName of Object.keys(params)) {
const value = params[propName] const value = params[propName]
var part = encodeURIComponent(propName) + "=" var part = encodeURIComponent(propName) + '='
if (value !== null && value !== "" && typeof (value) !== "undefined") { if (value !== null && value !== '' && typeof value !== 'undefined') {
if (typeof value === 'object') { if (typeof value === 'object') {
for (const key of Object.keys(value)) { for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
let params = propName + '[' + key + ']' let params = propName + '[' + key + ']'
var subPart = encodeURIComponent(params) + "=" var subPart = encodeURIComponent(params) + '='
result += subPart + encodeURIComponent(value[key]) + "&" result += subPart + encodeURIComponent(value[key]) + '&'
} }
} }
} else { } else {
result += part + encodeURIComponent(value) + "&" result += part + encodeURIComponent(value) + '&'
} }
} }
} }
return result return result
} }

View File

@ -1,11 +1,11 @@
export const auth = { export const auth = {
isUser: 'vuex_auth_isUser', isUser: 'vuex_auth_isUser',
isUserOpenid: 'vuex_auth_isUserOpenid', isUserOpenid: 'vuex_auth_isUserOpenid',
isResetPassword: 'vuex_auth_isResetPassword', isResetPassword: 'vuex_auth_isResetPassword',
name: 'vuex_auth_name', name: 'vuex_auth_name',
nickname: 'vuex_auth_nickname', nickname: 'vuex_auth_nickname',
gender: 'vuex_auth_gender', gender: 'vuex_auth_gender',
telephone: 'vuex_auth_telephone', telephone: 'vuex_auth_telephone',
avatar: 'vuex_auth_avatar', avatar: 'vuex_auth_avatar',
createDatetime: 'vuex_auth_createDatetime', createDatetime: 'vuex_auth_createDatetime',
roles: 'vuex_auth_roles', roles: 'vuex_auth_roles',

View File

@ -1,80 +0,0 @@
const TokenKey = 'Admin-Token'
// 获取客户端token
export function getToken() {
try {
const value = uni.getStorageSync(TokenKey);
if (value) {
return value;
}
return ""
} catch (e) {
// error
return ""
}
}
// 设置客户端token
export function setToken(token) {
uni.setStorage({
key: TokenKey,
data: token,
success: function (res) {
console.log('成功存储token');
},
fail:function(e){
console.log(e)
console.log("存储token失败");
}
});
}
// 删除客户端token
export function removeToken() {
uni.removeStorage({
key: TokenKey,
success: function (res) {
console.log('成功删除token');
}
});
}
// 获取客户端
export function getStorage(key) {
try {
const value = uni.getStorageSync(key);
if (value) {
// console.log("成功获取到 Storage", value);
return value;
}
return ""
} catch (e) {
// error
return ""
}
}
// 设置客户端 Storage
export function setStorage(key, value) {
uni.setStorage({
key: key,
data: value,
success: function (res) {
console.log('成功存储');
},
fail:function(e){
console.log(e)
console.log("存储失败");
}
});
}
// 删除客户端 Storage
export function removeStorage(key) {
uni.removeStorage({
key: key,
success: function (res) {
console.log('成功删除Storage');
}
});
}

View File

@ -17,14 +17,16 @@ module.exports = {
if (!log) return if (!log) return
log.error.apply(log, arguments) log.error.apply(log, arguments)
}, },
setFilterMsg(msg) { // 从基础库2.7.3开始支持 setFilterMsg(msg) {
// 从基础库2.7.3开始支持
if (!log || !log.setFilterMsg) return if (!log || !log.setFilterMsg) return
if (typeof msg !== 'string') return if (typeof msg !== 'string') return
log.setFilterMsg(msg) log.setFilterMsg(msg)
}, },
addFilterMsg(msg) { // 从基础库2.8.1开始支持 addFilterMsg(msg) {
// 从基础库2.8.1开始支持
if (!log || !log.addFilterMsg) return if (!log || !log.addFilterMsg) return
if (typeof msg !== 'string') return if (typeof msg !== 'string') return
log.addFilterMsg(msg) log.addFilterMsg(msg)
} }
} }

View File

@ -9,9 +9,9 @@ export function checkPermi(value) {
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const permissions = store.getters && store.getters.permissions const permissions = store.getters && store.getters.permissions
const permissionDatas = value const permissionDatas = value
const all_permission = "*:*:*" const all_permission = '*:*:*'
const hasPermission = permissions.some(permission => { const hasPermission = permissions.some((permission) => {
return all_permission === permission || permissionDatas.includes(permission) return all_permission === permission || permissionDatas.includes(permission)
}) })
@ -20,7 +20,7 @@ export function checkPermi(value) {
} }
return true return true
} else { } else {
console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) console.error('未获取到校验的字符权限!')
return false return false
} }
} }
@ -34,9 +34,9 @@ export function checkRole(value) {
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const roles = store.getters && store.getters.roles const roles = store.getters && store.getters.roles
const permissionRoles = value const permissionRoles = value
const super_admin = "admin" const super_admin = 'admin'
const hasRole = roles.some(role => { const hasRole = roles.some((role) => {
return super_admin === role || permissionRoles.includes(role) return super_admin === role || permissionRoles.includes(role)
}) })
@ -45,7 +45,7 @@ export function checkRole(value) {
} }
return true return true
} else { } else {
console.error(`need roles! Like checkRole="['admin','editor']"`) console.error('未获取到校验的角色!')
return false return false
} }
} }

View File

@ -5,34 +5,32 @@
博客https://www.jianshu.com/p/71ad2f45120c 博客https://www.jianshu.com/p/71ad2f45120c
*/ */
import config from '@/config.js' import config from '@/config.js'
import { getToken, removeToken } from '@/common/utils/cookies' import { getToken } from '@/common/utils/auth'
// 单个文件上传 // 单个文件上传
export function uploadFile(api, file, data={}) { export function uploadFile(api, file, data = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.uploadFile({ uni.uploadFile({
url: config.baseUrl + api, url: config.baseUrl + api,
filePath: file, filePath: file,
name: 'file', name: 'file',
timeout: 60000, timeout: 60000,
formData: data, formData: data,
header: { header: {
Authorization: getToken() Authorization: getToken()
}, },
success: (res) => { success: (res) => {
let data = JSON.parse(res.data); let data = JSON.parse(res.data)
if (data.code !== 200) { if (data.code !== 200) {
reject(data); reject(data)
} }
resolve(data); resolve(data)
}, },
fail: (err) => { fail: (err) => {
console.log("上传失败", err); console.log('上传失败', err)
reject(err); reject(err)
} }
}); })
}) })
} }

View File

@ -13,47 +13,47 @@
* getDate(new Date(), 3).fullDate # 三天后的日期 * getDate(new Date(), 3).fullDate # 三天后的日期
*/ */
export function getDate(date, AddDayCount = 0) { export function getDate(date, AddDayCount = 0) {
if (!date) { if (!date) {
date = new Date() date = new Date()
} }
if (typeof date !== 'object') { if (typeof date !== 'object') {
date = date.replace(/-/g, '/') date = date.replace(/-/g, '/')
} }
const dd = new Date(date) const dd = new Date(date)
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期 dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
const y = dd.getFullYear() const y = dd.getFullYear()
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期不足10补0 const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期不足10补0
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号不足10补0 const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号不足10补0
return { return {
fullDate: y + '-' + m + '-' + d, fullDate: y + '-' + m + '-' + d,
year: y, year: y,
month: m, month: m,
date: d, date: d,
day: dd.getDay() day: dd.getDay()
} }
} }
// 日期格式化 // 日期格式化
export function parseTime(time, pattern) { export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) { if (arguments.length === 0 || !time) {
return null; return null
} }
const format = pattern || "{y}-{m}-{d} {h}:{i}:{s}"; const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date; let date
if (typeof time === "object") { if (typeof time === 'object') {
date = time; date = time
} else { } else {
if ((typeof time === "string") && (/^[0-9]+$/.test(time))) { if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
time = parseInt(time); time = parseInt(time)
} else if (typeof time === "string") { } else if (typeof time === 'string') {
time = time.replace(new RegExp(/-/gm), "/"); time = time.replace(new RegExp(/-/gm), '/')
} }
if ((typeof time === "number") && (time.toString().length === 10)) { if (typeof time === 'number' && time.toString().length === 10) {
time = time * 1000; time = time * 1000
} }
date = new Date(time); date = new Date(time)
} }
const formatObj = { const formatObj = {
y: date.getFullYear(), y: date.getFullYear(),
@ -63,129 +63,132 @@ export function parseTime(time, pattern) {
i: date.getMinutes(), i: date.getMinutes(),
s: date.getSeconds(), s: date.getSeconds(),
a: date.getDay() a: date.getDay()
}; }
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]; let value = formatObj[key]
// Note: getDay() returns 0 on Sunday // Note: getDay() returns 0 on Sunday
if (key === "a") { return ["", "", "", "", "", "", ""][value]; } if (key === 'a') {
if (result.length > 0 && value < 10) { return ['', '', '', '', '', '', ''][value]
value = "0" + value;
} }
return value || 0; if (result.length > 0 && value < 10) {
}); value = '0' + value
return time_str; }
return value || 0
})
return time_str
} }
// 表单重置 // 表单重置
export function resetForm(refName) { export function resetForm(refName) {
if (this.$refs[refName]) { if (this.$refs[refName]) {
this.$refs[refName].resetFields(); this.$refs[refName].resetFields()
} }
} }
// 添加日期范围 // 添加日期范围
export function addDateRange(params, dateRange, propName) { export function addDateRange(params, dateRange, propName) {
const search = JSON.parse(JSON.stringify(params)); const search = JSON.parse(JSON.stringify(params))
if (dateRange != null && dateRange !== "" && dateRange.length !== 0) { if (dateRange != null && dateRange !== '' && dateRange.length !== 0) {
search.as = JSON.stringify({ create_datetime__range: dateRange }); search.as = JSON.stringify({ create_datetime__range: dateRange })
} }
return search; return search
} }
// 回显数据字典 // 回显数据字典
export function selectDictLabel(datas, value) { export function selectDictLabel(datas, value) {
var actions = []; var actions = []
Object.keys(datas).some((key) => { Object.keys(datas).some((key) => {
if (String(datas[key].value) === ("" + String(value))) { if (String(datas[key].value) === '' + String(value)) {
actions.push(datas[key].label); actions.push(datas[key].label)
return true; return true
} }
}); })
return actions.join(""); return actions.join('')
} }
// 获取字典默认值 // 获取字典默认值
export function selectDictDefault(datas) { export function selectDictDefault(datas) {
var actions = []; var actions = []
Object.keys(datas).some((key) => { Object.keys(datas).some((key) => {
if (datas[key].is_default === true) { if (datas[key].is_default === true) {
actions.push(datas[key].dictValue); actions.push(datas[key].dictValue)
return true; return true
} }
}); })
if (!actions[0] && datas[0]) { if (!actions[0] && datas[0]) {
actions.push(datas[0].dictValue); actions.push(datas[0].dictValue)
} }
return actions.join(""); return actions.join('')
} }
// 回显数据字典字符串数组 // 回显数据字典字符串数组
export function selectDictLabels(datas, value, separator) { export function selectDictLabels(datas, value, separator) {
var actions = []; var actions = []
var currentSeparator = undefined === separator ? "," : separator; var currentSeparator = undefined === separator ? ',' : separator
var temp = value.split(currentSeparator); var temp = value.split(currentSeparator)
Object.keys(value.split(currentSeparator)).some((val) => { Object.keys(value.split(currentSeparator)).some((val) => {
Object.keys(datas).some((key) => { Object.keys(datas).some((key) => {
if (datas[key].dictValue == ("" + temp[val])) { if (datas[key].dictValue == '' + temp[val]) {
actions.push(datas[key].dictLabel + currentSeparator); actions.push(datas[key].dictLabel + currentSeparator)
} }
}); })
}); })
return actions.join("").substring(0, actions.join("").length - 1); return actions.join('').substring(0, actions.join('').length - 1)
} }
// 转换字符串undefined,null等转化为"" // 转换字符串undefined,null等转化为""
export function praseStrEmpty(str) { export function praseStrEmpty(str) {
if (!str || str == "undefined" || str == "null") { if (!str || str == 'undefined' || str == 'null') {
return ""; return ''
} }
return str; return str
} }
// js模仿微信朋友圈计算时间显示几天/几小时/几分钟/刚刚 // js模仿微信朋友圈计算时间显示几天/几小时/几分钟/刚刚
//datetime 格式为2019-11-22 12:23:59样式 //datetime 格式为2019-11-22 12:23:59样式
export function timeConversion(datetime) { //dateTimeStamp是一个时间毫秒注意时间戳是秒的形式在这个毫秒的基础上除以1000就是十位数的时间戳13位数的都是时间毫秒 export function timeConversion(datetime) {
// var dateTimeStamp = new Date(datetime.replace(/ /, 'T')).getTime()-8 * 60 * 60 * 1000;//这里要减去中国的时区8小时 //dateTimeStamp是一个时间毫秒注意时间戳是秒的形式在这个毫秒的基础上除以1000就是十位数的时间戳13位数的都是时间毫秒
var dateTimeStamp = new Date(datetime.replace(/ /, 'T')).getTime();//这里不减去中国的时区8小时 // var dateTimeStamp = new Date(datetime.replace(/ /, 'T')).getTime()-8 * 60 * 60 * 1000;//这里要减去中国的时区8小时
var minute = 1000 * 60; //把分半个月一个月用毫秒表示 var dateTimeStamp = new Date(datetime.replace(/ /, 'T')).getTime() //这里不减去中国的时区8小时
var hour = minute * 60; var minute = 1000 * 60 //把分半个月一个月用毫秒表示
var day = hour * 24; var hour = minute * 60
var week = day * 7; var day = hour * 24
var halfamonth = day * 15; var week = day * 7
var month = day * 30; var month = day * 30
var now = new Date().getTime(); //获取当前时间毫秒 var now = new Date().getTime() //获取当前时间毫秒
var diffValue = now - dateTimeStamp; //时间差 var diffValue = now - dateTimeStamp //时间差
if (diffValue < 0) { if (diffValue < 0) {
return '刚刚'; return '刚刚'
} }
var minC = diffValue / minute; //计算时间差的分 var minC = diffValue / minute //计算时间差的分
var hourC = diffValue / hour; var hourC = diffValue / hour
var dayC = diffValue / day; var dayC = diffValue / day
var weekC = diffValue / week; var weekC = diffValue / week
var monthC = diffValue / month; var monthC = diffValue / month
var result = "2"; var result = '2'
if (monthC >= 1 && monthC <= 3) { if (monthC >= 1 && monthC <= 3) {
result = " " + parseInt(monthC) + "月前" result = ' ' + parseInt(monthC) + '月前'
} else if (weekC >= 1 && weekC <= 3) { } else if (weekC >= 1 && weekC <= 3) {
result = " " + parseInt(weekC) + "周前" result = ' ' + parseInt(weekC) + '周前'
} else if (dayC >= 1 && dayC <= 6) { } else if (dayC >= 1 && dayC <= 6) {
result = " " + parseInt(dayC) + "天前" result = ' ' + parseInt(dayC) + '天前'
} else if (hourC >= 1 && hourC <= 23) { } else if (hourC >= 1 && hourC <= 23) {
result = " " + parseInt(hourC) + "小时前" result = ' ' + parseInt(hourC) + '小时前'
} else if (minC >= 1 && minC <= 59) { } else if (minC >= 1 && minC <= 59) {
result = " " + parseInt(minC) + "分钟前" result = ' ' + parseInt(minC) + '分钟前'
} else if (diffValue >= 0 && diffValue <= minute) { } else if (diffValue >= 0 && diffValue <= minute) {
result = "刚刚" result = '刚刚'
} else { } else {
var datetime = new Date(); let datetime = new Date()
datetime.setTime(dateTimeStamp); datetime.setTime(dateTimeStamp)
var Nyear = datetime.getFullYear(); {} let Nyear = datetime.getFullYear()
var Nmonth = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1; var Nmonth =
var Ndate = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate(); datetime.getMonth() + 1 < 10 ? '0' + (datetime.getMonth() + 1) : datetime.getMonth() + 1
var Nhour = datetime.getHours() < 10 ? "0" + datetime.getHours() : datetime.getHours(); var Ndate = datetime.getDate() < 10 ? '0' + datetime.getDate() : datetime.getDate()
var Nminute = datetime.getMinutes() < 10 ? "0" + datetime.getMinutes() : datetime.getMinutes(); var Nhour = datetime.getHours() < 10 ? '0' + datetime.getHours() : datetime.getHours()
var Nsecond = datetime.getSeconds() < 10 ? "0" + datetime.getSeconds() : datetime.getSeconds(); var Nminute = datetime.getMinutes() < 10 ? '0' + datetime.getMinutes() : datetime.getMinutes()
result = Nyear + "-" + Nmonth + "-" + Ndate var Nsecond = datetime.getSeconds() < 10 ? '0' + datetime.getSeconds() : datetime.getSeconds()
} result = Nyear + '-' + Nmonth + '-' + Ndate
return result; }
} return result
}

View File

@ -13,7 +13,7 @@ let storageNodeKeys = [...Object.values(auth)]
let storageData = uni.getStorageSync(storageKey) || {} let storageData = uni.getStorageSync(storageKey) || {}
const storage = { const storage = {
set: function(key, value) { set: function (key, value) {
if (storageNodeKeys.indexOf(key) != -1) { if (storageNodeKeys.indexOf(key) != -1) {
let tmp = uni.getStorageSync(storageKey) let tmp = uni.getStorageSync(storageKey)
tmp = tmp ? tmp : {} tmp = tmp ? tmp : {}
@ -21,14 +21,14 @@ const storage = {
uni.setStorageSync(storageKey, tmp) uni.setStorageSync(storageKey, tmp)
} }
}, },
get: function(key) { get: function (key) {
return storageData[key] || "" return storageData[key] || ''
}, },
remove: function(key) { remove: function (key) {
delete storageData[key] delete storageData[key]
uni.setStorageSync(storageKey, storageData) uni.setStorageSync(storageKey, storageData)
}, },
clean: function() { clean: function () {
uni.removeStorageSync(storageKey) uni.removeStorageSync(storageKey)
} }
} }

View File

@ -7,7 +7,7 @@ import { toast, showConfirm, tansParams } from '@/common/utils/common'
let timeout = 10000 let timeout = 10000
const baseUrl = config.baseUrl const baseUrl = config.baseUrl
const upload = config => { const upload = (config) => {
// 是否需要设置 token // 是否需要设置 token
const isToken = (config.headers || {}).isToken === false const isToken = (config.headers || {}).isToken === false
config.header = config.header || {} config.header = config.header || {}
@ -21,47 +21,47 @@ const upload = config => {
config.url = url config.url = url
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.uploadFile({ uni.uploadFile({
timeout: config.timeout || timeout, timeout: config.timeout || timeout,
url: baseUrl + config.url, url: baseUrl + config.url,
filePath: config.filePath, filePath: config.filePath,
name: config.name || 'file', name: config.name || 'file',
header: config.header, header: config.header,
formData: config.formData, formData: config.formData,
success: (res) => { success: (res) => {
let result = JSON.parse(res.data) let result = JSON.parse(res.data)
const code = result.code || 200 const code = result.code || 200
const msg = errorCode[code] || result.msg || errorCode['default'] const msg = errorCode[code] || result.msg || errorCode['default']
if (code === 200) { if (code === 200) {
resolve(result) resolve(result)
} else if (code == 401) { } else if (code == 401) {
showConfirm("登录状态已过期,您可以继续留在该页面,或者重新登录?").then(res => { showConfirm('登录状态已过期,您可以继续留在该页面,或者重新登录?').then((res) => {
if (res.confirm) { if (res.confirm) {
store.dispatch('LogOut') store.dispatch('LogOut')
} }
}) })
reject('无效的会话,或者会话已过期,请重新登录。') reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) { } else if (code === 500) {
toast(msg) toast(msg)
reject('500') reject('500')
} else if (code !== 200) { } else if (code !== 200) {
toast(msg) toast(msg)
reject(code) reject(code)
}
},
fail: (error) => {
let { message } = error
if (message == 'Network Error') {
message = '后端接口连接异常'
} else if (message.includes('timeout')) {
message = '系统接口请求超时'
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常'
}
toast(message)
reject(error)
} }
}) },
fail: (error) => {
let { message } = error
if (message == 'Network Error') {
message = '后端接口连接异常'
} else if (message.includes('timeout')) {
message = '系统接口请求超时'
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常'
}
toast(message)
reject(error)
}
})
}) })
} }

View File

@ -1,16 +1,14 @@
// 应用全局配置 // 应用全局配置
module.exports = { module.exports = {
// 测试环境 // 测试环境
baseUrl: 'http://127.0.0.1:9000', baseUrl: 'http://127.0.0.1:9000',
// 生产环境
// baseUrl: 'https://api.kinit.ktianc.top',
// 应用信息 // 应用信息
appInfo: { appInfo: {
// 应用版本 // 应用版本
version: "1.1.0", version: '1.2.0',
// 隐私政策不支持本地路径 // 隐私政策不支持本地路径
privacy: "http://kinit.ktianc.top/docs/privacy", privacy: 'http://kinit.ktianc.top/docs/privacy',
// 用户协议不支持本地路径 // 用户协议不支持本地路径
agreement: "http://kinit.ktianc.top/docs/agreement" agreement: 'http://kinit.ktianc.top/docs/agreement'
} }
} }

8
kinit-uni/jsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
}

View File

@ -2,8 +2,8 @@ import Vue from 'vue'
import App from './App' import App from './App'
import store from './store' // store import store from './store' // store
import plugins from './plugins' // plugins import plugins from './plugins' // plugins
import {router,RouterMount} from './permission.js' // 路由拦截 import { router, RouterMount } from './permission.js' // 路由拦截
import uView from "uview-ui" import uView from 'uview-ui'
Vue.use(uView) Vue.use(uView)
Vue.use(router) Vue.use(router)
@ -14,28 +14,28 @@ Vue.use(plugins)
// 配置后很多组件的默认尺寸就变了需要手动调整不熟悉不建议开启 // 配置后很多组件的默认尺寸就变了需要手动调整不熟悉不建议开启
// 需要在Vue.use(uView)之后执行 // 需要在Vue.use(uView)之后执行
uni.$u.setConfig({ uni.$u.setConfig({
// 修改$u.config对象的属性 // 修改$u.config对象的属性
config: { config: {
// 修改默认单位为rpx相当于执行 uni.$u.config.unit = 'rpx' // 修改默认单位为rpx相当于执行 uni.$u.config.unit = 'rpx'
unit: 'rpx' unit: 'rpx'
}, },
// 修改$u.props对象的属性 // 修改$u.props对象的属性
props: { props: {
// 修改radio组件的size参数的默认值相当于执行 uni.$u.props.radio.size = 30 // 修改radio组件的size参数的默认值相当于执行 uni.$u.props.radio.size = 30
radio: { radio: {
size: 33, size: 33,
labelSize: 30 labelSize: 30
}, },
button: { button: {
loadingSize: 28 loadingSize: 28
}, },
text: { text: {
size: 30, size: 30,
color: '#000' color: '#000'
} }
// 其他组件属性配置 // 其他组件属性配置
// ...... // ......
} }
}) })
Vue.config.productionTip = false Vue.config.productionTip = false
@ -49,9 +49,9 @@ const app = new Vue({
//v1.3.5 H5端 你应该去除原有的app.$mount();使用路由自带的渲染方式 //v1.3.5 H5端 你应该去除原有的app.$mount();使用路由自带的渲染方式
// #ifdef H5 // #ifdef H5
RouterMount(app, router, '#app') RouterMount(app, router, '#app')
// #endif // #endif
// #ifndef H5 // #ifndef H5
app.$mount(); //为了兼容小程序及app端必须这样写才有效果 app.$mount() //为了兼容小程序及app端必须这样写才有效果
// #endif // #endif

View File

@ -3,5 +3,11 @@
"uni-read-pages": "^1.0.5", "uni-read-pages": "^1.0.5",
"uni-simple-router": "^2.0.8-beta.3", "uni-simple-router": "^2.0.8-beta.3",
"uview-ui": "^2.0.34" "uview-ui": "^2.0.34"
},
"devDependencies": {
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.7.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.9.0"
} }
} }

View File

@ -7,42 +7,42 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
title: '', title: '',
content: '' content: ''
}
},
onLoad(options) {
if (options.query) {
this.title = options.query.title
this.content = options.query.content
} else {
this.title = options.title
this.content = options.content
}
uni.setNavigationBarTitle({
title: options.title
})
} }
},
onLoad(options) {
if (options.query) {
this.title = options.query.title
this.content = options.query.content
} else {
this.title = options.title
this.content = options.content
}
uni.setNavigationBarTitle({
title: options.title
})
} }
}
</script> </script>
<style scoped> <style scoped>
page { page {
background-color: #ffffff; background-color: #ffffff;
} }
.view-title { .view-title {
font-weight: bold; font-weight: bold;
} }
.view-content { .view-content {
font-size: 26rpx; font-size: 26rpx;
padding: 12px 5px 0; padding: 12px 5px 0;
color: #333; color: #333;
line-height: 24px; line-height: 24px;
font-weight: normal; font-weight: normal;
} }
</style> </style>

View File

@ -1,35 +1,35 @@
<template> <template>
<view v-if="params.url"> <view v-if="params.url">
<!-- 不支持本地路径 --> <!-- 不支持本地路径 -->
<web-view :webview-styles="webviewStyles" :src="`${params.url}`"></web-view> <web-view :webview-styles="webviewStyles" :src="`${params.url}`"></web-view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() { props: {
return { src: {
params: {}, type: [String],
webviewStyles: { default: null
progress: { }
color: "#FF3333" },
} data() {
return {
params: {},
webviewStyles: {
progress: {
color: '#FF3333'
} }
} }
}, }
props: { },
src: { onLoad(event) {
type: [String], this.params = event
default: null if (event.title) {
} uni.setNavigationBarTitle({
}, title: event.title
onLoad(event) { })
this.params = event
if (event.title) {
uni.setNavigationBarTitle({
title: event.title
})
}
} }
} }
}
</script> </script>

View File

@ -8,48 +8,48 @@
</template> </template>
<script> <script>
import { wxShareMixins } from '@/common/mixins/share.js' import { wxShareMixins } from '@/common/mixins/share.js'
export default { export default {
mixins: [wxShareMixins], mixins: [wxShareMixins],
computed: { computed: {
name() { name() {
return this.$store.state.auth.name return this.$store.state.auth.name
},
logo() {
return this.$store.state.app.logo
},
logoImage() {
return this.$store.state.app.logoImage
}
}, },
logo() {
return this.$store.state.app.logo
},
logoImage() {
return this.$store.state.app.logoImage
}
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
.content { .content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.logo { .logo {
height: 200rpx; height: 200rpx;
width: 200rpx; width: 200rpx;
margin-top: 200rpx; margin-top: 200rpx;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 50rpx; margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
.title {
font-size: 36rpx;
color: #8f8f94;
}
}
} }
.text-area {
display: flex;
justify-content: center;
.title {
font-size: 36rpx;
color: #8f8f94;
}
}
}
</style> </style>

View File

@ -1,201 +1,212 @@
<template> <template>
<view class="normal-login-container"> <view class="normal-login-container">
<view class="logo-content align-center justify-center flex"> <view class="logo-content align-center justify-center flex">
<image v-if="logo" style="width: 100rpx;height: 100rpx;" :src="logoImage" mode="widthFix"> <image v-if="logo" style="width: 100rpx; height: 100rpx" :src="logoImage" mode="widthFix">
</image> </image>
<text class="title">{{ title }}</text> <text class="title">{{ title }}</text>
</view> </view>
<view class="login-form-content"> <view class="login-form-content">
<view class="input-item flex align-center"> <view class="input-item flex align-center">
<view class="iconfont icon-user icon"></view> <view class="iconfont icon-user icon"></view>
<input v-model="loginForm.telephone" class="input" type="text" placeholder="请输入手机号" maxlength="30" /> <input
v-model="loginForm.telephone"
class="input"
type="text"
placeholder="请输入手机号"
maxlength="30"
/>
</view> </view>
<view class="input-item flex align-center"> <view class="input-item flex align-center">
<view class="iconfont icon-password icon"></view> <view class="iconfont icon-password icon"></view>
<input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" /> <input
v-model="loginForm.password"
type="password"
class="input"
placeholder="请输入密码"
maxlength="20"
/>
</view> </view>
<view class="action-btn"> <view class="action-btn">
<!-- <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button> --> <!-- <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button> -->
<u-button type="primary" text="登录" @click="handleLogin" shape="circle"></u-button> <u-button type="primary" text="登录" shape="circle" @click="handleLogin"></u-button>
</view> </view>
</view> </view>
<view class="xieyi text-center"> <view class="xieyi text-center">
<text class="text-grey1">登录即代表同意</text> <text class="text-grey1">登录即代表同意</text>
<text @click="handleUserAgrement" class="text-blue">用户协议</text> <text class="text-blue" @click="handleUserAgrement">用户协议</text>
<text @click="handlePrivacy" class="text-blue">隐私协议</text> <text class="text-blue" @click="handlePrivacy">隐私协议</text>
</view>
<view class="footer text-center">
<u-button
type="primary"
text="微信一键登录"
shape="circle"
open-type="getPhoneNumber"
@getphonenumber="wxLogin"
></u-button>
</view> </view>
<view class="footer text-center">
<u-button
type="primary"
text="微信一键登录"
shape="circle"
open-type="getPhoneNumber"
@getphonenumber="wxLogin"
></u-button>
</view>
</view> </view>
</template> </template>
<script> <script>
import { wxLoginMixins } from '@/common/mixins/auth.js' import { wxLoginMixins } from '@/common/mixins/auth.js'
export default { export default {
mixins: [wxLoginMixins], mixins: [wxLoginMixins],
data() { data() {
return { return {
loginForm: { loginForm: {
telephone: "15020221010", telephone: '15020221010',
password: "kinit2022" password: 'kinit2022'
} }
}
},
computed: {
title() {
return this.$store.state.app.title
},
logo() {
return this.$store.state.app.logo
},
logoImage() {
return this.$store.state.app.logoImage
},
privacy() {
return this.$store.state.app.privacy
},
agreement() {
return this.$store.state.app.agreement
},
isResetPassword() {
return this.$store.state.auth.isResetPassword
}
},
methods: {
//
handlePrivacy() {
const title = '隐私政策'
this.$tab.navigateTo(`/pages/common/webview/index?title=${title}&url=${this.privacy}`)
},
//
handleUserAgrement() {
const title = '用户协议'
this.$tab.navigateTo(`/pages/common/webview/index?title=${title}&url=${this.agreement}`)
},
//
async handleLogin() {
if (this.loginForm.telephone === '') {
this.$modal.msgError('请输入您的手机号')
} else if (this.loginForm.password === '') {
this.$modal.msgError('请输入您的密码')
} else {
this.$modal.loading('正在登录中...')
this.pwdLogin()
} }
}, },
computed: { //
title() { async pwdLogin() {
return this.$store.state.app.title this.$store.dispatch('auth/Login', this.loginForm).then(() => {
}, this.$modal.closeLoading()
logo() { this.loginSuccess()
return this.$store.state.app.logo })
}, },
logoImage() { //
return this.$store.state.app.logoImage loginSuccess() {
}, if (this.isResetPassword) {
privacy() { this.$tab.reLaunch('/pages/index')
return this.$store.state.app.privacy } else {
}, this.$tab.reLaunch('/pages/mine/pwd/index')
agreement() { }
return this.$store.state.app.agreement },
}, //
isResetPassword() { wxLogin(detail) {
return this.$store.state.auth.isResetPassword this.onGetPhoneNumber(detail).then((res) => {
} this.loginSuccess()
}, })
methods: {
//
handlePrivacy() {
const title = '隐私政策'
this.$tab.navigateTo(`/pages/common/webview/index?title=${title}&url=${this.privacy}`)
},
//
handleUserAgrement() {
const title = '用户协议'
this.$tab.navigateTo(`/pages/common/webview/index?title=${title}&url=${this.agreement}`)
},
//
async handleLogin() {
if (this.loginForm.telephone === "") {
this.$modal.msgError("请输入您的手机号")
} else if (this.loginForm.password === "") {
this.$modal.msgError("请输入您的密码")
}else {
this.$modal.loading("正在登录中...")
this.pwdLogin()
}
},
//
async pwdLogin() {
this.$store.dispatch('auth/Login', this.loginForm).then(() => {
this.$modal.closeLoading()
this.loginSuccess()
})
},
//
loginSuccess() {
if (this.isResetPassword) {
this.$tab.reLaunch('/pages/index')
} else {
this.$tab.reLaunch('/pages/mine/pwd/index')
}
},
//
wxLogin(detail) {
this.onGetPhoneNumber(detail).then(res => {
this.loginSuccess()
})
},
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
page { page {
background-color: #ffffff; background-color: #ffffff;
} }
.normal-login-container { .normal-login-container {
width: 100%;
height: 100vh;
position: relative;
.logo-content {
width: 100%; width: 100%;
height: 100vh; font-size: 21px;
position: relative; text-align: center;
padding-top: 15%;
.logo-content { image {
width: 100%; border-radius: 4px;
font-size: 21px;
text-align: center;
padding-top: 15%;
image {
border-radius: 4px;
}
.title {
margin-left: 10px;
}
} }
.login-form-content { .title {
text-align: center; margin-left: 10px;
}
}
.login-form-content {
text-align: center;
margin: 20px auto;
margin-top: 15%;
width: 80%;
.input-item {
margin: 20px auto; margin: 20px auto;
margin-top: 15%; background-color: #f5f6f7;
width: 80%; height: 45px;
border-radius: 20px;
.input-item {
margin: 20px auto;
background-color: #f5f6f7;
height: 45px;
border-radius: 20px;
.icon {
font-size: 38rpx;
margin-left: 10px;
color: #999;
}
.input {
width: 100%;
font-size: 14px;
line-height: 20px;
text-align: left;
padding-left: 15px;
}
.icon {
font-size: 38rpx;
margin-left: 10px;
color: #999;
} }
.login-btn { .input {
margin-top: 40px; width: 100%;
height: 45px; font-size: 14px;
} line-height: 20px;
text-align: left;
.xieyi { padding-left: 15px;
color: #333;
margin-top: 20px;
} }
} }
.easyinput { .login-btn {
width: 100%; margin-top: 40px;
height: 45px;
}
.xieyi {
color: #333;
margin-top: 20px;
} }
.footer {
margin: 20px auto;
width: 80%;
position: absolute;
bottom: 30px;
left: 10%;
}
} }
.login-code-img { .easyinput {
height: 45px; width: 100%;
} }
.footer {
margin: 20px auto;
width: 80%;
position: absolute;
bottom: 30px;
left: 10%;
}
}
.login-code-img {
height: 45px;
}
</style> </style>

View File

@ -1,8 +1,7 @@
<template> <template>
<view class="about-container"> <view class="about-container">
<view class="header-section text-center"> <view class="header-section text-center">
<image style="width: 150rpx;height: 150rpx;" :src="logoImage" mode="widthFix"> <image style="width: 150rpx; height: 150rpx" :src="logoImage" mode="widthFix"> </image>
</image>
<uni-title type="h2" :title="title"></uni-title> <uni-title type="h2" :title="title"></uni-title>
</view> </view>
@ -11,7 +10,7 @@
<view class="list-cell list-cell-arrow"> <view class="list-cell list-cell-arrow">
<view class="menu-item-box"> <view class="menu-item-box">
<view>版本信息</view> <view>版本信息</view>
<view class="text-right">v{{version}}</view> <view class="text-right">v{{ version }}</view>
</view> </view>
</view> </view>
<view class="list-cell list-cell-arrow"> <view class="list-cell list-cell-arrow">
@ -30,7 +29,7 @@
<view class="menu-item-box"> <view class="menu-item-box">
<view>公司网站</view> <view>公司网站</view>
<view class="text-right"> <view class="text-right">
<uni-link :href="siteUrl" :text="siteUrl" showUnderLine="false"></uni-link> <uni-link :href="siteUrl" :text="siteUrl" show-under-line="false"></uni-link>
</view> </view>
</view> </view>
</view> </view>
@ -44,52 +43,52 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return {} return {}
},
computed: {
version() {
return this.$store.state.app.version
}, },
computed: { title() {
version() { return this.$store.state.app.title
return this.$store.state.app.version },
}, logoImage() {
title() { return this.$store.state.app.logoImage
return this.$store.state.app.title },
}, siteUrl() {
logoImage() { return this.$store.state.app.siteUrl
return this.$store.state.app.logoImage },
}, WXEmail() {
siteUrl() { return this.$store.state.app.WXEmail
return this.$store.state.app.siteUrl },
}, WXPhone() {
WXEmail() { return this.$store.state.app.WXPhone
return this.$store.state.app.WXEmail },
}, footerContent() {
WXPhone() { return this.$store.state.app.footerContent
return this.$store.state.app.WXPhone }
},
footerContent() {
return this.$store.state.app.footerContent
}
},
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
page { page {
background-color: #f8f8f8; background-color: #f8f8f8;
} }
.copyright { .copyright {
margin-top: 50rpx; margin-top: 50rpx;
text-align: center; text-align: center;
line-height: 60rpx; line-height: 60rpx;
color: #999; color: #999;
} }
.header-section { .header-section {
display: flex; display: flex;
padding: 30rpx 0 0; padding: 30rpx 0 0;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,15 @@
<view>{{ item.name }}</view> <view>{{ item.name }}</view>
</view> </view>
<view class="childList"> <view class="childList">
<view v-for="(issue, zindex) in item.issues" :key="zindex" class="question" hover-class="hover" <view
@click="handleText(issue)"> v-for="(issue, zindex) in item.issues"
:key="zindex"
class="question"
hover-class="hover"
@click="handleText(issue)"
>
<view class="text-item">{{ issue.title }}</view> <view class="text-item">{{ issue.title }}</view>
<view class="line" v-if="zindex !== item.issues.length - 1"></view> <view v-if="zindex !== item.issues.length - 1" class="line"></view>
</view> </view>
</view> </view>
</view> </view>
@ -16,68 +21,68 @@
</template> </template>
<script> <script>
import { getIssueCategoryList } from '@/common/request/api/vadmin/help/issue.js' import { getIssueCategoryList } from '@/common/request/api/vadmin/help/issue.js'
export default { export default {
data() { data() {
return { return {
list: [] list: []
} }
}, },
onLoad() { onLoad() {
getIssueCategoryList().then(res => { getIssueCategoryList().then((res) => {
this.list = res.data this.list = res.data
}) })
}, },
methods: { methods: {
handleText(item) { handleText(item) {
this.$tab.navigateTo(`/pages/mine/help/issue/info?id=${item.id}`) this.$tab.navigateTo(`/pages/mine/help/issue/info?id=${item.id}`)
}
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
page { page {
background-color: #f8f8f8; background-color: #f8f8f8;
} }
.help-container { .help-container {
margin-bottom: 100rpx; margin-bottom: 100rpx;
padding: 30rpx; padding: 30rpx;
} }
.list-title { .list-title {
margin-bottom: 30rpx; margin-bottom: 30rpx;
} }
.childList { .childList {
background: #ffffff; background: #ffffff;
box-shadow: 0px 0px 10rpx rgba(193, 193, 193, 0.2); box-shadow: 0px 0px 10rpx rgba(193, 193, 193, 0.2);
border-radius: 16rpx; border-radius: 16rpx;
margin-top: 10rpx; margin-top: 10rpx;
} }
.line { .line {
width: 100%; width: 100%;
height: 1rpx; height: 1rpx;
background-color: #F5F5F5; background-color: #f5f5f5;
} }
.text-title { .text-title {
color: #303133; color: #303133;
font-size: 32rpx; font-size: 32rpx;
font-weight: bold; font-weight: bold;
margin-left: 10rpx; margin-left: 10rpx;
} }
.text-item { .text-item {
font-size: 28rpx; font-size: 28rpx;
padding: 24rpx; padding: 24rpx;
} }
.question { .question {
color: #606266; color: #606266;
font-size: 28rpx; font-size: 28rpx;
} }
</style> </style>

View File

@ -1,70 +1,71 @@
<template> <template>
<view> <view>
<uni-card v-if="isSuccess" class="view-title" :title="model.title"> <uni-card v-if="isSuccess" class="view-title">
<u--text :text="model.title" bold></u--text>
<rich-text class="uni-body view-content" :nodes="model.content"></rich-text> <rich-text class="uni-body view-content" :nodes="model.content"></rich-text>
</uni-card> </uni-card>
<u-empty <u-empty
v-else v-else
mode="data" mode="data"
icon="https://cdn.uviewui.com/uview/empty/data.png" icon="https://cdn.uviewui.com/uview/empty/data.png"
:marginTop="100" :margin-top="100"
:width="300" :width="300"
:height="300" :height="300"
> >
</u-empty> </u-empty>
</view> </view>
</template> </template>
<script> <script>
import { getIssue, updateIssueAddViewNumber } from '@/common/request/api/vadmin/help/issue.js' import { getIssue, updateIssueAddViewNumber } from '@/common/request/api/vadmin/help/issue.js'
export default { export default {
data() { data() {
return { return {
isSuccess: true, isSuccess: true,
model: {}, model: {},
dataId: null dataId: null
}
},
onLoad(options) {
if (options.query) {
this.dataId = options.query.id
} else if (options) {
this.dataId = options.id
}
this.getData()
},
methods: {
getData() {
if (!this.dataId) {
return
} }
}, getIssue(this.dataId)
onLoad(options) { .then((res) => {
if (options.query) { this.model = res.data
this.dataId = options.query.id uni.setNavigationBarTitle({
} else if (options) { title: res.data.title
this.dataId = options.id })
} updateIssueAddViewNumber(this.dataId)
this.getData() })
}, .catch(() => {
methods: { this.isSuccess = false
getData() { })
if (!this.dataId) { return; } }
getIssue(this.dataId).then(res => {
this.model = res.data;
uni.setNavigationBarTitle({
title: res.data.title
})
updateIssueAddViewNumber(this.dataId)
}).catch(() => {
this.isSuccess = false
})
}
}
} }
}
</script> </script>
<style scoped> <style scoped>
page { page {
background-color: #ffffff; background-color: #ffffff;
} }
.view-title { .view-content {
font-weight: bold; font-size: 26rpx;
} padding: 12px 5px 0;
color: #333;
.view-content { line-height: 24px;
font-size: 26rpx; font-weight: normal;
padding: 12px 5px 0; }
color: #333;
line-height: 24px;
font-weight: normal;
}
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<view class="mine-container" :style="{height: `${windowHeight}px`}"> <view class="mine-container" :style="{ height: `${windowHeight}px` }">
<!--顶部个人信息栏--> <!--顶部个人信息栏-->
<view class="header-section"> <view class="header-section">
<view class="flex padding justify-between"> <view class="flex padding justify-between">
@ -7,19 +7,23 @@
<view v-if="!avatar" class="cu-avatar xl round bg-white"> <view v-if="!avatar" class="cu-avatar xl round bg-white">
<view class="iconfont icon-people text-gray icon"></view> <view class="iconfont icon-people text-gray icon"></view>
</view> </view>
<image v-if="avatar" @click="handleToAvatar" :src="avatar" class="cu-avatar xl round" mode="aspectFill"> <image
v-if="avatar"
:src="avatar"
class="cu-avatar xl round"
mode="aspectFill"
@click="handleToAvatar"
>
</image> </image>
<view v-if="!name" @click="handleToLogin" class="login-tip"> <view v-if="!name" class="login-tip" @click="handleToLogin"> 点击登录 </view>
点击登录 <view v-if="name" class="user-info" @click="handleToInfo">
</view>
<view v-if="name" @click="handleToInfo" class="user-info">
<view class="u_title"> <view class="u_title">
{{ name }} {{ name }}
</view> </view>
</view> </view>
</view> </view>
<view @click="handleToInfo" class="flex align-center"> <view class="flex align-center" @click="handleToInfo">
<text style="font-size: 30rpx;">个人信息</text> <text style="font-size: 30rpx">个人信息</text>
<view class="iconfont icon-right1"></view> <view class="iconfont icon-right1"></view>
</view> </view>
</view> </view>
@ -41,7 +45,9 @@
</view> </view>
<view class="action-item" @click="praiseMe"> <view class="action-item" @click="praiseMe">
<view class="iconfont icon-dianzan text-green icon"></view> <view class="iconfont icon-dianzan text-green icon"></view>
<view style="height: 0px;" :animation="animationData" class="praise-me animation-opacity"> +1 </view> <view style="height: 0px" :animation="animationData" class="praise-me animation-opacity">
+1
</view>
<text class="text">点赞我们</text> <text class="text">点赞我们</text>
</view> </view>
</view> </view>
@ -72,171 +78,167 @@
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import storage from '@/common/utils/storage' export default {
data() {
export default { return {
data() { animation: '',
return { animationData: {}
animation: "", }
animationData: {} },
} onLoad() {
}, // 1
onLoad() { this.animation = uni.createAnimation()
// 1 this.animationData = {}
this.animation = uni.createAnimation(); },
this.animationData = {}; onUnload() {
}, // 5
onUnload() { this.animationData = {}
// 5 },
this.animationData = {}; computed: {
}, version() {
computed: { return this.$store.state.app.version
version() {
return this.$store.state.app.version
},
name() {
return this.$store.state.auth.name
},
avatar() {
return this.$store.state.auth.avatar
},
windowHeight() {
return uni.getSystemInfoSync().windowHeight - 50
}
}, },
methods: { name() {
handleToInfo() { return this.$store.state.auth.name
this.$tab.navigateTo('/pages/mine/info/index') },
}, avatar() {
handleToEditInfo() { return this.$store.state.auth.avatar
this.$tab.navigateTo('/pages/mine/info/edit') },
}, windowHeight() {
handleToSetting() { return uni.getSystemInfoSync().windowHeight - 50
this.$tab.navigateTo('/pages/mine/setting/index') }
}, },
handleToLogin() { methods: {
this.$tab.reLaunch('/pages/login/login') handleToInfo() {
}, this.$tab.navigateTo('/pages/mine/info/index')
handleToAvatar() { },
this.$tab.navigateTo('/pages/mine/avatar/index') handleToEditInfo() {
}, this.$tab.navigateTo('/pages/mine/info/edit')
handleLogout() { },
this.$modal.confirm('确定注销并退出系统吗?').then(() => { handleToSetting() {
this.$store.dispatch('LogOut').then(() => { this.$tab.navigateTo('/pages/mine/setting/index')
this.$tab.reLaunch('/pages/index') },
}) handleToLogin() {
this.$tab.reLaunch('/pages/login/login')
},
handleToAvatar() {
this.$tab.navigateTo('/pages/mine/avatar/index')
},
handleLogout() {
this.$modal.confirm('确定注销并退出系统吗?').then(() => {
this.$store.dispatch('LogOut').then(() => {
this.$tab.reLaunch('/pages/index')
}) })
}, })
handleHelp() { },
this.$tab.navigateTo('/pages/mine/help/issue/index') handleHelp() {
}, this.$tab.navigateTo('/pages/mine/help/issue/index')
handleAbout() { },
this.$tab.navigateTo('/pages/mine/about/index') handleAbout() {
}, this.$tab.navigateTo('/pages/mine/about/index')
handleJiaoLiuQun() { },
this.$modal.showToast('模块建设中~') handleJiaoLiuQun() {
}, this.$modal.showToast('模块建设中~')
handleBuilding() { },
this.$modal.showToast('模块建设中~') handleBuilding() {
}, this.$modal.showToast('模块建设中~')
// },
praiseMe() { //
// 2 step() praiseMe() {
this.animation.translateY(-90).opacity(1).step({ // 2 step()
duration: 400 this.animation.translateY(-90).opacity(1).step({
}); duration: 400
})
// 3 exportanimation
this.animationData = this.animation.export(); // 3 exportanimation
this.animationData = this.animation.export()
// 4
setTimeout(()=> { // 4
this.animation.translateY(0).opacity(0).step({ setTimeout(() => {
duration: 0 this.animation.translateY(0).opacity(0).step({
}); duration: 0
this.animationData = this.animation.export(); })
}, 300) this.animationData = this.animation.export()
}, }, 300)
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.praise-me { .praise-me {
font-size: 14px; font-size: 14px;
color: #feab2a; color: #feab2a;
} }
.animation-opacity { .animation-opacity {
font-weight: bold; font-weight: bold;
opacity: 0; opacity: 0;
} }
page { page {
background-color: #f5f6f7; background-color: #f5f6f7;
}
.mine-container {
width: 100%;
height: 100%;
.header-section {
padding: 15px 15px 45px 15px;
background-color: #3c96f3;
color: white;
.login-tip {
font-size: 18px;
margin-left: 10px;
}
.cu-avatar {
border: 2px solid #eaeaea;
.icon {
font-size: 40px;
}
}
.user-info {
margin-left: 15px;
.u_title {
font-size: 37rpx;
line-height: 30px;
}
}
} }
.mine-container { .content-section {
width: 100%; position: relative;
height: 100%; top: -50px;
.mine-actions {
margin: 15px 15px;
padding: 20px 0px;
border-radius: 8px;
background-color: white;
.header-section { .action-item {
padding: 15px 15px 45px 15px;
background-color: #3c96f3;
color: white;
.login-tip {
font-size: 18px;
margin-left: 10px;
}
.cu-avatar {
border: 2px solid #eaeaea;
.icon { .icon {
font-size: 40px; font-size: 28px;
} }
}
.user-info { .text {
margin-left: 15px; display: block;
font-size: 13px;
.u_title { margin: 8px 0px;
font-size: 37rpx;
line-height: 30px;
}
}
}
.content-section {
position: relative;
top: -50px;
.mine-actions {
margin: 15px 15px;
padding: 20px 0px;
border-radius: 8px;
background-color: white;
.action-item {
.icon {
font-size: 28px;
}
.text {
display: block;
font-size: 13px;
margin: 8px 0px;
}
} }
} }
} }
} }
}
</style> </style>

View File

@ -1,168 +1,128 @@
<template> <template>
<view class="container"> <view class="container">
<view style="padding: 20px;"> <view style="padding: 20px">
<u--form <u--form ref="formRef" label-position="left" label-width="100px" :model="form" :rules="rules">
labelPosition="left" <u-form-item label="用户姓名" prop="name" border-bottom :required="true">
labelWidth="100px" <u--input v-model="form.name" placeholder="请输入用户姓名" border="none"></u--input>
:model="form" </u-form-item>
:rules="rules" <u-form-item label="用户昵称" prop="nickname" border-bottom :required="false">
ref="formRef" <u--input v-model="form.nickname" placeholder="请输入用户昵称" border="none"></u--input>
> </u-form-item>
<u-form-item <u-form-item label="手机号码" prop="telephone" border-bottom :required="true">
label="用户姓名" <u--input v-model="form.telephone" placeholder="请输入手机号码" border="none"></u--input>
prop="name" </u-form-item>
borderBottom <u-form-item label="用户性别" prop="gender" border-bottom :required="false">
:required="true" <u-radio-group v-model="form.gender">
> <u-radio
<u--input v-for="(item, index) in genderOptions"
v-model="form.name" :key="index"
placeholder="请输入用户姓名" :custom-style="{ marginRight: '16px' }"
border="none" :label="item.label"
></u--input> :name="item.value"
</u-form-item> >
<u-form-item </u-radio>
label="用户昵称" </u-radio-group>
prop="nickname" </u-form-item>
borderBottom </u--form>
:required="false" <view style="margin-top: 20px">
> <u-button :loading="btnLoading" type="primary" text="提交" @click="submit"> </u-button>
<u--input </view>
v-model="form.nickname" </view>
placeholder="请输入用户昵称"
border="none"
></u--input>
</u-form-item>
<u-form-item
label="手机号码"
prop="telephone"
borderBottom
:required="true"
>
<u--input
v-model="form.telephone"
placeholder="请输入手机号码"
border="none"
></u--input>
</u-form-item>
<u-form-item
label="用户性别"
prop="gender"
borderBottom
:required="false"
>
<u-radio-group v-model="form.gender">
<u-radio
:customStyle="{marginRight: '16px'}"
v-for="(item, index) in genderOptions"
:key="index"
:label="item.label"
:name="item.value"
>
</u-radio>
</u-radio-group>
</u-form-item>
</u--form>
<view style="margin-top: 20px;">
<u-button
:loading="btnLoading"
type="primary"
@click="submit"
text="提交"
>
</u-button>
</view>
</view>
</view> </view>
</template> </template>
<script> <script>
import { getInfo } from '@/common/request/api/login' import { getInfo } from '@/common/request/api/login'
import { updateCurrentUser } from '@/common/request/api/vadmin/auth/user.js' import { updateCurrentUser } from '@/common/request/api/vadmin/auth/user.js'
export default { export default {
data() { data() {
return { return {
btnLoading: false, btnLoading: false,
form: { form: {
name: "", name: '',
nickname: "", nickname: '',
telephone: "", telephone: '',
gender: "" gender: ''
}, },
rules: { rules: {
name: { name: {
type: 'string',
required: true,
message: '请填写姓名',
trigger: ['blur', 'change']
},
telephone: [
{
type: 'string', type: 'string',
required: true, required: true,
message: '请填写姓名', message: '请填写正确手机号',
trigger: ['blur', 'change'] trigger: ['blur', 'change']
}, },
telephone: [ {
{ validator: (rule, value, callback) => {
type: 'string', // truefalse
required: true, // uni.$u.test.mobile()truefalse
message: '请填写正确手机号', return uni.$u.test.mobile(value)
trigger: ['blur', 'change'] },
}, message: '手机号码不正确',
{ // blurchange
validator: (rule, value, callback) => { trigger: ['change', 'blur']
// truefalse }
// uni.$u.test.mobile()truefalse ]
return uni.$u.test.mobile(value);
},
message: '手机号码不正确',
// blurchange
trigger: ['change','blur'],
}
]
},
genderOptions: []
}
},
onLoad() {
this.$store.dispatch('dict/getDicts', ["sys_vadmin_gender"]).then(result => {
this.genderOptions = result.sys_vadmin_gender
})
// this.resetForm()
this.getUser()
},
onReady() {
//onReady uni-app
this.$refs.formRef.setRules(this.rules)
},
methods: {
resetForm() {
this.form = {
name: "",
nickname: "",
telephone: "",
gender: ""
}
},
getUser() {
this.$modal.loading("加载中")
getInfo().then(res => {
this.form = res.data
}).finally(() => {
this.$modal.closeLoading()
})
}, },
submit(ref) { genderOptions: []
this.$refs.formRef.validate().then(res => { }
this.btnLoading = true },
updateCurrentUser(this.form).then(res => { onLoad() {
this.$store.dispatch('auth/UpdateInfo', res.data) this.$store.dispatch('dict/getDicts', ['sys_vadmin_gender']).then((result) => {
this.$modal.msgSuccess("更新成功"); this.genderOptions = result.sys_vadmin_gender
}).finally(() => { })
this.btnLoading = false // this.resetForm()
}) this.getUser()
}) },
onReady() {
//onReady uni-app
this.$refs.formRef.setRules(this.rules)
},
methods: {
resetForm() {
this.form = {
name: '',
nickname: '',
telephone: '',
gender: ''
} }
},
getUser() {
this.$modal.loading('加载中')
getInfo()
.then((res) => {
this.form = res.data
})
.finally(() => {
this.$modal.closeLoading()
})
},
submit(ref) {
this.$refs.formRef.validate().then((res) => {
this.btnLoading = true
updateCurrentUser(this.form)
.then((res) => {
this.$store.dispatch('auth/UpdateInfo', res.data)
this.$modal.msgSuccess('更新成功')
})
.finally(() => {
this.btnLoading = false
})
})
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
page { page {
background-color: #ffffff; background-color: #ffffff;
} }
</style> </style>

View File

@ -1,49 +1,49 @@
<template> <template>
<view class="container"> <view class="container">
<u-cell-group> <u-cell-group>
<u-cell title="姓名" :value="name"> <u-cell title="姓名" :value="name">
<u-icon slot="icon" class="iconfont icon-user"></u-icon> <u-icon slot="icon" class="iconfont icon-user"></u-icon>
</u-cell> </u-cell>
<u-cell title="昵称" :value="nickname"> <u-cell title="昵称" :value="nickname">
<u-icon slot="icon" class="iconfont icon-user"></u-icon> <u-icon slot="icon" class="iconfont icon-user"></u-icon>
</u-cell> </u-cell>
<u-cell title="手机号码" :value="telephone"> <u-cell title="手机号码" :value="telephone">
<u-icon slot="icon" class="iconfont icon-dianhua"></u-icon> <u-icon slot="icon" class="iconfont icon-dianhua"></u-icon>
</u-cell> </u-cell>
<u-cell title="角色" :value="roles.join(',')"> <u-cell title="角色" :value="roles.join(',')">
<u-icon slot="icon" class="iconfont icon-xitongjiaose"></u-icon> <u-icon slot="icon" class="iconfont icon-xitongjiaose"></u-icon>
</u-cell> </u-cell>
<u-cell title="创建日期" :value="createDatetime"> <u-cell title="创建日期" :value="createDatetime">
<u-icon slot="icon" class="iconfont icon-jiaofuriqi"></u-icon> <u-icon slot="icon" class="iconfont icon-jiaofuriqi"></u-icon>
</u-cell> </u-cell>
</u-cell-group> </u-cell-group>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
computed: { computed: {
name() { name() {
return this.$store.state.auth.name return this.$store.state.auth.name
}, },
nickname() { nickname() {
return this.$store.state.auth.nickname return this.$store.state.auth.nickname
}, },
telephone() { telephone() {
return this.$store.state.auth.telephone return this.$store.state.auth.telephone
}, },
roles() { roles() {
return this.$store.state.auth.roles return this.$store.state.auth.roles
}, },
createDatetime() { createDatetime() {
return this.$store.state.auth.createDatetime return this.$store.state.auth.createDatetime
} }
},
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
page { page {
background-color: #ffffff; background-color: #ffffff;
} }
</style> </style>

View File

@ -1,104 +1,87 @@
<template> <template>
<view class="pwd-retrieve-container"> <view class="pwd-retrieve-container">
<view class="header"> <uni-forms ref="form" :value="form" label-width="80px">
<u--text
v-if="!isResetPassword"
text="第一次进入系统,必须先重置密码。"
:size="33"
align="center"
>
</u--text>
</view>
<uni-forms ref="form" :value="form" labelWidth="80px">
<uni-forms-item name="newPassword" label="新密码"> <uni-forms-item name="newPassword" label="新密码">
<uni-easyinput type="password" v-model="form.password" placeholder="请输入新密码" /> <uni-easyinput v-model="form.password" type="password" placeholder="请输入新密码" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="confirmPassword" label="确认密码"> <uni-forms-item name="confirmPassword" label="确认密码">
<uni-easyinput type="password" v-model="form.password_two" placeholder="请确认新密码" /> <uni-easyinput v-model="form.password_two" type="password" placeholder="请确认新密码" />
</uni-forms-item> </uni-forms-item>
<u-button text="提交" @click="submit" type="primary"></u-button> <u-button text="提交" color="#e09bc7" @click="submit"></u-button>
</uni-forms> </uni-forms>
</view> </view>
</template> </template>
<script> <script>
import { postCurrentUserResetPassword } from '@/common/request/api/vadmin/auth/user.js' import { postCurrentUserResetPassword } from '@/common/request/api/vadmin/auth/user.js'
export default { export default {
data() { data() {
return { return {
form: { form: {
password: undefined, password: undefined,
password_two: undefined password_two: undefined
},
rules: {
password: {
rules: [
{
required: true,
errorMessage: '新密码不能为空'
},
{
minLength: 8,
maxLength: 20,
errorMessage: '长度在 8 到 20 个字符'
}
]
}, },
rules: { password_two: {
password: { rules: [
rules: [{ {
required: true, required: true,
errorMessage: '新密码不能为空', errorMessage: '确认密码不能为空'
}, },
{ {
minLength: 8, validateFunction: (rule, value, data) => data.password === value,
maxLength: 20, errorMessage: '两次输入的密码不一致'
errorMessage: '长度在 8 到 20 个字符' }
} ]
]
},
password_two: {
rules: [{
required: true,
errorMessage: '确认密码不能为空'
}, {
validateFunction: (rule, value, data) => data.password === value,
errorMessage: '两次输入的密码不一致'
}
]
}
} }
} }
}, }
computed: { },
isResetPassword() { onReady() {
return this.$store.state.auth.isResetPassword this.$refs.form.setRules(this.rules)
} },
}, methods: {
onReady() { submit() {
this.$refs.form.setRules(this.rules) this.$refs.form.validate().then((res) => {
}, this.$modal.loading('正在提交')
methods: { postCurrentUserResetPassword(this.form)
submit() { .then((response) => {
this.$refs.form.validate().then(res => { this.form = {
this.$modal.loading("正在提交") password: '',
postCurrentUserResetPassword(this.form).then(response => { password_two: ''
this.form = { }
password: "", this.$modal.msgSuccess('修改成功')
password_two: "" })
} .finally(() => {
this.$modal.msgSuccess("重置成功") this.$modal.closeLoading()
if (!this.isResetPassword) { })
this.$store.commit('auth/SET_IS_RESET_PASSWORD', true) })
this.$tab.reLaunch('/pages/index')
}
}).finally(() => {
this.$modal.closeLoading()
})
})
}
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
page { page {
background-color: #ffffff; background-color: #ffffff;
} }
.pwd-retrieve-container { .pwd-retrieve-container {
padding-top: 36rpx; padding-top: 36rpx;
padding: 15px; padding: 15px;
}
.header {
padding-bottom: 36rpx;
}
}
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<view class="setting-container" :style="{height: `${windowHeight}px`}"> <view class="setting-container" :style="{ height: `${windowHeight}px` }">
<view class="menu-list"> <view class="menu-list">
<view class="list-cell list-cell-arrow" @click="handleToPwd"> <view class="list-cell list-cell-arrow" @click="handleToPwd">
<view class="menu-item-box"> <view class="menu-item-box">
@ -31,46 +31,46 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
windowHeight: uni.getSystemInfoSync().windowHeight windowHeight: uni.getSystemInfoSync().windowHeight
} }
},
methods: {
handleToPwd() {
this.$tab.navigateTo('/pages/mine/pwd/index')
}, },
methods: { handleToUpgrade() {
handleToPwd() { this.$modal.showToast('模块建设中~')
this.$tab.navigateTo('/pages/mine/pwd/index') },
}, handleCleanTmp() {
handleToUpgrade() { this.$modal.showToast('模块建设中~')
this.$modal.showToast('模块建设中~') },
}, handleLogout() {
handleCleanTmp() { this.$modal.confirm('确定注销并退出系统吗?').then(() => {
this.$modal.showToast('模块建设中~') this.$store.dispatch('auth/LogOut')
}, })
handleLogout() {
this.$modal.confirm('确定注销并退出系统吗?').then(() => {
this.$store.dispatch('auth/LogOut')
})
}
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
.page { .page {
background-color: #f8f8f8; background-color: #f8f8f8;
} }
.item-box { .item-box {
background-color: #FFFFFF; background-color: #ffffff;
margin: 30rpx; margin: 30rpx;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 10rpx; padding: 10rpx;
border-radius: 8rpx; border-radius: 8rpx;
color: #303133; color: #303133;
font-size: 32rpx; font-size: 32rpx;
} }
</style> </style>

View File

@ -2,99 +2,92 @@
<view class="work-container"> <view class="work-container">
<!-- 轮播图 --> <!-- 轮播图 -->
<u-swiper <u-swiper
:list="images" :list="images"
indicator indicator
indicatorMode="line" indicator-mode="line"
circular circular
:height="`${windowWidth / 2.5}px`" :height="`${windowWidth / 2.5}px`"
></u-swiper> ></u-swiper>
<!-- 宫格组件 --> <!-- 宫格组件 -->
<view class="grid-body"> <view class="grid-body">
<u-grid <u-grid :border="false" col="3" @click="changeGrid">
:border="false" <u-grid-item v-for="(item, index) in baseList" :key="index">
col="3" <view class="grid-item">
@click="changeGrid" <view :class="'iconfont ' + item.icon + ' grid-icon'"></view>
> <u--text :text="item.title" align="center" line-height="32px"></u--text>
<u-grid-item </view>
v-for="(item, index) in baseList" </u-grid-item>
:key="index" </u-grid>
>
<view class="grid-item">
<view :class="'iconfont ' + item.icon + ' grid-icon'"></view>
<u--text :text="item.title" align="center" lineHeight="32px"></u--text>
</view>
</u-grid-item>
</u-grid>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
windowWidth: uni.getSystemInfoSync().windowWidth, windowWidth: uni.getSystemInfoSync().windowWidth,
images: [ images: [
'https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-14/1.jpg', 'https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/system/banner/2022-11-14/1.jpg',
'/static/images/banner/banner03.jpg', '/static/images/banner/banner03.jpg',
'/static/images/banner/banner03.jpg' '/static/images/banner/banner03.jpg'
], ],
baseList: [ baseList: [
{ {
icon: 'icon-user1', icon: 'icon-user1',
title: '用户管理' title: '用户管理'
}, },
{ {
icon: 'icon-users', icon: 'icon-users',
title: '角色管理' title: '角色管理'
}, },
{ {
icon: 'icon-caidan3', icon: 'icon-caidan3',
title: '菜单管理' title: '菜单管理'
}, },
{ {
icon: 'icon-shezhitianchong', icon: 'icon-shezhitianchong',
title: '系统配置' title: '系统配置'
}, },
{ {
icon: 'icon-changguizidian', icon: 'icon-changguizidian',
title: '字典管理' title: '字典管理'
}, },
{ {
icon: 'icon-rizhi', icon: 'icon-rizhi',
title: '日志管理' title: '日志管理'
}, }
] ]
} }
}, },
methods: { methods: {
changeGrid(e) { changeGrid(e) {
this.$modal.showToast('模块建设中~') this.$modal.showToast('模块建设中~')
}
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
page { page {
background-color: #fff; background-color: #fff;
min-height: 100%; min-height: 100%;
height: auto; height: auto;
} }
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
.grid-body { .grid-body {
margin-top: 60rpx; margin-top: 60rpx;
.grid-item { .grid-item {
margin-bottom: 30rpx; margin-bottom: 30rpx;
text-align: center; text-align: center;
} }
.grid-icon { .grid-icon {
font-size: 40rpx; font-size: 40rpx;
} }
} }
</style> </style>

View File

@ -1,54 +1,60 @@
import { getToken } from '@/common/utils/auth' import { getToken } from '@/common/utils/auth'
import store from '@/store' import store from '@/store'
import { RouterMount, createRouter } from 'uni-simple-router'; import { RouterMount, createRouter } from 'uni-simple-router'
// uni-simple-router 官方文档https://www.hhyang.cn/v2/start/cross/codeRoute.html
// 登录页面 // 登录页面
const loginPage = "/pages/login/login" const loginPage = '/pages/login/login'
// 首页
const indexPage = '/pages/index'
const router = createRouter({ const router = createRouter({
platform: process.env.VUE_APP_PLATFORM, platform: process.env.VUE_APP_PLATFORM,
routes: [...ROUTES] detectBeforeLock: (router, to, navType) => {
}); if (navType === 'replaceAll' && (to.path === loginPage || to.path === indexPage)) {
router.$lockStatus = false // 取消跳转锁
}
},
routes: [...ROUTES] // ROUTES是通过webpack的defaultPlugin编译成全局变量
})
//全局路由前置守卫 //全局路由前置守卫
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (to.meta.loginAuth) { if (to.meta.loginAuth) {
// 如果跳转的路由需要登录权限则验证该权限 // 如果跳转的路由需要登录权限则验证该权限
if (getToken()) { if (getToken()) {
if (!store.state.auth.isUser) { if (!store.state.auth.isUser) {
store.dispatch('auth/GetInfo') store.dispatch('auth/GetInfo')
} }
if (to.path === loginPage) { if (to.path === loginPage) {
next({ next({
path: `/pages/index`, path: indexPage,
NAVTYPE: 'replaceAll' NAVTYPE: 'replaceAll'
}) })
} }
next(); next()
} else { } else {
next({ next({
path: loginPage, path: loginPage,
NAVTYPE: 'replaceAll' NAVTYPE: 'replaceAll'
}) })
} }
} else if (to.path === loginPage && getToken()) { } else if (to.path === loginPage && getToken()) {
// 如果跳转路由为登录页面并且存在token则跳转到首页 // 如果跳转路由为登录页面并且存在token则跳转到首页
next({ next({
path: `/pages/index`, path: indexPage,
NAVTYPE: 'replaceAll' NAVTYPE: 'replaceAll'
}) })
} else { } else {
// 不需要权限且不是登录页面则不进行验证 // 不需要权限且不是登录页面则不进行验证
next(); next()
} }
}); })
// 全局路由后置守卫 // 全局路由后置守卫
router.afterEach((to, from) => { router.afterEach((to, from) => {
// console.log('跳转结束') // console.log('跳转结束')
}) })
export { export { router, RouterMount }
router,
RouterMount
}

View File

@ -1,10 +1,10 @@
import store from '@/store' import store from '@/store'
function authPermission(permission) { function authPermission(permission) {
const all_permission = "*:*:*" const all_permission = '*:*:*'
const permissions = store.getters && store.getters.permissions const permissions = store.getters && store.getters.permissions
if (permission && permission.length > 0) { if (permission && permission.length > 0) {
return permissions.some(v => { return permissions.some((v) => {
return all_permission === v || v === permission return all_permission === v || v === permission
}) })
} else { } else {
@ -13,10 +13,10 @@ function authPermission(permission) {
} }
function authRole(role) { function authRole(role) {
const super_admin = "admin" const super_admin = 'admin'
const roles = store.getters && store.getters.roles const roles = store.getters && store.getters.roles
if (role && role.length > 0) { if (role && role.length > 0) {
return roles.some(v => { return roles.some((v) => {
return super_admin === v || v === role return super_admin === v || v === role
}) })
} else { } else {
@ -31,13 +31,13 @@ export default {
}, },
// 验证用户是否含有指定权限只需包含其中一个 // 验证用户是否含有指定权限只需包含其中一个
hasPermiOr(permissions) { hasPermiOr(permissions) {
return permissions.some(item => { return permissions.some((item) => {
return authPermission(item) return authPermission(item)
}) })
}, },
// 验证用户是否含有指定权限必须全部拥有 // 验证用户是否含有指定权限必须全部拥有
hasPermiAnd(permissions) { hasPermiAnd(permissions) {
return permissions.every(item => { return permissions.every((item) => {
return authPermission(item) return authPermission(item)
}) })
}, },
@ -47,13 +47,13 @@ export default {
}, },
// 验证用户是否含有指定角色只需包含其中一个 // 验证用户是否含有指定角色只需包含其中一个
hasRoleOr(roles) { hasRoleOr(roles) {
return roles.some(item => { return roles.some((item) => {
return authRole(item) return authRole(item)
}) })
}, },
// 验证用户是否含有指定角色必须全部拥有 // 验证用户是否含有指定角色必须全部拥有
hasRoleAnd(roles) { hasRoleAnd(roles) {
return roles.every(item => { return roles.every((item) => {
return authRole(item) return authRole(item)
}) })
} }

View File

@ -40,7 +40,7 @@ export default {
content: content, content: content,
cancelText: '取消', cancelText: '取消',
confirmText: '确定', confirmText: '确定',
success: function(res) { success: function (res) {
if (res.confirm) { if (res.confirm) {
resolve(res.confirm) resolve(res.confirm)
} }
@ -50,12 +50,12 @@ export default {
}, },
// 提示信息 // 提示信息
showToast(option) { showToast(option) {
if (typeof option === "object") { if (typeof option === 'object') {
uni.showToast(option) uni.showToast(option)
} else { } else {
uni.showToast({ uni.showToast({
title: option, title: option,
icon: "none", icon: 'none',
duration: 2500 duration: 2500
}) })
} }

View File

@ -0,0 +1,19 @@
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false,
vueIndentScriptAndStyle: false,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'none',
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
rangeStart: 0
}

View File

@ -1,26 +1,26 @@
const getters = { const getters = {
isUser: state => state.auth.isUser, isUser: (state) => state.auth.isUser,
isUserOpenid: state => state.auth.isUserOpenid, isUserOpenid: (state) => state.auth.isUserOpenid,
isResetPassword: state => state.auth.isResetPassword, isResetPassword: (state) => state.auth.isResetPassword,
token: state => state.auth.token, token: (state) => state.auth.token,
avatar: state => state.auth.avatar, avatar: (state) => state.auth.avatar,
name: state => state.auth.name, name: (state) => state.auth.name,
roles: state => state.auth.roles, roles: (state) => state.auth.roles,
permissions: state => state.auth.permissions, permissions: (state) => state.auth.permissions,
telephone: state => state.auth.telephone, telephone: (state) => state.auth.telephone,
version: state => state.app.version, version: (state) => state.app.version,
title: state => state.app.title, title: (state) => state.app.title,
logo: state => state.app.logo, logo: (state) => state.app.logo,
logoImage: state => state.app.logoImage, logoImage: (state) => state.app.logoImage,
footer: state => state.app.footer, footer: (state) => state.app.footer,
footerContent: state => state.app.footerContent, footerContent: (state) => state.app.footerContent,
icpNumber: state => state.app.icpNumber, icpNumber: (state) => state.app.icpNumber,
privacy: state => state.app.privacy, privacy: (state) => state.app.privacy,
agreement: state => state.app.agreement, agreement: (state) => state.app.agreement,
siteUrl: state => state.app.siteUrl, siteUrl: (state) => state.app.siteUrl,
WXEmail: state => state.app.WXEmail, WXEmail: (state) => state.app.WXEmail,
WXPhone: state => state.app.WXPhone, WXPhone: (state) => state.app.WXPhone,
dictObj: state => state.dict.dictObj, dictObj: (state) => state.dict.dictObj
} }
export default getters export default getters

View File

@ -21,5 +21,5 @@ const store = new Vuex.Store({
modules, modules,
getters getters
}) })
export default store export default store

View File

@ -2,76 +2,77 @@ import config from '@/config.js'
import { getSystemBaseConfigApi } from '@/common/request/api/vadmin/system/settings.js' import { getSystemBaseConfigApi } from '@/common/request/api/vadmin/system/settings.js'
const state = { const state = {
title: "", // 标题 title: '', // 标题
logo: true, // 是否开启logo显示 logo: true, // 是否开启logo显示
logoImage: '', // logo图片 logoImage: '', // logo图片
footer: true, // 显示页脚 footer: true, // 显示页脚
footerContent: '', // 页脚内容 footerContent: '', // 页脚内容
icpNumber: '', // 备案号 icpNumber: '', // 备案号
version: config.appInfo.version, // 版本 version: config.appInfo.version, // 版本
privacy: config.appInfo.privacy, // 隐私政策 privacy: config.appInfo.privacy, // 隐私政策
agreement: config.appInfo.agreement, // 用户协议 agreement: config.appInfo.agreement, // 用户协议
siteUrl: "", // 源码地址 siteUrl: '', // 源码地址
WXEmail: "", // 官方邮箱 WXEmail: '', // 官方邮箱
WXPhone: "" // 服务热线 WXPhone: '' // 服务热线
} }
const mutations = { const mutations = {
SET_TITLE: (state, title) => { SET_TITLE: (state, title) => {
state.title = title state.title = title
}, },
SET_LOGO: (state, logo) => { SET_LOGO: (state, logo) => {
state.logo = logo state.logo = logo
}, },
SET_LOGO_IMAGE: (state, logoImage) => { SET_LOGO_IMAGE: (state, logoImage) => {
state.logoImage = logoImage state.logoImage = logoImage
}, },
SET_FOOTER: (state, footer) => { SET_FOOTER: (state, footer) => {
state.footer = footer state.footer = footer
}, },
SET_FOOTER_CONTENT: (state, footerContent) => { SET_FOOTER_CONTENT: (state, footerContent) => {
state.footerContent = footerContent state.footerContent = footerContent
}, },
SET_ICPNUMBER: (state, icpNumber) => { SET_ICPNUMBER: (state, icpNumber) => {
state.icpNumber = icpNumber state.icpNumber = icpNumber
}, },
SET_VERSION: (state, version) => { SET_VERSION: (state, version) => {
state.version = version state.version = version
}, },
SET_SITE_URL: (state, siteUrl) => { SET_SITE_URL: (state, siteUrl) => {
state.siteUrl = siteUrl state.siteUrl = siteUrl
}, },
SET_WX_EMAIL: (state, WXEmail) => { SET_WX_EMAIL: (state, WXEmail) => {
state.WXEmail = WXEmail state.WXEmail = WXEmail
}, },
SET_WX_PHONE: (state, WXPhone) => { SET_WX_PHONE: (state, WXPhone) => {
state.WXPhone = WXPhone state.WXPhone = WXPhone
}, }
} }
const actions = { const actions = {
// 初始化系统配置 // 初始化系统配置
InitConfig({ commit }) { InitConfig({ commit }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getSystemBaseConfigApi().then(res => { getSystemBaseConfigApi()
commit('SET_TITLE', res.data.web_title || 'Kinit') .then((res) => {
commit('SET_LOGO_IMAGE', config.baseUrl + (res.data.web_logo || '/media/system/logo.png')) commit('SET_TITLE', res.data.web_title || 'Kinit')
commit('SET_FOOTER_CONTENT', res.data.web_copyright || 'Copyright ©2022-present K') commit('SET_LOGO_IMAGE', config.baseUrl + (res.data.web_logo || '/media/system/logo.png'))
commit('SET_ICPNUMBER', res.data.web_icp_number || '') commit('SET_FOOTER_CONTENT', res.data.web_copyright || 'Copyright ©2022-present K')
commit('SET_SITE_URL', res.data.wx_server_site || '') commit('SET_ICPNUMBER', res.data.web_icp_number || '')
commit('SET_WX_EMAIL', res.data.wx_server_email || '') commit('SET_SITE_URL', res.data.wx_server_site || '')
commit('SET_WX_PHONE', res.data.wx_server_phone || '') commit('SET_WX_EMAIL', res.data.wx_server_email || '')
resolve() commit('SET_WX_PHONE', res.data.wx_server_phone || '')
}).catch(error => { resolve()
reject(error) })
}) .catch((error) => {
}) reject(error)
} })
})
}
} }
export default { export default {
namespaced: true, // 使用命名空间去访问模块中属性user/login namespaced: true, // 使用命名空间去访问模块中属性user/login
state, state,
mutations, mutations,
actions actions

View File

@ -1,164 +1,192 @@
import storage from '@/common/utils/storage' import storage from '@/common/utils/storage'
import { auth } from '@/common/utils/constant' import { auth } from '@/common/utils/constant'
import { getInfo, wxCodeLogin, login } from '@/common/request/api/login' import { getInfo, wxCodeLogin, login } from '@/common/request/api/login'
import { getToken, setToken, removeToken } from '@/common/utils/auth' import {
getToken,
setToken,
removeToken,
getRefreshToken,
setRefreshToken,
removeRefreshToken
} from '@/common/utils/auth'
const state = { const state = {
token: getToken(), token: getToken(),
isUser: storage.get(auth.isUser) || false, refreshToken: getRefreshToken(),
isUserOpenid: storage.get(auth.isUserOpenid) || false, isUser: storage.get(auth.isUser) || false,
isResetPassword: storage.get(auth.isResetPassword) || false, isUserOpenid: storage.get(auth.isUserOpenid) || false,
name: storage.get(auth.name), isResetPassword: storage.get(auth.isResetPassword) || false,
nickname: storage.get(auth.nickname), name: storage.get(auth.name),
gender: storage.get(auth.gender), nickname: storage.get(auth.nickname),
telephone: storage.get(auth.telephone), gender: storage.get(auth.gender),
avatar: storage.get(auth.avatar), telephone: storage.get(auth.telephone),
createDatetime: storage.get(auth.createDatetime), avatar: storage.get(auth.avatar),
roles: storage.get(auth.roles), createDatetime: storage.get(auth.createDatetime),
permissions: storage.get(auth.permissions) roles: storage.get(auth.roles),
permissions: storage.get(auth.permissions)
} }
const mutations = { const mutations = {
SET_TOKEN: (state, token) => { SET_TOKEN: (state, token) => {
state.token = token state.token = token
}, },
SET_IS_USER_OPENID: (state, isUserOpenid) => { SET_REFRESH_TOKEN: (state, refreshToken) => {
state.isUserOpenid = isUserOpenid state.refreshToken = refreshToken
storage.set(auth.isUserOpenid, isUserOpenid) setRefreshToken(refreshToken)
}, },
SET_IS_RESET_PASSWORD: (state, isResetPassword) => { SET_IS_USER_OPENID: (state, isUserOpenid) => {
state.isResetPassword = isResetPassword state.isUserOpenid = isUserOpenid
storage.set(auth.isResetPassword, isResetPassword) storage.set(auth.isUserOpenid, isUserOpenid)
}, },
SET_NAME: (state, name) => { SET_IS_RESET_PASSWORD: (state, isResetPassword) => {
state.name = name state.isResetPassword = isResetPassword
storage.set(auth.name, name) storage.set(auth.isResetPassword, isResetPassword)
}, },
SET_GENDER: (state, gender) => { SET_NAME: (state, name) => {
state.gender = gender state.name = name
storage.set(auth.gender, gender) storage.set(auth.name, name)
}, },
SET_NICKNAME: (state, nickname) => { SET_GENDER: (state, gender) => {
state.nickname = nickname state.gender = gender
storage.set(auth.nickname, nickname) storage.set(auth.gender, gender)
}, },
SET_CREATE_DATETIME: (state, createDatetime) => { SET_NICKNAME: (state, nickname) => {
state.createDatetime = createDatetime state.nickname = nickname
storage.set(auth.createDatetime, createDatetime) storage.set(auth.nickname, nickname)
}, },
SET_AVATAR: (state, avatar) => { SET_CREATE_DATETIME: (state, createDatetime) => {
state.avatar = avatar state.createDatetime = createDatetime
storage.set(auth.avatar, avatar) storage.set(auth.createDatetime, createDatetime)
}, },
SET_ROLES: (state, roles) => { SET_AVATAR: (state, avatar) => {
state.roles = roles state.avatar = avatar
storage.set(auth.roles, roles) storage.set(auth.avatar, avatar)
}, },
SET_PERMISSIONS: (state, permissions) => { SET_ROLES: (state, roles) => {
state.permissions = permissions state.roles = roles
storage.set(auth.permissions, permissions) storage.set(auth.roles, roles)
}, },
SET_TELEPHONE: (state, telephone) => { SET_PERMISSIONS: (state, permissions) => {
state.telephone = telephone state.permissions = permissions
storage.set(auth.telephone, telephone) storage.set(auth.permissions, permissions)
}, },
SET_ISUSER: (state, isUser) => { SET_TELEPHONE: (state, telephone) => {
state.isUser = isUser state.telephone = telephone
storage.set(auth.isUser, isUser) storage.set(auth.telephone, telephone)
}, },
SET_ISUSER: (state, isUser) => {
state.isUser = isUser
storage.set(auth.isUser, isUser)
}
} }
const actions = { const actions = {
// 手机号密码登录 // 手机号密码登录
Login({ commit }, userInfo) { Login({ commit }, userInfo) {
const telephone = userInfo.telephone.trim() const telephone = userInfo.telephone.trim()
const password = userInfo.password const password = userInfo.password
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
login(telephone, password).then(res => { login(telephone, password)
setToken(`${res.data.token_type} ${res.data.access_token}`) .then((res) => {
commit('SET_TOKEN', `${res.data.token_type} ${res.data.access_token}`) setToken(`${res.data.token_type} ${res.data.access_token}`)
commit('SET_IS_USER_OPENID', res.data.is_wx_server_openid) commit('SET_TOKEN', `${res.data.token_type} ${res.data.access_token}`)
commit('SET_IS_RESET_PASSWORD', res.data.is_reset_password) commit('SET_REFRESH_TOKEN', res.data.refresh_token)
resolve(res) commit('SET_IS_USER_OPENID', res.data.is_wx_server_openid)
}).catch(error => { commit('SET_IS_RESET_PASSWORD', res.data.is_reset_password)
reject(error) resolve(res)
}) })
}) .catch((error) => {
}, reject(error)
})
// 微信一键登录 })
// 微信文档https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html },
wxLogin({ commit }, code) {
return new Promise((resolve, reject) => {
wxCodeLogin(code).then(res => {
setToken(`${res.data.token_type} ${res.data.access_token}`)
commit('SET_TOKEN', `${res.data.token_type} ${res.data.access_token}`)
commit('SET_IS_USER_OPENID', res.data.is_wx_server_openid)
commit('SET_IS_RESET_PASSWORD', res.data.is_reset_password)
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息 // 微信一键登录
GetInfo({ commit }) { // 微信文档https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html
return new Promise((resolve, reject) => { wxLogin({ commit }, code) {
getInfo().then(res => { return new Promise((resolve, reject) => {
const user = res.data wxCodeLogin(code)
const avatar = (user == null || user.avatar == "" || user.avatar == null) ? "https://vv-reserve.oss-cn-hangzhou.aliyuncs.com/avatar/2023-01-27/1674820804e81e7631.png" : user.avatar .then((res) => {
const name = (user == null || user.name == "" || user.name == null) ? "" : user.name setToken(`${res.data.token_type} ${res.data.access_token}`)
commit('SET_ROLES', user.roles.map((item) => item.name) || ['ROLE_DEFAULT']) commit('SET_TOKEN', `${res.data.token_type} ${res.data.access_token}`)
commit('SET_PERMISSIONS', user.permissions) commit('SET_REFRESH_TOKEN', res.data.refresh_token)
commit('SET_NAME', name) commit('SET_IS_USER_OPENID', res.data.is_wx_server_openid)
commit('SET_NICKNAME', user.nickname) commit('SET_IS_RESET_PASSWORD', res.data.is_reset_password)
commit('SET_GENDER', user.gender) resolve(res)
commit('SET_TELEPHONE', user.telephone) })
commit('SET_AVATAR', avatar) .catch((error) => {
commit('SET_CREATE_DATETIME', user.create_datetime) reject(error)
commit('SET_ISUSER', true) })
resolve(res) })
}).catch(error => { },
reject(error)
})
})
},
// 更新用户基本信息
UpdateInfo({ commit }, user) {
commit('SET_NAME', user.name)
commit('SET_NICKNAME', user.nickname)
commit('SET_GENDER', user.gender)
commit('SET_TELEPHONE', user.telephone)
},
// 退出系统 // 获取用户信息
LogOut({ commit }) { GetInfo({ commit }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
commit('SET_TOKEN', '') getInfo()
commit('SET_ROLES', []) .then((res) => {
commit('SET_PERMISSIONS', []) const user = res.data
commit('SET_NAME', "") const avatar =
commit('SET_NICKNAME', "") user == null || user.avatar == '' || user.avatar == null
commit('SET_GENDER', "") ? 'https://vv-reserve.oss-cn-hangzhou.aliyuncs.com/avatar/2023-01-27/1674820804e81e7631.png'
commit('SET_TELEPHONE', "") : user.avatar
commit('SET_AVATAR', "") const name = user == null || user.name == '' || user.name == null ? '' : user.name
commit('SET_CREATE_DATETIME', "") commit('SET_ROLES', user.roles.map((item) => item.name) || ['ROLE_DEFAULT'])
commit('SET_IS_USER_OPENID', false) commit('SET_PERMISSIONS', user.permissions)
commit('SET_IS_RESET_PASSWORD', false) commit('SET_NAME', name)
commit('SET_ISUSER', false) commit('SET_NICKNAME', user.nickname)
removeToken() commit('SET_GENDER', user.gender)
storage.clean() commit('SET_TELEPHONE', user.telephone)
uni.reLaunch({ url: '/pages/login/login' }) commit('SET_AVATAR', avatar)
resolve() commit('SET_CREATE_DATETIME', user.create_datetime)
}) commit('SET_ISUSER', true)
} resolve(res)
})
.catch((error) => {
reject(error)
})
})
},
// 更新用户基本信息
UpdateInfo({ commit }, user) {
commit('SET_NAME', user.name)
commit('SET_NICKNAME', user.nickname)
commit('SET_GENDER', user.gender)
commit('SET_TELEPHONE', user.telephone)
},
// 退出系统
LogOut({ commit }) {
return new Promise((resolve, reject) => {
commit('SET_TOKEN', '')
commit('SET_REFRESH_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
commit('SET_NAME', '')
commit('SET_NICKNAME', '')
commit('SET_GENDER', '')
commit('SET_TELEPHONE', '')
commit('SET_AVATAR', '')
commit('SET_CREATE_DATETIME', '')
commit('SET_IS_USER_OPENID', false)
commit('SET_IS_RESET_PASSWORD', false)
commit('SET_ISUSER', false)
removeToken()
removeRefreshToken()
storage.clean()
uni.reLaunch({
url: '/pages/login/login',
complete: () => {
resolve()
}
})
})
}
} }
export default { export default {
namespaced: true, // 使用命名空间去访问模块中属性user/login namespaced: true, // 使用命名空间去访问模块中属性user/login
state, state,
mutations, mutations,
actions actions

View File

@ -1,51 +1,52 @@
import { getDictTypeDetailsApi } from '@/common/request/api/vadmin/system/dict.js' import { getDictTypeDetailsApi } from '@/common/request/api/vadmin/system/dict.js'
const state = { const state = {
dictObj: {}, // 字典元素 dictObj: {} // 字典元素
} }
const mutations = { const mutations = {
SET_DICT_OBJ: (state, dictObj) => { SET_DICT_OBJ: (state, dictObj) => {
state.dictObj = dictObj state.dictObj = dictObj
} }
} }
const actions = { const actions = {
// 获取字典选项 // 获取字典选项
getDicts({ commit, state }, dictTypes) { getDicts({ commit, state }, dictTypes) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const result = {} const result = {}
const addList = [] const addList = []
const dictObj = JSON.parse(JSON.stringify(state.dictObj)) const dictObj = JSON.parse(JSON.stringify(state.dictObj))
for (const item of dictTypes) { for (const item of dictTypes) {
if (item in dictObj) { if (item in dictObj) {
result[item] = dictObj[item] result[item] = dictObj[item]
} else { } else {
result[item] = [] result[item] = []
addList.push(item) addList.push(item)
} }
} }
if (addList.length > 0) { if (addList.length > 0) {
getDictTypeDetailsApi(addList).then(res => { getDictTypeDetailsApi(addList)
for (const item of addList) { .then((res) => {
result[item] = res.data[item] for (const item of addList) {
dictObj[item] = res.data[item] result[item] = res.data[item]
} dictObj[item] = res.data[item]
commit('SET_DICT_OBJ', dictObj) }
resolve(result) commit('SET_DICT_OBJ', dictObj)
}).catch(error => { resolve(result)
reject(error) })
}) .catch((error) => {
} else { reject(error)
resolve(result) })
} } else {
}) resolve(result)
} }
})
}
} }
export default { export default {
namespaced: true, // 使用命名空间去访问模块中属性user/login namespaced: true, // 使用命名空间去访问模块中属性user/login
state, state,
mutations, mutations,
actions actions

View File

@ -1,26 +1,26 @@
//vue.config.js //vue.config.js
const TransformPages = require('uni-read-pages') const TransformPages = require('uni-read-pages')
const {webpack} = new TransformPages() const { webpack } = new TransformPages()
module.exports = { module.exports = {
configureWebpack: { configureWebpack: {
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
ROUTES: webpack.DefinePlugin.runtimeValue(() => { ROUTES: webpack.DefinePlugin.runtimeValue(() => {
const tfPages = new TransformPages({ const tfPages = new TransformPages({
// includes 中包含的是router会读取pages路由中的字段名 // includes 中包含的是router会读取pages路由中的字段名
// 后续如果有用到meta等路由信息可以在 includes 里增加 'meta' // 后续如果有用到meta等路由信息可以在 includes 里增加 'meta'
// 在pages路由中写对应的数据router中就可以获取得到 // 在pages路由中写对应的数据router中就可以获取得到
includes: ['path', 'name', 'aliasPath', 'meta'] includes: ['path', 'name', 'aliasPath', 'meta']
}); })
return JSON.stringify(tfPages.routes) return JSON.stringify(tfPages.routes)
}, true ) }, true)
}) })
] ]
}, },
devServer: { devServer: {
port: 8080, port: 8080,
https: false, https: false,
disableHostCheck: true // 禁止访问本地host文件 disableHostCheck: true // 禁止访问本地host文件
} }
} }