feat 用户基础消息修改, 一些报错修改

This commit is contained in:
xxm
2022-10-31 14:13:23 +08:00
parent de43ea4cd3
commit 9ca8e18f0e
17 changed files with 215 additions and 141 deletions

View File

@@ -5,7 +5,7 @@ VITE_USE_MOCK=true
VITE_PUBLIC_PATH=/ VITE_PUBLIC_PATH=/
# 跨域代理,您可以配置多个 ,请注意,没有换行符 # 跨域代理,您可以配置多个 ,请注意,没有换行符
VITE_PROXY=[["/api","http://localhost:9999"],["/upload","http://localhost:3300/upload"]] VITE_PROXY=[["/api","http://localhost:9999"],["/upload","http://localhost:9999/file/upload"]]
# 控制台不输出console # 控制台不输出console
VITE_DROP_CONSOLE=false VITE_DROP_CONSOLE=false

View File

@@ -1,5 +1,8 @@
import { defHttp } from '/@/utils/http/axios' import { defHttp } from '/@/utils/http/axios'
import { Result } from '/#/axios' import { Result, UploadFileParams } from '/#/axios'
import { UploadApiResult } from '/@/api/sys/model/uploadModel'
import { getAppEnvConfig } from '/@/utils/env'
const { VITE_GLOB_API_URL } = getAppEnvConfig()
/** /**
* 获取文件预览地址 * 获取文件预览地址
@@ -29,3 +32,34 @@ export const getFileDownloadUrl = (id) => {
params: { id }, params: { id },
}) })
} }
/**
* 上传文件
* @param params
* @param onUploadProgress
*/
export function uploadFile(params: UploadFileParams, onUploadProgress: (progressEvent: ProgressEvent) => void) {
return defHttp.uploadFile<UploadApiResult>(
{
url: VITE_GLOB_API_URL + '/file/upload',
onUploadProgress,
},
params,
)
}
/**
* 上传文件信息
*/
export interface UpdateFileInfo {
// 文件id
id: string
// 文件名称
fileName: string
// 文件后缀
fileSuffix: string
// 文件类型
fileType: string
// 文件大小
fileSize: number
}

View File

@@ -1,11 +1,7 @@
import { defHttp } from '/@/utils/http/axios' import { defHttp } from '/@/utils/http/axios'
import { getMenuListResultModel, MenuAndResource } from './model/menuModel' import { MenuAndResource } from './model/menuModel'
import { Result } from '/#/axios' import { Result } from '/#/axios'
enum Api {
GetMenuList = '/getMenuList',
}
/** /**
* 获取菜单和权限码 * 获取菜单和权限码
*/ */

View File

@@ -16,9 +16,16 @@ export interface LoginParams {
captcha: string captcha: string
} }
export interface RoleInfo { /**
roleName: string * 用户信息(用户详情)
value: string */
export interface UserDetails {
// 用户id
id: number
// 名称
name: string
// 账号
username: string
} }
/** /**
@@ -34,3 +41,19 @@ export interface GetUserInfoModel {
// 头像 // 头像
avatar: string avatar: string
} }
/**
* 用户基础消息
*/
export interface UserBaseInfo {
// 用户id
id: number
// 名称
name: string
// 性别
sex: number
// 头像
avatar: string
// 生日
birthday: string
}

View File

@@ -1,13 +1,6 @@
import { defHttp } from '/@/utils/http/axios' import { defHttp } from '/@/utils/http/axios'
import { LoginParams, GetUserInfoModel } from './model/userModel' import { LoginParams, GetUserInfoModel, UserBaseInfo, UserDetails } from './model/userModel'
import { Result } from '/#/axios'
import { ErrorMessageMode, Result } from '/#/axios'
enum Api {
Logout = '/logout',
GetPermCode = '/getPermCode',
TestRetry = '/testRetry',
}
/** /**
* 登录接口 返回token * 登录接口 返回token
@@ -18,6 +11,7 @@ export function loginApi(params: LoginParams) {
params, params,
}) })
} }
/** /**
* 登录后获取用户信息 * 登录后获取用户信息
*/ */
@@ -26,14 +20,31 @@ export function getUserInfo() {
} }
/** /**
* 获取用户菜单和资源权限 * 获取用户安全信息
*/ */
export function getPermissions(clientCode: string) { export function getUserSecurityInfo() {
return defHttp.get<Result<GetUserInfoModel>>({ url: '/role/menu/getPermissions', params: { clientCode } }) return defHttp.get<Result<UserDetails>>({
url: `/user/getUserSecurityInfo`,
})
} }
export function getPermCode() { /**
return defHttp.get<string[]>({ url: Api.GetPermCode }) * 获取用户基础信息
*/
export function getUserBaseInfo() {
return defHttp.get<Result<UserBaseInfo>>({
url: `/user/getUserBaseInfo`,
})
}
/**
* 更新用户基础信息
*/
export function updateBaseInfo(data) {
return defHttp.post({
url: '/user/updateBaseInfo',
data: data,
})
} }
/** /**
@@ -42,19 +53,3 @@ export function getPermCode() {
export function doLogout() { export function doLogout() {
return defHttp.post({ url: '/token/logout' }) return defHttp.post({ url: '/token/logout' })
} }
/**
* 测试重试
*/
export function testRetry() {
return defHttp.get(
{ url: Api.TestRetry },
{
retryRequest: {
isOpenRetry: true,
count: 5,
waitTime: 1000,
},
},
)
}

View File

@@ -65,10 +65,15 @@
}, },
) )
function handleUploadSuccess({ source }) { /**
* 响应
* @param source 文件bold内容
* @param data 上传成功后的文件信息 见 UpdateFileInfo
*/
function handleUploadSuccess({ source, data: { data } }) {
sourceValue.value = source sourceValue.value = source
emit('change', source) emit('change', data, source)
createMessage.success(t('component.cropper.uploadSuccess')) createMessage.success('上传成功')
} }
expose({ openModal: openModal.bind(null, true), closeModal }) expose({ openModal: openModal.bind(null, true), closeModal })

View File

@@ -1,5 +1,5 @@
<template> <template>
<BasicModal :footer="null" :title="t('layout.header.lockScreen')" v-bind="$attrs" :class="prefixCls" @register="register"> <BasicModal :footer="null" title="锁定屏幕" v-bind="$attrs" :class="prefixCls" @register="register">
<div :class="`${prefixCls}__entry`"> <div :class="`${prefixCls}__entry`">
<div :class="`${prefixCls}__header`"> <div :class="`${prefixCls}__header`">
<img :src="avatar" :class="`${prefixCls}__header-img`" /> <img :src="avatar" :class="`${prefixCls}__header-img`" />
@@ -11,9 +11,7 @@
<BasicForm @register="registerForm" /> <BasicForm @register="registerForm" />
<div :class="`${prefixCls}__footer`"> <div :class="`${prefixCls}__footer`">
<a-button type="primary" block class="mt-2" @click="handleLock"> <a-button type="primary" block class="mt-2" @click="handleLock"> 锁定 </a-button>
{{ t('layout.header.lockScreenBtn') }}
</a-button>
</div> </div>
</div> </div>
</BasicModal> </BasicModal>
@@ -46,7 +44,7 @@
schemas: [ schemas: [
{ {
field: 'password', field: 'password',
label: t('layout.header.lockScreenPassword'), label: '锁屏密码',
colProps: { colProps: {
span: 24, span: 24,
}, },

View File

@@ -86,7 +86,7 @@ export function createPermissionGuard(router: Router) {
// get userinfo while last fetch time is empty // get userinfo while last fetch time is empty
if (userStore.getLastUpdateTime === 0) { if (userStore.getLastUpdateTime === 0) {
try { try {
await userStore.getUserInfoAction() await userStore.refreshUserInfoAction()
} catch (err) { } catch (err) {
next() next()
return return

View File

@@ -14,10 +14,8 @@ import { router } from '/@/router'
import { usePermissionStore } from '/@/store/modules/permission' import { usePermissionStore } from '/@/store/modules/permission'
import { RouteRecordRaw } from 'vue-router' import { RouteRecordRaw } from 'vue-router'
import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic' import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'
import { isArray } from '/@/utils/is'
import { h } from 'vue' import { h } from 'vue'
import { getFilePreviewUrlPrefix } from '/@/api/common/FileUpload' import { getFilePreviewUrlPrefix } from '/@/api/common/FileUpload'
import headerImg from "/@/assets/images/header.jpg";
interface UserState { interface UserState {
userInfo: Nullable<UserInfo> userInfo: Nullable<UserInfo>
@@ -68,6 +66,10 @@ export const useUserStore = defineStore({
this.roleList = roleList this.roleList = roleList
setAuthCache(ROLES_KEY, roleList) setAuthCache(ROLES_KEY, roleList)
}, },
/**
* 保存用户信息, 不要直接使用
* 使用 UserStoreUtil 中的包装方法来处理
*/
setUserInfo(info: UserInfo | null) { setUserInfo(info: UserInfo | null) {
this.userInfo = info this.userInfo = info
this.lastUpdateTime = new Date().getTime() this.lastUpdateTime = new Date().getTime()
@@ -102,7 +104,7 @@ export const useUserStore = defineStore({
async afterLoginAction(goHome?: boolean) { async afterLoginAction(goHome?: boolean) {
if (!this.getToken) return null if (!this.getToken) return null
// 获取用户信息 // 获取用户信息
await this.getUserInfoAction() await this.refreshUserInfoAction()
const sessionTimeout = this.sessionTimeout const sessionTimeout = this.sessionTimeout
// 超时 // 超时
if (sessionTimeout) { if (sessionTimeout) {
@@ -122,18 +124,17 @@ export const useUserStore = defineStore({
goHome && (await router.replace(PageEnum.BASE_HOME)) goHome && (await router.replace(PageEnum.BASE_HOME))
} }
}, },
// 获取并存储用户信息 // 刷新登陆后用户信息
async getUserInfoAction() { async refreshUserInfoAction() {
if (!this.getToken) return null if (!this.getToken) return null
const { data: userInfo } = await getUserInfo() const { data: userInfo } = await getUserInfo()
// 设置头像 // 设置头像
const { data: urlPrefix } = await getFilePreviewUrlPrefix() const { data: urlPrefix } = await getFilePreviewUrlPrefix()
userInfo.avatar = userInfo.avatar ? urlPrefix + userInfo.avatar : '' userInfo.avatar = userInfo.avatar ? urlPrefix + userInfo.avatar : ''
this.setUserInfo(userInfo) this.setUserInfo(userInfo)
return userInfo
}, },
/** /**
* @description: logout * 退出
*/ */
async logout(goLogin = false) { async logout(goLogin = false) {
if (this.getToken) { if (this.getToken) {

View File

@@ -89,7 +89,6 @@ const transform: AxiosTransform = {
// 请求之前处理config // 请求之前处理config
beforeRequestHook: (config, options) => { beforeRequestHook: (config, options) => {
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options
if (joinPrefix) { if (joinPrefix) {
config.url = `${urlPrefix}${config.url}` config.url = `${urlPrefix}${config.url}`
} }

View File

@@ -5,11 +5,8 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { testRetry } from '/@/api/sys/user'
// @ts-ignore // @ts-ignore
const handleClick = async () => { const handleClick = async () => {}
await testRetry()
}
</script> </script>
<style lang="less"> <style lang="less">

View File

@@ -68,7 +68,7 @@
userStore.setToken(token) userStore.setToken(token)
// 重新获取用户信息和菜单 // 重新获取用户信息和菜单
userStore.getUserInfoAction() userStore.refreshUserInfoAction()
permissionStore.changePermissionCode() permissionStore.changePermissionCode()
} }

View File

@@ -44,7 +44,7 @@
userStore.setToken(token) userStore.setToken(token)
// 重新获取用户信息和菜单 // 重新获取用户信息和菜单
userStore.getUserInfoAction() userStore.refreshUserInfoAction()
refreshMenu() refreshMenu()
} }

View File

@@ -1,91 +1,131 @@
<template> <template>
<CollapseContainer title="基本设置" :canExpan="false"> <CollapseContainer title="基本设置" :canExpan="false">
<a-row :gutter="24"> <a-spin :spinning="confirmLoading">
<a-col :span="14"> <a-row :gutter="24">
<a-form <a-col :span="14">
class="small-from-item" <a-form
ref="formRef" ref="formRef"
:validate-trigger="['blur', 'change']" :validate-trigger="['blur', 'change']"
:model="form" :model="form"
:rules="rules" :rules="rules"
:label-col="labelCol" :label-col="labelCol"
:wrapper-col="wrapperCol" :wrapper-col="wrapperCol"
> >
<a-form-item label="名称" name="name"> <a-form-item label="名称" name="name">
<a-input v-model:value="form.name" :disabled="!edit" placeholder="请输入名称" /> <a-input v-model:value="form.name" :disabled="!edit" placeholder="请输入名称" />
</a-form-item> </a-form-item>
</a-form> <a-form-item label="性别" name="sex">
<!-- <BasicForm @register="register" />--> <a-select style="width: 200px" v-model:value="form.sex" :options="sexList" :disabled="!edit" placeholder="请选择性别" />
</a-col> </a-form-item>
<a-col :span="10"> <a-form-item label="生日" name="birthday">
<div class="change-avatar"> <a-date-picker placeholder="请选择日期" valueFormat="YYYY-MM-DD" :disabled="!edit" v-model:value="form.birthday" />
<div class="mb-2">头像</div> </a-form-item>
<CropperAvatar </a-form>
:uploadApi="uploadApi" </a-col>
:value="avatar" <a-col :span="10">
btnText="更换头像" <div class="change-avatar">
:btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }" <div class="mb-2">头像</div>
@change="updateAvatar" <CropperAvatar
width="150" v-if="edit"
/> :uploadApi="uploadFile"
</div> :value="avatar"
</a-col> btnText="上传头像"
</a-row> :btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
<a-button type="primary" @click="handleSubmit"> 更新基本信息 </a-button> @change="updateAvatar"
width="150"
/>
<img :src="avatar" style="width: 150px" v-else alt="avatar" />
</div>
</a-col>
</a-row>
<a-button v-if="edit" type="primary" @click="handleOk">更新基础信息</a-button>
<a-button v-else @click="edit = true">编辑基础信息</a-button>
</a-spin>
</CollapseContainer> </CollapseContainer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Button, Row, Col } from 'ant-design-vue' import headerImg from '/@/assets/images/header.jpg'
import { computed, defineComponent, onMounted, reactive } from 'vue' import { onMounted, reactive } from 'vue'
import { BasicForm, useForm } from '/@/components/Form/index'
import { CollapseContainer } from '/@/components/Container' import { CollapseContainer } from '/@/components/Container'
import { CropperAvatar } from '/@/components/Cropper' import { CropperAvatar } from '/@/components/Cropper'
import { useMessage } from '/@/hooks/web/useMessage' import { useMessage } from '/@/hooks/web/useMessage'
import headerImg from '/@/assets/images/header.jpg'
import { accountInfoApi } from '/@/api/demo/account'
import { baseSetschemas } from './data'
import { useUserStore } from '/@/store/modules/user' import { useUserStore } from '/@/store/modules/user'
import { uploadApi } from '/@/api/sys/upload'
import { $ref } from 'vue/macros' import { $ref } from 'vue/macros'
import { UserInfo } from '/#/store' import { FormInstance, Rule } from 'ant-design-vue/lib/form'
import { Rule } from 'ant-design-vue/lib/form' import { getUserBaseInfo, updateBaseInfo } from '/@/api/sys/user'
import { getFilePreviewUrlPrefix, UpdateFileInfo, uploadFile } from '/@/api/common/FileUpload'
import { UserBaseInfo } from '/@/api/sys/model/userModel'
import { useDict } from '/@/hooks/bootx/useDict'
import { LabeledValue } from 'ant-design-vue/lib/select'
const { createMessage } = useMessage() const { createMessage, createConfirm } = useMessage()
const userStore = useUserStore() const userStore = useUserStore()
const { dictDropDownNumber } = useDict()
let sexList = $ref<LabeledValue[]>()
// 表单项标题文字 // 表单项标题文字
const labelCol = { sm: { span: 3 } } const labelCol = { sm: { span: 3 } }
// 表单项内容 // 表单项内容
const wrapperCol = { sm: { span: 12 } } const wrapperCol = { sm: { span: 12 } }
let formRef = $ref<FormInstance>()
let confirmLoading = $ref(false)
let edit = $ref(false) let edit = $ref(false)
let avatar = $ref(headerImg)
let urlPrefix = $ref('')
// 用户信息 // 用户信息
let form = $ref({} as UserInfo) let form = $ref<UserBaseInfo>({
avatar: '',
birthday: '',
id: 0,
name: '',
sex: 0,
})
const rules = reactive({ const rules = reactive({
code: [{ required: true, message: '请输入表单编码' }], code: [{ required: true, message: '请输入表单编码' }],
name: [{ required: true, message: '请输入表单名称' }], name: [{ required: true, message: '请输入表单名称' }],
} as Record<string, Rule[]>) } as Record<string, Rule[]>)
// onMounted(async () => { onMounted(async () => {
// const data = await accountInfoApi() // 初始化用户信息
// setFieldsValue(data) await init()
// })
// 头像
const avatar = computed(() => {
const { avatar } = userStore.getUserInfo
return avatar || headerImg
}) })
// 更新头像 // 基础数据
function updateAvatar(src: string) { async function init() {
const userinfo = userStore.getUserInfo confirmLoading = true
userinfo.avatar = src // 初始化菜单
userStore.setUserInfo(userinfo) sexList = dictDropDownNumber('Sex')
const { data: userInfo } = await getUserBaseInfo()
// 设置头像
const result = await getFilePreviewUrlPrefix()
urlPrefix = result.data
avatar = userInfo.avatar ? urlPrefix + userInfo.avatar : ''
form = userInfo
confirmLoading = false
}
// 上传头像回调
function updateAvatar(file: UpdateFileInfo) {
console.log(file)
form.avatar = file.id
}
// 更新用户信息
async function handleOk() {
await formRef.validate()
createConfirm({
iconType: 'warning',
title: '警告',
content: '是否更新用户基础信息',
onOk: async () => {
confirmLoading = true
await updateBaseInfo(form)
createMessage.success('更新用户信息成功')
await userStore.refreshUserInfoAction()
confirmLoading = false
},
})
} }
function handleSubmit() {}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -1,6 +1,5 @@
<template> <template>
< forceRender :visible="visible" :maskClosable="true" width="60%" placement="right" :closable="true" @close="visible = false" > <basic-drawer forceRender showFooter v-bind="$attrs" title="字典列表" width="60%" :visible="visible" @close="visible = false">
<basic-drawer showFooter v-bind="$attrs" title="字典列表" width="60%" :visible="visible" @close="visible = false">
<vxe-toolbar ref="xToolbar" custom :refresh="{ query: queryPage }"> <vxe-toolbar ref="xToolbar" custom :refresh="{ query: queryPage }">
<template #buttons> <template #buttons>
<a-space> <a-space>

View File

@@ -9,16 +9,6 @@
:closable="true" :closable="true"
@close="visible = false" @close="visible = false"
> >
<basic-drawer
forceRender
v-bind="$attrs"
title="任务执行日志"
width="60%"
:maskClosable="false"
:visible="visible"
@close="visible = false"
>
<b-query :query-params="model.queryParam" :fields="fields" @query="queryPage" @reset="resetQueryParams" /> <b-query :query-params="model.queryParam" :fields="fields" @query="queryPage" @reset="resetQueryParams" />
<vxe-toolbar ref="xToolbar" custom :refresh="{ query: queryPage }" /> <vxe-toolbar ref="xToolbar" custom :refresh="{ query: queryPage }" />
<vxe-table row-id="id" ref="xTable" :data="pagination.records" :loading="loading"> <vxe-table row-id="id" ref="xTable" :data="pagination.records" :loading="loading">
@@ -49,17 +39,15 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, ref } from 'vue' import { nextTick } from 'vue'
import { $ref } from 'vue/macros' import { $ref } from 'vue/macros'
import { del, page } from './QuartzJobLog.api' import { del, page } from './QuartzJobLog.api'
import useTablePage from '/@/hooks/bootx/useTablePage' import useTablePage from '/@/hooks/bootx/useTablePage'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table' import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import BQuery from '/@/components/Bootx/Query/BQuery.vue' import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { FormEditType } from '/@/enums/formTypeEnum'
import { useMessage } from '/@/hooks/web/useMessage' import { useMessage } from '/@/hooks/web/useMessage'
import { QueryField } from '/@/components/Bootx/Query/Query' import { QueryField } from '/@/components/Bootx/Query/Query'
import { QuartzJob } from './QuartzJob.api' import { QuartzJob } from './QuartzJob.api'
import BasicDrawer from "/@/components/Drawer/src/BasicDrawer.vue";
// 使用hooks // 使用hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, pagination, pages, model, loading } = useTablePage(queryPage) const { handleTableChange, pageQueryResHandel, resetQueryParams, pagination, pages, model, loading } = useTablePage(queryPage)

3
types/store.d.ts vendored
View File

@@ -1,6 +1,5 @@
import { ErrorTypeEnum } from '/@/enums/exceptionEnum' import { ErrorTypeEnum } from '/@/enums/exceptionEnum'
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum' import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'
import { RoleInfo } from '/@/api/sys/model/userModel'
// Lock screen information // Lock screen information
export interface LockInfo { export interface LockInfo {
@@ -40,7 +39,7 @@ export interface UserInfo {
name: string name: string
// 账号 // 账号
username: string username: string
// 头像图片地址 // 头像图片id
avatar: string avatar: string
} }