初始版本提交

This commit is contained in:
yaoyn
2024-02-05 09:15:37 +08:00
parent b52d4414be
commit 445292105f
1848 changed files with 236859 additions and 75 deletions

View File

@ -0,0 +1,59 @@
<template>
<CollapseContainer title="账号绑定" :canExpan="false">
<List>
<template v-for="item in list" :key="item.key">
<ListItem>
<ListItemMeta>
<template #avatar>
<Icon v-if="item.avatar" class="avatar" :icon="item.avatar" :color="item.color" />
</template>
<template #title>
{{ item.title }}
<a-button type="link" size="small" v-if="item.extra" class="extra">
{{ item.extra }}
</a-button>
</template>
<template #description>
<div>{{ item.description }} </div>
</template>
</ListItemMeta>
</ListItem>
</template>
</List>
</CollapseContainer>
</template>
<script lang="ts">
import { List } from 'ant-design-vue';
import { defineComponent } from 'vue';
import { CollapseContainer } from '/@/components/Container/index';
import Icon from '/@/components/Icon/index';
import { accountBindList } from './data';
export default defineComponent({
components: {
CollapseContainer,
List,
ListItem: List.Item,
ListItemMeta: List.Item.Meta,
Icon,
},
setup() {
return {
list: accountBindList,
};
},
});
</script>
<style lang="less" scoped>
.avatar {
font-size: 40px !important;
}
.extra {
float: right;
margin-top: 10px;
margin-right: 30px;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,154 @@
<template>
<div class="overflow-y-auto h-full overflow-x-hidden">
<CollapseContainer :title="t('基本设置')">
<a-row :gutter="24">
<a-col :span="14">
<BasicForm @register="register" />
</a-col>
<a-col :span="10">
<div class="change-avatar">
<div class="mb-2"> {{ t('头像') }} </div>
<CropperAvatar
:btnText="t('更换头像')"
:uploadApi="uploadAvatar"
:value="avatar"
@change="uploadChange"
/>
</div>
</a-col>
</a-row>
<a-button type="primary" @click="handleSubmit"> {{ t('更新基本信息') }} </a-button>
</CollapseContainer>
<CollapseContainer :title="t('个人资料')">
<a-row :gutter="24">
<a-col :span="14">
<a-descriptions :column="1">
<a-descriptions-item
label="所属组织"
:contentStyle="contentStyle"
:labelStyle="labelStyle"
>
<div v-for="item in departmentNameList" :key="item">{{ item }}</div>
</a-descriptions-item>
<a-descriptions-item
label="所属角色"
:contentStyle="contentStyle"
:labelStyle="labelStyle"
>
<div v-for="item in roleNameList" :key="item">{{ item }}</div>
</a-descriptions-item>
<a-descriptions-item
label="所属岗位"
:contentStyle="contentStyle"
:labelStyle="labelStyle"
>
<div v-for="item in postNameList" :key="item">{{ item }}</div>
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
</CollapseContainer>
</div>
</template>
<script lang="ts">
import { Row, Col } from 'ant-design-vue';
import { computed, defineComponent, onMounted, ref } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { CollapseContainer } from '/@/components/Container/index';
import { CropperAvatar } from '/@/components/Cropper';
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 { getUserProfile, updateUserInfo, uploadAvatar } from '/@/api/system/user';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const contentStyle = { flexDirection: 'column' };
const labelStyle = { width: '120px', textAlign: 'right', display: 'block' };
export default defineComponent({
components: {
BasicForm,
CollapseContainer,
CropperAvatar,
[Row.name]: Row,
[Col.name]: Col,
},
setup() {
const { createMessage } = useMessage();
const userStore = useUserStore();
const id = ref('');
const departmentNameList = ref([]);
const postNameList = ref([]);
const roleNameList = ref([]);
const [register, { setFieldsValue, validate }] = useForm({
labelWidth: 120,
schemas: baseSetschemas,
showActionButtonGroup: false,
});
onMounted(async () => {
const data = userStore.getUserInfo;
id.value = data.id;
setFieldsValue(data);
getUserProfile().then((res) => {
departmentNameList.value = res.departmentNameList || [];
postNameList.value = res.postNameList || [];
roleNameList.value = res.roleNameList || [];
});
});
const avatar = computed(() => {
const { avatar } = userStore.getUserInfo;
return avatar || headerImg;
});
return {
avatar,
register,
uploadAvatar,
postNameList,
roleNameList,
departmentNameList,
contentStyle,
labelStyle,
handleSubmit: async () => {
const values = await validate();
const userInfo = userStore.getUserInfo;
values.id = id.value;
await updateUserInfo(values);
userInfo.id = id.value;
userInfo.name = values.name;
userInfo.mobile = values.mobile;
userInfo.email = values.email;
userInfo.remark = values.remark;
userInfo.address = values.address;
userStore.setUserInfo(userInfo);
createMessage.success(t('更新成功!'));
},
uploadChange: (url: string) => {
const userInfo = userStore.getUserInfo;
userInfo.avatar = url;
userStore.setUserInfo(userInfo);
},
t,
};
},
});
</script>
<style lang="less" scoped>
.change-avatar {
img {
display: block;
margin-bottom: 15px;
border-radius: 50%;
}
}
</style>

View File

@ -0,0 +1,178 @@
<template>
<PageLayout
layout-class="!pl-0 -ml-14px"
:hasOperationSlot="false"
:searchConfig="searchConfig"
@search="search"
@scroll-height="scrollHeight"
>
<template #search> </template>
<template #right>
<div v-if="data.dataSource.length > 0">
<a-radio-group v-model:value="data.checked" style="width: 100%" @change="handleChange">
<a-row
:gutter="16"
class="list-box"
:style="{ overflowY: 'auto', maxHeight: tableOptions.scrollHeight + 70 + 'px' }"
:key="data.renderKey"
>
<a-col :span="6" class="item" v-for="(item, index) in data.dataSource" :key="index">
<div class="image relative">
<img :src="item.backgroundUrl" />
<a-tag color="processing" v-if="item.isFirst" class="absolute right-0 bottom-2"
>默认首页</a-tag
>
</div>
<div class="main">
<a-radio :value="item.id">{{ item.name }}</a-radio>
</div>
</a-col>
</a-row>
</a-radio-group>
<!-- <div class="page-box">
<a-pagination
v-model:current="data.pagination.current"
:pageSize="data.pagination.pageSize"
:total="data.pagination.total"
show-less-items
@change="getList"
/>
</div> -->
</div>
<div v-else>
<EmptyBox />
</div>
</template>
</PageLayout>
</template>
<script lang="ts" setup>
import { onMounted, reactive } from 'vue';
import { DesktopPageModel } from '/@/api/desktop/model';
import { PageLayout, EmptyBox } from '/@/components/ModalPanel';
import userTableScrollHeight from '/@/hooks/setting/userTableScrollHeight';
import { useI18n } from '/@/hooks/web/useI18n';
import { getRolePrivateHome, setPrivateHome } from '/@/api/system/role';
import { notification } from 'ant-design-vue';
import { usePermissionStore } from '/@/store/modules/permission';
import { useUserStore } from '/@/store/modules/user';
const { t } = useI18n();
const searchConfig = [
{
field: 'keyword',
label: t('关键字'),
type: 'input',
},
];
const { tableOptions, scrollHeight } = userTableScrollHeight();
let data: {
dataSource: Array<DesktopPageModel>;
renderKey: number;
keyword: string;
checked: string;
// pagination: {
// current: number;
// total: number;
// pageSize: number;
// };
} = reactive({
dataSource: [],
renderKey: 0,
//pagination: { current: 1, total: 0, pageSize: 8 },
keyword: '',
checked: '',
});
const userStore = useUserStore();
const userInfo = userStore.getUserInfo;
onMounted(async () => {
//data.pagination.current = 1;
data.dataSource = [];
await getList();
setFirstHome();
});
async function getList() {
let params = {
//limit: data.pagination.current,
//size: data.pagination.pageSize,
keyword: data.keyword,
};
let res = await getRolePrivateHome(params);
data.dataSource = res;
// data.pagination.total = res.total;
data.renderKey++;
}
function search(params?: any) {
data.keyword = params.keyword;
// data.pagination.current = 1;
data.dataSource = [];
getList();
}
function setFirstHome() {
data.checked = userInfo.desktopSchema.id;
}
async function handleChange(v) {
try {
await setPrivateHome({ desktopId: v.target.value });
notification.success({
message: t('提示'),
description: t('修改首页成功'),
});
const permissionStore = usePermissionStore();
await permissionStore.changePermissionCode();
} catch (error) {
notification.error({
message: t('提示'),
description: t('修改首页失败'),
onClose: () => {
setFirstHome();
},
});
}
}
</script>
<style lang="less" scoped>
.list-box {
display: flex;
flex-wrap: wrap;
margin: 10px -8px;
.ant-row {
margin: 0 !important;
}
.item {
position: relative;
margin-bottom: 16px;
overflow: hidden;
border-radius: 4px;
}
.image {
width: 100%;
height: 140px;
img {
width: 100%;
height: 100%;
}
}
.main {
font-size: 12px;
text-align: center;
line-height: 40px;
}
}
.page-box {
position: absolute;
bottom: 20px;
right: 20px;
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<CollapseContainer title="新消息通知" :canExpan="false">
<List>
<template v-for="item in list" :key="item.key">
<ListItem>
<ListItemMeta>
<template #title>
{{ item.title }}
<Switch
class="extra"
checked-children=""
un-checked-children=""
default-checked
/>
</template>
<template #description>
<div>{{ item.description }} </div>
</template>
</ListItemMeta>
</ListItem>
</template>
</List>
</CollapseContainer>
</template>
<script lang="ts">
import { List, Switch } from 'ant-design-vue';
import { defineComponent } from 'vue';
import { CollapseContainer } from '/@/components/Container/index';
import { msgNotifyList } from './data';
export default defineComponent({
components: {
CollapseContainer,
List,
ListItem: List.Item,
ListItemMeta: List.Item.Meta,
Switch,
},
setup() {
return {
list: msgNotifyList,
};
},
});
</script>
<style lang="less" scoped>
.extra {
float: right;
margin-top: 10px;
margin-right: 30px;
}
</style>

View File

@ -0,0 +1,47 @@
<template>
<CollapseContainer title="安全设置" :canExpan="false">
<List>
<template v-for="item in list" :key="item.key">
<ListItem>
<ListItemMeta>
<template #title>
{{ item.title }}
<div class="extra" v-if="item.extra">
{{ item.extra }}
</div>
</template>
<template #description>
<div>{{ item.description }} </div>
</template>
</ListItemMeta>
</ListItem>
</template>
</List>
</CollapseContainer>
</template>
<script lang="ts">
import { List } from 'ant-design-vue';
import { defineComponent } from 'vue';
import { CollapseContainer } from '/@/components/Container/index';
import { secureSettingList } from './data';
export default defineComponent({
components: { CollapseContainer, List, ListItem: List.Item, ListItemMeta: List.Item.Meta },
setup() {
return {
list: secureSettingList,
};
},
});
</script>
<style lang="less" scoped>
.extra {
float: right;
margin-top: 10px;
margin-right: 30px;
font-weight: normal;
color: #1890ff;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<CollapseContainer :title="t('密码修改')" :canExpan="false">
<div class="flex flex-col items-center justify-center py-8 bg-white">
<BasicForm @register="register" />
<div class="flex justify-center">
<a-button @click="resetFields"> {{ t('重置') }} </a-button>
<a-button class="!ml-4" type="primary" @click="handleSubmit"> {{ t('确认') }} </a-button>
</div>
</div>
</CollapseContainer>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { BasicForm, useForm, FormSchema } from '/@/components/Form';
import { CollapseContainer } from '/@/components/Container/index';
import { PageEnum } from '/@/enums/pageEnum';
import { useGo } from '/@/hooks/web/usePage';
import { updatePassword } from '/@/api/system/user';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const pwdScore = ref(0);
export const formSchema: FormSchema[] = [
{
field: 'oldPassword',
label: t('当前密码'),
component: 'InputPassword',
required: true,
colProps: {
span: 24,
},
},
{
field: 'newPassword',
label: t('新密码'),
component: 'StrengthMeter',
componentProps: {
placeholder: t('新密码'),
onChange: (_, score) => {
pwdScore.value = score;
},
},
rules: [
{
validator: (_, value) => {
if (!value) {
return Promise.reject('请输入新密码');
}
if (pwdScore.value < 50) {
return Promise.reject('密码强度设置过低,请至少保证中等强度。');
}
return Promise.resolve();
},
trigger: 'change',
},
],
colProps: {
span: 24,
},
},
{
field: 'confirmPassword',
label: t('确认密码'),
component: 'InputPassword',
colProps: {
span: 24,
},
dynamicRules: ({ values }) => {
return [
{
required: true,
validator: (_, value) => {
if (!value) {
return Promise.reject(t('密码不能为空'));
}
if (value !== values.newPassword) {
return Promise.reject(t('两次输入的密码不一致!'));
}
return Promise.resolve();
},
},
];
},
},
];
export default defineComponent({
name: 'ChangePassword',
components: { BasicForm, CollapseContainer },
setup() {
const go = useGo();
const [register, { validate, resetFields }] = useForm({
size: 'large',
labelWidth: 100,
showActionButtonGroup: false,
schemas: formSchema,
});
async function handleSubmit() {
try {
const values = await validate();
// const { passwordOld, passwordNew } = values;
// TODO custom api
await updatePassword(values);
go(PageEnum.BASE_LOGIN);
} catch (error) {}
}
return { register, resetFields, handleSubmit, t };
},
});
</script>

View File

@ -0,0 +1,172 @@
import { FormSchema } from '/@/components/Form/index';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
export interface ListItem {
key: string;
title: string;
description: string;
extra?: string;
avatar?: string;
color?: string;
}
// tab的list
export const settingList = [
{
key: '1',
name: t('基本设置'),
component: 'BaseSetting',
},
{
key: '2',
name: t('密码修改'),
component: 'UpdatePassword',
},
{
key: '3',
name: t('首页设置'),
component: 'HomeSetting',
},
// {
// key: '2',
// name: '安全设置',
// component: 'SecureSetting',
// },
// {
// key: '3',
// name: '账号绑定',
// component: 'AccountBind',
// },
// {
// key: '4',
// name: '新消息通知',
// component: 'MsgNotify',
// },
];
// 基础设置 form
export const baseSetschemas: FormSchema[] = [
{
field: 'name',
component: 'Input',
label: t('姓名'),
colProps: { span: 18 },
required: true,
},
{
field: 'code',
component: 'Input',
label: t('编码'),
colProps: { span: 18 },
componentProps: {
disabled: true,
},
},
{
field: 'mobile',
component: 'Input',
label: t('联系电话'),
colProps: { span: 18 },
required: true,
},
{
field: 'email',
component: 'Input',
label: t('邮箱'),
colProps: { span: 18 },
},
{
field: 'remark',
component: 'InputTextArea',
label: t('备注'),
colProps: { span: 18 },
},
{
field: 'address',
component: 'InputTextArea',
label: t('所在地区'),
colProps: { span: 18 },
},
];
// 安全设置 list
export const secureSettingList: ListItem[] = [
{
key: '1',
title: '账户密码',
description: '当前密码强度::强',
extra: '修改',
},
{
key: '2',
title: '密保手机',
description: '已绑定手机138****8293',
extra: '修改',
},
{
key: '3',
title: '密保问题',
description: '未设置密保问题,密保问题可有效保护账户安全',
extra: '修改',
},
{
key: '4',
title: '备用邮箱',
description: '已绑定邮箱ant***sign.com',
extra: '修改',
},
{
key: '5',
title: 'MFA 设备',
description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
extra: '修改',
},
];
// 账号绑定 list
export const accountBindList: ListItem[] = [
{
key: '1',
title: '绑定淘宝',
description: '当前未绑定淘宝账号',
extra: '绑定',
avatar: 'ri:taobao-fill',
color: '#ff4000',
},
{
key: '2',
title: '绑定支付宝',
description: '当前未绑定支付宝账号',
extra: '绑定',
avatar: 'fa-brands:alipay',
color: '#2eabff',
},
{
key: '3',
title: '绑定钉钉',
description: '当前未绑定钉钉账号',
extra: '绑定',
avatar: 'ri:dingding-fill',
color: '#2eabff',
},
];
// 新消息通知 list
export const msgNotifyList: ListItem[] = [
{
key: '1',
title: '账户密码',
description: '其他用户的消息将以站内信的形式通知',
},
{
key: '2',
title: '系统消息',
description: '系统消息将以站内信的形式通知',
},
{
key: '3',
title: '待办任务',
description: '待办任务将以站内信的形式通知',
},
];

View File

@ -0,0 +1,73 @@
<template>
<ScrollContainer>
<PageWrapper dense contentFullHeight fixedHeight>
<div ref="wrapperRef" :class="prefixCls">
<Tabs tab-position="left" :tabBarStyle="tabBarStyle" class="h-full">
<template v-for="item in settingList" :key="item.key">
<TabPane :tab="item.name" class="h-full">
<component :is="item.component" />
</TabPane>
</template>
</Tabs>
</div>
</PageWrapper>
</ScrollContainer>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Tabs } from 'ant-design-vue';
import { PageWrapper } from '/@/components/Page';
import { ScrollContainer } from '/@/components/Container/index';
import { settingList } from './data';
import BaseSetting from './BaseSetting.vue';
import SecureSetting from './SecureSetting.vue';
import AccountBind from './AccountBind.vue';
import MsgNotify from './MsgNotify.vue';
import UpdatePassword from './UpdatePassword.vue';
import HomeSetting from './HomeSetting.vue';
export default defineComponent({
components: {
ScrollContainer,
Tabs,
TabPane: Tabs.TabPane,
BaseSetting,
SecureSetting,
AccountBind,
MsgNotify,
UpdatePassword,
HomeSetting,
PageWrapper,
},
setup() {
return {
prefixCls: 'account-setting',
settingList,
tabBarStyle: {
width: '220px',
},
};
},
});
</script>
<style lang="less" scoped>
.account-setting {
height: 100%;
background-color: @component-background;
margin-right: 7px;
.base-title {
padding-left: 0;
}
.ant-tabs-tab-active {
background-color: @item-active-bg;
}
:deep(.ant-tabs-content-holder),
:deep(.ant-tabs-content) {
height: 100%;
}
}
</style>