This commit is contained in:
ktianc 2022-10-10 16:40:41 +08:00
parent 7e7c6c2340
commit 5e6251880b
19 changed files with 432 additions and 40 deletions

View File

@ -38,7 +38,7 @@ github地址https://gitee.com/ktianc/kinit👩👦👦
- [x] 👨‍🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
- [ ] 🏡个人主页:配置用户个人信息,密码修改等。
- [x] 🏡个人主页:配置用户个人信息,密码修改等。
- [x] 📚字典管理:对系统中经常使用的一些较为固定的数据进行维护。
@ -48,7 +48,7 @@ github地址https://gitee.com/ktianc/kinit👩👦👦
- [ ] 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
- [ ] 🔒登录认证:目前支持用户使用手机号+密码方式登录。
- [x] 🔒登录认证:目前支持用户使用手机号+密码方式登录。
说明:新建用户密码默认为手机号后六位;

View File

@ -35,42 +35,42 @@ export default [
name: 'Github',
icon: 'akar-icons:github-fill',
message: 'workplace.introduction',
personal: 'Archer',
personal: 'kinit',
time: new Date()
},
{
name: 'Vue',
icon: 'logos:vue',
message: 'workplace.introduction',
personal: 'Archer',
personal: 'kinit',
time: new Date()
},
{
name: 'Angular',
icon: 'logos:angular-icon',
message: 'workplace.introduction',
personal: 'Archer',
personal: 'kinit',
time: new Date()
},
{
name: 'React',
icon: 'logos:react',
message: 'workplace.introduction',
personal: 'Archer',
personal: 'kinit',
time: new Date()
},
{
name: 'Webpack',
icon: 'logos:webpack',
message: 'workplace.introduction',
personal: 'Archer',
personal: 'kinit',
time: new Date()
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
message: 'workplace.introduction',
personal: 'Archer',
personal: 'kinit',
time: new Date()
}
]

View File

@ -23,3 +23,11 @@ export const getUserApi = (dataId: number): Promise<IResponse> => {
export const postCurrentUserResetPassword = (data: any): Promise<IResponse> => {
return request.post({ url: `/vadmin/auth/user/current/reset/password/`, data })
}
export const postCurrentUserUpdateInfo = (data: any): Promise<IResponse> => {
return request.post({ url: `/vadmin/auth/user/current/update/info/`, data })
}
export const getCurrentUserInfo = (): Promise<IResponse> => {
return request.get({ url: `/vadmin/auth/user/current/info/` })
}

View File

@ -1,12 +1,11 @@
<script setup lang="ts">
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { useCache } from '@/hooks/web/useCache'
import { resetRouter } from '@/router'
import { useRouter } from 'vue-router'
import { useDesign } from '@/hooks/web/useDesign'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useAuthStoreWithOut } from '@/store/modules/auth'
const tagsViewStore = useTagsViewStore()
@ -16,9 +15,9 @@ const prefixCls = getPrefixCls('user-info')
const { t } = useI18n()
const { wsCache } = useCache()
const authStore = useAuthStoreWithOut()
const { replace } = useRouter()
const { replace, push } = useRouter()
const loginOut = () => {
ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
@ -27,7 +26,7 @@ const loginOut = () => {
type: 'warning'
})
.then(async () => {
wsCache.clear()
authStore.logout()
tagsViewStore.delAllViews()
resetRouter() //
replace('/login')
@ -39,9 +38,11 @@ const toDocument = () => {
window.open('https://element-plus-admin-doc.cn/')
}
const appStore = useAppStoreWithOut()
const toHome = () => {
push('/system/home')
}
const user = wsCache.get(appStore.getUserInfo)
const user = authStore.getUser
</script>
<template>
@ -59,7 +60,7 @@ const user = wsCache.get(appStore.getUserInfo)
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem>
<div @click="toDocument">个人主页</div>
<div @click="toHome">个人主页</div>
</ElDropdownItem>
<ElDropdownItem>
<div @click="toDocument">前端项目文档</div>

View File

@ -201,7 +201,7 @@ export default {
yield: '产量',
dynamic: '动态',
push: '推送',
pushCode: 'Archer 推送 代码到 Github',
pushCode: 'kinit 推送 代码到 Github',
follow: '关注'
},
formDemo: {

View File

@ -7,10 +7,12 @@ import { useNProgress } from '@/hooks/web/useNProgress'
import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { usePageLoading } from '@/hooks/web/usePageLoading'
import { getRoleMenusApi } from '@/api/login'
import { useAuthStoreWithOut } from '@/store/modules/auth'
const permissionStore = usePermissionStoreWithOut()
const appStore = useAppStoreWithOut()
const authStore = useAuthStoreWithOut()
const { wsCache } = useCache()
@ -27,6 +29,9 @@ router.beforeEach(async (to, from, next) => {
if (to.path === '/login') {
next({ path: '/' })
} else {
if (!authStore.getIsUser) {
await authStore.getUserInfo()
}
if (permissionStore.getIsAddRouters) {
next()
return

View File

@ -4,26 +4,47 @@ import { UserLoginType } from '@/api/login/types'
import { loginApi } from '@/api/login'
import { useAppStore } from '@/store/modules/app'
import { useCache } from '@/hooks/web/useCache'
import { getCurrentUserInfo } from '@/api/vadmin/auth/user'
const appStore = useAppStore()
const { wsCache } = useCache()
export interface UserState {
id?: number
telephone?: string
name?: string
nickname?: string
avatar?: string
gender?: string
roles?: Recordable[]
create_datetime?: string
}
export interface AuthState {
user: UserState
isUser: boolean
}
export const useAuthStore = defineStore({
id: 'auth',
state: () => {
return {}
state: (): AuthState => {
return {
user: {},
isUser: false
}
},
persist: {
// 开启持久化存储
enabled: true,
strategies: [
{
key: 'authStore',
storage: localStorage
}
]
enabled: true
},
getters: {
getUser(): UserState {
return this.user
},
getIsUser(): boolean {
return this.isUser
}
},
getters: {},
actions: {
async login(formData: UserLoginType) {
const res = await loginApi(formData)
@ -31,8 +52,24 @@ export const useAuthStore = defineStore({
wsCache.set(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
// 存储用户信息
wsCache.set(appStore.getUserInfo, res.data.user)
this.user = res.data.user
}
return res
},
logout() {
wsCache.clear()
this.user = {}
},
updateUser(data: UserState) {
this.user.gender = data.gender
this.user.name = data.name
this.user.nickname = data.nickname
wsCache.set(appStore.getUserInfo, this.user)
},
async getUserInfo() {
const res = await getCurrentUserInfo()
wsCache.set(appStore.getUserInfo, res.data)
this.user = res.data
}
}
})

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { asyncRouterMap, constantRouterMap } from '@/router'
import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
import { constantRouterMap } from '@/router'
import { generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
import { store } from '../index'
import { cloneDeep } from 'lodash-es'

View File

@ -0,0 +1,73 @@
<script setup lang="ts">
import { ElCard, ElRow, ElCol, ElTabs, ElTabPane, ElAvatar } from 'element-plus'
import { ref } from 'vue'
import InfoWrite from './components/InfoWrite.vue'
import PasswordWrite from './components/PasswordWrite.vue'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import avatar from '@/assets/imgs/avatar.jpg'
import { selectDictLabel, DictDetail } from '@/utils/dict'
import { useDictStore } from '@/store/modules/dict'
const activeName = ref('info')
const authStore = useAuthStoreWithOut()
let genderOptions = ref<DictDetail[]>([])
const getOptions = async () => {
const dictStore = useDictStore()
const dictOptions = await dictStore.getDictObj(['sys_vadmin_gender'])
genderOptions.value = dictOptions.sys_vadmin_gender
}
getOptions()
const user = authStore.getUser
</script>
<template>
<ElRow :gutter="20">
<ElCol :xs="24" :sm="12" :md="8">
<ElCard shadow="hover" class="pb-30px">
<div class="text-center">
<ElAvatar :size="80" :src="user.avatar ? user.avatar : avatar" />
<p style="font-size: 24px">{{ user.name }}</p>
</div>
<div class="pl-20px pt-30px">
<div class="leading-relaxed">
<span class="pl-10px w-80px inline-block">姓名:</span>
<span class="pl-10px">{{ user.name }}</span>
</div>
<div class="leading-relaxed">
<span class="pl-10px w-80px inline-block">昵称:</span>
<span class="pl-10px">{{ user.nickname }}</span>
</div>
<div class="leading-relaxed">
<span class="pl-10px w-80px inline-block">手机号:</span>
<span class="pl-10px">{{ user.telephone }}</span>
</div>
<div class="leading-relaxed">
<span class="pl-10px w-80px inline-block">性别:</span>
<span class="pl-10px">{{ selectDictLabel(genderOptions, user.gender as string) }}</span>
</div>
<div class="leading-relaxed">
<span class="pl-10px w-80px inline-block">创建时间:</span>
<span class="pl-10px">{{ user.create_datetime }}</span>
</div>
</div>
</ElCard>
</ElCol>
<ElCol :xs="24" :sm="12" :md="16">
<ElCard shadow="hover">
<ElTabs v-model="activeName">
<ElTabPane label="基本信息" name="info">
<InfoWrite />
</ElTabPane>
<ElTabPane label="修改密码" name="password">
<PasswordWrite />
</ElTabPane>
</ElTabs>
</ElCard>
</ElCol>
</ElRow>
</template>

View File

@ -0,0 +1,120 @@
<script setup lang="ts">
import { reactive, unref, ref } from 'vue'
import { Form } from '@/components/Form'
import { ElButton } from 'element-plus'
import { useForm } from '@/hooks/web/useForm'
import { postCurrentUserUpdateInfo } from '@/api/vadmin/auth/user'
import { useValidator } from '@/hooks/web/useValidator'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { ElMessage } from 'element-plus'
const { required } = useValidator()
const authStore = useAuthStoreWithOut()
const rules = {
name: [required()],
gender: [required()]
}
const schema = reactive<FormSchema[]>([
{
field: 'name',
label: '用户名称',
component: 'Input',
colProps: {
span: 24
},
componentProps: {
style: {
width: '50%'
}
}
},
{
field: 'nickname',
label: '用户昵称',
component: 'Input',
colProps: {
span: 24
},
componentProps: {
style: {
width: '50%'
}
}
},
{
field: 'gender',
label: '性别',
colProps: {
span: 24
},
component: 'Radio',
componentProps: {
options: [
{
label: '男',
value: '0'
},
{
label: '女',
value: '1'
}
]
}
},
{
field: 'save',
colProps: {
span: 24
}
}
])
const { register, elFormRef, methods } = useForm()
const { setValues } = methods
setValues(authStore.getUser)
const loading = ref(false)
//
const save = async () => {
const formRef = unref(elFormRef)
await formRef?.validate(async (isValid) => {
if (isValid) {
loading.value = true
const { getFormData } = methods
const formData = await getFormData()
try {
const res = await postCurrentUserUpdateInfo(formData)
if (res) {
authStore.updateUser(res.data)
ElMessage.success('保存成功')
}
} finally {
loading.value = false
}
}
})
}
</script>
<template>
<Form
:schema="schema"
:rules="rules"
hide-required-asterisk
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
@register="register"
>
<template #save>
<div class="w-[50%]">
<ElButton :loading="loading" type="primary" class="w-[100%]" @click="save"> 保存 </ElButton>
</div>
</template>
</Form>
</template>
<style lang="less" scoped></style>

View File

@ -0,0 +1,114 @@
<script setup lang="ts">
import { reactive, unref, ref } from 'vue'
import { Form } from '@/components/Form'
import { ElButton } from 'element-plus'
import { useForm } from '@/hooks/web/useForm'
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
import { useValidator } from '@/hooks/web/useValidator'
import { useAuthStoreWithOut } from '@/store/modules/auth'
import { ElMessage } from 'element-plus'
const { required } = useValidator()
const authStore = useAuthStoreWithOut()
const rules = {
password: [
required(),
{ min: 8, max: 16, message: '长度需为8-16个字符,请重新输入。', trigger: 'blur' }
],
password_two: [
required(),
{ min: 8, max: 16, message: '长度需为8-16个字符,请重新输入。', trigger: 'blur' }
]
}
const schema = reactive<FormSchema[]>([
{
field: 'title',
colProps: {
span: 24
}
},
{
field: 'password',
label: '新密码',
component: 'InputPassword',
colProps: {
span: 24
},
componentProps: {
style: {
width: '50%'
},
placeholder: '请输入新密码'
}
},
{
field: 'password_two',
label: '再次输入新密码',
component: 'InputPassword',
colProps: {
span: 24
},
componentProps: {
style: {
width: '50%'
},
placeholder: '请再次输入新密码'
}
},
{
field: 'save',
colProps: {
span: 24
}
}
])
const { register, elFormRef, methods } = useForm()
const { setValues } = methods
setValues(authStore.getUser)
const loading = ref(false)
//
const save = async () => {
const formRef = unref(elFormRef)
await formRef?.validate(async (isValid) => {
if (isValid) {
loading.value = true
const { getFormData } = methods
const formData = await getFormData()
try {
const res = await postCurrentUserResetPassword(formData)
if (res) {
formRef.resetFields()
ElMessage.success('保存成功')
}
} finally {
loading.value = false
}
}
})
}
</script>
<template>
<Form
:schema="schema"
:rules="rules"
hide-required-asterisk
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
@register="register"
>
<template #save>
<div class="w-[50%]">
<ElButton :loading="loading" type="primary" class="w-[100%]" @click="save"> 保存 </ElButton>
</div>
</template>
</Form>
</template>
<style lang="less" scoped></style>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { reactive, unref, ref, watch } from 'vue'
import { Form } from '@/components/Form'
import { ElButton, ElCheckbox, ElLink } from 'element-plus'
import { ElButton } from 'element-plus'
import { useForm } from '@/hooks/web/useForm'
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
import { usePermissionStore } from '@/store/modules/permission'

View File

@ -24,14 +24,12 @@ let menuTypeOptions = ref<DictDetail[]>([])
const getOptions = async () => {
const dictStore = useDictStore()
const result = await dictStore.getDictObj(['sys_vadmin_menu_type'])
menuTypeOptions.value = result.sys_vadmin_menu_type
const dictOptions = await dictStore.getDictObj(['sys_vadmin_menu_type'])
menuTypeOptions.value = dictOptions.sys_vadmin_menu_type
}
getOptions()
console.log(menuTypeOptions)
const { register, tableObject, methods } = useTable<TableData>({
getListApi: getMenuListApi,
delListApi: delMenuListApi,
@ -115,6 +113,7 @@ getList()
:loading="tableObject.loading"
:selection="false"
row-key="id"
default-expand-all
@register="register"
>
<template #title="{ row }">

View File

@ -15,9 +15,21 @@ import Write from './components/Write.vue'
import { Dialog } from '@/components/Dialog'
import { ElButton, ElMessage, ElSwitch } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { selectDictLabel, DictDetail } from '@/utils/dict'
import { useDictStore } from '@/store/modules/dict'
const { t } = useI18n()
let genderOptions = ref<DictDetail[]>([])
const getOptions = async () => {
const dictStore = useDictStore()
const dictOptions = await dictStore.getDictObj(['sys_vadmin_gender'])
genderOptions.value = dictOptions.sys_vadmin_gender
}
getOptions()
const { register, tableObject, methods } = useTable({
getListApi: getUserListApi,
delListApi: delUserListApi,
@ -124,6 +136,10 @@ getList()
<template #is_active="{ row }">
<ElSwitch :value="row.is_active" disabled />
</template>
<template #gender="{ row }">
{{ selectDictLabel(genderOptions, row.gender) }}
</template>
</Table>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="700px">

View File

@ -65,8 +65,7 @@ ALLOW_HEADERS = ["*"]
"""
if DEBUG:
# 测试库
SQLALCHEMY_DATABASE_URL = "mysql+asyncmy://root:Ktianc123@rm-bp181adf0phw2o0r05o.mysql.rds.aliyuncs.com:3306/kinit"
# SQLALCHEMY_DATABASE_URL = "mysql+asyncmy://root:123456@127.0.0.1:3306/kinit"
SQLALCHEMY_DATABASE_URL = "mysql+asyncmy://root:123456@127.0.0.1:3306/kinit"
SQLALCHEMY_DATABASE_TYPE = "mysql"
else:
# 正式库

View File

@ -57,6 +57,18 @@ class UserDal(DalBase):
await self.db.flush()
return True
async def update_current_info(self, user: models.VadminUser, data: schemas.UserUpdate):
"""
更新当前用户信息
"""
user.name = data.name
user.nickname = data.nickname
user.gender = data.gender
self.db.add(user)
await self.db.flush()
await self.db.refresh(user)
return self.out_dict(user)
class RoleDal(DalBase):

View File

@ -52,10 +52,7 @@ class UserOut(UserSimpleOut):
class UserUpdate(BaseModel):
name: str
nickname: Optional[str] = None
is_active: Optional[bool] = True
is_cancel: Optional[bool] = False
gender: Optional[str] = "0"
role_ids: Optional[List[int]] = []
class ResetPwd(BaseModel):

View File

@ -53,6 +53,7 @@ async def login_for_access_token(request: Request, data: dict = Depends(authenti
"name": user.name,
"nickname": user.nickname,
"avatar": user.avatar,
"gender": user.gender,
"roles": [{"name": i.name, "value": i.role_key} for i in user.roles]
}
}

View File

@ -37,7 +37,7 @@ async def delete_users(ids: list = Depends(id_list), auth: Auth = Depends(login_
return SuccessResponse("删除成功")
@app.put("/users/{data_id}/", summary="更新用户基本信息")
@app.put("/users/{data_id}/", summary="更新用户信息")
async def put_user(data_id: int, data: schemas.User, auth: Auth = Depends(login_auth)):
return SuccessResponse(await crud.UserDal(auth.db).put_data(data_id, data))
@ -55,6 +55,16 @@ async def user_current_reset_password(data: schemas.ResetPwd, auth: Auth = Depen
return SuccessResponse(await crud.UserDal(auth.db).reset_current_password(auth.user, data))
@app.post("/user/current/update/info/", summary="更新当前用户基本信息")
async def post_user_current_update_info( data: schemas.UserUpdate, auth: Auth = Depends(login_auth)):
return SuccessResponse(await crud.UserDal(auth.db).update_current_info(auth.user, data))
@app.get("/user/current/info/", summary="获取当前用户基本信息")
async def get_user_current_info(auth: Auth = Depends(login_auth)):
return SuccessResponse(schemas.UserSimpleOut.from_orm(auth.user).dict())
###########################################################
# 角色管理
###########################################################