diff --git a/kinit-admin/package.json b/kinit-admin/package.json
index 0cc1ca1..e1c5c25 100644
--- a/kinit-admin/package.json
+++ b/kinit-admin/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-element-plus-admin",
- "version": "1.9.2",
+ "version": "1.9.4",
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
"author": "Archer <502431556@qq.com>",
"private": false,
@@ -25,28 +25,28 @@
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
- "@iconify/iconify": "^3.0.1",
+ "@iconify/iconify": "^3.1.0",
"@kjgl77/datav-vue3": "^1.4.2",
- "@vueuse/core": "^9.10.0",
+ "@vueuse/core": "^9.13.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.10",
- "@zxcvbn-ts/core": "^2.1.0",
+ "@zxcvbn-ts/core": "^2.2.1",
"animate.css": "^4.1.1",
- "axios": "^1.2.2",
+ "axios": "^1.3.4",
"echarts": "^5.4.1",
"echarts-wordcloud": "^2.1.0",
- "element-plus": "2.2.28",
+ "element-plus": "2.2.32",
"intro.js": "^6.0.0",
"lodash-es": "^4.17.21",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
"moment": "^2.29.4",
"nprogress": "^0.2.0",
- "pinia": "2.0.29",
+ "pinia": "^2.0.32",
"qrcode": "^1.5.1",
"qs": "^6.11.0",
"url": "^0.11.0",
- "vue": "3.2.45",
+ "vue": "3.2.47",
"vue-i18n": "9.2.2",
"vue-router": "^4.1.6",
"vue-types": "^5.0.2",
@@ -55,48 +55,48 @@
"web-storage-cache": "^1.1.1"
},
"devDependencies": {
- "@commitlint/cli": "^17.4.2",
- "@commitlint/config-conventional": "^17.4.2",
- "@iconify/json": "^2.2.7",
- "@intlify/unplugin-vue-i18n": "^0.8.1",
+ "@commitlint/cli": "^17.4.4",
+ "@commitlint/config-conventional": "^17.4.4",
+ "@iconify/json": "^2.2.29",
+ "@intlify/unplugin-vue-i18n": "^0.8.2",
"@purge-icons/generated": "^0.9.0",
- "@types/intro.js": "^5.1.0",
+ "@types/intro.js": "^5.1.1",
"@types/lodash-es": "^4.17.6",
- "@types/node": "^18.11.18",
+ "@types/node": "^18.14.2",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.5.0",
"@types/qs": "^6.9.7",
- "@typescript-eslint/eslint-plugin": "^5.48.1",
- "@typescript-eslint/parser": "^5.48.1",
- "@vitejs/plugin-legacy": "^3.0.1",
+ "@typescript-eslint/eslint-plugin": "^5.54.0",
+ "@typescript-eslint/parser": "^5.54.0",
+ "@vitejs/plugin-legacy": "^4.0.1",
"@vitejs/plugin-vue": "^4.0.0",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"autoprefixer": "^10.4.13",
"consola": "^2.15.3",
- "eslint": "^8.32.0",
+ "eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
- "eslint-define-config": "^1.14.0",
+ "eslint-define-config": "^1.15.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.9.0",
"husky": "^8.0.3",
"less": "^4.1.3",
- "lint-staged": "^13.1.0",
- "plop": "^3.1.1",
+ "lint-staged": "^13.1.2",
+ "plop": "^3.1.2",
"postcss": "^8.4.21",
"postcss-html": "^1.5.0",
"postcss-less": "^6.0.0",
- "prettier": "^2.8.3",
- "rimraf": "^4.0.7",
- "rollup": "^3.10.0",
- "stylelint": "^14.16.1",
+ "prettier": "^2.8.4",
+ "rimraf": "^4.1.2",
+ "rollup": "^3.17.3",
+ "stylelint": "^15.2.0",
"stylelint-config-html": "^1.1.0",
- "stylelint-config-prettier": "^9.0.4",
- "stylelint-config-recommended": "^9.0.0",
- "stylelint-config-standard": "^29.0.0",
- "stylelint-order": "^6.0.1",
- "terser": "^5.16.1",
- "typescript": "4.9.4",
- "vite": "4.0.4",
+ "stylelint-config-prettier": "^9.0.5",
+ "stylelint-config-recommended": "^10.0.1",
+ "stylelint-config-standard": "^30.0.1",
+ "stylelint-order": "^6.0.2",
+ "terser": "^5.16.5",
+ "typescript": "4.9.5",
+ "vite": "4.1.4",
"vite-plugin-ejs": "^1.6.4",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-mock": "^2.9.6",
@@ -105,7 +105,7 @@
"vite-plugin-style-import": "2.0.0",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-windicss": "^1.8.10",
- "vue-tsc": "^1.0.24",
+ "vue-tsc": "^1.2.0",
"windicss": "^3.5.6",
"windicss-analysis": "^0.3.5"
},
diff --git a/kinit-admin/src/components/Descriptions/src/Descriptions.vue b/kinit-admin/src/components/Descriptions/src/Descriptions.vue
index a1c7a07..4b70992 100644
--- a/kinit-admin/src/components/Descriptions/src/Descriptions.vue
+++ b/kinit-admin/src/components/Descriptions/src/Descriptions.vue
@@ -107,7 +107,13 @@ const toggleClick = () => {
v-bind="getBindItemValue(item)"
>
- {{ item.label }}
+ {{ item.label }}
diff --git a/kinit-admin/src/components/TagsView/src/TagsView.vue b/kinit-admin/src/components/TagsView/src/TagsView.vue
index ba650ab..464fb58 100644
--- a/kinit-admin/src/components/TagsView/src/TagsView.vue
+++ b/kinit-admin/src/components/TagsView/src/TagsView.vue
@@ -412,7 +412,10 @@ watch(
{
icon: 'ant-design:close-outlined',
label: t('common.closeTab'),
- disabled: !!visitedViews?.length && selectedTag?.meta.affix
+ disabled: !!visitedViews?.length && selectedTag?.meta.affix,
+ command: () => {
+ closeSelectedTag(selectedTag!)
+ }
},
{
divided: true,
diff --git a/kinit-admin/src/components/ThemeSwitch/src/ThemeSwitch.vue b/kinit-admin/src/components/ThemeSwitch/src/ThemeSwitch.vue
index 25221d1..71d8052 100644
--- a/kinit-admin/src/components/ThemeSwitch/src/ThemeSwitch.vue
+++ b/kinit-admin/src/components/ThemeSwitch/src/ThemeSwitch.vue
@@ -39,3 +39,9 @@ const themeChange = (val: boolean) => {
@change="themeChange"
/>
+
+
diff --git a/kinit-admin/src/hooks/web/useCrudSchemas.ts b/kinit-admin/src/hooks/web/useCrudSchemas.ts
index 1cc2025..5acf078 100644
--- a/kinit-admin/src/hooks/web/useCrudSchemas.ts
+++ b/kinit-admin/src/hooks/web/useCrudSchemas.ts
@@ -214,7 +214,6 @@ const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): For
for (const task of formRequestTask) {
task()
}
- console.log(formSchema)
return formSchema
}
@@ -243,7 +242,7 @@ const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[
// 给options添加国际化
const filterOptions = (options: Recordable, labelField?: string) => {
- return options.map((v: Recordable) => {
+ return options?.map((v: Recordable) => {
if (labelField) {
v['labelField'] = t(v.labelField)
} else {
diff --git a/kinit-admin/src/styles/index.less b/kinit-admin/src/styles/index.less
index c023316..c5122f3 100644
--- a/kinit-admin/src/styles/index.less
+++ b/kinit-admin/src/styles/index.less
@@ -1,2 +1,8 @@
@import './var.css';
@import 'element-plus/theme-chalk/dark/css-vars.css';
+
+// 解决抽屉弹出时,body宽度变化的问题
+.el-popup-parent--hidden {
+ width: 100% !important;
+}
+
diff --git a/kinit-api/application/settings.py b/kinit-api/application/settings.py
index d1ee2a6..b9e44cc 100644
--- a/kinit-api/application/settings.py
+++ b/kinit-api/application/settings.py
@@ -11,7 +11,7 @@ from fastapi.security import OAuth2PasswordBearer
"""
系统版本
"""
-VERSION = "1.6.0"
+VERSION = "1.6.1"
"""安全警告: 不要在生产中打开调试运行!"""
DEBUG = True
@@ -105,6 +105,10 @@ EVENTS = [
"""
# 默认密码,"0" 默认为手机号后六位
DEFAULT_PASSWORD = "0"
+# 默认头像
+DEFAULT_AVATAR = "https://vv-reserve.oss-cn-hangzhou.aliyuncs.com/avatar/2023-01-27/1674820804e81e7631.png"
+# 默认登陆时最大输入密码或验证码错误次数
+DEFAULT_AUTH_ERROR_MAX_NUMBER = 5
# 是否开启保存登录日志
LOGIN_LOG_RECORD = not DEBUG
# 是否开启保存每次请求日志到本地
diff --git a/kinit-api/apps/vadmin/auth/crud.py b/kinit-api/apps/vadmin/auth/crud.py
index 0b29ba3..cfbea00 100644
--- a/kinit-api/apps/vadmin/auth/crud.py
+++ b/kinit-api/apps/vadmin/auth/crud.py
@@ -60,6 +60,7 @@ class UserDal(DalBase):
raise CustomException("手机号已存在!", code=status.HTTP_ERROR)
password = data.telephone[5:12] if settings.DEFAULT_PASSWORD == "0" else settings.DEFAULT_PASSWORD
data.password = self.model.get_password_hash(password)
+ data.avatar = data.avatar if data.avatar else settings.DEFAULT_AVATAR
obj = self.model(**data.dict(exclude={'role_ids'}))
roles = await RoleDal(self.db).get_datas(limit=0, id=("in", data.role_ids), v_return_objs=True)
for role in roles:
diff --git a/kinit-api/apps/vadmin/auth/utils/current.py b/kinit-api/apps/vadmin/auth/utils/current.py
index ccc71d7..4c98a0a 100644
--- a/kinit-api/apps/vadmin/auth/utils/current.py
+++ b/kinit-api/apps/vadmin/auth/utils/current.py
@@ -6,6 +6,8 @@
# @desc : 获取认证后的信息工具
from typing import List, Optional
+
+from jose import jwt, JWTError
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload
from apps.vadmin.auth.crud import UserDal
@@ -33,6 +35,61 @@ def get_user_permissions(user: VadminUser) -> set:
return permissions
+class OpenAuth(AuthValidation):
+
+ """
+ 开放认证,无认证也可以访问
+ 认证了以后可以获取到用户信息,无认证则获取不到
+ """
+
+ @classmethod
+ def validate_token(cls, token: str | None, db: AsyncSession) -> str | None:
+ """
+ 验证用户 token,没有则返回 None
+ """
+ if not token:
+ return None
+ try:
+ payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
+ telephone: str = payload.get("sub")
+ if telephone is None:
+ return None
+ except JWTError:
+ return None
+ return telephone
+
+ @classmethod
+ async def validate_user(cls, request: Request, user: VadminUser, db: AsyncSession) -> Auth:
+ """
+ 验证用户信息
+ """
+ if user is None:
+ return Auth(db=db)
+ elif not user.is_active:
+ return Auth(db=db)
+ request.scope["telephone"] = user.telephone
+ try:
+ request.scope["body"] = await request.body()
+ except RuntimeError:
+ request.scope["body"] = "获取失败"
+ return Auth(user=user, db=db)
+
+ async def __call__(
+ self,
+ request: Request,
+ token: str = Depends(settings.oauth2_scheme),
+ db: AsyncSession = Depends(db_getter)
+ ):
+ """
+ 每次调用依赖此类的接口会执行该方法
+ """
+ telephone = self.validate_token(token, db)
+ if telephone:
+ user = await UserDal(db).get_data(telephone=telephone, v_return_none=True)
+ return await self.validate_user(request, user, db)
+ return Auth(db=db)
+
+
class AllUserAuth(AuthValidation):
"""
diff --git a/kinit-api/apps/vadmin/auth/utils/login.py b/kinit-api/apps/vadmin/auth/utils/login.py
index e3e5b7f..70ee680 100644
--- a/kinit-api/apps/vadmin/auth/utils/login.py
+++ b/kinit-api/apps/vadmin/auth/utils/login.py
@@ -39,77 +39,66 @@ from core.data_types import Telephone
app = APIRouter()
-@app.post("/login/", summary="手机号密码登录")
+@app.post("/login/", summary="手机号密码登录", description="员工登录通道,限制最多输错次数,达到最大值后将is_active=False")
async def login_for_access_token(
request: Request,
data: LoginForm,
manage: LoginManage = Depends(),
db: AsyncSession = Depends(db_getter)
):
- if data.method == "0":
- result = await manage.password_login(data, db, request)
- elif data.method == "1":
- result = await manage.sms_login(data, db, request)
- else:
- return ErrorResponse(msg="请使用正确的登录方式")
- if not result.status:
- resp = {"message": result.msg}
- await VadminLoginRecord.create_login_record(db, data, result.status, request, resp)
- return ErrorResponse(msg=result.msg)
- user = result.user
+ try:
+ if data.method == "0":
+ result = await manage.password_login(data, db, request)
+ elif data.method == "1":
+ result = await manage.sms_login(data, db, request)
+ else:
+ raise ValueError("无效参数")
- if data.platform in ["0", "1"] and not user.is_staff:
- msg = "此手机号无登录权限"
- await VadminLoginRecord.create_login_record(db, data, result.status, request, {"message": msg})
- return ErrorResponse(msg=msg)
+ if not result.status:
+ raise ValueError(result.msg)
- access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
- access_token = LoginManage.create_access_token(data={"sub": user.telephone}, expires_delta=access_token_expires)
- resp = {
- "access_token": access_token,
- "token_type": "bearer",
- "is_reset_password": user.is_reset_password,
- "is_wx_server_openid": user.is_wx_server_openid
- }
- await VadminLoginRecord.create_login_record(db, data, result.status, request, resp)
- return SuccessResponse(resp)
+ token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
+ token = LoginManage.create_access_token(data={"sub": result.user.telephone}, expires_delta=token_expires)
+ resp = {
+ "access_token": token,
+ "token_type": "bearer",
+ "is_reset_password": result.user.is_reset_password,
+ "is_wx_server_openid": result.user.is_wx_server_openid
+ }
+ await VadminLoginRecord.create_login_record(db, data, True, request, resp)
+ return SuccessResponse(resp)
+ except ValueError as e:
+ await VadminLoginRecord.create_login_record(db, data, False, request, {"message": str(e)})
+ return ErrorResponse(msg=str(e))
-@app.post("/wx/login/", summary="微信服务端一键登录")
+@app.post("/wx/login/", summary="微信服务端一键登录", description="员工登录通道")
async def wx_login_for_access_token(request: Request, data: WXLoginForm, db: AsyncSession = Depends(db_getter)):
- if data.platform not in ["0", "1"]:
- msg = "错误平台"
- await VadminLoginRecord.create_login_record(db, data, False, request, {"message": msg})
- return ErrorResponse(msg=msg)
- wx = WXOAuth(request.app.state.redis, 0)
- telephone = await wx.parsing_phone_number(data.code)
- if not telephone:
- msg = "无效Code"
- await VadminLoginRecord.create_login_record(db, data, False, request, {"message": msg})
- return ErrorResponse(msg=msg)
- data.telephone = telephone
- user = await UserDal(db).get_data(telephone=telephone, v_return_none=True)
- msg = None
- if not user:
- # 手机号不存在,创建新用户
- # model = UserIn(name=generate_string(), telephone=Telephone(telephone))
- # user = await UserDal(db).create_data(model, v_return_obj=True)
- msg = "手机号不存在!"
- elif not user.is_active:
- msg = "此手机号已被冻结!"
- elif data.platform in ["0", "1"] and not user.is_staff:
- msg = "此手机号无登录权限"
- if msg:
- await VadminLoginRecord.create_login_record(db, data, False, request, {"message": msg})
- return ErrorResponse(msg=msg)
+ try:
+ if data.platform != "1" or data.method != "2":
+ raise ValueError("无效参数")
+ wx = WXOAuth(request.app.state.redis, 0)
+ telephone = await wx.parsing_phone_number(data.code)
+ if not telephone:
+ raise ValueError("无效Code")
+ data.telephone = telephone
+ user = await UserDal(db).get_data(telephone=telephone, v_return_none=True)
+ if not user:
+ raise ValueError("此手机号不存在")
+ elif not user.is_active:
+ raise ValueError("此手机号已被冻结")
+ except ValueError as e:
+ await VadminLoginRecord.create_login_record(db, data, False, request, {"message": str(e)})
+ return ErrorResponse(msg=str(e))
+
# 更新登录时间
await user.update_login_info(db, request.client.host)
# 登录成功创建 token
- access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
- access_token = LoginManage.create_access_token(data={"sub": user.telephone}, expires_delta=access_token_expires)
+ token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
+ token = LoginManage.create_access_token(data={"sub": user.telephone}, expires_delta=token_expires)
resp = {
- "access_token": access_token,
+ "access_token": token,
"token_type": "bearer",
"is_reset_password": user.is_reset_password,
"is_wx_server_openid": user.is_wx_server_openid
diff --git a/kinit-api/apps/vadmin/auth/utils/validation/login.py b/kinit-api/apps/vadmin/auth/utils/validation/login.py
index 74b3b79..9c4b251 100644
--- a/kinit-api/apps/vadmin/auth/utils/validation/login.py
+++ b/kinit-api/apps/vadmin/auth/utils/validation/login.py
@@ -9,10 +9,13 @@
from fastapi import Request, Depends
from pydantic import BaseModel, validator
from sqlalchemy.ext.asyncio import AsyncSession
+
+from application.settings import DEFAULT_AUTH_ERROR_MAX_NUMBER
from apps.vadmin.auth import models, crud, schemas
from core.database import db_getter
from core.validator import vali_telephone
from typing import Optional
+from utils.count import Count
class LoginForm(BaseModel):
@@ -52,8 +55,8 @@ class LoginValidation:
async def __call__(self, data: LoginForm, db: AsyncSession, request: Request) -> LoginResult:
self.result = LoginResult()
- if data.platform not in ["0", "1"]:
- self.result.msg = "错误平台"
+ if data.platform not in ["0", "1"] or data.method not in ["0", "1"]:
+ self.result.msg = "无效参数"
return self.result
user = await crud.UserDal(db).get_data(telephone=data.telephone, v_return_none=True)
if not user:
@@ -62,11 +65,23 @@ class LoginValidation:
result = await self.func(self, data=data, user=user, request=request)
+ count_key = f"{data.telephone}_password_auth" if data.method == '0' else f"{data.telephone}_sms_auth"
+ count = Count(request.app.state.redis, count_key)
+
if not result.status:
self.result.msg = result.msg
+ number = await count.add(ex=86400)
+ if number >= DEFAULT_AUTH_ERROR_MAX_NUMBER:
+ await count.reset()
+ # 如果等于最大次数,那么就将用户 is_active=False
+ user.is_active = False
+ await db.flush()
elif not user.is_active:
self.result.msg = "此手机号已被冻结!"
- elif user:
+ elif data.platform in ["0", "1"] and not user.is_staff:
+ self.result.msg = "此手机号无权限!"
+ else:
+ await count.delete()
self.result.msg = "OK"
self.result.status = True
self.result.user = schemas.UserSimpleOut.from_orm(user)
diff --git a/kinit-uni/store/modules/auth.js b/kinit-uni/store/modules/auth.js
index d8c91a2..4c0acc3 100644
--- a/kinit-uni/store/modules/auth.js
+++ b/kinit-uni/store/modules/auth.js
@@ -107,7 +107,7 @@ const actions = {
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.data
- const avatar = (user == null || user.avatar == "" || user.avatar == null) ? require("@/static/images/avatar.jpg") : user.avatar
+ 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
const name = (user == null || user.name == "" || user.name == null) ? "" : user.name
commit('SET_ROLES', user.roles.map((item) => item.name) || ['ROLE_DEFAULT'])
commit('SET_PERMISSIONS', user.permissions)