feat: 初始化前端项目基础结构

- 添加项目配置文件(tsconfig, eslint, prettier等)
- 实现基础路由和页面布局
- 添加全局状态管理和API请求封装
- 集成UI组件库和主题系统
- 添加文档网站和示例页面
- 配置CI/CD和工作流
This commit is contained in:
zhangtao
2026-05-01 00:47:48 +08:00
parent ff738007cd
commit 5c29d22aa5
256 changed files with 45257 additions and 2 deletions
+140
View File
@@ -0,0 +1,140 @@
<script setup lang="ts">
definePage({
name: 'about',
style: {
navigationBarTitleText: '关于',
},
})
// 核心团队成员
const coreTeam = [
{
name: '不如摸鱼去',
avatar: 'https://avatars.githubusercontent.com/u/26426873?v=4',
role: '前端打工仔',
desc: '负责WotUI组件库的开发和维护',
github: 'https://github.com/Moonofweisheng',
},
{
name: '二狗',
avatar: 'https://avatars.githubusercontent.com/u/50100966?v=4',
role: '灵活就业大师',
desc: '问题毁灭者,总能迅速解决各种技术难题',
github: 'https://github.com/810505339',
},
{
name: 'RJQingHuan',
avatar: 'https://avatars.githubusercontent.com/u/53939074?v=4',
role: 'Pull Shark',
desc: '热衷参与开源组件建设',
github: 'https://github.com/RJQingHuan',
},
{
name: 'skiyee',
avatar: 'https://avatars.githubusercontent.com/u/120664167?v=4',
role: 'uni-ku 的创立者,重金雇佣兵',
desc: '精通 JS 和 TS 的全能攻城狮',
github: 'https://github.com/skiyee',
},
{
name: 'jasper-ops',
avatar: 'https://avatars.githubusercontent.com/u/85024227?v=4',
role: '新技术狂热分子',
desc: '始终走在技术前沿,热衷于探索最新的开发趋势',
github: 'https://github.com/jasper-ops',
},
]
function openUrl(url: string) {
window.open(url, '_blank')
}
// 打开公众号二维码
function openWeChat() {
uni.previewImage({
urls: ['https://wot-ui.cn/wechatPublicAccount.png'],
})
}
// 打开捐赠二维码
function donate() {
uni.previewImage({
urls: ['https://wot-ui.cn/weixinQrcode.jpg'],
})
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部介绍 -->
<view class="mx-3 mb-3 flex flex-col gap-2">
<text class="text-6 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
关于我们
</text>
<text class="text-3.5 text-gray-600 leading-snug dark:text-[var(--wot-dark-color2)]">
我是不如摸鱼去,一个前端打工仔,我和我的小伙伴们正在致力于开发轻量、高效的uni-app组件库和高效、易用的uni-app快速开发模板。
</text>
</view>
<!-- 核心团队 -->
<demo-block title="核心团队" transparent>
<view class="grid grid-cols-2 gap-3">
<view
v-for="member in coreTeam"
:key="member.name"
class="rounded-2 bg-white p-4 text-center dark:bg-[var(--wot-dark-background2)]"
@click="openUrl(member.github)"
>
<image
:src="member.avatar"
class="mx-auto mb-2 h-16 w-16 border-2 border-blue-200 rounded-full dark:border-blue-800"
/>
<view class="mb-1 text-3.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ member.name }}
</view>
<view class="mb-2 text-2.5 text-blue-600 dark:text-blue-400">
{{ member.role }}
</view>
<view class="text-2.5 text-gray-600 leading-snug dark:text-[var(--wot-dark-color2)]">
{{ member.desc }}
</view>
</view>
</view>
</demo-block>
<!-- 关于 uni-helper -->
<demo-block title="关于 uni-helper 团队" transparent>
<view class="rounded-3 bg-white p-5 dark:bg-[var(--wot-dark-background2)]">
<text class="mb-3 block text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
<text class="text-blue-600" @click="openUrl('https://uni-helper.cn/')">
uni-helper
</text>
是一个旨在增强 uni-app 系列产品的开发体验为爱发电的非官方组织。作为靠爱发电的非官方项目,uni-helper 提供了打包工具插件支持、编辑器扩展支持、NPM 包等并尽力维护它们。
</text>
<text class="text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
在此我们特别向 uni-helper 团队表示感谢,他们为 uni-app 系列产品提供了强大的支持,包括打包工具插件支持、编辑器扩展支持等,这使我们得以站在巨人的巨人的肩膀上完成此项目。
</text>
</view>
</demo-block>
<!-- 更多信息 -->
<demo-block title="更多信息" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell
title="关注公众号"
title-width="200px"
label="uni-app教程、组件库讯息一手掌握!"
is-link
@click="openWeChat"
/>
<wd-cell
title="捐赠"
title-width="200px"
label="每一份捐赠都是对我们莫大的鼓励!"
is-link
@click="donate"
/>
</wd-cell-group>
</demo-block>
</view>
</template>
+225
View File
@@ -0,0 +1,225 @@
<script setup lang="ts">
import type { PasswordChangeForm, UserInfo } from '@/api/user'
import { computed, onMounted, reactive, ref } from 'vue'
import UserAPI from '@/api/user'
definePage({
name: 'account',
style: {
navigationBarTitleText: '账号设置',
},
})
const userProfile = ref<UserInfo>() // 用户信息
const passwordChangeForm = reactive<PasswordChangeForm>({
old_password: '',
new_password: '',
confirm_password: '',
})
const passwordChangeFormRef = ref()
// 本页面中所有的校验规则
const rules = reactive({
oldPassword: [{ required: true, message: '请填写原密码' }],
newPassword: [{ required: true, message: '请填写新密码' }],
confirmPassword: [
{
required: true,
message: '请确认密码',
validator: (value: string) => {
if (!value) {
return Promise.reject(new Error('请确认密码'))
}
else {
if (value !== passwordChangeForm.new_password) {
return Promise.reject(new Error('两次输入的密码不一致'))
}
else {
return Promise.resolve()
}
}
},
},
],
mobile: [
{
required: true,
pattern: /^1[3-9]\d{9}$/,
message: '请填写正确的手机号码',
},
],
code: [{ required: true, message: '请填写验证码' }],
email: [
{
required: true,
pattern: /\w[-\w.+]*@([A-Z0-9][-A-Z0-9]+\.)+[A-Z]{2,14}/i,
message: '请填写正确的邮箱地址',
},
],
})
enum DialogType {
PASSWORD = 'password',
MOBILE = 'mobile',
EMAIL = 'email',
}
const dialog = reactive({
visible: false,
type: '' as DialogType,
})
const getDialogTitle = computed(() => {
switch (dialog?.type) {
case DialogType.PASSWORD:
return '修改密码'
case DialogType.MOBILE:
return '绑定手机'
case DialogType.EMAIL:
return '绑定邮箱'
default:
return '账号设置'
}
})
async function loadUserProfile() {
userProfile.value = await UserAPI.getCurrentUserInfo()
}
/**
* 打开弹窗
* @param type 弹窗类型 ACCOUNT: 账号资料 PASSWORD: 修改密码 MOBILE: 绑定手机 EMAIL: 绑定邮箱
*/
function handleOpenDialog(type: DialogType) {
dialog.type = type
dialog.visible = true
switch (type) {
case DialogType.PASSWORD:
passwordChangeForm.old_password = ''
passwordChangeForm.new_password = ''
passwordChangeForm.confirm_password = ''
break
}
}
// 提交表单
function handleSubmit() {
if (dialog.type === DialogType.PASSWORD) {
passwordChangeFormRef.value
.validate()
.then(({ valid }: { valid: boolean }) => {
if (valid) {
UserAPI.changeCurrentUserPassword(passwordChangeForm)
.then(() => {
uni.showToast({ title: '密码修改成功', icon: 'success' })
dialog.visible = false
})
.catch((error: any) => {
uni.showToast({
title: error?.message || '密码修改失败',
icon: 'error',
})
})
}
})
}
}
onMounted(() => {
loadUserProfile()
})
</script>
<template>
<view class="app-container">
<wd-card custom-style="margin-top: 20rpx" class="theme-card">
<wd-cell-group border>
<wd-cell
title="账户密码"
label="定期修改密码有助于保护账户安全"
value="修改"
is-link
@click="handleOpenDialog(DialogType.PASSWORD)"
>
<template #title>
<text class="theme-text-primary">
账户密码
</text>
</template>
<template #label>
<text class="theme-text-secondary">
定期修改密码有助于保护账户安全
</text>
</template>
<template #value>
<text class="theme-text-secondary">
修改
</text>
</template>
</wd-cell>
</wd-cell-group>
</wd-card>
<!-- 用户信息编辑弹出框 -->
<wd-popup
v-model="dialog.visible"
position="bottom"
custom-style="border-top-left-radius: 16rpx; border-top-right-radius: 16rpx;"
>
<view class="popup-header">
<wd-cell :title="getDialogTitle" center>
<template #right-icon>
<wd-icon name="close" size="20" @click="dialog.visible = false" />
</template>
</wd-cell>
</view>
<wd-divider />
<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.old_password"
label="原密码"
label-width="160rpx"
show-password
clearable
placeholder="请输入原密码"
prop="old_password"
:rules="rules.oldPassword"
/>
<wd-input
v-model="passwordChangeForm.new_password"
label="新密码"
label-width="160rpx"
show-password
clearable
placeholder="请输入新密码"
prop="new_password"
:rules="rules.newPassword"
/>
<wd-input
v-model="passwordChangeForm.confirm_password"
label="确认密码"
label-width="160rpx"
show-password
clearable
placeholder="请确认新密码"
prop="confirm_password"
:rules="rules.confirmPassword"
/>
</wd-cell-group>
<view class="p-6">
<wd-button type="primary" size="large" block @click="handleSubmit">
提交
</wd-button>
</view>
</wd-form>
</wd-popup>
</view>
</template>
@@ -0,0 +1,128 @@
<script setup lang="ts">
import BarChart from '@/subPages/echarts/components/BarChart.vue'
import DonutChart from '@/subPages/echarts/components/DonutChart.vue'
import FunnelChart from '@/subPages/echarts/components/FunnelChart.vue'
import GaugeChart from '@/subPages/echarts/components/GaugeChart.vue'
import LineChart from '@/subPages/echarts/components/LineChart.vue'
import LiquidFillChart from '@/subPages/echarts/components/LiquidFillChart.vue'
import MiniLineChart from '@/subPages/echarts/components/MiniLineChart.vue'
import PieChart from '@/subPages/echarts/components/PieChart.vue'
import RadarChart from '@/subPages/echarts/components/RadarChart.vue'
import ScatterChart from '@/subPages/echarts/components/ScatterChart.vue'
import StackedBarChart from '@/subPages/echarts/components/StackedBarChart.vue'
defineOptions({
componentPlaceholder: {
BarChart: 'view',
DonutChart: 'view',
FunnelChart: 'view',
GaugeChart: 'view',
LineChart: 'view',
LiquidFillChart: 'view',
MiniLineChart: 'view',
PieChart: 'view',
RadarChart: 'view',
ScatterChart: 'view',
StackedBarChart: 'view',
},
})
definePage({
name: 'echartsAsync',
style: {
navigationBarTitleText: 'echarts 异步跨包示例',
},
})
</script>
<template>
<view class="bg-gray-50 p-5">
<!-- 饼图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
饼图示例
</view>
<PieChart />
</view>
<!-- 柱状图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
柱状图示例
</view>
<BarChart />
</view>
<!-- 折线图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
折线图示例
</view>
<LineChart />
</view>
<!-- 雷达图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
雷达图示例
</view>
<RadarChart />
</view>
<!-- 散点图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
散点图示例
</view>
<ScatterChart />
</view>
<!-- 环形图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
环形图示例
</view>
<DonutChart />
</view>
<!-- 漏斗图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
漏斗图示例
</view>
<FunnelChart />
</view>
<!-- 仪表盘图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
仪表盘示例
</view>
<GaugeChart />
</view>
<!-- 迷你折线图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
迷你折线图示例
</view>
<MiniLineChart />
</view>
<!-- 堆叠柱状图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
堆叠柱状图示例
</view>
<StackedBarChart />
</view>
<!-- 水球图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
水球图示例
</view>
<LiquidFillChart />
</view>
</view>
</template>
+428
View File
@@ -0,0 +1,428 @@
<script setup lang="ts">
definePage({
name: 'ci',
style: {
navigationBarTitleText: 'CI 持续集成',
},
})
const { success: showSuccess } = useGlobalToast()
// 配置示例数据
const configExample = ref(`{
"weixin": {
"appid": "微信小程序appid",
"privateKeyPath": "key/private.appid.key",
"projectPath": "dist/build/mp-weixin"
},
"alipay": {
"appid": "支付宝小程序appid",
"toolId": "支付宝开放平台工具ID",
"privateKey": "私钥文本内容",
"projectPath": "dist/build/mp-alipay",
"autoincrement": true
},
"dd": {
"appid": "钉钉小程序appid",
"token": "钉钉开发者后台的 API Token",
"projectPath": "dist/build/mp-alipay",
"autoincrement": true
},
"version": "0.0.1",
"desc": "版本描述"
}`)
const packageScripts = ref(`{
"scripts": {
"upload:weixin": "minici --platform weixin",
"upload:alipay": "minici --platform alipay",
"upload:dd": "minici --platform dd",
"upload:mp-weixin": "uni build -p mp-weixin && minici --platform weixin",
"upload:mp-alipay": "uni build -p mp-alipay && minici --platform alipay",
"upload:mp-dingtalk": "uni build -p mp-dingtalk && minici --platform dd"
}
}`)
// 特性列表
const features = ref([
{
icon: '📱',
title: '多平台支持',
desc: '支持微信、支付宝、钉钉小程序的上传',
},
{
icon: '🔢',
title: '版本管理',
desc: '支持指定版本号和版本号自增',
},
{
icon: '⚙️',
title: '简单配置',
desc: '通过配置文件轻松管理多平台参数',
},
{
icon: '🔄',
title: 'CI/CD集成',
desc: '完美集成持续集成和持续部署流程',
},
{
icon: '🛠️',
title: '命令行工具',
desc: '支持全局和局部安装,灵活使用',
},
{
icon: '📦',
title: '打包上传',
desc: '支持打包和上传命令组合使用',
},
])
// 平台配置
const platforms = ref([
{
name: 'weixin',
title: '微信小程序',
icon: '💚',
color: 'text-green-600',
bgColor: 'bg-green-50',
borderColor: 'border-green-200',
features: ['支持企业微信', 'CI机器人选择', '编译设置', '代码压缩保护'],
},
{
name: 'alipay',
title: '支付宝小程序',
icon: '💙',
color: 'text-blue-600',
bgColor: 'bg-blue-50',
borderColor: 'border-blue-200',
features: ['多终端支持', '版本号自增', '私钥认证', '开放平台集成'],
},
{
name: 'dd',
title: '钉钉小程序',
icon: '💜',
color: 'text-purple-600',
bgColor: 'bg-purple-50',
borderColor: 'border-purple-200',
features: ['企业应用', '版本号自增', 'Token认证', '多应用类型'],
},
])
// 复制代码到剪贴板
function copyCode(code: string) {
uni.setClipboardData({
data: code,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: '代码已复制到剪贴板' })
},
})
}
// 链接导航处理
function handleNavigate(url: string) {
// #ifdef H5
window.open(url, '_blank')
// #endif
// #ifndef H5
uni.setClipboardData({
data: url,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: `${url} 已复制到剪贴板` })
},
})
// #endif
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部介绍 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-8 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-10">
🚀
</view>
<view class="mb-2 text-6 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
uni-mini-ci
</view>
<view class="mb-2 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
小程序持续集成的插件
</view>
<view class="text-3 text-gray-500 dark:text-[var(--wot-dark-color2)]">
支持上传微信、企业微信、钉钉、支付宝平台的小程序
</view>
</view>
</view>
<!-- 核心特性 -->
<demo-block title="核心特性" transparent>
<view class="grid grid-cols-2 gap-3">
<view
v-for="feature in features"
:key="feature.title"
class="rounded-2 bg-white p-4 text-center dark:bg-[var(--wot-dark-background2)]"
>
<view class="mb-2 text-6">
{{ feature.icon }}
</view>
<view class="mb-1 text-3.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ feature.title }}
</view>
<view class="text-3 text-gray-600 leading-snug dark:text-[var(--wot-dark-color2)]">
{{ feature.desc }}
</view>
</view>
</view>
</demo-block>
<!-- 安装方式 -->
<demo-block title="安装方式" transparent>
<view class="space-y-4">
<!-- 全局安装 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
全局安装
</view>
<view class="mb-3 text-3 text-gray-600 dark:text-[var(--wot-dark-color2)]">
推荐在开发环境中全局安装,方便在任何项目中使用
</view>
<view class="space-y-2">
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('npm i uni-mini-ci -g')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
npm i uni-mini-ci -g
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
</view>
</view>
<!-- 局部安装 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
局部安装
</view>
<view class="mb-3 text-3 text-gray-600 dark:text-[var(--wot-dark-color2)]">
在项目中作为开发依赖安装,适合团队协作
</view>
<view class="space-y-2">
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('npm i uni-mini-ci --save-dev')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
npm i uni-mini-ci --save-dev
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('pnpm add uni-mini-ci -D')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
pnpm add uni-mini-ci -D
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
</view>
</view>
</view>
</demo-block>
<!-- 配置文件 -->
<demo-block title="配置文件" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
.minicirc 配置文件
</view>
<view class="mb-3 text-3 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
在项目根目录创建 <text class="rounded bg-gray-100 px-1 font-mono dark:bg-[var(--wot-dark-background3)]">
.minicirc
</text> 文件,配置各平台的上传参数
</view>
<view class="mb-3 border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode(configExample)">
<view class="mb-2 flex items-center justify-between">
<text class="text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
配置示例:
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<view class="max-h-60 overflow-y-auto">
<pre class="whitespace-pre-wrap text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">{{ configExample }}</pre>
</view>
</view>
<view class="border border-orange-200 rounded-2 bg-orange-50 p-3 dark:bg-orange-900/20">
<view class="mb-2 text-3.5 text-orange-700 font-bold dark:text-orange-300">
💡 配置说明
</view>
<view class="text-3 text-orange-600 leading-relaxed space-y-1 dark:text-orange-200">
<view>• version: 上传版本号,不传时默认读取 package.json 的 version 字段</view>
<view>• desc: 上传时的描述信息</view>
<view>• autoincrement: 版本号自增,建议测试环境开启</view>
</view>
</view>
</view>
</demo-block>
<!-- 支持平台 -->
<demo-block title="支持平台" transparent>
<view class="space-y-3">
<view
v-for="platform in platforms"
:key="platform.name"
class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]"
>
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
{{ platform.icon }}
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ platform.title }}
</view>
</view>
<view class="grid grid-cols-2 mb-3 gap-2">
<view
v-for="feature in platform.features"
:key="feature"
class="rounded bg-gray-100 px-2 py-1 text-center text-2.5 text-gray-600 dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color2)]"
>
{{ feature }}
</view>
</view>
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode(`minici --platform ${platform.name}`)">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
minici --platform {{ platform.name }}
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
</view>
</view>
</demo-block>
<!-- 添加命令 -->
<demo-block title="添加命令" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
package.json 脚本配置
</view>
<view class="mb-3 text-3 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
在 package.json 中添加上传脚本,支持单独上传和打包上传组合
</view>
<view class="mb-3 border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode(packageScripts)">
<view class="mb-2 flex items-center justify-between">
<text class="text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
脚本配置:
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<view class="max-h-60 overflow-y-auto">
<pre class="whitespace-pre-wrap text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">{{ packageScripts }}</pre>
</view>
</view>
<view class="space-y-2">
<view class="text-3.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
使用命令:
</view>
<view class="grid grid-cols-1 gap-2">
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('pnpm upload:weixin')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
pnpm upload:weixin
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('pnpm upload:mp-weixin')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
pnpm upload:mp-weixin
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
</view>
</view>
</view>
</demo-block>
<!-- 使用步骤 -->
<demo-block title="使用步骤" transparent>
<view class="space-y-3">
<view
v-for="(step, index) in [
{ title: '安装插件', desc: '选择全局或局部安装方式', icon: '📦' },
{ title: '创建配置', desc: '在根目录创建 .minicirc 配置文件', icon: '⚙️' },
{ title: '配置平台', desc: '填写各平台的 appid、密钥等信息', icon: '🔑' },
{ title: '添加脚本', desc: '在 package.json 中添加上传命令', icon: '📝' },
{ title: '执行上传', desc: '运行命令上传到对应平台', icon: '🚀' },
]"
:key="index"
class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]"
>
<view class="flex items-center">
<view class="mr-3 h-8 w-8 flex items-center justify-center rounded-full bg-blue-100 text-3.5 text-blue-600 font-bold dark:bg-blue-900/20 dark:text-blue-400">
{{ index + 1 }}
</view>
<view class="mr-2 text-5">
{{ step.icon }}
</view>
<view class="flex-1">
<view class="text-3.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ step.title }}
</view>
<view class="text-3 text-gray-600 dark:text-[var(--wot-dark-color2)]">
{{ step.desc }}
</view>
</view>
</view>
</view>
</view>
</demo-block>
<!-- 注意事项 -->
<demo-block title="注意事项" transparent>
<view class="space-y-3">
<view class="border border-red-200 rounded-2 bg-red-50 p-4 dark:bg-red-900/20">
<view class="mb-2 text-3.5 text-red-700 font-bold dark:text-red-300">
⚠️ 版本号限制
</view>
<view class="text-3 text-red-600 leading-relaxed dark:text-red-200">
支付宝和钉钉小程序不支持上传的版本号小于或等于现有版本号。建议测试环境开启 autoincrement 字段,生产环境手动指定版本号。
</view>
</view>
<view class="border border-blue-200 rounded-2 bg-blue-50 p-4 dark:bg-blue-900/20">
<view class="mb-2 text-3.5 text-blue-700 font-bold dark:text-blue-300">
💡 最佳实践
</view>
<view class="text-3 text-blue-600 leading-relaxed space-y-1 dark:text-blue-200">
<view>• 测试环境:开启版本号自增,频繁调试更方便</view>
<view>• 生产环境:手动指定版本号,便于版本管理</view>
<view>• 密钥安全:不要将私钥文件提交到代码仓库</view>
<view>• CI/CD:结合打包命令,实现自动化部署</view>
</view>
</view>
<view class="border border-green-200 rounded-2 bg-green-50 p-4 dark:bg-green-900/20">
<view class="mb-2 text-3.5 text-green-700 font-bold dark:text-green-300">
🔒 安全建议
</view>
<view class="text-3 text-green-600 leading-relaxed space-y-1 dark:text-green-200">
<view>• 使用环境变量存储敏感信息</view>
<view>• 将配置文件添加到 .gitignore</view>
<view>• 定期更换密钥和 Token</view>
<view>• 限制 CI 机器人权限范围</view>
</view>
</view>
</view>
</demo-block>
<!-- 相关链接 -->
<demo-block title="相关链接" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell title="📦 NPM 包" value="uni-mini-ci" is-link @click="handleNavigate('https://www.npmjs.com/package/uni-mini-ci')" />
<wd-cell title="🐙 GitHub 仓库" value="源码仓库" is-link @click="handleNavigate('https://github.com/Moonofweisheng/uni-mini-ci')" />
<wd-cell title="📱 微信小程序 CI" value="官方文档" is-link @click="handleNavigate('https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html')" />
<wd-cell title="💙 支付宝小程序 CI" value="官方文档" is-link @click="handleNavigate('https://opendocs.alipay.com/mini/02q29z')" />
<wd-cell title="💜 钉钉小程序 CI" value="官方文档" is-link @click="handleNavigate('https://github.com/open-dingtalk/dingtalk-design-cli')" />
</wd-cell-group>
</demo-block>
</view>
</template>
@@ -0,0 +1,252 @@
<script setup lang="ts">
definePage({
name: 'create-uni',
style: {
navigationBarTitleText: 'CreateUni 脚手架',
},
})
const { success: showSuccess } = useGlobalToast()
// 核心优势
const advantages = ref([
{
icon: '🚀',
title: '简单易用',
desc: '一行命令即可创建项目,无需手动配置开发环境',
},
{
icon: '🔧',
title: '功能强大',
desc: '内置多种实用工具和插件,支持一键集成 WotUI',
},
{
icon: '🔄',
title: '持续更新',
desc: '由 uni-helper 团队积极维护,及时跟进最新技术栈',
},
{
icon: '👥',
title: '社区活跃',
desc: 'GitHub 获得众多 star,活跃的开发者社区',
},
{
icon: '🌐',
title: '网络稳定',
desc: '解决官方 CLI 网络不稳定问题,支持国内环境',
},
{
icon: '⚡️',
title: '开发效率',
desc: '提升开发效率 100%,快速搭建现代化项目',
},
])
// 解决的问题
const problems = ref([
{
problem: '网络问题',
desc: 'degit 命令只支持 GitHub,国内网络不稳定',
},
{
problem: '维护问题',
desc: '官方模板维护不够积极,版本往往落后',
},
{
problem: '工程化问题',
desc: '项目配置过于基础,缺少现代工程化支持',
},
{
problem: '集成问题',
desc: '需要手动集成组件库,配置过程繁琐',
},
])
// WotUI 快速创建
const wotQuickStart = ref({
title: '快速创建 WotUI 项目',
command: 'pnpm create uni@latest -t wot-starter <你的项目名称>',
desc: '一键创建集成了 WotUI 的完整项目',
features: [
'✅ TypeScript 项目',
'✅ 集成 Pinia 状态管理',
'✅ 自动配置 WotUI 组件库',
'✅ 添加 ESLint 支持',
],
})
// 其他使用方式
const quickMethods = ref([
{
title: 'GUI 模式',
command: 'pnpm create uni --gui',
desc: '使用图形界面创建项目',
},
{
title: '环境信息',
command: 'npx @create-uni/info@latest',
desc: '获取当前项目环境信息',
},
])
// 复制命令到剪贴板
function copyCommand(command: string) {
uni.setClipboardData({
data: command,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({
msg: `${command}已成功复制到剪贴板`,
})
},
})
}
function handleNavigate(url: string) {
// #ifdef H5
window.open(url, '_blank')
// #endif
// #ifndef H5
copyCommand(url)
// #endif
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部介绍 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-8 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-10">
🌱
</view>
<view class="mb-2 text-6 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
CreateUni 脚手架
</view>
<view class="mb-2 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
由 uni-helper 团队开发的轻量级脚手架工具
</view>
<view class="text-3 text-gray-500 dark:text-[var(--wot-dark-color2)]">
提升开发效率 100%,告别繁琐配置,一键集成 WotUI 组件库
</view>
</view>
</view>
<!-- 为什么选择 create-uni -->
<demo-block title="为什么选择 create-uni?" transparent>
<view class="mb-3 rounded-3 bg-white p-3 dark:bg-[var(--wot-dark-background2)]">
<text class="text-3.5 text-gray-700 leading-relaxed dark:text-[var(--wot-dark-color)]">
在使用 uni-app 官方 CLI 工具时,我们经常会遇到以下问题:
</text>
</view>
<view class="mb-3">
<view
v-for="item in problems"
:key="item.problem"
class="mb-2 rounded-3 bg-white p-3 dark:bg-[var(--wot-dark-background2)]"
>
<view class="mb-1 text-3.5 text-red-600 font-bold dark:text-red-400">
❌ {{ item.problem }}
</view>
<view class="text-3 text-gray-600 leading-snug dark:text-[var(--wot-dark-color2)]">
{{ item.desc }}
</view>
</view>
</view>
<view class="rounded-3 bg-white p-3 dark:bg-[var(--wot-dark-background2)]">
<text class="text-3.5 text-green-600 font-bold dark:text-green-400">
✅ 而 create-uni 的出现,完美解决了这些问题!
</text>
</view>
</demo-block>
<!-- 核心优势 -->
<demo-block title="核心优势" transparent>
<view class="grid grid-cols-2 gap-3">
<view
v-for="advantage in advantages"
:key="advantage.title"
class="rounded-2 bg-white p-4 text-center dark:bg-[var(--wot-dark-background2)]"
>
<view class="mb-2 text-6">
{{ advantage.icon }}
</view>
<view class="mb-1 text-3.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ advantage.title }}
</view>
<view class="text-3 text-gray-600 leading-snug dark:text-[var(--wot-dark-color2)]">
{{ advantage.desc }}
</view>
</view>
</view>
</demo-block>
<!-- 快速创建 WotUI 项目 -->
<demo-block title="快速创建 WotUI 项目" transparent>
<view class="rounded-3 bg-white p-5 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-7">
🎨
</view>
<view class="text-4.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ wotQuickStart.title }}
</view>
</view>
<view class="mb-3 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
{{ wotQuickStart.desc }}
</view>
<view class="mb-3 flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCommand(wotQuickStart.command)">
<text class="flex-1 break-all text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
{{ wotQuickStart.command }}
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<view>
<view class="mb-2 text-3.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
这个命令会:
</view>
<view
v-for="feature in wotQuickStart.features"
:key="feature"
class="mb-1 text-3 text-gray-600 leading-snug dark:text-[var(--wot-dark-color2)]"
>
{{ feature }}
</view>
</view>
</view>
</demo-block>
<!-- 其他使用方式 -->
<demo-block title="其他使用方式" transparent>
<view
v-for="method in quickMethods"
:key="method.title"
class="mb-3 rounded-2 bg-white p-4 last:mb-0 dark:bg-[var(--wot-dark-background2)]"
>
<view class="mb-2 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ method.title }}
</view>
<view class="mb-2 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
{{ method.desc }}
</view>
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-2 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCommand(method.command)">
<text class="flex-1 break-all text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
{{ method.command }}
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
</view>
</demo-block>
<!-- 相关链接 -->
<demo-block title="相关链接" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell title="📦 NPM 包" value="create uni" is-link @click="handleNavigate('https://www.npmjs.com/package/create-uni')" />
<wd-cell title="🐙 GitHub 仓库" value="create uni" is-link @click="handleNavigate('https://github.com/uni-helper/create-uni')" />
<wd-cell title="🎨 Wot UI" value="UI 组件库" is-link @click="handleNavigate('https://wot-ui.cn/')" />
<wd-cell title="🛠️ Uni Helper" value="先进工具集合" is-link @click="handleNavigate('https://github.com/uni-helper')" />
<wd-cell title="💝 赞助CreateUni" value="支持开发者" is-link @click="handleNavigate('https://github.com/uni-helper/create-uni')" />
</wd-cell-group>
</demo-block>
</view>
</template>
@@ -0,0 +1,72 @@
<!--
* @Author: weisheng
* @Date: 2025-09-15 18:51:03
* @LastEditTime: 2025-09-16 13:12:22
* @LastEditors: weisheng
* @Description:
* @FilePath: /wot-starter/src/subPages/echarts/components/BarChart.vue
* 记得注释
-->
<script setup lang="ts">
import { BarChart } from 'echarts/charts'
import { DatasetComponent, GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
GridComponent,
LegendComponent,
TooltipComponent,
DatasetComponent,
BarChart,
CanvasRenderer,
])
const option = ref({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
legend: {
data: ['销售额', '利润'],
top: 30,
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '销售额',
type: 'bar',
data: [120, 200, 150, 80, 70, 110],
itemStyle: {
color: '#5470c6',
},
},
{
name: '利润',
type: 'bar',
data: [20, 40, 30, 15, 12, 22],
itemStyle: {
color: '#91cc75',
},
},
],
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,106 @@
<script setup lang="ts">
import { PieChart } from 'echarts/charts'
import { DatasetComponent, GraphicComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
LegendComponent,
TooltipComponent,
DatasetComponent,
PieChart,
CanvasRenderer,
GraphicComponent,
])
const option = ref({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)',
textStyle: {
// #ifdef MP-WEIXIN
// 临时解决微信小程序 tooltip 文字阴影问题
textShadowBlur: 1,
// #endif
},
},
legend: {
orient: 'horizontal',
bottom: 10,
left: 'center',
itemWidth: 12,
itemHeight: 12,
textStyle: {
fontSize: 12,
},
},
series: [
{
name: '用户占比',
type: 'pie',
radius: ['45%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: 18,
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
itemStyle: {
borderWidth: 2,
borderColor: '#ffffff',
},
data: [
{
value: 335,
name: 'iOS',
itemStyle: {
color: '#5470c6',
},
},
{
value: 310,
name: 'Android',
itemStyle: {
color: '#91cc75',
},
},
{
value: 234,
name: 'H5',
itemStyle: {
color: '#fac858',
},
},
{
value: 135,
name: '小程序',
itemStyle: {
color: '#ee6666',
},
},
{
value: 86,
name: '其他',
itemStyle: {
color: '#73c0de',
},
},
],
},
],
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,116 @@
<script setup lang="ts">
import { FunnelChart } from 'echarts/charts'
import { LegendComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
LegendComponent,
TooltipComponent,
FunnelChart,
CanvasRenderer,
])
const option = ref({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)',
textStyle: {
// #ifdef MP-WEIXIN
// 临时解决微信小程序 tooltip 文字阴影问题
textShadowBlur: 1,
// #endif
},
},
legend: {
orient: 'horizontal',
bottom: 10,
left: 'center',
itemWidth: 12,
itemHeight: 12,
textStyle: {
fontSize: 12,
},
},
series: [
{
name: '销售漏斗',
type: 'funnel',
left: '10%',
top: 60,
width: '80%',
height: '60%',
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending',
gap: 2,
label: {
show: true,
position: 'inside',
fontSize: 12,
color: '#fff',
fontWeight: 'bold',
},
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: 'solid',
},
},
itemStyle: {
borderColor: '#fff',
borderWidth: 1,
},
emphasis: {
label: {
fontSize: 14,
},
},
data: [
{
value: 100,
name: '访问量',
itemStyle: {
color: '#5470c6',
},
},
{
value: 80,
name: '浏览商品',
itemStyle: {
color: '#91cc75',
},
},
{
value: 60,
name: '加入购物车',
itemStyle: {
color: '#fac858',
},
},
{
value: 40,
name: '提交订单',
itemStyle: {
color: '#ee6666',
},
},
{
value: 20,
name: '支付成功',
itemStyle: {
color: '#73c0de',
},
},
],
},
],
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,105 @@
<script setup lang="ts">
import { GaugeChart } from 'echarts/charts'
import { TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
TooltipComponent,
GaugeChart,
CanvasRenderer,
])
const option = ref({
tooltip: {
formatter: '{a} <br/>{b}: {c}%',
textStyle: {
// #ifdef MP-WEIXIN
// 临时解决微信小程序 tooltip 文字阴影问题
textShadowBlur: 1,
// #endif
},
},
series: [
{
name: '性能指标',
type: 'gauge',
center: ['50%', '60%'],
radius: '75%',
min: 0,
max: 100,
splitNumber: 10,
axisLine: {
lineStyle: {
width: 10,
color: [
[0.3, '#ee6666'],
[0.7, '#fac858'],
[1, '#91cc75'],
],
},
},
pointer: {
itemStyle: {
color: 'auto',
},
},
axisTick: {
distance: -30,
length: 8,
lineStyle: {
color: '#fff',
width: 2,
},
},
splitLine: {
distance: -30,
length: 30,
lineStyle: {
color: '#fff',
width: 4,
},
},
axisLabel: {
color: 'auto',
distance: 40,
fontSize: 12,
},
detail: {
valueAnimation: true,
formatter: '{value}%',
color: 'auto',
fontSize: 20,
fontWeight: 'bold',
offsetCenter: [0, '70%'],
},
data: [
{
value: 75,
name: 'CPU使用率',
},
],
},
],
})
// 模拟数据变化
let timer: NodeJS.Timeout | null = null
onMounted(() => {
timer = setInterval(() => {
const newValue = Math.round(Math.random() * 100)
option.value.series[0].data[0].value = newValue
}, 3000)
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,103 @@
<script setup lang="ts">
import { LineChart } from 'echarts/charts'
import { DatasetComponent, GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
GridComponent,
LegendComponent,
TooltipComponent,
DatasetComponent,
LineChart,
CanvasRenderer,
])
const option = ref({
tooltip: {
trigger: 'axis',
},
legend: {
data: ['访问量', '用户数'],
top: 30,
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '访问量',
type: 'line',
stack: 'Total',
data: [120, 132, 101, 134, 90, 230, 210],
smooth: true,
itemStyle: {
color: '#5470c6',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(84, 112, 198, 0.3)',
},
{
offset: 1,
color: 'rgba(84, 112, 198, 0.1)',
},
],
},
},
},
{
name: '用户数',
type: 'line',
stack: 'Total',
data: [220, 182, 191, 234, 290, 330, 310],
smooth: true,
itemStyle: {
color: '#91cc75',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(145, 204, 117, 0.3)',
},
{
offset: 1,
color: 'rgba(145, 204, 117, 0.1)',
},
],
},
},
},
],
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,142 @@
<script setup lang="ts">
import { PieChart } from 'echarts/charts'
import { TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
TooltipComponent,
PieChart,
CanvasRenderer,
])
const progress = ref(68)
const option = computed(() => ({
tooltip: {
// formatter: () => `完成度: ${progress.value}%`,
textStyle: {
// #ifdef MP-WEIXIN
// 临时解决微信小程序 tooltip 文字阴影问题
textShadowBlur: 1,
// #endif
},
},
graphic: [
{
type: 'text',
left: 'center',
top: 'center',
style: {
text: `完成度:${progress.value}%`,
fontSize: 24,
fontWeight: 'bold',
fill: '#5470c6',
},
},
],
series: [
{
type: 'pie',
radius: ['60%', '80%'],
center: ['50%', '50%'],
startAngle: 90,
clockwise: true,
silent: true,
label: {
show: false,
},
labelLine: {
show: false,
},
data: [
{
value: progress.value,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#5470c6',
},
{
offset: 1,
color: '#91cc75',
},
],
},
borderRadius: 10,
},
},
{
value: 100 - progress.value,
itemStyle: {
color: '#f0f0f0',
borderRadius: 10,
},
},
],
},
// 内圆背景
{
type: 'pie',
radius: ['0%', '58%'],
center: ['50%', '50%'],
silent: true,
label: {
show: false,
},
labelLine: {
show: false,
},
data: [
{
value: 100,
itemStyle: {
color: {
type: 'radial',
x: 0.5,
y: 0.5,
r: 0.5,
colorStops: [
{
offset: 0,
color: 'rgba(84, 112, 198, 0.1)',
},
{
offset: 1,
color: 'rgba(84, 112, 198, 0.05)',
},
],
},
},
},
],
},
],
}))
// 模拟进度变化
let timer: NodeJS.Timeout | null = null
onMounted(() => {
timer = setInterval(() => {
progress.value = Math.round(Math.random() * 100)
}, 4000)
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,134 @@
<script setup lang="ts">
import { LineChart } from 'echarts/charts'
import { GridComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
GridComponent,
TooltipComponent,
LineChart,
CanvasRenderer,
])
const option = ref({
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(50,50,50,0.8)',
borderColor: 'transparent',
textStyle: {
color: '#fff',
fontSize: 12,
// #ifdef MP-WEIXIN
// 临时解决微信小程序 tooltip 文字阴影问题
textShadowBlur: 1,
// #endif
},
formatter: (params: any) => {
const param = params[0]
return `${param.name}<br/>${param.seriesName}: ${param.value}`
},
},
grid: {
left: '8%',
right: '8%',
top: '25%',
bottom: '15%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
axisLabel: {
fontSize: 10,
color: '#666',
},
axisLine: {
lineStyle: {
color: '#e0e0e0',
},
},
axisTick: {
show: false,
},
},
yAxis: {
type: 'value',
axisLabel: {
fontSize: 10,
color: '#666',
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
lineStyle: {
color: '#f0f0f0',
type: 'dashed',
},
},
},
series: [
{
name: '访问量',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 3,
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [
{
offset: 0,
color: '#5470c6',
},
{
offset: 1,
color: '#91cc75',
},
],
},
},
itemStyle: {
color: '#5470c6',
borderWidth: 2,
borderColor: '#fff',
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(84, 112, 198, 0.3)',
},
{
offset: 1,
color: 'rgba(84, 112, 198, 0.05)',
},
],
},
},
data: [120, 80, 150, 200, 180, 250, 160],
},
],
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,65 @@
<script setup lang="ts">
import { PieChart } from 'echarts/charts'
import { DatasetComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
LegendComponent,
TooltipComponent,
DatasetComponent,
PieChart,
CanvasRenderer,
])
const option = ref({
legend: {
top: 10,
left: 'center',
},
tooltip: {
trigger: 'item',
textStyle: {
// #ifdef MP-WEIXIN
// 临时解决微信小程序 tooltip 文字阴影问题
textShadowBlur: 1,
// #endif
},
},
series: [
{
type: 'pie',
radius: ['30%', '52%'],
label: {
show: false,
position: 'center',
},
itemStyle: {
borderWidth: 2,
borderColor: '#ffffff',
borderRadius: 10,
},
emphasis: {
label: {
show: true,
fontSize: 20,
},
},
},
],
dataset: {
dimensions: ['来源', '数量'],
source: [
['Search Engine', 1048],
['Direct', 735],
['Email', 580],
['Union Ads', 484],
['Video Ads', 300],
],
},
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,84 @@
<script setup lang="ts">
import { RadarChart } from 'echarts/charts'
import { LegendComponent, RadarComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
RadarComponent,
LegendComponent,
TooltipComponent,
RadarChart,
CanvasRenderer,
])
const option = ref({
tooltip: {
trigger: 'item',
},
legend: {
data: ['张三', '李四'],
top: 30,
},
radar: {
indicator: [
{ name: '技术能力', max: 100 },
{ name: '沟通能力', max: 100 },
{ name: '学习能力', max: 100 },
{ name: '团队协作', max: 100 },
{ name: '创新能力', max: 100 },
{ name: '执行力', max: 100 },
],
shape: 'polygon',
splitNumber: 5,
axisName: {
color: '#666',
},
splitLine: {
lineStyle: {
color: 'rgba(211, 253, 250, 0.8)',
},
},
splitArea: {
show: false,
},
axisLine: {
lineStyle: {
color: 'rgba(211, 253, 250, 0.8)',
},
},
},
series: [
{
name: '能力评估',
type: 'radar',
data: [
{
value: [85, 75, 90, 80, 70, 85],
name: '张三',
itemStyle: {
color: '#5470c6',
},
areaStyle: {
color: 'rgba(84, 112, 198, 0.3)',
},
},
{
value: [70, 90, 80, 85, 85, 75],
name: '李四',
itemStyle: {
color: '#91cc75',
},
areaStyle: {
color: 'rgba(145, 204, 117, 0.3)',
},
},
],
},
],
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,120 @@
<script setup lang="ts">
import { ScatterChart } from 'echarts/charts'
import { DatasetComponent, GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
GridComponent,
LegendComponent,
TooltipComponent,
DatasetComponent,
ScatterChart,
CanvasRenderer,
])
const option = ref({
tooltip: {
trigger: 'item',
formatter: (params: any) => {
return `${params.seriesName}<br/>身高: ${params.value[0]}cm<br/>体重: ${params.value[1]}kg`
},
},
legend: {
data: ['男性', '女性'],
top: 30,
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'value',
name: '身高(cm)',
nameLocation: 'middle',
nameGap: 30,
min: 150,
max: 190,
splitLine: {
lineStyle: {
type: 'dashed',
},
},
},
yAxis: {
type: 'value',
name: '体重(kg)',
nameLocation: 'middle',
nameGap: 30,
min: 40,
max: 90,
splitLine: {
lineStyle: {
type: 'dashed',
},
},
},
series: [
{
name: '男性',
type: 'scatter',
data: [
[175, 70],
[180, 75],
[170, 65],
[185, 80],
[172, 68],
[178, 72],
[182, 78],
[168, 62],
[176, 71],
[174, 69],
],
symbolSize: 8,
itemStyle: {
color: '#5470c6',
opacity: 0.8,
},
emphasis: {
itemStyle: {
borderColor: '#5470c6',
borderWidth: 2,
},
},
},
{
name: '女性',
type: 'scatter',
data: [
[160, 50],
[165, 55],
[158, 48],
[170, 60],
[162, 52],
[168, 58],
[155, 45],
[172, 62],
[164, 54],
[166, 56],
],
symbolSize: 8,
itemStyle: {
color: '#91cc75',
opacity: 0.8,
},
emphasis: {
itemStyle: {
borderColor: '#91cc75',
borderWidth: 2,
},
},
},
],
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
@@ -0,0 +1,127 @@
<script setup lang="ts">
import { BarChart } from 'echarts/charts'
import { DatasetComponent, GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
GridComponent,
LegendComponent,
TooltipComponent,
DatasetComponent,
BarChart,
CanvasRenderer,
])
const option = ref({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
textStyle: {
// #ifdef MP-WEIXIN
// 临时解决微信小程序 tooltip 文字阴影问题
textShadowBlur: 1,
// #endif
},
},
legend: {
data: ['产品A', '产品B', '产品C'],
top: 35,
left: 'center',
itemWidth: 12,
itemHeight: 12,
textStyle: {
fontSize: 12,
},
},
grid: {
left: '8%',
right: '8%',
top: '25%',
bottom: '15%',
containLabel: true,
},
xAxis: {
type: 'category',
data: ['Q1', 'Q2', 'Q3', 'Q4'],
axisLabel: {
fontSize: 12,
color: '#666',
},
axisLine: {
lineStyle: {
color: '#e0e0e0',
},
},
axisTick: {
show: false,
},
},
yAxis: {
type: 'value',
axisLabel: {
fontSize: 12,
color: '#666',
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
lineStyle: {
color: '#f0f0f0',
type: 'dashed',
},
},
},
series: [
{
name: '产品A',
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series',
},
itemStyle: {
color: '#5470c6',
borderRadius: [0, 0, 0, 0],
},
data: [120, 132, 101, 134],
},
{
name: '产品B',
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series',
},
itemStyle: {
color: '#91cc75',
borderRadius: [0, 0, 0, 0],
},
data: [220, 182, 191, 234],
},
{
name: '产品C',
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series',
},
itemStyle: {
color: '#fac858',
borderRadius: [4, 4, 0, 0],
},
data: [150, 212, 201, 154],
},
],
})
</script>
<template>
<uni-echarts custom-class="h-300px" :option="option" />
</template>
+129
View File
@@ -0,0 +1,129 @@
<script setup lang="ts">
import BarChart from './components/BarChart.vue'
import DonutChart from './components/DonutChart.vue'
import FunnelChart from './components/FunnelChart.vue'
import GaugeChart from './components/GaugeChart.vue'
import LineChart from './components/LineChart.vue'
import LiquidFillChart from './components/LiquidFillChart.vue'
import MiniLineChart from './components/MiniLineChart.vue'
import PieChart from './components/PieChart.vue'
import RadarChart from './components/RadarChart.vue'
import ScatterChart from './components/ScatterChart.vue'
import StackedBarChart from './components/StackedBarChart.vue'
definePage({
name: 'echarts',
style: {
navigationBarTitleText: 'Echarts 演示',
},
})
const router = useRouter()
function navigateToAsync() {
router.push({
name: 'echartsAsync',
})
}
</script>
<template>
<view class="bg-gray-50 p-5">
<!-- 异步 Echarts 入口 -->
<view class="mb-5 rounded-3 bg-white p-3 shadow-sm">
<view
class="rounded-2 bg-blue-500 px-6 py-3 text-white font-medium transition-colors hover:bg-blue-600"
@click="navigateToAsync"
>
查看异步 Echarts 示例
</view>
</view>
<!-- 饼图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
饼图示例
</view>
<PieChart />
</view>
<!-- 柱状图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
柱状图示例
</view>
<BarChart />
</view>
<!-- 折线图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
折线图示例
</view>
<LineChart />
</view>
<!-- 雷达图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
雷达图示例
</view>
<RadarChart />
</view>
<!-- 散点图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
散点图示例
</view>
<ScatterChart />
</view>
<!-- 环形图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
环形图示例
</view>
<DonutChart />
</view>
<!-- 漏斗图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
漏斗图示例
</view>
<FunnelChart />
</view>
<!-- 仪表盘图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
仪表盘示例
</view>
<GaugeChart />
</view>
<!-- 迷你折线图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
迷你折线图示例
</view>
<MiniLineChart />
</view>
<!-- 堆叠柱状图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
堆叠柱状图示例
</view>
<StackedBarChart />
</view>
<!-- 水球图 -->
<view class="mb-5 rounded-3 bg-white p-5 shadow-sm">
<view class="mb-5 text-center text-base text-gray-800 font-medium">
水球图示例
</view>
<LiquidFillChart />
</view>
</view>
</template>
@@ -0,0 +1,581 @@
<script setup lang="ts">
definePage({
name: 'feedback',
style: {
navigationBarTitleText: '全局反馈',
},
})
const { success: showSuccess } = useGlobalToast()
const { show: showToast, success, error, warning, info } = useGlobalToast()
const { loading, close: closeLoading } = useGlobalLoading()
const { confirm, alert, prompt } = useGlobalMessage()
// 表单数据
const customMessage = ref('这是一个自定义消息')
const loadingText = ref('正在处理中...')
// Toast 演示方法
function demoToastSuccess() {
success({ msg: '操作成功!' })
}
function demoToastError() {
error({ msg: '操作失败!' })
}
function demoToastWarning() {
warning({ msg: '警告提示!' })
}
function demoToastInfo() {
info({ msg: '信息提示!' })
}
function demoCustomToast() {
if (!customMessage.value.trim()) {
warning({ msg: '请输入自定义消息' })
return
}
showToast({
msg: customMessage.value,
duration: 3000,
position: 'middle',
})
}
function demoToastPosition(position: 'top' | 'middle' | 'bottom') {
showToast({
msg: `显示在${position === 'top' ? '顶部' : position === 'middle' ? '中间' : '底部'}的提示`,
position,
duration: 2000,
})
}
// Loading 演示方法
function demoBasicLoading() {
loading('加载中...')
setTimeout(() => {
closeLoading()
success({ msg: '加载完成!' })
}, 3000)
}
function demoCustomLoading() {
if (!loadingText.value.trim()) {
warning({ msg: '请输入加载文本' })
return
}
loading({
msg: loadingText.value,
cover: true,
})
setTimeout(() => {
closeLoading()
success({ msg: '自定义加载完成!' })
}, 2500)
}
function demoLoadingNoCover() {
loading({
msg: '无遮罩加载',
cover: false,
})
setTimeout(() => {
closeLoading()
}, 2000)
}
// Message 演示方法
function demoConfirm() {
confirm({
title: '确认操作',
msg: '您确定要执行此操作吗?',
success: (res) => {
if (res.action === 'confirm') {
success({ msg: '用户确认了操作' })
}
else {
info({ msg: '用户取消了操作' })
}
},
})
}
function demoAlert() {
alert({
title: '重要提醒',
msg: '这是一个重要的提醒信息,请仔细阅读!',
success: () => {
success({ msg: '用户已阅读提醒' })
},
})
}
function demoPrompt() {
prompt({
title: '输入信息',
msg: '请输入您的姓名:',
success: (res) => {
if (res.action === 'confirm') {
if (res.value && String(res.value).trim()) {
success({ msg: `您好,${res.value}!` })
}
else {
warning({ msg: '输入不能为空' })
}
}
else {
info({ msg: '用户取消了输入' })
}
},
})
}
function demoCustomConfirm() {
confirm({
title: '自定义确认',
msg: '这是一个自定义的确认弹窗,可以配置按钮文字和样式',
confirmButtonText: '好的',
cancelButtonText: '算了',
success: (res) => {
if (res.action === 'confirm') {
success({ msg: '点击了"好的"' })
}
else {
info({ msg: '点击了"算了"' })
}
},
})
}
// 组合演示
function demoComplex() {
loading('准备演示...')
setTimeout(() => {
closeLoading()
confirm({
title: '组合演示',
msg: '这是一个组合演示,将依次展示不同的反馈效果。是否继续?',
success: (res) => {
if (res.action === 'confirm') {
success({ msg: '开始演示' })
setTimeout(() => {
loading('演示进行中...')
setTimeout(() => {
closeLoading()
info({ msg: '演示完成!' })
}, 2000)
}, 1000)
}
else {
info({ msg: '已取消演示' })
}
},
})
}, 1000)
}
// 代码示例常量
const codeExamples = {
basicToast: `const { success, error, warning, info } = useGlobalToast()
// 使用方法
success({ msg: '操作成功!' })
error({ msg: '操作失败!' })
warning({ msg: '警告提示!' })
info({ msg: '信息提示!' })`,
customToast: `const { show } = useGlobalToast()
show({
msg: '自定义消息',
duration: 3000,
position: 'middle' // 'top' | 'middle' | 'bottom'
})`,
basicLoading: `const { loading, close } = useGlobalLoading()
// 基础用法
loading('加载中...')
// 手动关闭
close()`,
customLoading: `loading({
msg: '正在处理中...',
cover: true // 是否显示遮罩
})`,
basicMessage: `const { confirm, alert, prompt } = useGlobalMessage()
// 确认弹窗
confirm({
title: '确认操作',
msg: '您确定要执行此操作吗?',
success: (res) => {
if (res.action === 'confirm') {
// 用户确认
}
}
})`,
customMessage: `confirm({
title: '自定义确认',
msg: '自定义的确认弹窗',
confirmButtonText: '好的',
cancelButtonText: '算了',
success: (res) => {
// res.action: 'confirm' | 'cancel'
}
})`,
}
// 复制代码到剪贴板
function copyCode(code: string) {
uni.setClipboardData({
data: code,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: '代码已复制到剪贴板' })
},
})
}
// 链接导航处理
function handleNavigate(url: string) {
// #ifdef H5
window.open(url, '_blank')
// #endif
// #ifndef H5
uni.setClipboardData({
data: url,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: `${url} 已复制到剪贴板` })
},
})
// #endif
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部介绍 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-8 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-10">
💬
</view>
<view class="mb-2 text-6 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
全局反馈组件
</view>
<view class="mb-2 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
GlobalToast / GlobalLoading / GlobalMessage
</view>
<view class="text-3 text-gray-500 dark:text-[var(--wot-dark-color2)]">
统一的用户反馈交互体验,支持在路由导航守卫和网络请求拦截器中使用,扩展 WotUI 交互组件能力。
</view>
</view>
</view>
<!-- GlobalToast 演示 -->
<demo-block title="GlobalToast 轻提示" transparent>
<view class="space-y-4">
<!-- 基础用法 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
基础用法
</view>
<view class="grid grid-cols-2 mb-3 gap-2">
<wd-button type="success" size="small" @click="demoToastSuccess">
成功提示
</wd-button>
<wd-button type="error" size="small" @click="demoToastError">
错误提示
</wd-button>
<wd-button type="warning" size="small" @click="demoToastWarning">
警告提示
</wd-button>
<wd-button type="info" size="small" @click="demoToastInfo">
信息提示
</wd-button>
</view>
<view
class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]" @click="copyCode(codeExamples.basicToast)"
>
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
代码示例:
</view>
<view class="whitespace-pre-line text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
const { success, error, warning, info } = useGlobalToast()
success({ msg: '操作成功!' })
error({ msg: '操作失败!' })
</view>
</view>
</view>
<!-- 自定义用法 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
自定义配置
</view>
<view class="mb-3">
<wd-input v-model="customMessage" placeholder="请输入自定义消息" />
</view>
<view class="grid grid-cols-2 mb-3 gap-2">
<wd-button type="primary" size="small" @click="demoCustomToast">
自定义消息
</wd-button>
<wd-button type="success" size="small" @click="demoToastPosition('top')">
顶部显示
</wd-button>
<wd-button type="warning" size="small" @click="demoToastPosition('middle')">
中间显示
</wd-button>
<wd-button type="info" size="small" @click="demoToastPosition('bottom')">
底部显示
</wd-button>
</view>
<view
class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]" @click="copyCode(codeExamples.customToast)"
>
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
自定义配置:
</view>
<view class="whitespace-pre-line text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
show({
msg: '自定义消息',
duration: 3000,
position: 'middle'
})
</view>
</view>
</view>
</view>
</demo-block>
<!-- GlobalLoading 演示 -->
<demo-block title="GlobalLoading 加载提示" transparent>
<view class="space-y-4">
<!-- 基础用法 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
基础用法
</view>
<view class="grid grid-cols-2 mb-3 gap-2">
<wd-button type="primary" size="small" @click="demoBasicLoading">
显示加载(3秒)
</wd-button>
<wd-button type="warning" size="small" @click="closeLoading">
手动关闭
</wd-button>
<wd-button type="success" size="small" @click="demoLoadingNoCover">
无遮罩加载
</wd-button>
<wd-button type="info" size="small" @click="demoComplex">
组合演示
</wd-button>
</view>
<view
class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]" @click="copyCode(codeExamples.basicLoading)"
>
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
代码示例:
</view>
<view class="whitespace-pre-line text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
const { loading, close } = useGlobalLoading()
loading('加载中...')
close()
</view>
</view>
</view>
<!-- 自定义用法 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
自定义配置
</view>
<view class="mb-3">
<wd-input v-model="loadingText" placeholder="请输入加载文本" />
</view>
<view class="mb-3">
<wd-button type="primary" block @click="demoCustomLoading">
自定义加载文本
</wd-button>
</view>
<view
class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]" @click="copyCode(codeExamples.customLoading)"
>
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
自定义配置:
</view>
<view class="whitespace-pre-line text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
loading({
msg: '正在处理中...',
cover: true // 是否显示遮罩
})
</view>
</view>
</view>
</view>
</demo-block>
<!-- GlobalMessage 演示 -->
<demo-block title="GlobalMessage 弹窗提示" transparent>
<view class="space-y-4">
<!-- 基础用法 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
基础用法
</view>
<view class="grid grid-cols-3 mb-3 gap-2">
<wd-button type="primary" size="small" @click="demoConfirm">
确认弹窗
</wd-button>
<wd-button type="warning" size="small" @click="demoAlert">
警告弹窗
</wd-button>
<wd-button type="success" size="small" @click="demoPrompt">
输入弹窗
</wd-button>
</view>
<view
class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]" @click="copyCode(codeExamples.basicMessage)"
>
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
代码示例:
</view>
<view class="whitespace-pre-line text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
confirm({
title: '确认操作',
msg: '您确定要执行此操作吗?',
success: (res) => { /* 处理结果 */ }
})
</view>
</view>
</view>
<!-- 自定义用法 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
自定义配置
</view>
<view class="mb-3">
<wd-button type="primary" block @click="demoCustomConfirm">
自定义按钮文字
</wd-button>
</view>
<view
class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]" @click="copyCode(codeExamples.customMessage)"
>
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
自定义按钮:
</view>
<view class="whitespace-pre-line text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
confirm({
confirmButtonText: '好的',
cancelButtonText: '算了'
})
</view>
</view>
</view>
</view>
</demo-block>
<!-- 组件特性 -->
<demo-block title="组件特性" transparent>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
🎯
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
设计理念
</view>
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>
• <text class="text-blue-600 font-bold dark:text-blue-400">
全局单例
</text>:整个应用只有一个实例,避免重复创建
</view>
<view>
• <text class="text-green-600 font-bold dark:text-green-400">
状态管理
</text>:基于 Pinia 管理组件状态,响应式更新
</view>
<view>
• <text class="text-purple-600 font-bold dark:text-purple-400">
类型安全
</text>:完整的 TypeScript 类型定义
</view>
<view>
• <text class="text-orange-600 font-bold dark:text-orange-400">
易于使用
</text>:简洁的 API 设计,开箱即用
</view>
</view>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
⚡
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
使用优势
</view>
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>• 统一的用户反馈体验</view>
<view>• 避免组件重复创建和销毁</view>
<view>• 支持深色模式自动适配</view>
<view>• 可在任意组件中调用</view>
</view>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
📝
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
最佳实践
</view>
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>• Toast:用于简短的状态反馈,不打断用户操作</view>
<view>• Loading:用于异步操作的等待提示</view>
<view>• Message:用于重要信息确认和用户输入</view>
<view>• 合理控制显示时长,避免信息过载</view>
</view>
</view>
</view>
</demo-block>
<!-- 相关链接 -->
<demo-block title="相关链接" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell title="📚 WotUI Toast" value="轻提示组件" is-link @click="handleNavigate('https://wot-ui.cn/component/toast.html')" />
<wd-cell title="🔄 WotUI Loading" value="加载组件" is-link @click="handleNavigate('https://wot-ui.cn/component/loading.html')" />
<wd-cell title="💬 WotUI MessageBox" value="弹窗组件" is-link @click="handleNavigate('https://wot-ui.cn/component/message-box.html')" />
<wd-cell title="🍍 Pinia 状态管理" value="Vue 状态库" is-link @click="handleNavigate('https://pinia.vuejs.org/zh/')" />
</wd-cell-group>
</demo-block>
</view>
</template>
+483
View File
@@ -0,0 +1,483 @@
<script setup lang="ts">
definePage({
name: 'icon',
style: {
navigationBarTitleText: '图标演示',
},
})
const { success: showSuccess } = useGlobalToast()
// WotUI 内置图标列表(精选9个)
const wotIcons = ref([
{ name: 'add', desc: '添加' },
{ name: 'close', desc: '关闭' },
{ name: 'check', desc: '勾选' },
{ name: 'arrow-right', desc: '右箭头' },
{ name: 'bags', desc: '购物袋' },
{ name: 'search', desc: '搜索' },
{ name: 'star', desc: '星标' },
{ name: 'heart', desc: '喜欢' },
{ name: 'setting', desc: '设置' },
])
// Carbon 图标是编译时的,不能动态渲染,所以直接在模板中静态写入
// 图标大小选项
const iconSizes = ref([
{ name: '小', value: '16px' },
{ name: '中', value: '20px' },
{ name: '大', value: '24px' },
{ name: '超大', value: '32px' },
])
const currentIconSize = ref('20px')
// 复制图标代码
function copyIconCode(iconName: string, isCarbon = false) {
const code = isCarbon
? `<div class="i-carbon:${iconName} text-xl"></div>`
: `<wd-icon name="${iconName}" size="20px" />`
uni.setClipboardData({
data: code,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: '代码已复制到剪贴板' })
},
})
}
// 链接导航处理
function handleNavigate(url: string) {
// #ifdef H5
window.open(url, '_blank')
// #endif
// #ifndef H5
uni.setClipboardData({
data: url,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: `${url} 已复制到剪贴板` })
},
})
// #endif
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部介绍 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-8 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-10">
🎯
</view>
<view class="mb-2 text-6 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
图标使用演示
</view>
<view class="mb-2 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
WotUI 图标 & Iconify 图标集
</view>
<view class="text-3 text-gray-500 dark:text-[var(--wot-dark-color2)]">
丰富的图标资源,满足各种使用场景
</view>
</view>
</view>
<!-- 图标大小选择 -->
<demo-block title="图标大小" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
选择图标大小
</view>
<view class="mb-4 flex flex-wrap gap-2">
<view
v-for="size in iconSizes"
:key="size.value"
class="rounded-2 px-3 py-2 text-3 transition-all duration-200"
:class="[
currentIconSize === size.value
? 'bg-blue-500 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color)] dark:hover:bg-[var(--wot-dark-border)]',
]"
@click="currentIconSize = size.value"
>
{{ size.name }} ({{ size.value }})
</view>
</view>
<view class="flex items-center justify-center rounded-2 bg-gray-50 p-4 space-x-4 dark:bg-[var(--wot-dark-background3)]">
<wd-icon name="star" :size="currentIconSize" color="#f59e0b" />
<view class="i-carbon:star text-yellow-500" :style="{ fontSize: currentIconSize }" />
<text class="text-3 text-gray-600 dark:text-[var(--wot-dark-color2)]">
当前大小: {{ currentIconSize }}
</text>
</view>
</view>
</demo-block>
<!-- WotUI 图标 -->
<demo-block title="WotUI 内置图标(精选示例)" transparent>
<!-- 说明文字 -->
<view class="mb-4 border border-blue-200 rounded-2 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20">
<view class="mb-1 text-3.5 text-blue-700 font-bold dark:text-blue-300">
💡 说明
</view>
<view class="text-3 text-blue-600 leading-relaxed dark:text-blue-200">
这里只展示了部分常用图标作为示例。完整的图标列表请查看
<text class="text-blue-700 underline dark:text-blue-300" @click="handleNavigate('https://wot-ui.cn/component/icon.html')">
WotUI 官方文档
</text>
</view>
</view>
<!-- 图标网格 -->
<view class="grid grid-cols-3 gap-3 md:grid-cols-6 sm:grid-cols-4">
<view
v-for="icon in wotIcons"
:key="icon.name"
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode(icon.name)"
>
<view class="mb-2 flex justify-center">
<wd-icon :name="icon.name" :size="currentIconSize" color="#666" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ icon.name }}
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
{{ icon.desc }}
</view>
</view>
</view>
<!-- 使用示例 -->
<view class="mt-4 rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
使用示例
</view>
<view class="space-y-3">
<view class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]">
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
基础用法:
</view>
<view class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
&lt;wd-icon name="star" size="20px" color="#f59e0b" /&gt;
</view>
</view>
<view class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]">
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
自定义样式:
</view>
<view class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
&lt;wd-icon name="home" size="24px" color="var(--wot-color-theme)" /&gt;
</view>
</view>
</view>
</view>
</demo-block>
<!-- Carbon 图标 -->
<demo-block title="Carbon 图标集" transparent>
<view class="mb-4 border border-blue-200 rounded-2 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20">
<view class="mb-1 text-3.5 text-blue-700 font-bold dark:text-blue-300">
💡 说明
</view>
<view class="text-3 text-blue-600 leading-relaxed dark:text-blue-200">
Iconify 图标集资源异常丰富,这里我们以 Carbon 图标集为例,展示如何使用图标。
<text class="text-blue-700 underline dark:text-blue-300" @click="handleNavigate('https://icones.js.org/collection/carbon')">
Carbon 图标集
</text>
</view>
</view>
<!-- 图标网格 - 静态渲染 -->
<view class="grid grid-cols-3 gap-3 md:grid-cols-6 sm:grid-cols-4">
<!-- add -->
<view
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode('add', true)"
>
<view class="mb-2 flex justify-center">
<view class="i-carbon:add text-gray-600 dark:text-[var(--wot-dark-color)]" :style="{ fontSize: currentIconSize }" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
add
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
添加
</view>
</view>
<!-- close -->
<view
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode('close', true)"
>
<view class="mb-2 flex justify-center">
<view class="i-carbon:close text-gray-600 dark:text-[var(--wot-dark-color)]" :style="{ fontSize: currentIconSize }" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
close
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
关闭
</view>
</view>
<!-- checkmark -->
<view
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode('checkmark', true)"
>
<view class="mb-2 flex justify-center">
<view class="i-carbon:checkmark text-gray-600 dark:text-[var(--wot-dark-color)]" :style="{ fontSize: currentIconSize }" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
checkmark
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
确认
</view>
</view>
<!-- arrow-right -->
<view
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode('arrow-right', true)"
>
<view class="mb-2 flex justify-center">
<view class="i-carbon:arrow-right text-gray-600 dark:text-[var(--wot-dark-color)]" :style="{ fontSize: currentIconSize }" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
arrow-right
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
右箭头
</view>
</view>
<!-- home -->
<view
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode('home', true)"
>
<view class="mb-2 flex justify-center">
<view class="i-carbon:home text-gray-600 dark:text-[var(--wot-dark-color)]" :style="{ fontSize: currentIconSize }" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
home
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
首页
</view>
</view>
<!-- search -->
<view
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode('search', true)"
>
<view class="mb-2 flex justify-center">
<view class="i-carbon:search text-gray-600 dark:text-[var(--wot-dark-color)]" :style="{ fontSize: currentIconSize }" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
search
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
搜索
</view>
</view>
<!-- star -->
<view
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode('star', true)"
>
<view class="mb-2 flex justify-center">
<view class="i-carbon:star text-gray-600 dark:text-[var(--wot-dark-color)]" :style="{ fontSize: currentIconSize }" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
star
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
星级
</view>
</view>
<!-- favorite -->
<view
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode('favorite', true)"
>
<view class="mb-2 flex justify-center">
<view class="i-carbon:favorite text-gray-600 dark:text-[var(--wot-dark-color)]" :style="{ fontSize: currentIconSize }" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
favorite
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
收藏
</view>
</view>
<!-- settings -->
<view
class="rounded-2 bg-white p-4 text-center transition-all duration-200 active:scale-95 dark:bg-[var(--wot-dark-background2)] hover:shadow-md"
@click="copyIconCode('settings', true)"
>
<view class="mb-2 flex justify-center">
<view class="i-carbon:settings text-gray-600 dark:text-[var(--wot-dark-color)]" :style="{ fontSize: currentIconSize }" />
</view>
<view class="mb-1 text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
settings
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
设置
</view>
</view>
</view>
<!-- 使用示例 -->
<view class="mt-4 rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
使用示例
</view>
<view class="space-y-3">
<view class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]">
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
UnoCSS 方式:
</view>
<view class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
&lt;div class="i-carbon:star text-xl text-yellow-500"&gt;&lt;/div&gt;
</view>
</view>
<view class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]">
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
自定义大小和颜色:
</view>
<view class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
&lt;div class="i-carbon:home" style="font-size: 24px; color: #3b82f6;"&gt;&lt;/div&gt;
</view>
</view>
</view>
</view>
</demo-block>
<!-- 对比演示 -->
<demo-block title="图标对比" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-4 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
相同功能图标对比
</view>
<view class="space-y-4">
<view class="grid grid-cols-2 gap-4">
<view class="rounded-2 bg-blue-50 p-4 text-center dark:bg-blue-900/20">
<view class="mb-2 text-3.5 text-blue-700 font-bold dark:text-blue-300">
WotUI 图标
</view>
<view class="mb-3 flex justify-center space-x-4">
<wd-icon name="home" :size="20" color="#3b82f6" />
<wd-icon name="search1" :size="20" color="#3b82f6" />
<wd-icon name="star" :size="20" color="#3b82f6" />
<wd-icon name="heart" :size="20" color="#3b82f6" />
</view>
<view class="text-2.5 text-blue-600 dark:text-blue-200">
组件化使用,API统一
</view>
</view>
<view class="rounded-2 bg-green-50 p-4 text-center dark:bg-green-900/20">
<view class="mb-2 text-3.5 text-green-700 font-bold dark:text-green-300">
Iconify 图标
</view>
<view class="mb-3 flex justify-center space-x-4">
<view class="i-carbon:home text-20px text-green-600" />
<view class="i-carbon:search text-20px text-green-600" />
<view class="i-carbon:star text-20px text-green-600" />
<view class="i-carbon:favorite text-20px text-green-600" />
</view>
<view class="text-2.5 text-green-600 dark:text-green-200">
原子化CSS,更加灵活
</view>
</view>
</view>
</view>
</view>
</demo-block>
<!-- 使用建议 -->
<demo-block title="使用建议" transparent>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
💡
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
WotUI 图标适用场景
</view>
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>• 与WotUI组件配合使用时</view>
<view>• 需要统一的图标API时</view>
<view>• 快速开发,不需要过多自定义时</view>
<view>• 移动端应用,需要保证兼容性时</view>
</view>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
🎨
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
Iconify 图标适用场景
</view>
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>• 需要更多图标选择时</view>
<view>• 使用UnoCSS原子化CSS时</view>
<view>• 需要灵活自定义样式时</view>
<view>• 追求更小的包体积时</view>
</view>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
🤝
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
结合使用建议
</view>
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>
• <text class="text-blue-600 font-bold dark:text-blue-400">
WotUI图标
</text>:用于组件内置图标,如按钮、表单等
</view>
<view>
• <text class="text-green-600 font-bold dark:text-green-400">
Iconify图标
</text>:用于装饰性图标,如页面图标、状态图标等
</view>
<view>• 两者可以在同一个项目中混用,各取所长</view>
<view>• 建议制定团队规范,明确使用场景</view>
</view>
</view>
</view>
</demo-block>
<!-- 相关链接 -->
<demo-block title="相关链接" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell title="📚 WotUI 图标文档" value="组件库文档" is-link @click="handleNavigate('https://wot-ui.cn/component/icon.html')" />
<wd-cell title="🎯 Carbon 图标集" value="IBM设计系统" is-link @click="handleNavigate('https://icones.js.org/collection/carbon')" />
<wd-cell title="🔧 Iconify 图标集" value="巨量图标合集" is-link @click="handleNavigate('https://iconify.design/')" />
<wd-cell title="⚡ UnoCSS 图标" value="原子化CSS图标" is-link @click="handleNavigate('https://unocss.dev/presets/icons')" />
</wd-cell-group>
</demo-block>
</view>
</template>
+547
View File
@@ -0,0 +1,547 @@
<script setup lang="ts">
import { defineStore } from 'pinia'
definePage({
name: 'pinia',
style: {
navigationBarTitleText: 'Pinia 演示',
},
})
const { success: showSuccess } = useGlobalToast()
// 定义一个基础的计数器store
const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
function reset() {
count.value = 0
}
return {
count,
doubleCount,
increment,
decrement,
reset,
}
})
// 定义一个用户信息store(演示持久化)
const useUserStore = defineStore('user', () => {
const userInfo = ref({
name: '',
email: '',
avatar: '',
theme: 'light',
})
const isLoggedIn = computed(() => !!userInfo.value.name)
function setUserInfo(info: Partial<typeof userInfo.value>) {
userInfo.value = { ...userInfo.value, ...info }
}
function clearUserInfo() {
userInfo.value = {
name: '',
email: '',
avatar: '',
theme: 'light',
}
}
function toggleTheme() {
userInfo.value.theme = userInfo.value.theme === 'light' ? 'dark' : 'light'
}
return {
userInfo,
isLoggedIn,
setUserInfo,
clearUserInfo,
toggleTheme,
}
})
// 定义一个临时store(不持久化)
const useTempStore = defineStore('temp', () => {
const tempData = ref({
message: '',
timestamp: Date.now(),
})
function setMessage(msg: string) {
tempData.value.message = msg
tempData.value.timestamp = Date.now()
}
function clearMessage() {
tempData.value.message = ''
tempData.value.timestamp = Date.now()
}
return {
tempData,
setMessage,
clearMessage,
}
})
// 使用stores
const counterStore = useCounterStore()
const userStore = useUserStore()
const tempStore = useTempStore()
// 表单数据
const userForm = ref({
name: '',
email: '',
})
const tempMessage = ref('')
// 复制代码到剪贴板
function copyCode(code: string) {
uni.setClipboardData({
data: code,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: '代码已复制到剪贴板' })
},
})
}
// 保存用户信息
function saveUserInfo() {
if (!userForm.value.name) {
uni.showToast({
title: '请输入用户名',
icon: 'none',
})
return
}
userStore.setUserInfo({
name: userForm.value.name,
email: userForm.value.email,
avatar: '😆',
})
showSuccess({ msg: '用户信息已保存(会持久化)' })
}
// 设置临时消息
function setTempMessage() {
if (!tempMessage.value) {
uni.showToast({
title: '请输入消息内容',
icon: 'none',
})
return
}
tempStore.setMessage(tempMessage.value)
tempMessage.value = ''
showSuccess({ msg: '临时消息已设置(不会持久化)' })
}
// 链接导航处理
function handleNavigate(url: string) {
// #ifdef H5
window.open(url, '_blank')
// #endif
// #ifndef H5
uni.setClipboardData({
data: url,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: `${url} 已复制到剪贴板` })
},
})
// #endif
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部介绍 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-8 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-10">
🍍
</view>
<view class="mb-2 text-6 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
Pinia 状态管理
</view>
<view class="mb-2 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
Vue 3 的直观状态管理库
</view>
<view class="mb-2 text-3 text-gray-500 dark:text-[var(--wot-dark-color2)]">
轻量、类型安全、支持组合式API
</view>
<view class="text-3 text-gray-500 dark:text-[var(--wot-dark-color2)]">
本项目默认持久化所有 Store 数据,开发者可以配置排除列表
</view>
</view>
</view>
<!-- 基础计数器演示 -->
<demo-block title="基础使用 - 计数器" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-4 text-center">
<view class="mb-2 text-8 text-blue-500">
{{ counterStore.count }}
</view>
<view class="text-3 text-gray-600 dark:text-[var(--wot-dark-color2)]">
双倍值: {{ counterStore.doubleCount }}
</view>
</view>
<view class="grid grid-cols-3 mb-4 gap-2">
<wd-button type="success" size="small" @click="counterStore.increment">
+1
</wd-button>
<wd-button type="warning" size="small" @click="counterStore.decrement">
-1
</wd-button>
<wd-button type="info" size="small" @click="counterStore.reset">
重置
</wd-button>
</view>
<!-- 代码示例 -->
<view class="space-y-3">
<view class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]">
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
定义 Store:
</view>
<view
class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]" @click="copyCode(`const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})`)"
>
const useCounterStore = defineStore('counter', () => {\n
&nbsp;&nbsp;const count = ref(0)\n
&nbsp;&nbsp;const doubleCount = computed(() => count.value * 2)\n
&nbsp;&nbsp;function increment() { count.value++ }\n
&nbsp;&nbsp;return { count, doubleCount, increment }\n
})
</view>
</view>
<view class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]">
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
使用 Store:
</view>
<view
class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]" @click="copyCode(`const counterStore = useCounterStore()
// 直接使用
counterStore.increment()
// 响应式访问
console.log(counterStore.count)`)"
>
const counterStore = useCounterStore()\n
counterStore.increment() // 调用方法\n
console.log(counterStore.count) // 访问状态
</view>
</view>
</view>
</view>
</demo-block>
<!-- 用户信息演示(持久化) -->
<demo-block title="持久化演示 - 用户信息" transparent>
<view class="mb-3 border border-green-200 rounded-2 bg-green-50 p-3 dark:border-green-800 dark:bg-green-900/20">
<view class="mb-1 text-3.5 text-green-700 font-bold dark:text-green-300">
✅ 持久化存储
</view>
<view class="text-3 text-green-600 leading-relaxed dark:text-green-200">
这个 Store 的数据会自动保存到本地存储,刷新页面后数据不会丢失
</view>
</view>
<view class="space-y-4">
<!-- 当前用户信息 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
当前用户信息
</view>
<view v-if="userStore.isLoggedIn" class="flex items-center space-x-3">
<view class="rounded-full text-12">
{{ userStore.userInfo.avatar }}
</view>
<view class="flex-1">
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ userStore.userInfo.name }}
</view>
<view class="text-3 text-gray-600 dark:text-[var(--wot-dark-color2)]">
{{ userStore.userInfo.email }}
</view>
</view>
</view>
<view v-else class="text-center text-gray-500 dark:text-[var(--wot-dark-color2)]">
暂无用户信息
</view>
</view>
<!-- 用户信息表单 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
设置用户信息
</view>
<view class="space-y-3">
<view>
<view class="mb-1 text-3 text-gray-700 dark:text-[var(--wot-dark-color)]">
用户名:
</view>
<wd-input v-model="userForm.name" placeholder="请输入用户名" />
</view>
<view>
<view class="mb-1 text-3 text-gray-700 dark:text-[var(--wot-dark-color)]">
邮箱:
</view>
<wd-input v-model="userForm.email" placeholder="请输入邮箱" />
</view>
<view class="grid grid-cols-2 gap-2">
<wd-button type="primary" @click="saveUserInfo">
保存信息
</wd-button>
<wd-button type="error" @click="userStore.clearUserInfo">
清除信息
</wd-button>
</view>
</view>
</view>
</view>
</demo-block>
<!-- 临时数据演示(不持久化) -->
<demo-block title="非持久化演示 - 临时数据" transparent>
<view class="mb-3 border border-orange-200 rounded-2 bg-orange-50 p-3 dark:border-orange-800 dark:bg-orange-900/20">
<view class="mb-1 text-3.5 text-orange-700 font-bold dark:text-orange-300">
⚠️ 临时存储
</view>
<view class="text-3 text-orange-600 leading-relaxed dark:text-orange-200">
这个 Store 的数据不会持久化,刷新页面后数据会丢失
</view>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
临时消息
</view>
<view v-if="tempStore.tempData.message" class="mb-4 rounded-2 bg-blue-50 p-3 dark:bg-blue-900/20">
<view class="text-3.5 text-blue-700 font-bold dark:text-blue-300">
{{ tempStore.tempData.message }}
</view>
<view class="text-2.5 text-blue-600 dark:text-blue-200">
时间戳: {{ new Date(tempStore.tempData.timestamp).toLocaleString() }}
</view>
</view>
<view v-else class="mb-4 text-center text-gray-500 dark:text-[var(--wot-dark-color2)]">
暂无临时消息
</view>
<view class="space-y-3">
<wd-input v-model="tempMessage" placeholder="请输入临时消息" />
<view class="grid grid-cols-2 gap-2">
<wd-button type="primary" size="small" @click="setTempMessage">
设置消息
</wd-button>
<wd-button type="warning" size="small" @click="tempStore.clearMessage">
清除消息
</wd-button>
</view>
</view>
</view>
</demo-block>
<!-- 持久化原理 -->
<demo-block title="持久化实现原理" transparent>
<view class="space-y-4">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
持久化插件
</view>
<view class="mb-3 text-3 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
项目使用自定义的 Pinia 插件实现持久化,位于 <text class="text-blue-600 font-mono dark:text-blue-400">
src/store/persist.ts
</text>
</view>
<view
class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]" @click="copyCode(`function persist({ store }, excludedIds) {
// 检查是否需要排除
if (excludedIds.includes(store.$id)) return
// 从本地存储读取数据
const storageState = uni.getStorageSync(store.$id)
if (storageState) {
store.$state = storageState
}
// 监听状态变化并保存
store.$subscribe(() => {
uni.setStorageSync(store.$id, store.$state)
})
}`)"
>
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
核心实现:
</view>
<view class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
function persist({ store }, excludedIds) {\n
&nbsp;&nbsp;// 检查是否需要排除\n
&nbsp;&nbsp;if (excludedIds.includes(store.$id)) return\n
&nbsp;&nbsp;// 从本地存储读取数据\n
&nbsp;&nbsp;const storageState = uni.getStorageSync(store.$id)\n
&nbsp;&nbsp;// 监听状态变化并保存\n
&nbsp;&nbsp;store.$subscribe(() => {\n
&nbsp;&nbsp;&nbsp;&nbsp;uni.setStorageSync(store.$id, store.$state)\n
&nbsp;&nbsp;})\n
}
</view>
</view>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
排除列表
</view>
<view class="mb-3 text-3 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
可以通过排除列表控制哪些 Store 不需要持久化
</view>
<view
class="rounded bg-gray-100 p-3 dark:bg-[var(--wot-dark-background3)]" @click="copyCode(`// 在 persist.ts 中配置排除列表
persist(context, ['global-register', 'temp'])`)"
>
<view class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
// 当前排除的 Store ID:\n
['global-register', 'temp']
</view>
</view>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
使用说明
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>
• <text class="text-green-600 font-bold dark:text-green-400">
自动持久化
</text>:默认情况下,所有 Store 都会自动持久化
</view>
<view>
• <text class="text-orange-600 font-bold dark:text-orange-400">
排除机制
</text>:通过 Store ID 控制哪些不需要持久化
</view>
<view>
• <text class="text-blue-600 font-bold dark:text-blue-400">
深拷贝保护
</text>:使用深拷贝避免引用问题
</view>
<view>
• <text class="text-purple-600 font-bold dark:text-purple-400">
自动恢复
</text>:页面刷新时自动从本地存储恢复数据
</view>
</view>
</view>
</view>
</demo-block>
<!-- 最佳实践 -->
<demo-block title="最佳实践" transparent>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
💡
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
Store 设计建议
</view>
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>• 使用组合式 API 风格定义 Store</view>
<view>• 合理拆分 Store,避免单个 Store 过于庞大</view>
<view>• 使用 TypeScript 获得更好的类型支持</view>
<view>• 为 Store 提供清晰的命名和注释</view>
</view>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
🔒
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
持久化建议
</view>
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>• 敏感数据(如密码)不要持久化存储</view>
<view>• 大量数据考虑使用数据库而非本地存储</view>
<view>• 临时状态(如加载状态)不需要持久化</view>
<view>• 定期清理无用的持久化数据</view>
</view>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-6">
⚡
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
性能优化
</view>
</view>
<view class="text-3 text-gray-600 leading-relaxed space-y-2 dark:text-[var(--wot-dark-color2)]">
<view>• 避免在 Store 中存储大量数据</view>
<view>• 使用 computed 缓存复杂计算结果</view>
<view>• 合理使用 $subscribe 监听状态变化</view>
<view>• 考虑使用 $patch 批量更新状态</view>
</view>
</view>
</view>
</demo-block>
<!-- 相关链接 -->
<demo-block title="相关链接" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell title="📚 Pinia 官方文档" value="状态管理库" is-link @click="handleNavigate('https://pinia.vuejs.org/zh/')" />
<wd-cell title="🎯 Vue 3 文档" value="组合式 API" is-link @click="handleNavigate('https://cn.vuejs.org/guide/extras/composition-api-faq.html')" />
<wd-cell title="🔧 uni-app 存储" value="本地存储 API" is-link @click="handleNavigate('https://uniapp.dcloud.net.cn/api/storage/storage.html')" />
</wd-cell-group>
</demo-block>
</view>
</template>
+507
View File
@@ -0,0 +1,507 @@
<script setup lang="ts">
import type { FormRules } from 'wot-design-uni/components/wd-form/types'
import type { UserInfo } from '@/api/user'
import { onLoad } from '@dcloudio/uni-app'
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import UserAPI from '@/api/user'
// 移除了 FileAPI 导入,该模块不存在
import { useUserStore } from '@/store/userStore'
definePage({
name: 'profile',
style: {
navigationBarTitleText: '个人中心',
},
})
const userStore = useUserStore()
const originalSrc = ref<string>('') // 选取的原图路径
const avatarShow = ref<boolean>(false) // 显示头像裁剪
const userProfile = ref<UserInfo>() // 用户信息
/** 加载用户信息 */
async function loadUserProfile() {
userProfile.value = await UserAPI.getCurrentUserInfo()
}
// 头像选择
function avatarUpload() {
uni.chooseImage({
count: 1,
success: (res) => {
originalSrc.value = res.tempFilePaths[0]
avatarShow.value = true
},
})
}
// 头像裁剪完成
function handleAvatarConfirm(event: any) {
const { tempFilePath } = event
// 使用 UserAPI 的头像上传方法
UserAPI.uploadCurrentUserAvatar({ file: tempFilePath }).then(
(fileInfo: UploadFileResult) => {
const avatarForm = {
name: userProfile.value?.name || '',
gender: userProfile.value?.gender || '0',
mobile: userProfile.value?.mobile || '',
email: userProfile.value?.email || '',
username: userProfile.value?.username || '',
dept_name: userProfile.value?.dept_name || '',
positions: userProfile.value?.positions || [],
roles: userProfile.value?.roles || [],
avatar: fileInfo.file_url,
created_at: userProfile.value?.created_at || '',
}
// 头像路径保存至后端
UserAPI.updateCurrentUserInfo(avatarForm).then(() => {
uni.showToast({ title: '头像上传成功', icon: 'none' })
loadUserProfile()
})
},
)
}
// 本页面中所有的校验规则
const rules: FormRules = {
name: [{ required: true, message: '请填写昵称' }],
gender: [{ required: true, message: '请选择性别' }],
}
const dialog = reactive<{
visible: boolean
title?: string
field?: string
}>({
visible: false,
})
const userProfileForm = reactive<{
name?: string
gender?: string
}>({})
const userProfileFormRef = ref()
// 打开弹窗
function handleOpenDialog(field: string) {
dialog.field = field
dialog.visible = true
// 初始化表单数据
if (field === 'name') {
dialog.title = '编辑昵称'
userProfileForm.name = userProfile.value?.name || ''
}
else if (field === 'gender') {
dialog.title = '选择性别'
userProfileForm.gender = userProfile.value?.gender || '0'
}
}
// 提交表单
function handleSubmit() {
userProfileFormRef.value.validate().then(({ valid }: { valid: boolean }) => {
if (valid) {
UserAPI.updateCurrentUserInfo({
name: userProfileForm.name,
gender: userProfileForm.gender,
mobile: userProfile.value?.mobile || '',
email: userProfile.value?.email || '',
username: userProfile.value?.username || '',
dept_name: userProfile.value?.dept_name || '',
positions: userProfile.value?.positions || [],
roles: userProfile.value?.roles || [],
avatar: userProfile.value?.avatar || '',
created_at: userProfile.value?.created_at || '',
}).then(() => {
uni.showToast({ title: '账号资料修改成功', icon: 'none' })
dialog.visible = false
loadUserProfile()
})
}
})
}
// 检查登录状态
onLoad(() => {
if (!userStore.checkLogin())
return
// #ifdef H5
document.addEventListener('touchstart', touchstartListener, {
passive: false,
})
document.addEventListener('touchmove', touchmoveListener, { passive: false })
// #endif
loadUserProfile()
})
onMounted(() => {
// 在onMounted中不再重复检查登录状态和加载用户信息
// 如果需要检查登录状态和加载用户信息,请使用onLoad中的逻辑
})
// 页面销毁前移除事件监听
onBeforeUnmount(() => {
// #ifdef H5
document.removeEventListener('touchstart', touchstartListener)
document.removeEventListener('touchmove', touchmoveListener)
// #endif
})
// 禁用浏览器双指缩放,使头像裁剪时双指缩放能够起作用
function touchstartListener(event: TouchEvent) {
if (event.touches.length > 1) {
event.preventDefault()
}
}
// 禁用浏览器下拉刷新,使头像裁剪时能够移动图片
function touchmoveListener(event: TouchEvent) {
event.preventDefault()
}
</script>
<template>
<view class="app-container">
<wd-card
v-if="userProfile"
custom-style="margin-top: 20rpx"
class="theme-card"
>
<wd-cell-group border>
<wd-cell
class="avatar-cell"
title="头像"
center
is-link
@click="avatarUpload"
>
<template #title>
<text class="theme-text-primary">
头像
</text>
</template>
<view class="avatar flex-center">
<view
v-if="!userProfile.avatar"
class="img flex-center"
@click="avatarUpload"
>
<wd-icon
name="fill-camera"
custom-class="img-icon"
/>
</view>
<wd-img
v-if="userProfile.avatar"
round
width="80px"
height="80px"
:src="userProfile.avatar"
mode="aspectFit"
custom-class="profile-img"
@click="avatarUpload"
/>
</view>
</wd-cell>
<wd-cell
title="昵称"
:value="userProfile.name"
is-link
@click="handleOpenDialog('name')"
>
<template #title>
<text class="theme-text-primary">
昵称
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{
userProfile.name || "未设置"
}}
</text>
</template>
</wd-cell>
<wd-cell
title="性别"
:value="
userProfile.gender === '0'
? '男'
: userProfile.gender === '1'
? '女'
: '未知'
"
is-link
@click="handleOpenDialog('gender')"
>
<template #title>
<text class="theme-text-primary">
性别
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{
userProfile.gender === "0"
? "男"
: userProfile.gender === "1"
? "女"
: "未知"
}}
</text>
</template>
</wd-cell>
<wd-cell title="用户名" :value="userProfile.username">
<template #title>
<text class="theme-text-primary">
用户名
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{ userProfile.username }}
</text>
</template>
</wd-cell>
<wd-cell v-if="userProfile" title="状态" center>
<template #title>
<text class="theme-text-primary">
状态
</text>
</template>
<wd-tag :type="userProfile.status ? 'success' : 'danger'">
{{ userProfile.status ? "启用" : "停用" }}
</wd-tag>
</wd-cell>
<wd-cell v-if="userProfile" title="是否超管" center>
<template #title>
<text class="theme-text-primary">
是否超管
</text>
</template>
<wd-tag
plain
:type="userProfile.is_superuser ? 'primary' : 'default'"
>
{{ userProfile.is_superuser ? "是" : "否" }}
</wd-tag>
</wd-cell>
<wd-cell title="手机号" :value="userProfile.mobile">
<template #title>
<text class="theme-text-primary">
手机号
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{
userProfile.mobile || "未绑定"
}}
</text>
</template>
</wd-cell>
<wd-cell title="邮箱" :value="userProfile.email">
<template #title>
<text class="theme-text-primary">
邮箱
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{
userProfile.email || "未绑定"
}}
</text>
</template>
</wd-cell>
<wd-cell title="部门" :value="userProfile.dept_name">
<template #title>
<text class="theme-text-primary">
部门
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{
userProfile.dept_name || "未分配"
}}
</text>
</template>
</wd-cell>
<wd-cell
title="角色"
:value="userProfile.roles?.map((item) => item.name).join(', ')"
>
<template #title>
<text class="theme-text-primary">
角色
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{
userProfile.roles?.map((item) => item.name).join(", ")
|| "未分配"
}}
</text>
</template>
</wd-cell>
<wd-cell
title="岗位"
:value="userProfile.positions?.map((item) => item.name).join(', ')"
>
<template #title>
<text class="theme-text-primary">
岗位
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{
userProfile.positions?.map((item) => item.name).join(", ")
|| "未分配"
}}
</text>
</template>
</wd-cell>
<wd-cell title="备注" :value="userProfile.description">
<template #title>
<text class="theme-text-primary">
备注
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{
userProfile.description || "无"
}}
</text>
</template>
</wd-cell>
<wd-cell title="创建日期" :value="userProfile.created_at">
<template #title>
<text class="theme-text-primary">
创建日期
</text>
</template>
<template #value>
<text class="theme-text-secondary">
{{
userProfile.created_at
}}
</text>
</template>
</wd-cell>
</wd-cell-group>
</wd-card>
<!-- 头像裁剪 -->
<wd-img-cropper
v-model="avatarShow"
:img-src="originalSrc"
@confirm="handleAvatarConfirm"
/>
<!-- 用户信息编辑弹出框 -->
<wd-popup
v-model="dialog.visible"
position="bottom"
custom-style="border-top-left-radius: 16rpx; border-top-right-radius: 16rpx;"
>
<view class="popup-header">
<wd-cell :title="dialog.title" center>
<template #right-icon>
<wd-icon name="close" size="20" @click="dialog.visible = false" />
</template>
</wd-cell>
</view>
<wd-divider />
<wd-form
ref="userProfileFormRef"
:model="userProfileForm"
custom-class="edit-form"
>
<wd-cell-group border>
<wd-input
v-if="dialog.field === 'name'"
v-model="userProfileForm.name"
label="昵称"
label-width="160rpx"
placeholder="请输入昵称"
prop="name"
:rules="rules.name"
/>
<wd-cell
v-if="dialog.field === 'gender'"
title="性别"
title-width="160rpx"
center
prop="gender"
:rules="rules.gender"
>
<wd-radio-group
v-model="userProfileForm.gender"
shape="button"
class="ef-radio-group"
>
<wd-radio :value="0">
男
</wd-radio>
<wd-radio :value="1">
女
</wd-radio>
<wd-radio :value="2">
未知
</wd-radio>
</wd-radio-group>
</wd-cell>
</wd-cell-group>
<view>
<wd-button type="primary" block @click="handleSubmit">
提交
</wd-button>
</view>
</wd-form>
</wd-popup>
</view>
</template>
<style lang="scss" scoped>
.avatar-cell {
:deep(.wd-cell__body) {
align-items: center;
}
.avatar {
display: flex;
align-items: center;
justify-content: right;
.img {
position: relative;
width: 80px;
height: 80px;
background-color: rgba(0, 0, 0, 0.04);
border-radius: 50%;
.img-icon {
position: absolute;
top: 50%;
left: 50%;
color: #fff;
}
}
}
}
.edit-form {
padding-top: 40rpx;
.ef-radio-group {
line-height: 1;
text-align: left;
}
}
</style>
+290
View File
@@ -0,0 +1,290 @@
<script setup lang="ts">
definePage({
name: 'request',
style: {
navigationBarTitleText: '网络请求',
},
})
const GlobalToast = useGlobalToast()
// Alova 核心特性
const alovaFeatures = [
{
icon: '⚡',
title: '简单易用',
description: '观看视频5分钟上手',
details: '极简的API设计,直观的使用方式,让开发者快速掌握并投入使用',
color: 'blue',
},
{
icon: '🔧',
title: '完美兼容',
description: '兼容你最喜欢的技术栈',
details: '支持Vue、React、Angular等主流框架,以及各种HTTP客户端库',
color: 'green',
},
{
icon: '🚀',
title: '高性能模块',
description: '20+ 高性能的业务模块',
details: '提供分页、实时数据、文件上传下载等开箱即用的高性能解决方案',
color: 'purple',
},
{
icon: '🔗',
title: 'OpenAPI 解决方案',
description: '更先进的 openAPI 解决方案',
details: '在代码中和API信息高效交互,自动生成类型定义和请求函数',
color: 'orange',
},
{
icon: '📦',
title: '请求共享缓存',
description: '请求共享和响应缓存',
details: '智能的请求去重和响应缓存机制,显著提升应用性能',
color: 'cyan',
},
{
icon: '🛡️',
title: '类型安全',
description: '完整的 TypeScript 支持',
details: '从请求到响应的全链路类型安全,减少运行时错误',
color: 'red',
},
]
// 使用真正的 useRequest hook - 获取宠物列表
const {
data: petData,
loading: petLoading,
error: petError,
send: loadPetData,
} = useRequest(
(status: 'available' | 'pending' | 'sold' = 'available') => Apis.pet.findPetsByStatus({
params: { status },
}),
{
immediate: false,
},
).onError((error) => {
GlobalToast.error(error.error?.message || '获取宠物列表失败')
})
// 使用真正的 useRequest hook - 用户登录演示
const {
data: loginData,
loading: loginLoading,
error: loginError,
send: performLogin,
} = useRequest(
(username: string, password: string) => Apis.user.loginUser({
params: {
username,
password,
},
}),
{
immediate: false,
},
).onError((error) => {
GlobalToast.error(error.error?.message || '用户登录失败')
})
// useRequest 演示函数
function demoLoadPets() {
loadPetData('available')
}
function demoLogin() {
performLogin('testuser', 'testpass')
}
// 链接导航处理
function handleNavigate(url: string) {
// #ifdef H5
window.open(url, '_blank')
// #endif
// #ifndef H5
uni.setClipboardData({
data: url,
showToast: false,
success: () => {
uni.hideToast()
GlobalToast.success(`${url} 已复制到剪贴板`)
},
})
// #endif
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部介绍 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-8 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-10">
🌐
</view>
<view class="mb-2 text-6 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
Alova 网络请求
</view>
<view class="mb-2 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
极致高效的请求工具集
</view>
<view class="text-3 text-gray-500 leading-relaxed dark:text-[var(--wot-dark-color2)]">
alova完美兼容你最喜欢的HTTP client和UI框架,快速开发客户端和服务的应用的业务逻辑,同时让API信息与代码进行交互,像虫洞一样拉近后端协作距离,极致高效地集成你的APIs
</view>
</view>
</view>
<!-- Alova 核心特性 -->
<demo-block title="Alova 核心特性" transparent>
<view class="grid grid-cols-2 gap-3">
<view
v-for="feature in alovaFeatures"
:key="feature.title"
class="rounded-2 bg-white p-3 dark:bg-[var(--wot-dark-background2)]"
>
<view class="mb-2 flex items-center">
<view class="mr-2 text-5">
{{ feature.icon }}
</view>
<view class="flex-1">
<view class="text-3.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ feature.title }}
</view>
<view class="text-2.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
{{ feature.description }}
</view>
</view>
</view>
</view>
</view>
</demo-block>
<!-- Alova useRequest 演示 -->
<demo-block title="useRequest Hook 演示" transparent>
<view class="space-y-3">
<!-- 宠物列表请求 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-5">
🐾
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
宠物列表请求
</view>
</view>
<view class="mb-3 text-3 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
使用 useRequest 获取宠物列表,支持参数传递和错误处理
</view>
<view class="mb-3">
<wd-button
type="primary"
block
:loading="petLoading"
@click="demoLoadPets"
>
获取宠物列表
</wd-button>
</view>
<!-- 请求状态显示 -->
<view class="space-y-2">
<view v-if="petLoading" class="flex items-center text-3 text-blue-600">
<wd-icon name="loading" size="14px" class="mr-1" />
正在加载宠物数据...
</view>
<view v-if="petError" class="text-3 text-red-600">
❌ 请求失败: {{ petError.message }}
</view>
<view v-if="petData && !petLoading" class="text-3 text-green-600">
✅ 成功获取 {{ petData.length }} 只宠物数据
</view>
</view>
<!-- 代码示例 -->
<view class="mt-3 rounded-2 bg-gray-50 p-3 dark:bg-[var(--wot-dark-background3)]">
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
代码示例:
</view>
<view class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
const { data, loading, send } = useRequest(\n
&nbsp;&nbsp;(status) => Apis.pet.findPetsByStatus({ params: { status } }),\n
&nbsp;&nbsp;{ immediate: false }\n
).onError((error) => { ... })
</view>
</view>
</view>
<!-- 用户登录请求 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 flex items-center">
<view class="mr-2 text-5">
👤
</view>
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
用户登录请求
</view>
</view>
<view class="mb-3 text-3 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
演示多参数传递和POST请求处理
</view>
<view class="mb-3">
<wd-button
type="warning"
block
:loading="loginLoading"
@click="demoLogin"
>
模拟用户登录
</wd-button>
</view>
<!-- 请求状态显示 -->
<view class="space-y-2">
<view v-if="loginLoading" class="flex items-center text-3 text-blue-600">
<wd-icon name="loading" size="14px" class="mr-1" />
正在登录中...
</view>
<view v-if="loginError" class="text-3 text-red-600">
❌ 登录失败: {{ loginError.message }}
</view>
<view v-if="loginData && !loginLoading" class="text-3 text-green-600">
✅ 登录成功!
</view>
</view>
<!-- 代码示例 -->
<view class="mt-3 rounded-2 bg-gray-50 p-3 dark:bg-[var(--wot-dark-background3)]">
<view class="mb-2 text-3 text-gray-700 font-bold dark:text-[var(--wot-dark-color)]">
代码示例:
</view>
<view class="text-2.5 text-gray-600 leading-relaxed font-mono dark:text-[var(--wot-dark-color2)]">
const { data, loading, send } = useRequest(\n
&nbsp;&nbsp;(username, password) => Apis.user.loginUser({\n
&nbsp;&nbsp;&nbsp;&nbsp;params: { username, password }\n
&nbsp;&nbsp;}),\n
&nbsp;&nbsp;{ immediate: false }\n
).onError((error) => { ... })
</view>
</view>
</view>
</view>
</demo-block>
<!-- 相关链接 -->
<demo-block title="相关链接" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell title="📚 Alova 官方文档" value="alova.js.org" is-link @click="handleNavigate('https://alova.js.org/')" />
<wd-cell title="🐙 GitHub 仓库" value="alovajs/alova" is-link @click="handleNavigate('https://github.com/alovajs/alova')" />
<wd-cell title="📖 uni-app 文档" value="网络请求" is-link @click="handleNavigate('https://uniapp.dcloud.net.cn/api/request/request.html')" />
<wd-cell title="🎯 Alova Hooks" value="请求策略" is-link @click="handleNavigate('https://alova.js.org/zh-CN/tutorial/client/strategy/')" />
<wd-cell title="💡 最佳实践" value="使用指南" is-link @click="handleNavigate('https://alova.js.org/zh-CN/tutorial/project/best-practice/')" />
</wd-cell-group>
</demo-block>
</view>
</template>
@@ -0,0 +1,54 @@
<script setup lang="ts">
definePage({
name: 'demo-aftereach',
style: {
navigationBarTitleText: 'afterEach 钩子演示',
},
})
const router = useRouter()
function goBack() {
router.back()
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-6 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-8">
📊
</view>
<view class="mb-2 text-5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
afterEach 钩子演示
</view>
<view class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
这个页面用于演示 afterEach 钩子的触发
</view>
</view>
</view>
<!-- 内容 -->
<demo-block title="afterEach 演示成功" transparent>
<view class="border border-green-200 rounded-2 bg-green-50 p-4 dark:bg-green-900/20">
<view class="mb-2 text-4 text-green-700 font-bold dark:text-green-300">
🎉 恭喜!
</view>
<view class="text-3.5 text-green-600 leading-relaxed dark:text-green-200">
如果你能看到这个页面,说明 afterEach 钩子已经触发了!\n
打开浏览器控制台(F12 → Console)可以看到相关的日志输出。\n
afterEach 钩子常用于页面统计、埋点上报等导航后处理。
</view>
</view>
</demo-block>
<!-- 操作按钮 -->
<demo-block title="导航" transparent>
<wd-button type="warning" block @click="goBack">
返回上一页
</wd-button>
</demo-block>
</view>
</template>
@@ -0,0 +1,83 @@
<script setup lang="ts">
definePage({
name: 'demo-guard',
style: {
navigationBarTitleText: '导航守卫演示',
},
})
const router = useRouter()
function goBack() {
router.back()
}
// 演示权限拦截
function handleBeforeEach() {
router.push({
name: 'demo-protected',
})
}
// 跳转到afterEach演示页面
function demoAfterEachPage() {
router.push('/subPages/router/demo-aftereach')
}
</script>
<template>
<view class="bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-6 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-8">
🛡️
</view>
<view class="mb-2 text-5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
导航守卫演示
</view>
<view class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
演示 beforeEach 和 afterEach 守卫功能
</view>
</view>
</view>
<!-- 守卫演示 -->
<demo-block title="守卫演示" transparent>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
前置导航守卫拦截
</view>
<view class="mb-3 text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
跳转前交互,可以拦截导航
</view>
<wd-button type="error" block @click="handleBeforeEach">
📊 beforeEach 演示
</wd-button>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
后置导航钩子演示
</view>
<view class="mb-3 text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
跳转到专门的 afterEach 演示页面,体验页面统计、埋点上报等功能
</view>
<wd-button type="success" block @click="demoAfterEachPage">
📊 afterEach 演示
</wd-button>
</view>
</view>
</demo-block>
<!-- 操作按钮 -->
<demo-block title="导航" transparent>
<view class="px-3">
<wd-button type="warning" block @click="goBack">
返回上一页
</wd-button>
</view>
</demo-block>
</view>
</template>
@@ -0,0 +1,123 @@
<script setup lang="ts">
definePage({
name: 'demo-object',
style: {
navigationBarTitleText: '对象路径跳转',
},
})
const router = useRouter()
const route = useRoute()
function goBack() {
router.back()
}
function pushToParams() {
router.push({ name: 'demo-params', params: { username: 'eduardo' } })
}
function pushToQuery() {
router.push({ path: '/subPages/router/demo-query', query: { keyword: 'vue', type: 'framework' } })
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-6 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-8">
🎯
</view>
<view class="mb-2 text-5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
对象路径跳转演示
</view>
<view class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
使用 router.push({ path: '/path' }) 进行跳转
</view>
</view>
</view>
<!-- 跳转信息 -->
<demo-block title="跳转信息" transparent>
<view class="rounded-3 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
当前页面信息
</view>
<view class="space-y-2">
<view class="flex items-center justify-between border-b border-gray-100 py-2 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
路径:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ route.path }}
</text>
</view>
<view class="flex items-center justify-between border-b border-gray-100 py-2 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
跳转方式:
</text>
<text class="text-3.5 text-gray-800 dark:text-[var(--wot-dark-color)]">
对象路径
</text>
</view>
<view class="flex items-center justify-between py-2">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
代码:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
router.push({ path: '/subPages/router/demo-object' })
</text>
</view>
</view>
</view>
</demo-block>
<!-- 参数传递演示 -->
<demo-block title="参数传递演示" transparent>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
params 参数传递
</view>
<view class="mb-3 text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
使用 name + params 组合传递参数
</view>
<view class="mb-3 border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]">
<text class="text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.push({ name: 'demo-params', params: { username: 'eduardo' } })
</text>
</view>
<wd-button type="primary" block @click="pushToParams">
跳转并传递 params
</wd-button>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
query 参数传递
</view>
<view class="mb-3 text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
使用 path + query 组合传递参数
</view>
<view class="mb-3 border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]">
<text class="text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.push({ path: '/demo-query', query: { keyword: 'vue' } })
</text>
</view>
<wd-button type="success" block @click="pushToQuery">
跳转并传递 query
</wd-button>
</view>
</view>
</demo-block>
<!-- 操作按钮 -->
<demo-block title="导航" transparent>
<wd-button type="warning" block @click="goBack">
返回上一页
</wd-button>
</demo-block>
</view>
</template>
@@ -0,0 +1,182 @@
<script setup lang="ts">
definePage({
name: 'demo-params',
style: {
navigationBarTitleText: '参数接收演示',
},
})
const router = useRouter()
const route = useRoute()
// 接收参数
const receivedParams = ref({})
const decodedUser = ref(null)
onLoad((option) => {
console.log('onLoad option:', option)
receivedParams.value = option || {}
// 如果有user参数,尝试解码对象
if (option && option.user) {
try {
decodedUser.value = JSON.parse(decodeURIComponent(decodeURIComponent(option.user)))
}
catch (e) {
console.error('解码user参数失败:', e)
}
}
})
function goBack() {
router.back()
}
function pushToGuard() {
router.push('/subPages/router/demo-guard')
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-6 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-8">
📥
</view>
<view class="mb-2 text-5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
params 参数接收演示
</view>
<view class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
演示如何接收和处理路由参数
</view>
</view>
</view>
<!-- 路由信息 -->
<demo-block title="当前路由信息" transparent>
<view class="rounded-3 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="space-y-2">
<view class="flex items-center justify-between border-b border-gray-100 py-2 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
路径:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ route.path }}
</text>
</view>
<view class="flex items-center justify-between border-b border-gray-100 py-2 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
名称:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ route.name }}
</text>
</view>
<view class="flex items-center justify-between py-2">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
接收方式:
</text>
<text class="text-3.5 text-gray-800 dark:text-[var(--wot-dark-color)]">
onLoad(option)
</text>
</view>
</view>
</view>
</demo-block>
<!-- 参数信息 -->
<demo-block title="接收到的参数" transparent>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
原始参数 (option)
</view>
<view class="border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]">
<text class="text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
{{ JSON.stringify(receivedParams, null, 2) }}
</text>
</view>
</view>
<view v-if="decodedUser" class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
解码后的对象参数
</view>
<view class="border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]">
<text class="text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
{{ JSON.stringify(decodedUser, null, 2) }}
</text>
</view>
<view class="mt-3 text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
使用 JSON.parse(decodeURIComponent(decodeURIComponent(option.user))) 解码
</view>
</view>
</view>
</demo-block>
<!-- API 说明 -->
<demo-block title="API 说明" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
Params vs Query 的区别
</view>
<view class="mb-3 border border-orange-200 rounded-2 bg-orange-50 p-3 dark:bg-orange-900/20">
<view class="mb-2 text-3.5 text-orange-700 font-bold dark:text-orange-300">
⚠️ 重要说明
</view>
<view class="text-3 text-orange-600 leading-relaxed dark:text-orange-200">
在 @wot-ui/router 中,params 和 query 参数在实际效果上并无区别,都会以查询字符串形式放在 URL 中。这种 API 设计主要是为了与 vue-router 保持一致。
</view>
</view>
<view class="border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]">
<text class="text-3 text-gray-700 leading-relaxed font-mono dark:text-[var(--wot-dark-color)]">
// Params 写法 (当前演示)
router.push({ name: 'demo-params', params: { username: 'eduardo' } })
// 结果: /demo-params?username=eduardo
// Query 写法 (效果相同)
router.push({ path: '/demo-params', query: { username: 'eduardo' } })
// 结果: /demo-params?username=eduardo
</text>
</view>
</view>
</demo-block>
<!-- 代码示例 -->
<demo-block title="代码示例" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
接收参数的标准写法
</view>
<view class="border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]">
<text class="text-3 text-gray-700 leading-relaxed font-mono dark:text-[var(--wot-dark-color)]">
onLoad((option) => {
if (option && option.username) {
const username = option.username
}
// 对象参数需要解码
if (option && option.user) {
const user = JSON.parse(decodeURIComponent(decodeURIComponent(option.user)))
}
})
</text>
</view>
</view>
</demo-block>
<!-- 操作按钮 -->
<demo-block title="继续演示" transparent>
<view class="space-y-3">
<wd-button type="primary" block @click="pushToGuard">
跳转到导航守卫演示
</wd-button>
<wd-button type="warning" block @click="goBack">
返回上一页
</wd-button>
</view>
</demo-block>
</view>
</template>
@@ -0,0 +1,54 @@
<script setup lang="ts">
definePage({
name: 'demo-protected',
style: {
navigationBarTitleText: '受保护页面',
},
})
const router = useRouter()
function goBack() {
router.back()
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-6 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-8">
🔒
</view>
<view class="mb-2 text-5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
受保护的页面
</view>
<view class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
这个页面被路由守卫保护,只有登录用户才能访问
</view>
</view>
</view>
<!-- 内容 -->
<demo-block title="守卫拦截成功" transparent>
<view class="border border-green-200 rounded-2 bg-green-50 p-4 dark:bg-green-900/20">
<view class="mb-2 text-4 text-green-700 font-bold dark:text-green-300">
🎉 恭喜!
</view>
<view class="text-3.5 text-green-600 leading-relaxed dark:text-green-200">
如果你能看到这个页面,说明路由守卫验证通过了!\n
但实际上,这个页面被配置为需要登录才能访问,\n
守卫应该会拦截并重定向你到首页。
</view>
</view>
</demo-block>
<!-- 操作按钮 -->
<demo-block title="导航" transparent>
<wd-button type="warning" block @click="goBack">
返回上一页
</wd-button>
</demo-block>
</view>
</template>
@@ -0,0 +1,201 @@
<script setup lang="ts">
definePage({
name: 'demo-query',
style: {
navigationBarTitleText: '查询参数接收演示',
},
})
const router = useRouter()
const route = useRoute()
// 接收参数
const receivedQuery = ref({})
const decodedUser = ref(null)
onLoad((option) => {
console.log('onLoad option:', option)
receivedQuery.value = option || {}
// 如果有user参数,尝试解码对象
if (option && option.user) {
try {
decodedUser.value = JSON.parse(decodeURIComponent(decodeURIComponent(option.user)))
}
catch (e) {
console.error('解码user参数失败:', e)
}
}
})
function goBack() {
router.back()
}
function pushToGuard() {
router.push('/subPages/router/demo-guard')
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-6 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-8">
📨
</view>
<view class="mb-2 text-5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
query 参数接收演示
</view>
<view class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
演示如何接收和处理查询参数
</view>
</view>
</view>
<!-- 路由信息 -->
<demo-block title="当前路由信息" transparent>
<view class="rounded-3 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="space-y-2">
<view class="flex items-center justify-between border-b border-gray-100 py-2 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
路径:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ route.path }}
</text>
</view>
<view class="flex items-center justify-between border-b border-gray-100 py-2 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
查询字符串:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ JSON.stringify(route.query) }}
</text>
</view>
<view class="flex items-center justify-between py-2">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
接收方式:
</text>
<text class="text-3.5 text-gray-800 dark:text-[var(--wot-dark-color)]">
onLoad(option)
</text>
</view>
</view>
</view>
</demo-block>
<!-- 参数信息 -->
<demo-block title="接收到的查询参数" transparent>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
原始查询参数 (option)
</view>
<view class="border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]">
<text class="text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
{{ JSON.stringify(receivedQuery, null, 2) }}
</text>
</view>
<view class="mt-3 text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
URL: /demo-query?keyword={{ (receivedQuery as any).keyword }}&type={{ (receivedQuery as any).type }}
</view>
</view>
<view v-if="decodedUser" class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
解码后的对象参数
</view>
<view class="border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]">
<text class="text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
{{ JSON.stringify(decodedUser, null, 2) }}
</text>
</view>
<view class="mt-3 text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
使用 JSON.parse(decodeURIComponent(decodeURIComponent(option.user))) 解码
</view>
</view>
</view>
</demo-block>
<!-- Query vs Params 说明 -->
<demo-block title="Query vs Params 说明" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
两种参数传递方式说明
</view>
<view class="mb-3 border border-orange-200 rounded-2 bg-orange-50 p-3 dark:bg-orange-900/20">
<view class="mb-2 text-3.5 text-orange-700 font-bold dark:text-orange-300">
⚠️ 重要说明
</view>
<view class="text-3 text-orange-600 leading-relaxed dark:text-orange-200">
在 @wot-ui/router 中,query 和 params 参数都会放在 URL 中,两者在实际效果上并无区别。这种 API 设计主要是为了与 vue-router 保持一致。
</view>
</view>
<view class="space-y-3">
<view class="border border-blue-200 rounded-2 bg-blue-50 p-3 dark:bg-blue-900/20">
<view class="mb-2 text-3.5 text-blue-700 font-bold dark:text-blue-300">
Query 写法 (当前演示)
</view>
<view class="text-3 text-blue-600 leading-relaxed dark:text-blue-200">
• 使用 path + query 组合\n
• 参数以查询字符串形式出现\n
• 写法: router.push({ path: '/page', query: { key: 'value' } })\n
• 结果: /page?key=value
</view>
</view>
<view class="border border-green-200 rounded-2 bg-green-50 p-3 dark:bg-green-900/20">
<view class="mb-2 text-3.5 text-green-700 font-bold dark:text-green-300">
Params 写法
</view>
<view class="text-3 text-green-600 leading-relaxed dark:text-green-200">
• 使用 name + params 组合\n
• 参数同样以查询字符串形式出现\n
• 写法: router.push({ name: 'page', params: { key: 'value' } })\n
• 结果: /page?key=value (效果相同)
</view>
</view>
</view>
</view>
</demo-block>
<!-- 代码示例 -->
<demo-block title="代码示例" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
接收查询参数的标准写法
</view>
<view class="border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]">
<text class="text-3 text-gray-700 leading-relaxed font-mono dark:text-[var(--wot-dark-color)]">
// 发送方
router.push({
path: '/demo-query',
query: { keyword: 'vue', type: 'framework' }
})
// 接收方
onLoad((option) => {
if (option && option.keyword) {
const keyword = option.keyword // 'vue'
const type = option.type // 'framework'
}
})
</text>
</view>
</view>
</demo-block>
<!-- 操作按钮 -->
<demo-block title="继续演示" transparent>
<view class="space-y-3">
<wd-button type="primary" block @click="pushToGuard">
跳转到导航守卫演示
</wd-button>
<wd-button type="warning" block @click="goBack">
返回上一页
</wd-button>
</view>
</demo-block>
</view>
</template>
@@ -0,0 +1,85 @@
<script setup lang="ts">
definePage({
name: 'demo-string',
style: {
navigationBarTitleText: '字符串路径跳转',
},
})
const router = useRouter()
const route = useRoute()
function goBack() {
router.back()
}
function pushToObject() {
router.push({ path: '/subPages/router/demo-object' })
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-6 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-8">
📝
</view>
<view class="mb-2 text-5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
字符串路径跳转演示
</view>
<view class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
使用 router.push('/path') 进行跳转
</view>
</view>
</view>
<!-- 跳转信息 -->
<demo-block title="跳转信息" transparent>
<view class="rounded-3 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
当前页面信息
</view>
<view class="space-y-2">
<view class="flex items-center justify-between border-b border-gray-100 py-2 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
路径:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ route.path }}
</text>
</view>
<view class="flex items-center justify-between border-b border-gray-100 py-2 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
跳转方式:
</text>
<text class="text-3.5 text-gray-800 dark:text-[var(--wot-dark-color)]">
字符串路径
</text>
</view>
<view class="flex items-center justify-between py-2">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
代码:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
router.push('/subPages/router/demo-string')
</text>
</view>
</view>
</view>
</demo-block>
<!-- 操作按钮 -->
<demo-block title="继续演示" transparent>
<view class="space-y-3">
<wd-button type="primary" block @click="pushToObject">
跳转到对象路径演示
</wd-button>
<wd-button type="warning" block @click="goBack">
返回上一页
</wd-button>
</view>
</demo-block>
</view>
</template>
+469
View File
@@ -0,0 +1,469 @@
<script setup lang="ts">
definePage({
name: 'router',
style: {
navigationBarTitleText: '路由管理',
},
})
const { success: showSuccess } = useGlobalToast()
const router = useRouter()
const route = useRoute()
// 表单数据
const userId = ref('eduardo')
const searchKeyword = ref('vue')
const userName = ref('小星星')
const userLabel = ref('小熊熊')
// 基础导航方法
function pushByString() {
// 字符串路径
router.push('/subPages/router/demo-string')
showSuccess({ msg: '使用字符串路径跳转' })
}
function pushByPath() {
// 带有路径的对象
router.push({ path: '/subPages/router/demo-object' })
showSuccess({ msg: '使用path对象跳转' })
}
function pushByName() {
// 命名的路由
router.push({ name: 'demo-object' })
showSuccess({ msg: '使用name跳转' })
}
// 参数传递示例
function pushWithParams() {
if (!userId.value) {
uni.showToast({
title: '请输入用户ID',
icon: 'none',
})
return
}
// 命名的路由,并加上参数
router.push({ name: 'demo-params', params: { username: userId.value } })
showSuccess({ msg: `传递参数: ${userId.value}` })
}
function pushWithQuery() {
if (!searchKeyword.value) {
uni.showToast({
title: '请输入搜索关键词',
icon: 'none',
})
return
}
// 带查询参数
router.push({ path: '/subPages/router/demo-query', query: { username: searchKeyword.value } })
showSuccess({ msg: `传递查询参数: ${searchKeyword.value}` })
}
// 传递对象参数
function pushWithObjectParams() {
const user = {
name: userName.value,
label: userLabel.value,
}
// 命名路由传递对象参数
router.push({
name: 'demo-params',
params: { user: encodeURIComponent(JSON.stringify(user)) },
})
showSuccess({ msg: '传递对象参数(params)' })
}
function pushWithObjectQuery() {
const user = {
name: userName.value,
label: userLabel.value,
}
// path+query传递对象参数
router.push({
path: '/subPages/router/demo-query',
query: { user: encodeURIComponent(JSON.stringify(user)) },
})
showSuccess({ msg: '传递对象参数(query)' })
}
// 导航守卫演示
function demoNavigationGuards() {
showSuccess({ msg: '跳转到完整的导航守卫演示页面' })
router.push({
name: 'demo-guard',
})
}
// Router方法演示
function demoPush() {
router.push('/subPages/router/demo-string')
showSuccess({ msg: 'router.push() - 保留当前页面跳转' })
}
function demoPushTab() {
router.pushTab('/pages/index/index')
showSuccess({ msg: 'router.pushTab() - 跳转到tabBar页面' })
}
function demoReplace() {
router.replace('/subPages/router/demo-object')
showSuccess({ msg: 'router.replace() - 替换当前页面' })
}
function demoReplaceAll() {
router.replaceAll('/pages/index/index')
showSuccess({ msg: 'router.replaceAll() - 关闭所有页面重新开始' })
}
function demoBack() {
router.back()
showSuccess({ msg: 'router.back() - 返回上一页' })
}
// 复制代码到剪贴板
function copyCode(code: string) {
uni.setClipboardData({
data: code,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: '代码已复制到剪贴板' })
},
})
}
// 链接导航处理
function handleNavigate(url: string) {
// #ifdef H5
window.open(url, '_blank')
// #endif
// #ifndef H5
uni.setClipboardData({
data: url,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: `${url} 已复制到剪贴板` })
},
})
// #endif
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部介绍 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-8 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-10">
🚀
</view>
<view class="mb-2 text-6 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
@wot-ui/router
</view>
<view class="mb-2 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
轻量级路由管理解决方案
</view>
<view class="text-3 text-gray-500 dark:text-[var(--wot-dark-color2)]">
支持编程式导航、参数传递、导航守卫等功能
</view>
</view>
</view>
<!-- 当前路由信息 -->
<demo-block title="当前路由信息" transparent>
<view class="rounded-3 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="flex items-center justify-between border-b border-gray-100 py-2 last:border-b-0 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
路径:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ route.path }}
</text>
</view>
<view class="flex items-center justify-between border-b border-gray-100 py-2 last:border-b-0 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
名称:
</text>
<text class="text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ route.name || '未设置' }}
</text>
</view>
<view class="flex items-center justify-between border-b border-gray-100 py-2 last:border-b-0 dark:border-[var(--wot-dark-border)]">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
参数:
</text>
<text class="break-all text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ JSON.stringify(route.params) }}
</text>
</view>
<view class="flex items-center justify-between py-2">
<text class="text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
查询:
</text>
<text class="break-all text-3.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ JSON.stringify(route.query) }}
</text>
</view>
</view>
</demo-block>
<!-- 编程式导航 -->
<demo-block title="编程式导航" transparent>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
基础用法
</view>
<view class="mb-3 flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.push(\'/user\')')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.push('/user')
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<view class="grid grid-cols-1 gap-2">
<wd-button type="primary" size="small" @click="pushByString">
字符串路径跳转
</wd-button>
<wd-button type="success" size="small" @click="pushByPath">
对象路径跳转
</wd-button>
<wd-button type="warning" size="small" @click="pushByName">
命名路由跳转
</wd-button>
</view>
</view>
</view>
</demo-block>
<!-- Router方法演示 -->
<demo-block title="Router方法演示" transparent>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
push 方法
</view>
<view class="mb-3 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
保留当前页面,跳转到应用内的某个页面,相当于 uni.navigateTo()
</view>
<view class="mb-3 flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.push(target)')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.push(target)
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<wd-button type="primary" block @click="demoPush">
演示 push 方法
</wd-button>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
pushTab 方法
</view>
<view class="mb-3 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面,相当于 uni.switchTab()
</view>
<view class="mb-3 flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.pushTab(target)')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.pushTab(target)
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<wd-button type="success" block @click="demoPushTab">
演示 pushTab 方法
</wd-button>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
replace 方法
</view>
<view class="mb-3 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
关闭当前页面,跳转到应用内的某个页面,相当于 uni.redirectTo()
</view>
<view class="mb-3 flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.replace(target)')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.replace(target)
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<wd-button type="warning" block @click="demoReplace">
演示 replace 方法
</wd-button>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
replaceAll 方法
</view>
<view class="mb-3 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
关闭所有页面,打开到应用内的某个页面,相当于 uni.reLaunch()
</view>
<view class="mb-3 flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.replaceAll(target)')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.replaceAll(target)
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<wd-button type="error" block @click="demoReplaceAll">
演示 replaceAll 方法
</wd-button>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
back 方法
</view>
<view class="mb-3 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
关闭当前页面,返回上一页面或多级页面,相当于 uni.navigateBack()
</view>
<view class="mb-3 space-y-2">
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.back()')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.back()
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.back({ delta: 2 })')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.back({ delta: 2 })
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
</view>
<wd-button type="info" block @click="demoBack">
返回上一页
</wd-button>
</view>
</view>
</demo-block>
<!-- 参数传递 -->
<demo-block title="参数传递" transparent>
<view class="mb-3 border border-orange-200 rounded-2 bg-orange-50 p-3 dark:bg-orange-900/20">
<view class="mb-2 text-3.5 text-orange-700 font-bold dark:text-orange-300">
⚠️ 重要提示
</view>
<view class="text-3 text-orange-600 leading-relaxed dark:text-orange-200">
在 @wot-ui/router 中,params 和 query 参数都会以查询字符串形式放在 URL 中,两者在实际效果上并无区别。这种 API 设计主要是为了与 vue-router 保持一致。
</view>
</view>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
params 参数
</view>
<view class="mb-3 text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
注意:name 和 params 搭配使用,与 query 效果相同
</view>
<view class="mb-3">
<wd-input v-model="userId" placeholder="请输入用户名" />
</view>
<view class="mb-3 flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.push({ name: \'user\', params: { username: \'eduardo\' } })')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.push({ name: 'user', params: { username: 'eduardo' } })
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<wd-button type="primary" block @click="pushWithParams">
传递 params 参数
</wd-button>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
query 参数
</view>
<view class="mb-3 text-3.5 text-gray-600 dark:text-[var(--wot-dark-color2)]">
注意:path 可以与 query 一起使用,与 params 效果相同
</view>
<view class="mb-3">
<wd-input v-model="searchKeyword" placeholder="请输入搜索关键词" />
</view>
<view class="mb-3 flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.push({ path: \'/user\', query: { username: \'eduardo\' } })')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.push({ path: '/user', query: { username: 'eduardo' } })
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<wd-button type="success" block @click="pushWithQuery">
传递 query 参数
</wd-button>
</view>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
传递对象参数
</view>
<view class="mb-3 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
URL有长度限制,复杂对象需要使用 encodeURIComponent 编码
</view>
<view class="grid grid-cols-2 mb-3 gap-2">
<wd-input v-model="userName" placeholder="姓名" />
<wd-input v-model="userLabel" placeholder="标签" />
</view>
<view class="grid grid-cols-2 gap-2">
<wd-button type="primary" size="small" @click="pushWithObjectParams">
对象 params
</wd-button>
<wd-button type="success" size="small" @click="pushWithObjectQuery">
对象 query
</wd-button>
</view>
</view>
</view>
</demo-block>
<!-- 导航守卫 -->
<demo-block title="导航守卫" transparent>
<view class="mb-3 border border-blue-200 rounded-2 bg-blue-50 p-3 dark:bg-blue-900/20">
<view class="mb-2 text-3.5 text-blue-700 font-bold dark:text-blue-300">
💡 完整演示
</view>
<view class="text-3 text-blue-600 leading-relaxed dark:text-blue-200">
导航守卫功能比较复杂,我们准备了专门的演示页面,包含实时日志、权限验证、参数检查等完整功能演示。
</view>
</view>
<view class="space-y-3">
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
基础守卫 API
</view>
<view class="mb-3 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
beforeEach 和 afterEach 是最常用的导航守卫
</view>
<view class="mb-3 space-y-2">
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.beforeEach((to, from, next) => { next() })')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.beforeEach((to, from, next) => { next() })
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
<view class="flex items-center justify-between border border-gray-200 rounded-2 bg-gray-50 p-3 dark:border-[var(--wot-dark-border)] dark:bg-[var(--wot-dark-background3)]" @click="copyCode('router.afterEach((to, from) => { })')">
<text class="flex-1 text-3 text-gray-700 font-mono dark:text-[var(--wot-dark-color)]">
router.afterEach((to, from) => { console.log(to) })
</text>
<wd-icon name="copy" size="16px" color="#666" />
</view>
</view>
<wd-button type="primary" block @click="demoNavigationGuards">
🛡️ 进入完整守卫演示
</wd-button>
</view>
</view>
</demo-block>
<!-- 相关链接 -->
<demo-block title="相关链接" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell title="📚 @wot-ui/router 文档" value="路由管理" is-link @click="handleNavigate('https://moonofweisheng.github.io/@wot-ui/router/')" />
<wd-cell title="🐙 GitHub 仓库" value="@wot-ui/router" is-link @click="handleNavigate('https://my-uni.wot-ui.cn/')" />
<wd-cell title="📖 uni-app 路由文档" value="页面路由" is-link @click="handleNavigate('https://uniapp.dcloud.net.cn/tutorial/page.html')" />
</wd-cell-group>
</demo-block>
</view>
</template>
@@ -0,0 +1,210 @@
<script lang="ts" setup>
definePage({
name: 'settings',
style: {
navigationBarTitleText: '系统设置',
},
})
// 是否正在清理
const clearing = ref(false)
// 缓存大小
const cacheSize = ref<any>('计算中...')
const {
theme,
toggleTheme,
currentThemeColor,
showThemeColorSheet,
themeColorOptions,
openThemeColorPicker,
closeThemeColorPicker,
selectThemeColor,
setFollowSystem,
} = useManualTheme()
const isDark = computed({
get() {
return theme.value === 'dark'
},
set() {
toggleTheme()
},
})
// 处理主题色选择
function handleThemeColorSelect(option: any) {
selectThemeColor(option)
}
// 获取缓存大小
async function getCacheSize() {
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 = '获取失败'
}
}
// 格式化存储大小
function 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`
}
}
// 处理清除缓存
async function handleClearCache() {
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 {
uni.showToast({
title: '清理失败',
icon: 'error',
})
}
finally {
clearing.value = false
}
}
// 页面加载时初始化
onLoad(() => {
getCacheSize()
})
</script>
<template>
<view class="app-container">
<demo-block title="基础设置" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell title="暗黑模式">
<wd-switch v-model="isDark" size="18px" />
</wd-cell>
<wd-cell title="跟随系统">
<wd-button size="small" @click="setFollowSystem">
跟随系统
</wd-button>
</wd-cell>
<wd-cell title="选择主题色" is-link @click="openThemeColorPicker">
<view class="flex items-center justify-end gap-2">
<view
class="h-4 w-4 rounded-full"
:style="{ backgroundColor: currentThemeColor.primary }"
/>
<text>{{ currentThemeColor.name }}</text>
</view>
</wd-cell>
<wd-divider />
<wd-cell
title="清空缓存"
icon="delete1"
:value="cacheSize"
clickable
@click="handleClearCache"
/>
</wd-cell-group>
</demo-block>
<!-- 主题色选择 ActionSheet -->
<wd-action-sheet
v-model="showThemeColorSheet"
title="选择主题色"
:close-on-click-action="true"
@cancel="closeThemeColorPicker"
>
<view class="px-4 pb-4">
<view
v-for="option in themeColorOptions"
:key="option.value"
class="flex items-center justify-between border-b border-gray-100 py-3 last:border-b-0 dark:border-gray-700"
@click="handleThemeColorSelect(option)"
>
<view class="flex items-center gap-3">
<view
class="h-6 w-6 border-2 border-gray-200 rounded-full dark:border-gray-600"
:style="{ backgroundColor: option.primary }"
/>
<text class="text-4 text-gray-800 dark:text-gray-200">
{{ option.name }}
</text>
</view>
<wd-icon
v-if="currentThemeColor.value === option.value"
name="check"
:color="option.primary"
size="20px"
/>
</view>
</view>
<wd-gap :height="50" />
</wd-action-sheet>
<!-- 使用wot-design-uni的Loading组件 -->
<wd-loading
v-if="clearing"
v-model="clearing"
text="正在清理..."
mask
custom-class="loading-center"
/>
</view>
</template>
<style lang="scss">
</style>
+158
View File
@@ -0,0 +1,158 @@
<script setup lang="ts">
import { useToast } from 'wot-design-uni'
definePage({
name: 'skills',
layout: 'default',
style: {
navigationBarTitleText: 'Agent Skills',
navigationBarBackgroundColor: '#f9fafb',
navigationBarTextStyle: 'black',
},
})
const toast = useToast()
const skills = [
{
name: 'alova-api-module',
desc: '快速创建 Alova 请求模块和 Mock 数据',
icon: 'swap',
color: '#007AFF',
},
{
name: 'global-feedback',
desc: '全局反馈组件(Toast/Message/Loading)使用指南',
icon: 'chat',
color: '#34C759',
},
{
name: 'pinia-store-generator',
desc: '创建符合项目规范的 Pinia Store',
icon: 'folder',
color: '#FFCC00',
},
{
name: 'uni-page-generator',
desc: '基于项目规范快速生成 uni-app 页面',
icon: 'file',
color: '#FF9500',
},
{
name: 'vue-composable-creator',
desc: '快速创建 Vue 3 组合式函数',
icon: 'setting',
color: '#5856D6',
},
{
name: 'wot-router-usage',
desc: '@wot-ui/router 轻量级路由库使用指南',
icon: 'location',
color: '#FF2D55',
},
]
function handleClick(skill: string) {
toast.success(`已选择: ${skill}`)
}
function getIconBg(color: string) {
// Simple hex to rgba with low opacity
const r = Number.parseInt(color.slice(1, 3), 16)
const g = Number.parseInt(color.slice(3, 5), 16)
const b = Number.parseInt(color.slice(5, 7), 16)
return `rgba(${r}, ${g}, ${b}, 0.1)`
}
</script>
<template>
<view class="pb-safe box-border min-h-screen bg-[#f9fafb]">
<!-- Header -->
<view class="px-6 pb-6 pt-8">
<view class="mb-2 text-3xl text-gray-900 font-bold leading-tight">
Agent Skills
</view>
<view class="text-base text-gray-500 leading-relaxed">
利用智能工具赋能开发效率,<br>打造极致开发体验。
</view>
</view>
<!-- Content -->
<view class="px-5 space-y-4">
<view
v-for="(skill, index) in skills"
:key="skill.name"
class="group relative animate-fade-in-up overflow-hidden rounded-[24px] bg-white p-5 shadow-sm transition-all duration-300 active:scale-[0.98]"
:style="{ animationDelay: `${index * 100}ms` }"
@click="handleClick(skill.name)"
>
<view class="flex items-start gap-4">
<!-- Icon Container -->
<view
class="h-14 w-14 flex shrink-0 items-center justify-center rounded-[18px] transition-transform duration-300 group-active:scale-110"
:style="{ backgroundColor: getIconBg(skill.color) }"
>
<wd-icon
:name="skill.icon"
size="28px"
:color="skill.color"
custom-style="display: block;"
/>
</view>
<!-- Text Content -->
<view class="min-w-0 flex-1 pt-0.5">
<view class="flex items-center justify-between">
<view class="truncate pr-2 text-lg text-gray-900 font-bold">
{{ skill.name }}
</view>
<wd-icon name="arrow-right" color="#E5E7EB" size="20px" />
</view>
<view class="line-clamp-2 mt-1 text-sm text-gray-500 leading-relaxed">
{{ skill.desc }}
</view>
</view>
</view>
<!-- Decoration (optional glow or highlight) -->
<view
class="pointer-events-none absolute h-24 w-24 rounded-full opacity-[0.03] -bottom-4 -right-4"
:style="{ backgroundColor: skill.color }"
/>
</view>
</view>
<wd-toast />
</view>
</template>
<style scoped>
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
opacity: 0; /* meaningful for delay */
animation: fadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
/* Utilities */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.pb-safe {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
</style>
+493
View File
@@ -0,0 +1,493 @@
<script setup lang="ts">
definePage({
name: 'styles',
style: {
navigationBarTitleText: 'UnoCSS 演示',
},
})
const { success: showSuccess } = useGlobalToast()
// 演示状态
const animationState = ref(false)
// UnoCSS 核心特性
const unocssFeatures = ref([
{
icon: '⚡',
title: '即时按需',
desc: '只生成你使用的CSS',
example: 'text-red-500 bg-blue-100',
},
{
icon: '🎨',
title: '完全可定制',
desc: '灵活的配置和预设',
example: 'custom-color-brand',
},
{
icon: '🔧',
title: '预设丰富',
desc: '内置Tailwind、Windi等预设',
example: '@unocss/preset-wind',
},
{
icon: '🚀',
title: '性能优异',
desc: '极快的构建速度',
example: '< 200ms',
},
])
// 颜色系统演示
const colorCategories = ref([
{
name: '灰色系',
colors: [
{ name: 'gray-50', class: 'bg-gray-50', value: '#f9fafb' },
{ name: 'gray-100', class: 'bg-gray-100', value: '#f3f4f6' },
{ name: 'gray-200', class: 'bg-gray-200', value: '#e5e7eb' },
{ name: 'gray-300', class: 'bg-gray-300', value: '#d1d5db' },
{ name: 'gray-400', class: 'bg-gray-400', value: '#9ca3af' },
{ name: 'gray-500', class: 'bg-gray-500', value: '#6b7280' },
],
},
{
name: '主题色',
colors: [
{ name: 'blue-500', class: 'bg-blue-500', value: '#3b82f6' },
{ name: 'green-500', class: 'bg-green-500', value: '#10b981' },
{ name: 'red-500', class: 'bg-red-500', value: '#ef4444' },
{ name: 'yellow-500', class: 'bg-yellow-500', value: '#f59e0b' },
{ name: 'purple-500', class: 'bg-purple-500', value: '#8b5cf6' },
{ name: 'pink-500', class: 'bg-pink-500', value: '#ec4899' },
],
},
])
// 布局演示数据
const layoutExamples = ref([
{
title: 'Flex 居中',
code: 'flex items-center justify-center',
class: 'flex items-center justify-center h-20 bg-blue-100 rounded-2 text-blue-800',
},
{
title: 'Grid 网格',
code: 'grid grid-cols-3 gap-4',
class: 'grid grid-cols-3 gap-2',
},
{
title: 'Space Between',
code: 'flex justify-between items-center',
class: 'flex justify-between items-center h-16 bg-green-100 rounded-2 px-4 text-green-800',
},
])
// 间距系统
const spacingExamples = ref([
{ name: 'p-1', value: '4px', class: 'p-1 bg-red-100 inline-block m-1 rounded' },
{ name: 'p-2', value: '8px', class: 'p-2 bg-blue-100 inline-block m-1 rounded' },
{ name: 'p-4', value: '16px', class: 'p-4 bg-green-100 inline-block m-1 rounded' },
{ name: 'p-6', value: '24px', class: 'p-6 bg-yellow-100 inline-block m-1 rounded' },
{ name: 'p-8', value: '32px', class: 'p-8 bg-purple-100 inline-block m-1 rounded' },
])
// 字体系统
const fontExamples = ref([
{ name: 'text-xs', size: '12px', class: 'text-xs' },
{ name: 'text-sm', size: '14px', class: 'text-sm' },
{ name: 'text-base', size: '16px', class: 'text-base' },
{ name: 'text-lg', size: '18px', class: 'text-lg' },
{ name: 'text-xl', size: '20px', class: 'text-xl' },
{ name: 'text-2xl', size: '24px', class: 'text-2xl' },
{ name: 'text-3xl', size: '30px', class: 'text-3xl' },
])
// 阴影和效果
const effectExamples = ref([
{ name: 'shadow-sm', class: 'shadow-sm bg-white p-4 rounded-2 mb-3' },
{ name: 'shadow', class: 'shadow bg-white p-4 rounded-2 mb-3' },
{ name: 'shadow-md', class: 'shadow-md bg-white p-4 rounded-2 mb-3' },
{ name: 'shadow-lg', class: 'shadow-lg bg-white p-4 rounded-2 mb-3' },
{ name: 'shadow-xl', class: 'shadow-xl bg-white p-4 rounded-2 mb-3' },
])
// 切换动画
function toggleAnimation() {
animationState.value = !animationState.value
showSuccess({ msg: `动画状态: ${animationState.value ? '开启' : '关闭'}` })
}
// 复制代码
function copyCode(code: string) {
uni.setClipboardData({
data: code,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: '代码已复制到剪贴板' })
},
})
}
// 链接导航处理
function handleNavigate(url: string) {
// #ifdef H5
window.open(url, '_blank')
// #endif
// #ifndef H5
uni.setClipboardData({
data: url,
showToast: false,
success: () => {
uni.hideToast()
showSuccess({ msg: `${url} 已复制到剪贴板` })
},
})
// #endif
}
</script>
<template>
<view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
<!-- 头部介绍 -->
<view class="mx-3 mb-3">
<view class="rounded-3 bg-white px-5 py-8 text-center dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-10">
🎨
</view>
<view class="mb-2 text-6 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
UnoCSS 原子化CSS
</view>
<view class="mb-2 text-3.5 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
即时按需的原子化CSS引擎
</view>
<view class="text-3 text-gray-500 dark:text-[var(--wot-dark-color2)]">
具有高性能且极具灵活性的即时原子化CSS引擎
</view>
</view>
</view>
<!-- UnoCSS 核心特性 -->
<demo-block title="UnoCSS 核心特性" transparent>
<view class="grid grid-cols-2 gap-3">
<view
v-for="feature in unocssFeatures"
:key="feature.title"
class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]"
>
<view class="mb-2 text-6">
{{ feature.icon }}
</view>
<view class="mb-1 text-3.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ feature.title }}
</view>
<view class="mb-2 text-3 text-gray-600 leading-snug dark:text-[var(--wot-dark-color2)]">
{{ feature.desc }}
</view>
<view class="rounded bg-gray-100 px-2 py-1 text-2.5 text-gray-700 font-mono dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color2)]">
{{ feature.example }}
</view>
</view>
</view>
</demo-block>
<!-- 颜色系统 -->
<demo-block title="颜色系统" transparent>
<view
v-for="category in colorCategories"
:key="category.name"
class="mb-4 last:mb-0"
>
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ category.name }}
</view>
<view class="grid grid-cols-3 gap-2">
<view
v-for="color in category.colors"
:key="color.name"
class="overflow-hidden rounded-2 bg-white shadow-sm dark:bg-[var(--wot-dark-background2)]"
@click="copyCode(color.class)"
>
<view :class="color.class" class="h-12 w-full" />
<view class="p-2">
<view class="text-2.5 text-gray-800 font-mono dark:text-[var(--wot-dark-color)]">
{{ color.name }}
</view>
<view class="text-2 text-gray-500 dark:text-[var(--wot-dark-color2)]">
{{ color.value }}
</view>
</view>
</view>
</view>
</view>
</demo-block>
<!-- 布局系统 -->
<demo-block title="布局系统" transparent>
<view class="space-y-4">
<view
v-for="example in layoutExamples"
:key="example.title"
class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]"
>
<view class="mb-3 flex items-center justify-between">
<view class="text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
{{ example.title }}
</view>
<view class="rounded bg-gray-100 px-2 py-1 text-2.5 text-gray-700 font-mono dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color2)]" @click="copyCode(example.code)">
{{ example.code }}
</view>
</view>
<view v-if="example.title === 'Flex 居中'" :class="example.class">
<text class="text-3.5">
居中内容
</text>
</view>
<view v-else-if="example.title === 'Grid 网格'" :class="example.class">
<view
v-for="n in 6"
:key="n"
class="rounded bg-blue-100 p-2 text-center text-3 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300"
>
{{ n }}
</view>
</view>
<view v-else :class="example.class">
<text class="text-3.5">
左侧
</text>
<text class="text-3.5">
右侧
</text>
</view>
</view>
</view>
</demo-block>
<!-- 间距系统 -->
<demo-block title="间距系统" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
内边距 (Padding)
</view>
<view class="mb-4 text-3 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
点击下方色块复制对应的 padding 类名
</view>
<view class="flex flex-wrap items-center gap-2">
<view
v-for="spacing in spacingExamples"
:key="spacing.name"
:class="spacing.class"
class="cursor-pointer"
@click="copyCode(spacing.name)"
>
<text class="text-2.5 text-gray-700 font-mono">
{{ spacing.name }}
</text>
</view>
</view>
<view class="mt-4 text-2.5 text-gray-500 dark:text-[var(--wot-dark-color2)]">
💡 提示:p-1 = 4px, p-2 = 8px, p-4 = 16px, p-6 = 24px, p-8 = 32px
</view>
</view>
</demo-block>
<!-- 字体系统 -->
<demo-block title="字体系统" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
字体大小
</view>
<view class="space-y-3">
<view
v-for="font in fontExamples"
:key="font.name"
class="flex items-center justify-between border-b border-gray-100 pb-2 last:border-b-0 dark:border-[var(--wot-dark-border)]"
@click="copyCode(font.name)"
>
<text :class="font.class" class="text-gray-800 dark:text-[var(--wot-dark-color)]">
字体演示文字
</text>
<view class="flex items-center space-x-2">
<text class="text-2.5 text-gray-500 dark:text-[var(--wot-dark-color2)]">
{{ font.size }}
</text>
<text class="rounded bg-gray-100 px-2 py-1 text-2.5 text-gray-700 font-mono dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color2)]">
{{ font.name }}
</text>
</view>
</view>
</view>
</view>
</demo-block>
<!-- 阴影效果 -->
<demo-block title="阴影效果" transparent>
<view class="space-y-3">
<view
v-for="effect in effectExamples"
:key="effect.name"
:class="effect.class"
@click="copyCode(effect.name)"
>
<view class="flex items-center justify-between">
<text class="text-3.5 text-gray-800">
阴影效果演示
</text>
<text class="rounded bg-gray-100 px-2 py-1 text-2.5 text-gray-700 font-mono">
{{ effect.name }}
</text>
</view>
</view>
</view>
</demo-block>
<!-- 交互效果 -->
<demo-block title="交互效果" transparent>
<view class="space-y-4">
<!-- 悬停效果 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
悬停效果
</view>
<view class="grid grid-cols-2 gap-3">
<view class="rounded-2 bg-blue-100 p-4 text-center transition-all duration-300 active:scale-95 hover:scale-105 hover:bg-blue-200">
<text class="text-3 text-blue-800">
悬停变色
</text>
</view>
<view class="rounded-2 bg-green-100 p-4 text-center transition-all duration-300 hover:shadow-lg hover:-translate-y-1">
<text class="text-3 text-green-800">
悬停上浮
</text>
</view>
</view>
<view class="mt-3 rounded bg-gray-100 p-2 text-2.5 text-gray-700 font-mono dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color2)]" @click="copyCode('hover:bg-blue-200 hover:scale-105 transition-all duration-300')">
hover:bg-blue-200 hover:scale-105 transition-all duration-300
</view>
</view>
<!-- 动画效果 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
动画效果
</view>
<wd-button type="primary" block @click="toggleAnimation">
切换动画状态
</wd-button>
<view class="mt-4 flex justify-center">
<view
class="rounded-2 p-6 text-center transition-all duration-500 ease-in-out"
:class="[
animationState
? 'bg-gradient-to-r from-purple-500 to-pink-500 text-white transform rotate-12 scale-110 shadow-lg'
: 'bg-gray-100 text-gray-800 transform rotate-0 scale-100 shadow-sm dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color)]',
]"
>
<text class="text-4 font-bold">
动画演示
</text>
</view>
</view>
<view class="mt-3 rounded bg-gray-100 p-2 text-2.5 text-gray-700 leading-relaxed font-mono dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color2)]" @click="copyCode('transition-all duration-500 ease-in-out transform rotate-12 scale-110')">
transition-all duration-500 ease-in-out\n
transform rotate-12 scale-110
</view>
</view>
</view>
</demo-block>
<!-- 响应式设计 -->
<demo-block title="响应式设计" transparent>
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
响应式断点
</view>
<view class="mb-4 text-3 text-gray-600 leading-relaxed dark:text-[var(--wot-dark-color2)]">
UnoCSS 支持响应式断点,可以根据屏幕尺寸应用不同样式
</view>
<view class="mb-4 rounded-2 bg-red-100 p-4 text-center dark:bg-red-900/30 lg:bg-yellow-100 md:bg-blue-100 sm:bg-green-100 dark:lg:bg-yellow-900/30 dark:md:bg-blue-900/30 dark:sm:bg-green-900/30">
<text class="text-3.5 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
响应式背景色
</text>
</view>
<view class="grid grid-cols-1 gap-3 lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2">
<view
v-for="n in 8"
:key="n"
class="rounded bg-purple-100 p-3 text-center text-3 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300"
>
项目 {{ n }}
</view>
</view>
<view class="mt-4 rounded bg-gray-100 p-2 text-2.5 text-gray-700 leading-relaxed font-mono dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color2)]" @click="copyCode('grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4')">
grid-cols-1 sm:grid-cols-2\n
md:grid-cols-3 lg:grid-cols-4
</view>
</view>
</demo-block>
<!-- 实用工具类 -->
<demo-block title="实用工具类" transparent>
<view class="space-y-4">
<!-- 显示隐藏 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
显示/隐藏
</view>
<view class="mb-3 flex flex-wrap gap-2">
<view class="rounded bg-green-100 px-3 py-2 text-3 text-green-800">
显示 (block)
</view>
<view class="rounded bg-blue-100 px-3 py-2 text-3 text-blue-800">
内联 (inline)
</view>
<view class="rounded bg-purple-100 px-3 py-2 text-3 text-purple-800">
弹性 (flex)
</view>
</view>
<view class="rounded bg-gray-100 p-2 text-2.5 text-gray-700 font-mono dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color2)]" @click="copyCode('block inline flex hidden')">
block inline flex hidden
</view>
</view>
<!-- 定位 -->
<view class="rounded-2 bg-white p-4 dark:bg-[var(--wot-dark-background2)]">
<view class="mb-3 text-4 text-gray-800 font-bold dark:text-[var(--wot-dark-color)]">
定位系统
</view>
<view class="relative h-24 rounded-2 bg-gray-100 dark:bg-[var(--wot-dark-background3)]">
<view class="absolute left-2 top-2 rounded bg-red-500 px-2 py-1 text-2.5 text-white">
top-2 left-2
</view>
<view class="absolute right-2 top-2 rounded bg-blue-500 px-2 py-1 text-2.5 text-white">
top-2 right-2
</view>
<view class="absolute bottom-2 left-2 rounded bg-green-500 px-2 py-1 text-2.5 text-white">
bottom-2 left-2
</view>
<view class="absolute bottom-2 right-2 rounded bg-purple-500 px-2 py-1 text-2.5 text-white">
bottom-2 right-2
</view>
</view>
<view class="mt-3 rounded bg-gray-100 p-2 text-2.5 text-gray-700 font-mono dark:bg-[var(--wot-dark-background3)] dark:text-[var(--wot-dark-color2)]" @click="copyCode('absolute relative top-2 left-2 right-2 bottom-2')">
absolute relative top-2 left-2 right-2 bottom-2
</view>
</view>
</view>
</demo-block>
<!-- 相关链接 -->
<demo-block title="相关链接" transparent>
<wd-cell-group border custom-class="rounded-2! overflow-hidden">
<wd-cell title="📚 UnoCSS 官方文档" value="unocss.dev" is-link @click="handleNavigate('https://unocss.dev/')" />
<wd-cell title="🐙 GitHub 仓库" value="unocss/unocss" is-link @click="handleNavigate('https://github.com/unocss/unocss')" />
<wd-cell title="🎮 在线演练场" value="在线体验" is-link @click="handleNavigate('https://unocss.dev/play/')" />
<wd-cell title="📖 Tailwind CSS" value="参考文档" is-link @click="handleNavigate('https://tailwindcss.com/docs')" />
<wd-cell title="💡 最佳实践" value="使用指南" is-link @click="handleNavigate('https://unocss.dev/guide/')" />
</wd-cell-group>
</demo-block>
</view>
</template>
@@ -0,0 +1,31 @@
<script setup lang="ts">
definePage({
name: 'root',
style: {
navigationBarTitleText: 'uni-ku/root',
},
})
const show = ref<boolean>(false)
</script>
<template>
<page-meta :page-style="`overflow:${show ? 'hidden' : 'visible'};`" />
<view class="min-h-200vh">
<demo-block title="锁定滚动" transparent>
<wd-cell-group border>
<wd-cell title="锁定滚动" is-link @click="show = !show" />
</wd-cell-group>
</demo-block>
<wd-popup
v-model="show"
lock-scroll
position="bottom"
closable
:safe-area-inset-bottom="true"
custom-style="height: 200px;"
@close="show = false"
/>
</view>
</template>