feat:将 pages 非 tabbar 页面,全部迁移到 pages-core,降低主包大小

This commit is contained in:
YunaiV
2025-12-22 20:15:24 +08:00
parent 555806f3da
commit 54585c4cde
30 changed files with 22 additions and 22 deletions

View File

@@ -0,0 +1,91 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="联系客服"
left-arrow
placeholder
safe-area-inset-top
fixed
@click-left="handleBack"
/>
<!-- 客服卡片 -->
<view class="mx-30rpx mt-20rpx rounded-16rpx bg-white px-60rpx py-80rpx">
<view class="flex flex-col items-center">
<!-- 二维码图片 -->
<view class="mb-30rpx h-280rpx w-280rpx overflow-hidden rounded-16rpx">
<wd-img
:src="qrCodeUrl"
width="280rpx"
height="280rpx"
mode="aspectFit"
/>
</view>
<text class="mb-40rpx text-32rpx text-gray-800 font-bold">
添加客服二维码
</text>
<text class="mb-16rpx text-28rpx text-gray-500">
服务时间早上 9:00 - 22:00
</text>
<!-- 客服电话 -->
<view class="flex items-center text-28rpx text-gray-500">
<text>客服电话{{ servicePhone }}</text>
<text
class="ml-10rpx text-blue-500 underline"
@click="handleCallPhone"
>
拨打
</text>
</view>
<!-- 保存按钮 -->
<view class="mt-60rpx w-full">
<wd-button type="primary" block @click="handleSaveQRCode">
保存二维码图片
</wd-button>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { navigateBackPlus } from '@/utils'
import { saveImageToAlbum, staticUrl } from '@/utils/download'
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
const toast = useToast()
const qrCodeUrl = ref(staticUrl('/static/qrcode.png')) // 客服二维码图片地址
const servicePhone = ref('18818818818') // 客服电话号码
/** 返回上一页 */
function handleBack() {
navigateBackPlus('/pages/user/index')
}
/** 拨打电话 */
function handleCallPhone() {
uni.makePhoneCall({
phoneNumber: servicePhone.value,
fail: (_err) => {
toast.show('拨打失败')
},
})
}
/** 保存二维码图片 */
async function handleSaveQRCode() {
await saveImageToAlbum(qrCodeUrl.value, 'weixin_qrcode.png')
}
</script>

View File

@@ -0,0 +1,66 @@
/**
* FAQ 常见问题数据
*/
export interface FaqItem {
/** 问题标题 */
title: string
/** 问题答案 */
content: string
}
export interface FaqCategory {
/** 分类图标 */
icon: string
/** 分类标题 */
title: string
/** 问题列表 */
childList: FaqItem[]
}
/** FAQ 数据列表 */
export const faqList: FaqCategory[] = [
{
icon: 'github-filled',
title: '芋道问题',
childList: [
{
title: '芋道开源吗?',
content: '开源,基于 MIT 协议,可免费商用。',
},
{
title: '芋道可以商用吗?',
content: '可以,芋道采用 MIT 开源协议,允许商业使用。',
},
{
title: '芋道官网地址多少?',
content: 'https://www.iocoder.cn',
},
{
title: '芋道文档地址多少?',
content: 'https://doc.iocoder.cn',
},
],
},
{
icon: 'warning',
title: '其他问题',
childList: [
{
title: '如何退出登录?',
content: '请点击 [我的] - [退出登录] 即可退出登录。',
},
{
title: '如何修改用户头像?',
content: '请点击 [我的] - [个人资料] - [选择头像] 即可更换用户头像。',
},
{
title: '如何修改登录密码?',
content: '请点击 [我的] - [账号安全] - [修改密码] 即可修改登录密码。',
},
{
title: '如何切换用户?',
content: '请先退出当前账号,然后使用其他账号重新登录即可。',
},
],
},
]

View File

@@ -0,0 +1,99 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="常见问题"
left-arrow
placeholder
safe-area-inset-top
fixed
@click-left="handleBack"
/>
<!-- 搜索框 -->
<wd-search
v-model="searchValue"
placeholder="请输入你想咨询的问题"
placeholder-left
hide-cancel
light
/>
<!-- FAQ Tabs -->
<wd-tabs v-model="activeTab" auto-line-width>
<wd-tab
v-for="(category, index) in faqList"
:key="index"
:title="category.title"
:name="index"
>
<view class="min-h-[calc(100vh-300rpx)] bg-white">
<wd-collapse v-model="activeNames" custom-class="faq-collapse">
<wd-collapse-item
v-for="(item, idx) in filteredList(category.childList)"
:key="idx"
:name="`${index}-${idx}`"
>
<template #title>
<view class="flex items-center">
<wd-icon name="edit-outline" size="18px" color="#1890ff" class="mr-16rpx" />
<text>{{ item.title }}</text>
</view>
</template>
<view class="text-28rpx text-gray-500 leading-relaxed">
{{ item.content }}
</view>
</wd-collapse-item>
</wd-collapse>
</view>
</wd-tab>
</wd-tabs>
</view>
</template>
<script lang="ts" setup>
import type { FaqItem } from './data'
import { ref } from 'vue'
import { navigateBackPlus } from '@/utils'
import { faqList } from './data'
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
const activeTab = ref<number>(0) // 当前选中的 Tab
const searchValue = ref('') // 搜索关键词
const activeNames = ref<string[]>([]) // 展开的问题
/** 过滤问题列表 */
function filteredList(list: FaqItem[]) {
if (!searchValue.value) {
return list
}
return list.filter(item =>
item.title.includes(searchValue.value) || item.content.includes(searchValue.value),
)
}
/** 返回上一页 */
function handleBack() {
navigateBackPlus('/pages/user/index')
}
</script>
<style lang="scss" scoped>
:deep(.faq-collapse) {
background: #fff;
.wd-collapse-item__header {
padding: 24rpx;
}
.wd-collapse-item__wrapper {
background: #f9fafb;
}
}
</style>

View File

@@ -0,0 +1,201 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="意见反馈"
left-arrow placeholder safe-area-inset-top fixed
@click-left="handleBack"
/>
<!-- 表单区域 -->
<view class="p-24rpx">
<wd-form ref="formRef" :model="formData" :rules="formRules">
<wd-cell-group custom-class="cell-group" border>
<wd-textarea
v-model="formData.content"
label="反馈内容"
label-width="180rpx"
prop="content"
placeholder="请输入您的宝贵意见和建议"
:maxlength="500"
show-word-limit
clearable
:rows="5"
/>
<wd-cell title="反馈图片" title-width="180rpx" />
<!-- TODO @芋艿图片上传的接入 -->
<view class="px-24rpx pb-24rpx">
<wd-upload
v-model:file-list="fileList"
:upload-method="customUpload"
accept="image"
multiple
:limit="9"
/>
</view>
</wd-cell-group>
</wd-form>
</view>
<!-- 底部提交按钮 -->
<view class="yd-detail-footer">
<wd-button
type="primary"
block
:loading="formLoading"
@click="handleSubmit"
>
提交反馈
</wd-button>
</view>
</view>
</template>
<script lang="ts" setup>
import type { UploadFile, UploadMethod } from 'wot-design-uni/components/wd-upload/types'
import { ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { getEnvBaseUrl, navigateBackPlus } from '@/utils/index'
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
const toast = useToast()
const formLoading = ref(false)
const fileList = ref<UploadFile[]>([])
const formData = ref({
content: '',
})
const formRules = {
content: [
{ required: true, message: '请输入反馈内容' },
{
required: true,
validator: (value: string) => value.length >= 10,
message: '反馈内容至少10个字符',
},
],
}
const formRef = ref()
/** 返回上一页 */
function handleBack() {
navigateBackPlus('/pages/user/index')
}
/** 自定义上传方法 */
const customUpload: UploadMethod = (file, formData, options) => {
const uploadTask = uni.uploadFile({
url: `${getEnvBaseUrl()}/infra/file/upload`,
header: {
...options.header,
},
name: options.name,
fileType: options.fileType,
formData,
filePath: file.url,
success(res) {
if (res.statusCode === options.statusCode) {
options.onSuccess(res, file, formData)
} else {
options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
}
},
fail(err) {
options.onError(err, file, formData)
},
})
uploadTask.onProgressUpdate((res) => {
options.onProgress(res, file)
})
}
/** 提交表单 */
async function handleSubmit() {
const { valid } = await formRef.value.validate()
if (!valid) {
return
}
formLoading.value = true
try {
// 构建提交数据
const submitData = {
content: formData.value.content,
// 提取已上传成功的图片 URL
images: fileList.value
.filter(file => file.status === 'success')
.map((file) => {
// 尝试从响应中解析 URL
if (file.response) {
try {
const res = typeof file.response === 'string' ? JSON.parse(file.response) : file.response
return res.data || file.url
} catch {
return file.url
}
}
return file.url
}),
}
// TODO: 替换为真实 API 调用
await mockSubmitFeedback(submitData)
toast.success('提交成功,感谢您的反馈!')
setTimeout(() => {
handleBack()
}, 1500)
} finally {
formLoading.value = false
}
}
// ===================== Mock API =====================
// TODO: 后端 API 实现后,删除此 mock 函数,替换为真实 API 调用
interface FeedbackData {
content: string
images: string[]
}
/**
* Mock 提交反馈接口
*
* @param data 反馈数据
*/
function mockSubmitFeedback(data: FeedbackData): Promise<{ code: number, message: string }> {
return new Promise((resolve, reject) => {
console.log('[Mock] 提交反馈数据:', data)
// 模拟网络延迟
setTimeout(() => {
// 模拟成功
if (data.content && data.content.length >= 10) {
resolve({
code: 0,
message: '提交成功',
})
} else {
reject(new Error('反馈内容不能少于 10 个字符'))
}
}, 1000)
})
}
</script>
<style lang="scss" scoped>
:deep(.cell-group) {
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.06);
}
.safe-area-inset-bottom {
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
}
</style>

View File

@@ -0,0 +1,148 @@
<template>
<wd-popup
v-model="visible"
position="bottom"
custom-style="border-radius: 24rpx 24rpx 0 0;"
safe-area-inset-bottom
@close="handleClose"
>
<view class="p-32rpx">
<view class="mb-32rpx text-center text-32rpx text-[#333] font-semibold">
{{ title }}
</view>
<!-- 昵称输入 -->
<template v-if="field === 'nickname'">
<wd-input
v-model="formValue"
placeholder="请输入昵称"
clearable
:focus="visible"
/>
</template>
<!-- 性别选择 -->
<template v-else-if="field === 'sex'">
<wd-radio-group v-model="formValue" cell>
<wd-radio v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)" :key="dict.value" :value="dict.value">
{{ dict.label }}
</wd-radio>
</wd-radio-group>
</template>
<!-- 手机输入 -->
<template v-else-if="field === 'mobile'">
<wd-input
v-model="formValue"
placeholder="请输入手机号"
type="number"
clearable
:focus="visible"
:maxlength="11"
/>
</template>
<!-- 邮箱输入 -->
<template v-else-if="field === 'email'">
<wd-input
v-model="formValue"
placeholder="请输入邮箱"
clearable
:focus="visible"
/>
</template>
<!-- 按钮 -->
<view class="mt-30rpx">
<wd-button block type="primary" :loading="submitting" @click="handleConfirm">
确定
</wd-button>
</view>
</view>
</wd-popup>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import { useToast } from 'wot-design-uni'
import { updateUserProfile } from '@/api/system/user/profile'
import { getIntDictOptions } from '@/hooks/useDict'
import { DICT_TYPE } from '@/utils/constants'
import { isBlank, isEmail, isMobile } from '@/utils/validator'
const props = defineProps<{
modelValue: boolean
field: 'nickname' | 'sex' | 'mobile' | 'email'
value: string | number
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
'success': []
}>()
const toast = useToast()
const visible = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val),
})
const formValue = ref<string | number>('') // 表单值
const submitting = ref(false) // 提交中状态
const title = computed(() => {
switch (props.field) {
case 'nickname':
return '修改昵称'
case 'sex':
return '修改性别'
case 'mobile':
return '修改手机'
case 'email':
return '修改邮箱'
default:
return '修改'
}
})
/** 监听弹窗打开,初始化值 */
watch(
() => props.modelValue,
(val) => {
if (val) {
formValue.value = props.value
}
},
)
/** 处理关闭 */
function handleClose() {
visible.value = false
}
/** 处理确认 */
async function handleConfirm() {
// 参数校验
if (props.field === 'sex' && !formValue.value) {
toast.warning('请选择性别')
return
}
if (props.field !== 'sex' && isBlank(formValue.value as string)) {
toast.warning(`请输入${title.value.replace('修改', '')}`)
return
}
if (props.field === 'mobile' && !isMobile(formValue.value as string)) {
toast.warning('请输入正确的手机号')
return
}
if (props.field === 'email' && !isEmail(formValue.value as string)) {
toast.warning('请输入正确的邮箱')
return
}
// 调用更新接口
submitting.value = true
try {
await updateUserProfile({ [props.field]: formValue.value })
toast.success('修改成功')
handleClose()
emit('success')
} finally {
submitting.value = false
}
}
</script>

View File

@@ -0,0 +1,139 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="个人资料"
left-arrow placeholder safe-area-inset-top fixed
@click-left="handleBack"
/>
<!-- 信息区域 -->
<wd-cell-group custom-class="cell-group" border>
<wd-cell title="头像" is-link center @click="handleEditAvatar">
<view class="ml-auto h-50rpx w-50rpx overflow-hidden rounded-full">
<image :src="userProfile?.avatar" mode="aspectFill" class="h-full w-full" />
</view>
</wd-cell>
<wd-cell title="昵称" :value="userProfile?.nickname || '-'" is-link @click="handleEdit('nickname')" />
<wd-cell title="性别" :value="getDictLabel(DICT_TYPE.SYSTEM_USER_SEX, userProfile?.sex) || '-'" is-link @click="handleEdit('sex')" />
<wd-cell title="手机" :value="userProfile?.mobile || '-'" is-link @click="handleEdit('mobile')" />
<wd-cell title="邮箱" :value="userProfile?.email || '-'" is-link @click="handleEdit('email')" />
</wd-cell-group>
<wd-cell-group custom-class="cell-group mt-24rpx" border>
<wd-cell title="部门" :value="userProfile?.dept?.name || '-'" />
<wd-cell title="岗位" :value="userProfile?.posts?.map(p => p.name).join('、') || '-'" />
<wd-cell title="角色" :value="userProfile?.roles?.map(r => r.name).join('、') || '-'" />
</wd-cell-group>
<!-- 头像裁剪 -->
<wd-img-cropper
v-model="showCropper"
:img-src="cropperSrc"
@confirm="handleCropperConfirm"
/>
<!-- 编辑弹窗 -->
<Form
v-model="formVisible"
:field="formType"
:value="formValue"
@success="loadUserProfile"
/>
</view>
</template>
<script lang="ts" setup>
import type { UserProfileVO } from '@/api/system/user/profile'
import { onMounted, ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { getUserProfile, updateUserProfile } from '@/api/system/user/profile'
import { getDictLabel } from '@/hooks/useDict'
import { useUserStore } from '@/store/user'
import { navigateBackPlus } from '@/utils'
import { DICT_TYPE } from '@/utils/constants'
import { uploadFileFromPath } from '@/utils/uploadFile'
import Form from './components/form.vue'
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
const toast = useToast()
const userStore = useUserStore()
const loading = ref(true)
const userProfile = ref<UserProfileVO | null>(null)
// 头像裁剪相关
const showCropper = ref(false)
const cropperSrc = ref('')
// 编辑弹窗相关
const formVisible = ref(false)
const formType = ref<'nickname' | 'sex' | 'mobile' | 'email'>('nickname')
const formValue = ref<string | number>('')
/** 返回上一页 */
function handleBack() {
navigateBackPlus('/pages/user/index')
}
/** 加载用户信息 */
async function loadUserProfile() {
loading.value = true
try {
userProfile.value = await getUserProfile()
} finally {
loading.value = false
}
}
/** 编辑头像 */
function handleEditAvatar() {
uni.chooseImage({
count: 1,
success: (res) => {
cropperSrc.value = res.tempFilePaths[0]
showCropper.value = true
},
})
}
/** 头像裁剪确认 */
async function handleCropperConfirm(event: { tempFilePath: string }) {
// 1.1 上传文件,获取 URL
const avatarUrl = await uploadFileFromPath(event.tempFilePath, 'avatar')
// 1.2 更新用户头像
await updateUserProfile({ avatar: avatarUrl })
// 2.1 直接更新本地状态,避免重新加载
if (userProfile.value) {
userProfile.value.avatar = avatarUrl
}
// 2.2 同步更新 userStore 中的头像
userStore.setUserAvatar(avatarUrl)
toast.success('头像修改成功')
}
/** 编辑字段 */
function handleEdit(field: 'nickname' | 'sex' | 'mobile' | 'email') {
formType.value = field
formValue.value = userProfile.value?.[field] ?? (field === 'sex' ? 1 : '')
formVisible.value = true
}
/** 初始化 */
onMounted(() => {
loadUserProfile()
})
</script>
<style lang="scss" scoped>
:deep(.cell-group) {
margin: 24rpx;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.06);
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<wd-popup
v-model="visible"
position="bottom"
custom-style="border-radius: 24rpx 24rpx 0 0;"
safe-area-inset-bottom
@close="handleClose"
>
<view class="p-32rpx">
<view class="mb-32rpx text-center text-32rpx text-[#333] font-semibold">
修改密码
</view>
<wd-input
v-model="formData.oldPassword"
label="旧密码"
placeholder="请输入旧密码"
show-password
clearable
/>
<wd-input
v-model="formData.newPassword"
label="新密码"
placeholder="请输入新密码"
show-password
clearable
/>
<wd-input
v-model="formData.confirmPassword"
label="确认密码"
placeholder="请再次输入新密码"
show-password
clearable
/>
<view class="mt-30rpx">
<wd-button block type="primary" :loading="submitting" @click="handleConfirm">
确定
</wd-button>
</view>
</view>
</wd-popup>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue'
import { useToast } from 'wot-design-uni'
import { updateUserPassword } from '@/api/system/user/profile'
import { isBlank } from '@/utils/validator'
const props = defineProps<{
modelValue: boolean
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
'success': []
}>()
const toast = useToast()
const visible = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val),
})
const formData = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: '',
})
const submitting = ref(false)
/** 监听弹窗打开,重置表单 */
watch(
() => props.modelValue,
(val) => {
if (val) {
formData.oldPassword = ''
formData.newPassword = ''
formData.confirmPassword = ''
}
},
)
/** 处理关闭 */
function handleClose() {
visible.value = false
}
/** 处理确认 */
async function handleConfirm() {
// 参数校验
if (isBlank(formData.oldPassword)) {
toast.warning('请输入旧密码')
return
}
if (isBlank(formData.newPassword)) {
toast.warning('请输入新密码')
return
}
if (isBlank(formData.confirmPassword)) {
toast.warning('请确认新密码')
return
}
if (formData.newPassword !== formData.confirmPassword) {
toast.warning('两次输入的密码不一致')
return
}
// 调用更新接口
submitting.value = true
try {
await updateUserPassword({
oldPassword: formData.oldPassword,
newPassword: formData.newPassword,
})
toast.success('密码修改成功')
handleClose()
emit('success')
} finally {
submitting.value = false
}
}
</script>

View File

@@ -0,0 +1,88 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="账号安全"
left-arrow placeholder safe-area-inset-top fixed
@click-left="handleBack"
/>
<!-- 安全设置区域 -->
<wd-cell-group custom-class="cell-group" border>
<wd-cell title="修改密码" is-link @click="handleChangePassword">
<template #icon>
<wd-icon name="lock-on" size="20px" color="#1890ff" class="mr-16rpx" />
</template>
</wd-cell>
</wd-cell-group>
<!-- 第三方绑定区域 -->
<wd-cell-group custom-class="cell-group mt-24rpx" border>
<wd-cell title="微信小程序" is-link @click="handleBindWechatMiniProgram">
<template #icon>
<wd-icon name="chat" size="20px" color="#07c160" class="mr-16rpx" />
</template>
<view class="text-[#999]">
未绑定
</view>
</wd-cell>
<wd-cell title="微信公众号" is-link @click="handleBindWechatOfficialAccount">
<template #icon>
<wd-icon name="chat" size="20px" color="#07c160" class="mr-16rpx" />
</template>
<view class="text-[#999]">
未绑定
</view>
</wd-cell>
</wd-cell-group>
<!-- 修改密码弹窗 -->
<PasswordForm v-model="showPasswordPopup" />
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { navigateBackPlus } from '@/utils'
import PasswordForm from './components/password-form.vue'
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
const toast = useToast()
const showPasswordPopup = ref(false) // 密码弹窗相关
/** 返回上一页 */
function handleBack() {
navigateBackPlus('/pages/user/index')
}
/** 打开修改密码弹窗 */
function handleChangePassword() {
showPasswordPopup.value = true
}
/** 绑定微信小程序 */
function handleBindWechatMiniProgram() {
toast.info('正在开发中')
}
/** 绑定微信公众号 */
function handleBindWechatOfficialAccount() {
toast.info('正在开发中')
}
</script>
<style lang="scss" scoped>
:deep(.cell-group) {
margin: 24rpx;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.06);
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="用户协议"
left-arrow
placeholder
safe-area-inset-top
fixed
@click-left="handleBack"
/>
<!-- 协议内容 -->
<view class="p-32rpx">
<view class="mb-40rpx text-center">
<text class="text-36rpx text-gray-800 font-bold">用户服务协议</text>
</view>
<view class="text-28rpx text-gray-600 leading-relaxed">
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">总则</text>
<text class="block">
1.1 欢迎使用芋道移动端应用以下简称"本应用"在使用本应用前请您仔细阅读本协议
</text>
<text class="mt-16rpx block">
1.2 您在使用本应用时即表示您已充分阅读理解并接受本协议的全部内容并与本应用达成协议
</text>
<text class="mt-16rpx block">
1.3 本应用有权根据需要不时地修订本协议修订后的协议一经公布即生效
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">服务内容</text>
<text class="block">
2.1 本应用为用户提供企业管理数据分析业务处理等相关服务
</text>
<text class="mt-16rpx block">
2.2 本应用保留随时变更中断或终止部分或全部服务的权利
</text>
<text class="mt-16rpx block">
2.3 用户理解本应用仅提供相关的网络服务除此之外与相关网络服务有关的设备及所需的费用均应由用户自行承担
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">用户账号</text>
<text class="block">
3.1 用户在使用本应用服务前需要注册账号用户应当对其账号和密码的安全负责
</text>
<text class="mt-16rpx block">
3.2 用户不得将账号转让出借或以任何方式提供给他人使用
</text>
<text class="mt-16rpx block">
3.3 如发现账号被盗用或存在安全漏洞用户应立即通知本应用
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">用户行为规范</text>
<text class="block">
4.1 用户在使用本应用服务时必须遵守国家法律法规不得利用本应用从事违法违规活动
</text>
<text class="mt-16rpx block">
4.2 用户不得干扰本应用的正常运行不得侵入本应用及国家计算机信息系统
</text>
<text class="mt-16rpx block">
4.3 用户不得传播违法有害骚扰侵害他人隐私等信息
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">知识产权</text>
<text class="block">
5.1 本应用的所有内容包括但不限于文字图片音频视频软件程序版面设计等均受知识产权法律法规保护
</text>
<text class="mt-16rpx block">
5.2 未经本应用书面许可任何人不得擅自使用复制修改传播本应用的任何内容
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">免责声明</text>
<text class="block">
6.1 本应用不对因网络状况通讯线路等任何技术原因导致的服务中断或其他缺陷承担责任
</text>
<text class="mt-16rpx block">
6.2 用户因使用本应用而产生的任何损失本应用不承担任何责任
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">其他</text>
<text class="block">
7.1 本协议的订立执行和解释及争议的解决均应适用中华人民共和国法律
</text>
<text class="mt-16rpx block">
7.2 如本协议中的任何条款无论因何种原因完全或部分无效或不具有执行力本协议的其余条款仍应有效并且有约束力
</text>
</view>
<view class="mt-48rpx text-center text-24rpx text-gray-400">
<text>最后更新日期2025 1 1</text>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { navigateBackPlus } from '@/utils'
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
/** 返回上一页 */
function handleBack() {
navigateBackPlus()
}
</script>

View File

@@ -0,0 +1,149 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="应用设置"
left-arrow
placeholder
safe-area-inset-top
fixed
@click-left="handleBack"
/>
<!-- Logo 区域 -->
<view class="flex flex-col items-center py-60rpx">
<image
class="mb-24rpx h-150rpx w-150rpx rounded-full"
src="/static/logo.svg"
mode="aspectFit"
/>
<text class="text-40rpx text-gray-800 font-medium">芋道移动端</text>
</view>
<!-- 设置列表 -->
<view class="mx-24rpx">
<wd-cell-group custom-class="cell-group" border>
<wd-cell
title="当前版本"
:value="`v${version}`"
is-link
@click="handleShowVersion"
>
<template #icon>
<wd-icon name="warning" size="20px" color="#1890ff" class="mr-16rpx" />
</template>
</wd-cell>
<wd-cell
title="本地缓存"
:value="storageSize"
is-link
@click="handleClearCache"
>
<template #icon>
<wd-icon name="delete" size="20px" color="#faad14" class="mr-16rpx" />
</template>
</wd-cell>
</wd-cell-group>
</view>
<!-- 底部协议和版权 -->
<view class="mt-80rpx flex flex-col items-center">
<view class="mb-40rpx flex items-center text-26rpx">
<text class="text-[#1890ff]" @click="handleGoAgreement">用户协议</text>
<text class="text-gray-500"></text>
<text class="text-[#1890ff]" @click="handleGoPrivacy">隐私协议</text>
</view>
<text class="mb-10rpx text-24rpx text-gray-400">
Copyright © 2026 iocoder.cn All Rights Reserved.
</text>
<text class="text-24rpx text-gray-400">
芋道源码
</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { navigateBackPlus } from '@/utils'
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
const toast = useToast()
const version = ref('1.0.0') // 当前版本号
const storageSize = ref('') // 本地缓存大小
/** 返回上一页 */
function handleBack() {
navigateBackPlus('/pages/user/index')
}
/** 获取应用版本号 */
function getAppVersion() {
// #ifdef APP-PLUS
const appInfo = uni.getSystemInfoSync()
version.value = appInfo.appVersion || '1.0.0'
// #endif
}
/** 获取本地缓存大小 */
function getStorageSize() {
const info = uni.getStorageInfoSync()
storageSize.value = `${info.currentSize}KB`
}
/** 显示版本信息 */
function handleShowVersion() {
toast.info(`当前版本v${version.value}`)
}
/** 清除缓存 */
function handleClearCache() {
uni.showModal({
title: '提示',
content: '确定要清除本地缓存吗?',
success: (res) => {
if (!res.confirm) {
return
}
try {
uni.clearStorageSync()
getStorageSize()
toast.success('缓存清除成功')
} catch {
toast.error('缓存清除失败')
}
},
})
}
/** 跳转到用户协议 */
function handleGoAgreement() {
uni.navigateTo({ url: '/pages-core/user/settings/agreement/index' })
}
/** 跳转到隐私协议 */
function handleGoPrivacy() {
uni.navigateTo({ url: '/pages-core/user/settings/privacy/index' })
}
/** 初始化 */
onMounted(() => {
getStorageSize()
getAppVersion()
})
</script>
<style lang="scss" scoped>
:deep(.cell-group) {
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 3rpx 8rpx rgba(24, 144, 255, 0.06);
}
</style>

View File

@@ -0,0 +1,159 @@
<template>
<view class="yd-page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="隐私协议"
left-arrow
placeholder
safe-area-inset-top
fixed
@click-left="handleBack"
/>
<!-- 协议内容 -->
<view class="p-32rpx">
<view class="mb-40rpx text-center">
<text class="text-36rpx text-gray-800 font-bold">隐私保护政策</text>
</view>
<view class="text-28rpx text-gray-600 leading-relaxed">
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">引言</text>
<text class="block">
芋道移动端以下简称"我们"非常重视用户的隐私和个人信息保护本隐私政策旨在向您说明我们如何收集使用存储和保护您的个人信息请您在使用我们的服务前仔细阅读并理解本隐私政策
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">我们收集的信息</text>
<text class="block">
1.1 账号信息当您注册账号时我们会收集您的手机号码邮箱地址等信息
</text>
<text class="mt-16rpx block">
1.2 个人资料您可以选择填写昵称头像性别生日等个人资料信息
</text>
<text class="mt-16rpx block">
1.3 设备信息我们可能会收集您的设备型号操作系统版本设备标识符等信息
</text>
<text class="mt-16rpx block">
1.4 日志信息我们会收集您使用服务时的操作日志包括访问时间功能使用记录等
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">信息的使用</text>
<text class="block">
2.1 为您提供服务我们使用收集的信息来提供维护和改进我们的服务
</text>
<text class="mt-16rpx block">
2.2 安全保障我们使用信息来验证身份预防欺诈和保护账号安全
</text>
<text class="mt-16rpx block">
2.3 服务优化我们可能使用信息来分析服务使用情况以优化用户体验
</text>
<text class="mt-16rpx block">
2.4 通知推送我们可能向您发送服务通知安全提醒等信息
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">信息的存储</text>
<text class="block">
3.1 我们会采取合理的安全措施来保护您的个人信息防止未经授权的访问使用或泄露
</text>
<text class="mt-16rpx block">
3.2 您的个人信息将存储在中华人民共和国境内的服务器上
</text>
<text class="mt-16rpx block">
3.3 我们仅在实现服务目的所必需的期限内保留您的个人信息
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">信息的共享</text>
<text class="block">
4.1 未经您的同意我们不会向第三方共享您的个人信息但以下情况除外
</text>
<text class="mt-16rpx block">
- 为遵守法律法规或政府机关的要求
</text>
<text class="mt-16rpx block">
- 为保护我们或用户的合法权益
</text>
<text class="mt-16rpx block">
- 在涉及合并收购或资产转让时我们可能会转移您的信息
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">您的权利</text>
<text class="block">
5.1 访问权您有权访问我们持有的关于您的个人信息
</text>
<text class="mt-16rpx block">
5.2 更正权如果您发现我们持有的个人信息不准确您有权要求更正
</text>
<text class="mt-16rpx block">
5.3 删除权在特定情况下您有权要求我们删除您的个人信息
</text>
<text class="mt-16rpx block">
5.4 撤回同意您可以随时撤回之前给予的同意
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">未成年人保护</text>
<text class="block">
6.1 我们非常重视对未成年人个人信息的保护如果您是未满18周岁的未成年人请在监护人的陪同下阅读本政策
</text>
<text class="mt-16rpx block">
6.2 我们不会主动收集未成年人的个人信息如果发现我们在未经监护人同意的情况下收集了未成年人的信息我们将尽快删除相关信息
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">政策更新</text>
<text class="block">
7.1 我们可能会不时更新本隐私政策更新后的政策将在应用内公布
</text>
<text class="mt-16rpx block">
7.2 对于重大变更我们会通过显著方式通知您
</text>
</view>
<view class="mb-32rpx">
<text class="mb-16rpx block text-gray-800 font-bold">联系我们</text>
<text class="block">
如果您对本隐私政策有任何疑问意见或建议请通过以下方式联系我们
</text>
<text class="mt-16rpx block">
邮箱7685413@qq.com
</text>
<text class="mt-16rpx block">
电话400-999-9999
</text>
</view>
<view class="mt-48rpx text-center text-24rpx text-gray-400">
<text>最后更新日期2025 1 1 </text>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { navigateBackPlus } from '@/utils'
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
/** 返回上一页 */
function handleBack() {
navigateBackPlus()
}
</script>