refactor: ♻️ 页面优化调整

This commit is contained in:
Ray.Hao
2024-12-01 18:48:26 +08:00
parent adcdf4f03e
commit 7337506a06
18 changed files with 780 additions and 634 deletions

View File

@@ -89,10 +89,9 @@
"@dcloudio/uni-mp-weixin": "3.0.0-4020420240722002",
"@dcloudio/uni-mp-xhs": "3.0.0-4020420240722002",
"@dcloudio/uni-quickapp-webview": "3.0.0-4020420240722002",
"@qiun/uni-ucharts": "2.5.0-20230101",
"pinia": "^2.2.2",
"vue": "^3.4.21",
"wot-design-uni": "^1.3.11"
"vue": "^3.5.13",
"wot-design-uni": "^1.4.0"
},
"devDependencies": {
"@commitlint/cli": "^19.5.0",
@@ -117,7 +116,7 @@
"postcss-html": "^1.7.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.3.3",
"sass": "1.79.3",
"sass": "1.78.0",
"sass-loader": "^16.0.2",
"stylelint": "^16.9.0",
"stylelint-config-html": "^1.1.0",

View File

@@ -33,21 +33,33 @@
}
},
{
"path": "pages/mine/user-agreement/index",
"path": "pages/mine/settings/index",
"style": {
"navigationBarTitleText": "设置"
}
},
{
"path": "pages/mine/settings/account/index",
"style": {
"navigationBarTitleText": "账号和安全"
}
},
{
"path": "pages/mine/settings/agreement/index",
"style": {
"navigationBarTitleText": "用户协议"
}
},
{
"path": "pages/mine/privacy/index",
"path": "pages/mine/settings/privacy/index",
"style": {
"navigationBarTitleText": "隐私政策"
}
},
{
"path": "pages/mine/network-test/index",
"path": "pages/mine/settings/network/index",
"style": {
"navigationBarTitleText": "网络测"
"navigationBarTitleText": "网络测"
}
},
{
@@ -62,6 +74,7 @@
"navigationBarTitleText": "个人资料"
}
},
{
"path": "pages/work/user/index",
"style": {

View File

@@ -27,7 +27,7 @@
<!-- 通知公告 -->
<wd-notice-bar
class="mt-2"
custom-style="margin-top:10rpx"
text="这是一条消息提示信息,这是一条消息提示信息,这是一条消息提示信息"
prefix="check-outline"
color="#34D19D"
@@ -35,7 +35,7 @@
/>
<!-- 数据统计 -->
<wd-grid :column="2" class="mt-2" :gutter="2">
<wd-grid :column="2" custom-style="margin-top:10rpx" :gutter="2">
<wd-grid-item
v-for="(item, index) in visitStatsData"
:key="index"
@@ -47,7 +47,7 @@
<image class="w-80rpx h-80rpx rounded-8rpx" :src="item.icon" />
<view class="ml-5 text-left">
<view class="font-bold">{{ item.title }}</view>
<view class="mt-1">{{ item.todayCount }}</view>
<view class="mt-2">{{ item.todayCount }}</view>
</view>
</view>
</view>
@@ -136,7 +136,10 @@ const chartData = ref({});
// 日期范围
const recentDaysRange = ref(7);
const swiperList = ref(["https://oss.youlai.tech/blog/banner3.png"]);
const swiperList = ref([
"https://oss.youlai.tech/blog/banner9.png",
"https://oss.youlai.tech/blog/banner8.png",
]);
// 快捷导航列表
const navList = reactive([

View File

@@ -32,13 +32,9 @@
<view class="agreement-text">
<view class="agreement-links">
<text class="normal-text">登录即同意</text>
<text class="link" @click="goToUserAgreement">用户协议</text>
<text class="link" @click="navigateToUserAgreement">用户协议</text>
<text class="normal-text"></text>
<text class="link" @click="goToPrivacy">隐私政策</text>
</view>
<view class="copyright">
<text class="copyright-text">Copyright © {{ getYear() }} 有来开源组织</text>
<text class="copyright-text">All Rights Reserved</text>
<text class="link" @click="navigateToPrivacy">隐私政策</text>
</view>
</view>
</view>
@@ -75,19 +71,16 @@ const handleLogin = () => {
}
});
};
const goToUserAgreement = () => {
const navigateToUserAgreement = () => {
uni.navigateTo({
url: "/pages/mine/user-agreement/index",
});
};
const goToPrivacy = () => {
const navigateToPrivacy = () => {
uni.navigateTo({
url: "/pages/mine/privacy/index",
});
};
const getYear = () => {
return new Date().getFullYear();
};
</script>
<style lang="scss" scoped>

View File

@@ -1,269 +1,121 @@
<template>
<view class="mine">
<!-- 个人资料 -->
<view class="header">
<view class="mine-container">
<view class="mine-header">
<view class="flex items-center">
<!-- 头像 -->
<image
class="w-100rpx h-100rpx rounded-full"
:src="isLogin ? userInfo!.avatar : defaultAvatar"
/>
<view class="ml-20rpx text-32rpx">
<text v-if="isLogin">{{ userInfo!.nickname }}</text>
<view v-else @click="goToLoginPage">
<text>您还未登录请先</text>
<text class="cursor-pointer">
登录
<wd-icon name="arrow-right" />
</text>
<!-- 用户信息 -->
<view class="ml-20rpx">
<!-- 已登录 -->
<view v-if="isLogin">
<view class="text-32rpx">
{{ userInfo!.nickname }}
</view>
<view class="text-28rpx mt2">{{ userInfo?.username }}</view>
</view>
<!-- 未登录 -->
<view v-else @click="navigateToLoginPage">
<view class="login-tip">您还未登录请先登录</view>
<view class="w180rpx mt-2">
<button class="login-btn" @click="navigateToLoginPage">登录</button>
</view>
</view>
</view>
</view>
<view>
<text v-if="isLogin" class="cursor-pointer" @click="goToProfile">
个人信息
<!-- 编辑资料 -->
<view v-if="isLogin" class="cursor-pointer">
<wd-icon name="setting" />
</view>
</view>
<!-- 功能操作 -->
<wd-grid clickable custom-class="rounded-[10rpx] bg-white">
<wd-grid-item text="我的消息" icon="mail" @itemclick="handleItemclick" />
<wd-grid-item text="我的待办" icon="list" @itemclick="handleItemclick" />
<wd-grid-item text="我的收藏" icon="star" @itemclick="handleItemclick" />
<wd-grid-item text="浏览历史" icon="view" @itemclick="handleItemclick" />
</wd-grid>
<!-- 菜单列表 -->
<view class="mt40rpx">
<wd-cell-group border>
<wd-cell title="关于我们" icon="info-circle" clickable @click="navigateToAbout">
<wd-icon name="arrow-right" />
</text>
</view>
</view>
<view class="relative -top-[50px] p-[40rpx]">
<!-- 功能操作 -->
<wd-grid clickable class="rounded-[10rpx] bg-white">
<wd-grid-item :to="{ path: '/group' }" icon="setting" text="交流群" />
<wd-grid-item :to="{ path: '/customer-service' }" icon="setting" text="在线客服" />
<wd-grid-item :to="{ path: '/feedback' }" icon="setting" text="反馈社区" />
<wd-grid-item :to="{ path: '/like-us' }" icon="setting" text="点赞我们" />
</wd-grid>
<!-- 菜单列表 -->
<view class="mt50rpx">
<wd-cell-group border>
<wd-cell title="常见问题" icon="help-circle" @click="goToFAQ">
<wd-icon name="arrow-right" />
</wd-cell>
<wd-cell title="关于我们" icon="warning" clickable @click="goToAbout">
<wd-icon name="arrow-right" />
</wd-cell>
<wd-cell title="用户协议" icon="user" clickable @click="goToUserAgreement">
<wd-icon name="arrow-right" />
</wd-cell>
<wd-cell title="隐私政策" icon="folder" clickable @click="goToPrivacy">
<wd-icon name="arrow-right" />
</wd-cell>
<wd-cell title="网络检测" icon="wifi" clickable @click="goToNetworkTest">
<wd-icon name="arrow-right" />
</wd-cell>
<wd-cell
title="清除缓存"
icon="delete"
:value="cacheSize"
clickable
@click="handleClearCache"
/>
<wd-cell title="应用设置" icon="setting" clickable @click="goToSettings">
<wd-icon name="arrow-right" />
</wd-cell>
<wd-cell title="退出登录" icon="logout" clickable @click="handleLogout">
<wd-icon name="arrow-right" />
</wd-cell>
</wd-cell-group>
</view>
</view>
<!-- 全屏 loading -->
<view v-if="clearing" class="loading-mask">
<view class="loading-content">
<view class="loading-icon" />
<text class="loading-text">正在清理...</text>
</view>
</wd-cell>
<wd-cell title="常见问题" icon="help-circle" @click="navigateToFAQ">
<wd-icon name="arrow-right" />
</wd-cell>
<wd-cell title="问题反馈" icon="check-circle" clickable @click="navigateToAbout">
<wd-icon name="arrow-right" />
</wd-cell>
<wd-cell title="设置" icon="setting1" clickable @click="navigateToSettings">
<wd-icon name="arrow-right" />
</wd-cell>
</wd-cell-group>
</view>
<wd-toast />
</view>
</template>
<script lang="ts" setup>
import { useToast } from "wot-design-uni";
import { useUserStore } from "@/store/modules/user";
const toast = useToast();
const userStore = useUserStore();
const userInfo = computed(() => userStore.userInfo);
const isLogin = computed(() => !!userInfo.value);
const defaultAvatar = "/static/images/default-avatar.png";
// 登录
const goToLoginPage = () => {
const navigateToLoginPage = () => {
uni.navigateTo({ url: "/pages/login/index" });
};
// 个人信息
const goToProfile = () => {
const navigateToProfile = () => {
uni.navigateTo({ url: "/pages/mine/profile/index" });
};
// 用户协议
const goToUserAgreement = () => {
uni.navigateTo({ url: "/pages/mine/user-agreement/index" });
};
// 隐私政策
const goToPrivacy = () => {
uni.navigateTo({ url: "/pages/mine/privacy/index" });
};
// 网络测试
const goToNetworkTest = () => {
uni.navigateTo({ url: "/pages/mine/network-test/index" });
};
// 常见问题
const goToFAQ = () => {
const navigateToFAQ = () => {
uni.navigateTo({ url: "/pages/faq/index" });
};
// 关于我们
const goToAbout = () => {
const navigateToAbout = () => {
uni.navigateTo({ url: "/pages/mine/about/index" });
};
// 应用设置
const goToSettings = () => {
uni.showToast({
title: "建设中...",
icon: "none",
});
//uni.navigateTo({ url: "/pages/settings/index" });
};
// 退出登录
const handleLogout = () => {
userStore.logout().then(() => {
uni.showToast({ title: "已退出登录", icon: "success" });
});
};
// 是否正在清理
const clearing = ref(false);
// 缓存大小
const cacheSize = ref<any>("计算中...");
// 获取缓存大小
const getCacheSize = async () => {
try {
// #ifdef MP-WEIXIN
const res = await uni.getStorageInfo();
cacheSize.value = formatSize(res.currentSize);
// #endif
// #ifdef H5
cacheSize.value = formatSize(
Object.keys(localStorage).reduce((size, key) => size + localStorage[key].length, 0)
);
// #endif
if (!cacheSize.value) {
cacheSize.value = "0B";
}
} catch (error) {
console.error("获取缓存大小失败:", error);
cacheSize.value = "获取失败";
}
const navigateToSettings = () => {
uni.navigateTo({ url: "/pages/mine/settings/index" });
};
// 格式化存储大小
const formatSize = (size: number) => {
if (size < 1024) {
return size + "B";
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(2) + "KB";
} else {
return (size / 1024 / 1024).toFixed(2) + "MB";
}
const handleItemclick = (item: any) => {
toast.show("建设中...");
};
// 处理清除缓存
const handleClearCache = async () => {
if (cacheSize.value === "获取失败") {
uni.showToast({
title: "获取缓存信息失败,请稍后重试",
icon: "none",
duration: 2000,
});
return;
}
if (cacheSize.value === "0B") {
uni.showToast({
title: "暂无缓存需要清理",
icon: "none",
duration: 2000,
});
return;
}
if (clearing.value) {
return;
}
try {
clearing.value = true;
// 模拟清理过程
await new Promise((resolve) => setTimeout(resolve, 1500));
// 清除缓存
await uni.clearStorage();
// 更新缓存大小显示
await getCacheSize();
// 提示清理成功
uni.showToast({
title: "清理成功",
icon: "success",
});
} catch (error) {
uni.showToast({
title: "清理失败",
icon: "error",
});
} finally {
clearing.value = false;
}
};
onShow(() => {
getCacheSize();
});
</script>
<style lang="scss" scoped>
/* stylelint-disable selector-type-no-unknown */
page {
background: #f8f8f8;
}
/* stylelint-enable selector-type-no-unknown */
.mine {
height: 100%;
.header {
.mine-container {
.mine-header {
display: flex;
align-items: center;
align-items: start;
justify-content: space-between;
padding: 50rpx 20rpx 100rpx 20rpx;
padding: 60rpx 20rpx;
color: #fff;
background: linear-gradient(60deg, #165dff, #6aa1ff);
}
}
.loading-mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.4);
background: linear-gradient(60deg, #517cf0, #769ef5);
.loading-content {
padding: 30rpx 40rpx;
text-align: center;
background-color: rgba(255, 255, 255, 0.95);
border-radius: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
.loading-icon {
width: 60rpx;
height: 60rpx;
margin: 0 auto 20rpx;
border: 4rpx solid #f3f3f3;
border-top: 4rpx solid #409eff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.loading-text {
.login-tip {
font-size: 28rpx;
font-weight: 500;
color: #333;
color: #fff;
}
.login-btn {
line-height: 60rpx;
color: #4d80f0;
background-color: #fff;
border-radius: 10rpx;
}
}
}

View File

@@ -19,17 +19,12 @@
/>
</view>
</wd-cell>
<wd-cell
title="昵称"
:value="userProfile.nickname"
is-link
@click="handleOpenDialog(DialogType.ACCOUNT)"
/>
<wd-cell title="昵称" :value="userProfile.nickname" is-link @click="handleOpenDialog()" />
<wd-cell
title="性别"
:value="userProfile.gender === 1 ? '男' : userProfile.gender === 2 ? '女' : '未知'"
is-link
@click="handleOpenDialog(DialogType.ACCOUNT)"
@click="handleOpenDialog()"
/>
<wd-cell title="用户名" :value="userProfile.username" />
<wd-cell title="部门" :value="userProfile.deptName" />
@@ -38,41 +33,12 @@
</wd-cell-group>
</view>
<view class="profile-card">
<wd-cell-group border>
<wd-cell
title="账户密码"
label="定期修改密码有助于保护账户安全"
value="修改"
is-link
@click="handleOpenDialog(DialogType.PASSWORD)"
/>
<wd-cell
title="绑定手机"
:value="userProfile?.mobile"
is-link
@click="handleOpenDialog(DialogType.MOBILE)"
/>
<wd-cell
title="绑定邮箱"
:value="userProfile?.email ? userProfile.email : '未绑定邮箱'"
is-link
@click="handleOpenDialog(DialogType.EMAIL)"
/>
</wd-cell-group>
</view>
<!--头像裁剪-->
<wd-img-cropper v-model="avatarShow" :img-src="originalSrc" @confirm="handleAvatarConfirm" />
<!--用户信息编辑弹出框-->
<wd-popup v-model="dialog.visible" position="bottom">
<wd-form
v-if="dialog.type === DialogType.ACCOUNT"
ref="userProfileFormRef"
:model="userProfileForm"
custom-class="edit-form"
>
<wd-form ref="userProfileFormRef" :model="userProfileForm" custom-class="edit-form">
<wd-cell-group border>
<wd-input
v-model="userProfileForm.nickname"
@@ -93,128 +59,6 @@
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</wd-form>
<wd-form
v-if="dialog.type === DialogType.PASSWORD"
ref="passwordChangeFormRef"
:model="passwordChangeForm"
custom-class="edit-form"
>
<wd-cell-group border>
<wd-input
v-model="passwordChangeForm.oldPassword"
label="原密码"
label-width="160rpx"
show-password
clearable
placeholder="请输入原密码"
prop="oldPassword"
:rules="rules.oldPassword"
/>
<wd-input
v-model="passwordChangeForm.newPassword"
label="新密码"
label-width="160rpx"
show-password
clearable
placeholder="请输入新密码"
prop="newPassword"
:rules="rules.newPassword"
/>
<wd-input
v-model="passwordChangeForm.confirmPassword"
label="确认密码"
label-width="160rpx"
show-password
clearable
placeholder="请确认新密码"
prop="confirmPassword"
:rules="rules.confirmPassword"
/>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</wd-form>
<wd-form
v-if="dialog.type === DialogType.MOBILE"
ref="mobileBindingFormRef"
:model="mobileBindingForm"
custom-class="edit-form"
>
<wd-cell-group border>
<wd-input
v-model="mobileBindingForm.mobile"
label="手机号码"
label-width="160rpx"
clearable
placeholder="请输入手机号码"
prop="mobile"
:rules="rules.mobile"
/>
<wd-input
v-model="mobileBindingForm.code"
label="验证码"
label-width="160rpx"
clearable
placeholder="请输入验证码"
prop="code"
:rules="rules.code"
>
<template #suffix>
<wd-button
plain
:disabled="mobileCountdown > 0"
@click="handleSendVerificationCode('MOBILE')"
>
{{ mobileCountdown > 0 ? `${mobileCountdown}s后重新发送` : "发送验证码" }}
</wd-button>
</template>
</wd-input>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</wd-form>
<wd-form
v-if="dialog.type === DialogType.EMAIL"
ref="emailBindingFormRef"
:model="emailBindingForm"
custom-class="edit-form"
>
<wd-cell-group border>
<wd-input
v-model="emailBindingForm.email"
label="邮箱"
label-width="160rpx"
clearable
placeholder="请输入邮箱"
prop="email"
:rules="rules.email"
/>
<wd-input
v-model="emailBindingForm.code"
label="验证码"
label-width="160rpx"
clearable
placeholder="请输入验证码"
prop="code"
:rules="rules.code"
>
<template #suffix>
<wd-button
plain
:disabled="emailCountdown > 0"
@click="handleSendVerificationCode('EMAIL')"
>
{{ emailCountdown > 0 ? `${emailCountdown}s后重新发送` : "发送验证码" }}
</wd-button>
</template>
</wd-input>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</wd-form>
</wd-popup>
</view>
</template>
@@ -222,7 +66,6 @@
import UserAPI, {
type UserProfileVO,
UserProfileForm,
PasswordChangeForm,
MobileBindingForm,
EmailBindingForm,
} from "@/api/system/user";
@@ -262,173 +105,41 @@ function handleAvatarConfirm(event) {
});
}
const validatorConfirmPassword = (value) => {
if (!value) {
return Promise.reject("请确认密码");
} else {
if (value !== passwordChangeForm.newPassword) {
return Promise.reject("两次输入的密码不一致");
} else {
return Promise.resolve();
}
}
};
// 本页面中所有的校验规则
const rules = reactive({
nickname: [{ required: true, message: "请填写昵称" }],
gender: [{ required: true, message: "请选择性别" }],
oldPassword: [{ required: true, message: "请填写原密码" }],
newPassword: [{ required: true, message: "请填写新密码" }],
confirmPassword: [{ required: true, message: "请确认密码", validator: validatorConfirmPassword }],
mobile: [{ required: true, pattern: /^1[3-9]\d{9}$/, message: "请填写正确的手机号码" }],
code: [{ required: true, message: "请填写验证码" }],
email: [
{
required: true,
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
message: "请填写正确的邮箱地址",
},
],
});
enum DialogType {
ACCOUNT = "account",
PASSWORD = "password",
MOBILE = "mobile",
EMAIL = "email",
}
const dialog = reactive({
visible: false,
type: "" as DialogType, // 修改账号资料,修改密码、绑定手机、绑定邮箱
});
const userProfileForm = reactive<UserProfileForm>({});
const passwordChangeForm = reactive<PasswordChangeForm>({});
const mobileBindingForm = reactive<MobileBindingForm>({});
const emailBindingForm = reactive<EmailBindingForm>({});
const userProfileFormRef = ref();
const passwordChangeFormRef = ref();
const mobileBindingFormRef = ref();
const emailBindingFormRef = ref();
const mobileCountdown = ref(0);
const mobileTimer = ref<NodeJS.Timeout | null>(null);
const emailCountdown = ref(0);
const emailTimer = ref<NodeJS.Timeout | null>(null);
/**
* 打开弹窗
* @param type 弹窗类型 ACCOUNT: 账号资料 PASSWORD: 修改密码 MOBILE: 绑定手机 EMAIL: 绑定邮箱
*/
const handleOpenDialog = (type: DialogType) => {
dialog.type = type;
const handleOpenDialog = () => {
dialog.visible = true;
switch (type) {
case DialogType.ACCOUNT:
// 初始化表单数据
userProfileForm.nickname = userProfile.value?.nickname;
userProfileForm.gender = userProfile.value?.gender;
break;
case DialogType.PASSWORD:
passwordChangeForm.oldPassword = "";
passwordChangeForm.newPassword = "";
passwordChangeForm.confirmPassword = "";
break;
case DialogType.MOBILE:
mobileBindingForm.mobile = "";
mobileBindingForm.code = "";
break;
case DialogType.EMAIL:
emailBindingForm.email = "";
emailBindingForm.code = "";
break;
}
};
/**
* 发送验证码
*
* @param contactType 联系方式类型 MOBILE: 手机号码 EMAIL: 邮箱
*/
const handleSendVerificationCode = async (contactType: string) => {
if (contactType === "MOBILE") {
mobileBindingFormRef.value.validate("mobile").then(({ valid }) => {
if (valid) {
UserAPI.sendVerificationCode(mobileBindingForm.mobile, "MOBILE").then(() => {
uni.showToast({ title: "验证码已发送", icon: "none" });
mobileCountdown.value = 60;
mobileTimer.value = setInterval(() => {
if (mobileCountdown.value > 0) {
mobileCountdown.value -= 1;
} else {
clearInterval(mobileTimer.value!);
}
}, 1000);
});
}
});
} else if (contactType === "EMAIL") {
emailBindingFormRef.value.validate("email").then(({ valid }) => {
if (valid) {
UserAPI.sendVerificationCode(emailBindingForm.email, "EMAIL").then(() => {
uni.showToast({ title: "验证码已发送", icon: "none" });
emailCountdown.value = 60;
emailTimer.value = setInterval(() => {
if (emailCountdown.value > 0) {
emailCountdown.value -= 1;
} else {
clearInterval(emailTimer.value!);
}
}, 1000);
});
}
});
}
// 初始化表单数据
userProfileForm.nickname = userProfile.value?.nickname;
userProfileForm.gender = userProfile.value?.gender;
};
// 提交表单
function handleSubmit() {
if (dialog.type === DialogType.ACCOUNT) {
userProfileFormRef.value.validate().then(({ valid }) => {
if (valid) {
UserAPI.updateProfile(userProfileForm).then(() => {
uni.showToast({ title: "账号资料修改成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
});
}
});
} else if (dialog.type === DialogType.PASSWORD) {
passwordChangeFormRef.value.validate().then(({ valid }) => {
if (valid) {
UserAPI.changePassword(passwordChangeForm).then(() => {
uni.showToast({ title: "密码修改成功", icon: "none" });
dialog.visible = false;
});
}
});
} else if (dialog.type === DialogType.MOBILE) {
mobileBindingFormRef.value.validate().then(({ valid }) => {
if (valid) {
UserAPI.bindMobile(mobileBindingForm).then(() => {
uni.showToast({ title: "手机号绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
});
}
});
} else if (dialog.type === DialogType.EMAIL) {
emailBindingFormRef.value.validate().then(({ valid }) => {
if (valid) {
UserAPI.bindEmail(emailBindingForm).then(() => {
uni.showToast({ title: "邮箱绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
});
}
});
}
userProfileFormRef.value.validate().then(({ valid }) => {
if (valid) {
UserAPI.updateProfile(userProfileForm).then(() => {
uni.showToast({ title: "账号资料修改成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
});
}
});
}
onMounted(() => {
@@ -485,10 +196,7 @@ function touchmoveListener(event) {
position: absolute;
top: 50%;
left: 50%;
font-size: 60px;
color: #fff;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
}
}

View File

@@ -0,0 +1,343 @@
<template>
<view class="profile">
<view class="profile-card">
<wd-cell-group border>
<wd-cell
title="账户密码"
label="定期修改密码有助于保护账户安全"
value="修改"
is-link
@click="handleOpenDialog(DialogType.PASSWORD)"
/>
<wd-cell
title="绑定手机"
:value="userProfile?.mobile"
is-link
@click="handleOpenDialog(DialogType.MOBILE)"
/>
<wd-cell
title="绑定邮箱"
:value="userProfile?.email ? userProfile.email : '未绑定邮箱'"
is-link
@click="handleOpenDialog(DialogType.EMAIL)"
/>
</wd-cell-group>
</view>
<!--用户信息编辑弹出框-->
<wd-popup v-model="dialog.visible" position="bottom">
<wd-form
v-if="dialog.type === DialogType.PASSWORD"
ref="passwordChangeFormRef"
:model="passwordChangeForm"
custom-class="edit-form"
>
<wd-cell-group border>
<wd-input
v-model="passwordChangeForm.oldPassword"
label="原密码"
label-width="160rpx"
show-password
clearable
placeholder="请输入原密码"
prop="oldPassword"
:rules="rules.oldPassword"
/>
<wd-input
v-model="passwordChangeForm.newPassword"
label="新密码"
label-width="160rpx"
show-password
clearable
placeholder="请输入新密码"
prop="newPassword"
:rules="rules.newPassword"
/>
<wd-input
v-model="passwordChangeForm.confirmPassword"
label="确认密码"
label-width="160rpx"
show-password
clearable
placeholder="请确认新密码"
prop="confirmPassword"
:rules="rules.confirmPassword"
/>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</wd-form>
<wd-form
v-if="dialog.type === DialogType.MOBILE"
ref="mobileBindingFormRef"
:model="mobileBindingForm"
custom-class="edit-form"
>
<wd-cell-group border>
<wd-input
v-model="mobileBindingForm.mobile"
label="手机号码"
label-width="160rpx"
clearable
placeholder="请输入手机号码"
prop="mobile"
:rules="rules.mobile"
/>
<wd-input
v-model="mobileBindingForm.code"
label="验证码"
label-width="160rpx"
clearable
placeholder="请输入验证码"
prop="code"
:rules="rules.code"
>
<template #suffix>
<wd-button
plain
:disabled="mobileCountdown > 0"
@click="handleSendVerificationCode('MOBILE')"
>
{{ mobileCountdown > 0 ? `${mobileCountdown}s后重新发送` : "发送验证码" }}
</wd-button>
</template>
</wd-input>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</wd-form>
<wd-form
v-if="dialog.type === DialogType.EMAIL"
ref="emailBindingFormRef"
:model="emailBindingForm"
custom-class="edit-form"
>
<wd-cell-group border>
<wd-input
v-model="emailBindingForm.email"
label="邮箱"
label-width="160rpx"
clearable
placeholder="请输入邮箱"
prop="email"
:rules="rules.email"
/>
<wd-input
v-model="emailBindingForm.code"
label="验证码"
label-width="160rpx"
clearable
placeholder="请输入验证码"
prop="code"
:rules="rules.code"
>
<template #suffix>
<wd-button
plain
:disabled="emailCountdown > 0"
@click="handleSendVerificationCode('EMAIL')"
>
{{ emailCountdown > 0 ? `${emailCountdown}s后重新发送` : "发送验证码" }}
</wd-button>
</template>
</wd-input>
</wd-cell-group>
<view class="footer">
<wd-button type="primary" size="large" block @click="handleSubmit">提交</wd-button>
</view>
</wd-form>
</wd-popup>
</view>
</template>
<script setup lang="ts">
import UserAPI, {
PasswordChangeForm,
MobileBindingForm,
EmailBindingForm,
UserProfileVO,
} from "@/api/system/user";
const validatorConfirmPassword = (value: string) => {
if (!value) {
return Promise.reject("请确认密码");
} else {
if (value !== passwordChangeForm.newPassword) {
return Promise.reject("两次输入的密码不一致");
} else {
return Promise.resolve();
}
}
};
// 本页面中所有的校验规则
const rules = reactive({
oldPassword: [{ required: true, message: "请填写原密码" }],
newPassword: [{ required: true, message: "请填写新密码" }],
confirmPassword: [{ required: true, message: "请确认密码", validator: validatorConfirmPassword }],
mobile: [{ required: true, pattern: /^1[3-9]\d{9}$/, message: "请填写正确的手机号码" }],
code: [{ required: true, message: "请填写验证码" }],
email: [
{
required: true,
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
message: "请填写正确的邮箱地址",
},
],
});
enum DialogType {
PASSWORD = "password",
MOBILE = "mobile",
EMAIL = "email",
}
const dialog = reactive({
visible: false,
type: "" as DialogType, // 修改账号资料,修改密码、绑定手机、绑定邮箱
});
const userProfile = ref<UserProfileVO>(); //用户信息
const passwordChangeForm = reactive<PasswordChangeForm>({});
const mobileBindingForm = reactive<MobileBindingForm>({});
const emailBindingForm = reactive<EmailBindingForm>({});
const passwordChangeFormRef = ref();
const mobileBindingFormRef = ref();
const emailBindingFormRef = ref();
const mobileCountdown = ref(0);
const mobileTimer = ref<ReturnType<typeof setInterval> | null>(null);
const emailCountdown = ref(0);
const emailTimer = ref<ReturnType<typeof setTimeout> | null>(null);
/** 加载用户信息 */
const loadUserProfile = async () => {
userProfile.value = await UserAPI.getProfile();
};
/**
* 打开弹窗
* @param type 弹窗类型 ACCOUNT: 账号资料 PASSWORD: 修改密码 MOBILE: 绑定手机 EMAIL: 绑定邮箱
*/
const handleOpenDialog = (type: DialogType) => {
dialog.type = type;
dialog.visible = true;
switch (type) {
case DialogType.PASSWORD:
passwordChangeForm.oldPassword = "";
passwordChangeForm.newPassword = "";
passwordChangeForm.confirmPassword = "";
break;
case DialogType.MOBILE:
mobileBindingForm.mobile = "";
mobileBindingForm.code = "";
break;
case DialogType.EMAIL:
emailBindingForm.email = "";
emailBindingForm.code = "";
break;
}
};
/**
* 发送验证码
*
* @param contactType 联系方式类型 MOBILE: 手机号码 EMAIL: 邮箱
*/
const handleSendVerificationCode = async (contactType: string) => {
if (contactType === "MOBILE") {
mobileBindingFormRef.value.validate("mobile").then(({ valid }: { valid: boolean }) => {
if (valid) {
UserAPI.sendVerificationCode(mobileBindingForm.mobile!, "MOBILE").then(() => {
uni.showToast({ title: "验证码已发送", icon: "none" });
mobileCountdown.value = 60;
mobileTimer.value = setInterval(() => {
if (mobileCountdown.value > 0) {
mobileCountdown.value -= 1;
} else {
clearInterval(mobileTimer.value!);
}
}, 1000);
});
}
});
} else if (contactType === "EMAIL") {
emailBindingFormRef.value.validate("email").then(({ valid }: { valid: boolean }) => {
if (valid) {
UserAPI.sendVerificationCode(emailBindingForm.email!, "EMAIL").then(() => {
uni.showToast({ title: "验证码已发送", icon: "none" });
emailCountdown.value = 60;
emailTimer.value = setInterval(() => {
if (emailCountdown.value > 0) {
emailCountdown.value -= 1;
} else {
clearInterval(emailTimer.value!);
}
}, 1000);
});
}
});
}
};
// 提交表单
function handleSubmit() {
if (dialog.type === DialogType.PASSWORD) {
passwordChangeFormRef.value.validate().then(({ valid }: { valid: boolean }) => {
if (valid) {
UserAPI.changePassword(passwordChangeForm).then(() => {
uni.showToast({ title: "密码修改成功", icon: "none" });
dialog.visible = false;
});
}
});
} else if (dialog.type === DialogType.MOBILE) {
mobileBindingFormRef.value.validate().then(({ valid }: { valid: boolean }) => {
if (valid) {
UserAPI.bindMobile(mobileBindingForm).then(() => {
uni.showToast({ title: "手机号绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
});
}
});
} else if (dialog.type === DialogType.EMAIL) {
emailBindingFormRef.value.validate().then(({ valid }: { valid: boolean }) => {
if (valid) {
UserAPI.bindEmail(emailBindingForm).then(() => {
uni.showToast({ title: "邮箱绑定成功", icon: "none" });
dialog.visible = false;
loadUserProfile();
});
}
});
}
}
onMounted(() => {
loadUserProfile();
});
</script>
<style lang="scss" scoped>
.profile {
.profile-card {
padding: 10rpx;
margin-bottom: 24rpx;
line-height: 1.1;
background-color: rgb(255, 255, 255);
border-radius: 16px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.05);
}
.edit-form {
padding-top: 40rpx;
.ef-radio-group {
line-height: 1;
text-align: left;
}
.footer {
padding: 24rpx;
}
}
}
</style>

View File

@@ -0,0 +1,193 @@
<template>
<view class="settings-container">
<wd-cell-group>
<wd-cell title="账号和安全" icon="secured" is-link @click="navigateToAccount" />
<wd-cell title="用户协议" icon="user" is-link @click="navigateToUserAgreement" />
<wd-cell title="隐私政策" icon="folder" is-link @click="navigateToPrivacy" />
</wd-cell-group>
<wd-cell-group custom-style="margin-top:40rpx">
<wd-cell title="网络测试" icon="wifi" is-link @click="navigateToNetworkTest" />
<wd-cell
title="清空缓存"
icon="delete1"
:value="cacheSize"
clickable
@click="handleClearCache"
/>
</wd-cell-group>
<wd-cell-group v-if="isLogin" custom-style="margin-top:40rpx">
<wd-cell title="退出" icon="logout" is-link @click="handleLogout" />
</wd-cell-group>
<!-- 全屏 loading -->
<view v-if="clearing" class="loading-mask">
<view class="loading-content">
<view class="loading-icon" />
<text class="loading-text">正在清理...</text>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { useUserStore } from "@/store/modules/user";
const userStore = useUserStore();
const isLogin = computed(() => !!userStore.userInfo);
// 账号和安全
const navigateToAccount = () => {
uni.navigateTo({ url: "/pages/mine/settings/account/index" });
};
// 用户协议
const navigateToUserAgreement = () => {
uni.navigateTo({ url: "/pages/mine/settings/agreement/index" });
};
// 隐私政策
const navigateToPrivacy = () => {
uni.navigateTo({ url: "/pages/mine/settings/privacy/index" });
};
// 网络测试
const navigateToNetworkTest = () => {
uni.navigateTo({ url: "/pages/mine/settings/network/index" });
};
// 是否正在清理
const clearing = ref(false);
// 缓存大小
const cacheSize = ref<any>("计算中...");
// 获取缓存大小
const getCacheSize = async () => {
try {
// #ifdef MP-WEIXIN
const res = await uni.getStorageInfo();
cacheSize.value = formatSize(res.currentSize);
// #endif
// #ifdef H5
cacheSize.value = formatSize(
Object.keys(localStorage).reduce((size, key) => size + localStorage[key].length, 0)
);
// #endif
if (!cacheSize.value) {
cacheSize.value = "0B";
}
} catch (error) {
console.error("获取缓存大小失败:", error);
cacheSize.value = "获取失败";
}
};
// 格式化存储大小
const formatSize = (size: number) => {
if (size < 1024) {
return size + "B";
} else if (size < 1024 * 1024) {
return (size / 1024).toFixed(2) + "KB";
} else {
return (size / 1024 / 1024).toFixed(2) + "MB";
}
};
// 处理清除缓存
const handleClearCache = async () => {
if (cacheSize.value === "获取失败") {
uni.showToast({
title: "获取缓存信息失败,请稍后重试",
icon: "none",
duration: 2000,
});
return;
}
if (cacheSize.value === "0B") {
uni.showToast({
title: "暂无缓存需要清理",
icon: "none",
duration: 2000,
});
return;
}
if (clearing.value) {
return;
}
try {
clearing.value = true;
// 模拟清理过程
await new Promise((resolve) => setTimeout(resolve, 1500));
// 清除缓存
await uni.clearStorage();
// 更新缓存大小显示
await getCacheSize();
// 提示清理成功
uni.showToast({
title: "清理成功",
icon: "success",
});
} catch (error) {
uni.showToast({
title: "清理失败",
icon: "error",
});
} finally {
clearing.value = false;
}
};
// 退出登录
const handleLogout = () => {
userStore.logout().then(() => {
uni.showToast({ title: "已退出登录", icon: "success" });
// 跳转到登录页
uni.reLaunch({ url: "/pages/mine/index" });
});
};
onLoad(() => {
getCacheSize();
});
</script>
<style lang="scss" scoped>
.settings-container {
padding: 20px;
.loading-mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.4);
.loading-content {
padding: 30rpx 40rpx;
text-align: center;
background-color: rgba(255, 255, 255, 0.95);
border-radius: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
.loading-icon {
width: 60rpx;
height: 60rpx;
margin: 0 auto 20rpx;
border: 4rpx solid #f3f3f3;
border-top: 4rpx solid #409eff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.loading-text {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
}
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<view class="network-test">
<view class="network-container">
<!-- 网络状态展示 -->
<view class="status-card">
<view class="status-header">
@@ -193,7 +193,7 @@ onBeforeUnmount(() => {
</script>
<style lang="scss" scoped>
.network-test {
.network-container {
min-height: 100vh;
padding: 20px;
background-color: #f5f5f5;

View File

@@ -39,16 +39,6 @@ const gridList = reactive([
url: "/pages/work/role/index",
prem: "sys:role:query",
},
{
icon: "/static/icons/dept.png",
title: "部门管理",
prem: "sys:dept:query",
},
{
icon: "/static/icons/dict.png",
title: "字典管理",
prem: "sys:dict:query",
},
{
icon: "/static/icons/config.png",
title: "系统配置",
@@ -66,13 +56,9 @@ const gridList = reactive([
{
title: "系统监控",
children: [
{
icon: "/static/icons/monitor.png",
title: "实时监控",
},
{
icon: "/static/icons/log.png",
title: "日志管理",
title: "系统日志",
url: "/pages/work/log/index",
prem: "sys:log:query",
},

View File

@@ -168,19 +168,19 @@ onReachBottom(() => {
// 操作按钮
const handleAction = (item: RolePageVO) => {
const actions = ["删除", "分配权限", "编辑"];
const actions = ["编辑", "分配权限", "删除"];
uni.showActionSheet({
itemList: actions,
success: ({ tapIndex }) => {
switch (actions[tapIndex]) {
case "删除":
handleDelete(item.id);
case "编辑":
handleOpenDialog(item.id);
break;
case "分配权限":
handleAssignPerm(item.id);
break;
case "编辑":
handleOpenDialog(item.id);
case "删除":
handleDelete(item.id);
break;
}
},
@@ -288,6 +288,15 @@ onLoad(() => {
});
</script>
<script lang="ts">
// https://wot-design-uni.pages.dev/guide/common-problems#%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%A0%B7%E5%BC%8F%E9%9A%94%E7%A6%BB
export default {
options: {
styleIsolation: "shared",
},
};
</script>
<style lang="scss" scoped>
.role {
:deep(.wd-drop-menu .wd-drop-menu__item) {
@@ -307,6 +316,15 @@ onLoad(() => {
}
}
}
:deep(.wd-cell__wrapper) {
padding: 4rpx 0;
}
:deep(.wd-cell) {
padding-right: 10rpx;
background: #f8f8f8;
}
:deep(.wd-fab__trigger) {
width: 80rpx !important;
height: 80rpx !important;

View File

@@ -1,21 +1,26 @@
<template>
<view class="user">
<view style="width: 100%; height: var(--status-bar-height)" />
<!-- 自定义导航栏的占位 -->
<view style="width: 100%; height: var(--status-bar-height)" />
<view class="user-container">
<!-- 自定义导航栏 -->
<wd-navbar title="用户管理">
<template #left>
<wd-icon name="thin-arrow-left" @click="handleNavigateback()" />
</template>
</wd-navbar>
<!-- 排序筛选 -->
<view class="mb-24rpx">
<view class="filter-container">
<wd-drop-menu>
<wd-drop-menu-item
v-model="sortValue"
:options="sortOptions"
title="排序"
icon="unfold-more"
@change="handleSortChange"
/>
<wd-drop-menu-item ref="filterDropMenu" title="筛选">
<wd-drop-menu-item ref="filterDropMenu" title="筛选" icon="filter">
<view>
<wd-input
v-model="queryParams.keywords"
@@ -26,69 +31,84 @@
<cu-date-query v-model="queryParams.createTime" label="创建时间" />
<view class="flex-between py-2">
<wd-button custom-class="w-20per" type="info" @click="handleResetQuery">
重置
</wd-button>
<wd-button custom-class="w-70per" @click="handleQuery">查询</wd-button>
<wd-button type="info" @click="handleResetQuery">重置</wd-button>
<wd-button @click="handleQuery">查询</wd-button>
</view>
</view>
</wd-drop-menu-item>
</wd-drop-menu>
</view>
<!-- 用户卡片 -->
<wd-card v-for="item in pageData" :key="item.id">
<template #title>
<view class="flex-between">
<view class="flex-center">
<wd-img :width="50" :height="50" round :src="item.avatar" />
<view class="ml-2">
<view class="font-bold">
{{ item.nickname }}
<wd-icon v-if="item.gender == 1" name="gender-male" class="color-#4D80F0" />
<wd-icon v-else-if="item.gender == 2" name="gender-female" class="color-#FA4350" />
<!-- 数据列表 -->
<view class="data-container">
<wd-card v-for="item in pageData" :key="item.id">
<template #title>
<view class="flex-between">
<view class="flex-center">
<wd-img :width="50" :height="50" round :src="item.avatar" />
<view class="ml-2">
<view class="font-bold">
{{ item.nickname }}
<wd-icon v-if="item.gender == 1" name="gender-male" class="color-#4D80F0" />
<wd-icon
v-else-if="item.gender == 2"
name="gender-female"
class="color-#FA4350"
/>
</view>
<view class="mt-1"><wd-text :text="item.deptName" size="12px" /></view>
</view>
<view class="mt-1"><wd-text :text="item.deptName" size="12px" /></view>
</view>
<view>
<wd-tag v-if="item.status === 1" type="success" plain>正常</wd-tag>
<wd-tag v-else-if="item.status === 0" plain>禁用</wd-tag>
</view>
</view>
<view>
<wd-tag v-if="item.status === 1" type="success" plain>正常</wd-tag>
<wd-tag v-else-if="item.status === 0" plain>禁用</wd-tag>
</template>
<wd-cell-group>
<wd-cell title="用户名" :value="item.username" icon="user" />
<wd-cell title="角色" :value="item.roleNames" icon="usergroup" />
<wd-cell title="手机号码" :value="item.mobile" icon="mobile" />
<wd-cell title="邮箱" :value="item.email" icon="mail" />
</wd-cell-group>
<template #footer>
<view class="flex-between">
<view class="text-left">
<wd-text text="创建时间:" size="small" class="font-bold" />
<wd-text :text="item.createTime" size="small" />
</view>
<view class="text-right">
<wd-button
type="primary"
size="small"
plain
:v-has-perm="'sys:user:edit'"
@click="handleOpenDialog(item.id)"
>
编辑
</wd-button>
&nbsp;
<wd-button
type="error"
size="small"
plain
:v-has-perm="'sys:user:delete'"
@click="handleDelete(item.id)"
>
删除
</wd-button>
</view>
</view>
</view>
</template>
</template>
</wd-card>
<wd-cell-group>
<wd-cell title="用户名" :value="item.username" icon="user" />
<wd-cell title="角色" :value="item.roleNames" icon="usergroup" />
<wd-cell title="手机号码" :value="item.mobile" icon="mobile" />
<wd-cell title="邮箱" :value="item.email" icon="mail" />
</wd-cell-group>
<wd-loadmore v-if="total > 0" :state="loadMoreState" @reload="loadmore" />
<wd-status-tip v-else-if="total == 0" image="search" tip="当前搜索无结果" />
</view>
<template #footer>
<view class="flex-between">
<view class="text-left">
<wd-text text="创建时间:" size="small" class="font-bold" />
<wd-text :text="item.createTime" size="small" />
</view>
<view class="text-right">
<wd-button type="primary" size="small" plain @click="handleOpenDialog(item.id)">
编辑
</wd-button>
&nbsp;
<wd-button type="error" size="small" plain @click="handleDelete(item.id)">
删除
</wd-button>
</view>
</view>
</template>
</wd-card>
<wd-loadmore v-if="total > 0" :state="loadMoreState" @reload="loadmore" />
<wd-status-tip v-else-if="total == 0" image="search" tip="当前搜索无结果" />
<wd-message-box />
<!-- 编辑弹窗 -->
<!-- 弹窗表单 -->
<wd-popup v-model="dialog.visible" position="bottom" @close="hancleCloseDialog">
<wd-form ref="userFormRef" :model="formData" :rules="rules">
<wd-cell-group border>
@@ -113,17 +133,21 @@
</wd-cell>
</wd-cell-group>
</wd-form>
<view class="footer">
<view class="popup-footer">
<wd-button type="primary" block @click="handleSubmit">提交</wd-button>
</view>
</wd-popup>
<!-- 悬浮操作按钮 -->
<wd-fab
:v-has-perm="'sys:user:add'"
position="left-bottom"
:expandable="false"
customStyle="width: 1rem; height: 1rem; line-height: 1rem;z-index:9"
custom-style="z-index: 9"
@click="handleOpenDialog"
/>
<wd-message-box />
</view>
</template>
@@ -368,24 +392,38 @@ onLoad(() => {
});
</script>
<script lang="ts">
// https://wot-design-uni.pages.dev/guide/common-problems#%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%A0%B7%E5%BC%8F%E9%9A%94%E7%A6%BB
export default {
options: {
styleIsolation: "shared",
},
};
</script>
<style lang="scss" scoped>
.user {
.user-container {
:deep(.wd-cell__wrapper) {
padding: 4rpx 0;
}
:deep(.wd-cell) {
padding-right: 10rpx;
background: #f8f8f8;
}
:deep(.wd-fab__trigger) {
width: 80rpx !important;
height: 80rpx !important;
}
:deep(.w-20per) {
width: 20%;
.filter-container {
padding: 10rpx;
background: #fff;
}
:deep(.w-70per) {
width: 70%;
.data-container {
margin-top: 20rpx;
}
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB