commit 4c6243a27480671148554f858707ea108b7c973a Author: tanxin Date: Thu Oct 29 10:08:39 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..296a331 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +unpackage/ +.hbuilderx/ +.env +.env.* +*.keystore +*.jks +*.p12 diff --git a/App.uvue b/App.uvue new file mode 100644 index 0000000..0aeb3ec --- /dev/null +++ b/App.uvue @@ -0,0 +1,51 @@ + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ebc414 --- /dev/null +++ b/README.md @@ -0,0 +1,557 @@ +# 爱学蝶变家长端 + +## 1. 文档用途 + +本文档是本项目的实现契约。阅读本文档后,其他 AI 应能够在不阅读原始页面代码的情况下,使用 uni-app x 重新生成结构、功能、校验、接口调用、状态共享和页面跳转基本一致的家长端应用。 + +实现或重构时必须以本文档为准,并保持以下行为不变: + +- 登录、验证码登录、找回密码和家长注册流程完整可用。 +- 注册流程最多绑定 3 名学生,至少绑定 1 名学生才能继续。 +- 学生信息不能通过 URL 参数传递。 +- Web 与 Android/iOS 的扫码实现必须区分平台。 +- 业务 API 使用统一请求封装,当前默认启用 mock 响应。 +- 表单和页面必须适配手机窄屏及底部安全区。 + +## 2. 技术基线 + +| 项目项 | 当前约定 | +| --- | --- | +| 框架 | uni-app x | +| Vue | Vue 3,` + + diff --git a/components/main-tab-bar.uvue b/components/main-tab-bar.uvue new file mode 100644 index 0000000..d083dd5 --- /dev/null +++ b/components/main-tab-bar.uvue @@ -0,0 +1,64 @@ + + + + + diff --git a/components/qr-scanner.uvue b/components/qr-scanner.uvue new file mode 100644 index 0000000..5d16463 --- /dev/null +++ b/components/qr-scanner.uvue @@ -0,0 +1,149 @@ + + + + + diff --git a/components/study-activity-chart.uvue b/components/study-activity-chart.uvue new file mode 100644 index 0000000..b4178d4 --- /dev/null +++ b/components/study-activity-chart.uvue @@ -0,0 +1,29 @@ + + + + + diff --git a/components/study-trend-chart.uvue b/components/study-trend-chart.uvue new file mode 100644 index 0000000..92fed8d --- /dev/null +++ b/components/study-trend-chart.uvue @@ -0,0 +1,57 @@ + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..8793fef --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..8bdcc86 --- /dev/null +++ b/main.ts @@ -0,0 +1,9 @@ +import App from './App.uvue' + +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..b00b905 --- /dev/null +++ b/manifest.json @@ -0,0 +1,72 @@ +{ + "name": "爱学蝶变家长端", + "appid": "__UNI__800D1E3", + "description": "", + "versionName": "1.0.0", + "versionCode": "100", + "uni-app-x": { + "styleIsolationVersion": "2", + "vapor": true, + "vapor-default-script-lang": "ts", + "vapor-render-target": "bytecode" + }, + "quickapp": {}, + "mp-weixin": { + "appid": "", + "setting": { + "urlCheck": false + }, + "usingComponents": true + }, + "mp-alipay": { + "usingComponents": true + }, + "mp-baidu": { + "usingComponents": true + }, + "mp-toutiao": { + "usingComponents": true + }, + "uniStatistics": { + "enable": false + }, + "vueVersion": "3", + "app": { + "distribute": { + "icons": { + "android": { + "hdpi": "", + "xhdpi": "", + "xxhdpi": "", + "xxxhdpi": "" + } + } + } + }, + "app-android": { + "distribute": { + "modules": {}, + "icons": { + "hdpi": "", + "xhdpi": "", + "xxhdpi": "", + "xxxhdpi": "" + }, + "splashScreens": { + "default": {} + } + } + }, + "app-ios": { + "distribute": { + "modules": {}, + "icons": {}, + "splashScreens": {} + } + }, + "web": { + "router": { + "mode": "" + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5e38367 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,60 @@ +{ + "name": "app.k12.parents", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "app.k12.parents", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@zxing/browser": "^0.2.1" + } + }, + "node_modules/@zxing/browser": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.2.1.tgz", + "integrity": "sha512-92pVfVDUXbc15xu9vIEmhNyqIEASMEkDF92CwT6T+7sg2EHXJRktH9CQKoQSXe51UtbNsj+Fm09H/JBWHMs/Sw==", + "license": "MIT", + "optionalDependencies": { + "@zxing/text-encoding": "^0.9.0" + }, + "peerDependencies": { + "@zxing/library": "^0.23.0" + } + }, + "node_modules/@zxing/library": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.23.0.tgz", + "integrity": "sha512-6fkkoFwP8CHxl6ugnPsj74PLJgX2iRv5zczGAyt5OBzQgxFhuhF0NCEc4t4OvSr8xAv2MRLlI0Iu9ZGDZQ2urA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "ts-custom-error": "^3.3.1" + }, + "engines": { + "node": ">= 24.0.0" + }, + "optionalDependencies": { + "@zxing/text-encoding": "~0.9.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "license": "(Unlicense OR Apache-2.0)", + "optional": true + }, + "node_modules/ts-custom-error": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", + "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9a3cacb --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "app.k12.parents", + "version": "1.0.0", + "description": "家长学情是基于 uni-app x Vapor 模式开发的移动端应用。当前实现家长账号的注册、密码登录、验证码登录和密码重置流程。", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "@zxing/browser": "^0.2.1" + } +} diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..b9b3ddc --- /dev/null +++ b/pages.json @@ -0,0 +1,130 @@ +{ + "easycom": { + "autoscan": true + }, + "pages": [ + { + "path": "pages/login/index", + "style": { + "navigationBarTitleText": "登录", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" + } + }, + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "首页" + } + }, + { + "path": "pages/study/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/vocabulary/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/growth/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/tasks/index", + "style": { + "navigationBarTitleText": "任务" + } + }, + { + "path": "pages/profile/index", + "style": { + "navigationBarTitleText": "我的" + } + }, + { + "path": "pages/profile/verify-student", + "style": { "navigationBarTitleText": "验证学生" } + }, + { + "path": "pages/alerts/index", + "style": { + "navigationBarTitleText": "预警信息" + } + }, + { + "path": "pages/today-learning/index", + "style": { + "navigationStyle": "custom" + } + }, + { + "path": "pages/login/verify-code", + "style": { + "navigationBarTitleText": "验证码登录", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" + } + }, + { + "path": "pages/login/forgot-password", + "style": { + "navigationBarTitleText": "忘记密码", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" + } + }, + { + "path": "pages/register/index", + "style": { + "navigationBarTitleText": "家长注册", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" + } + }, + { + "path": "pages/register/scan-student", + "style": { + "navigationBarTitleText": "家长注册", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" + } + }, + { + "path": "pages/register/input-student", + "style": { + "navigationBarTitleText": "家长注册", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" + } + }, + { + "path": "pages/register/parent-account", + "style": { + "navigationBarTitleText": "家长注册", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" + } + }, + { + "path": "pages/register/complete", + "style": { + "navigationBarTitleText": "注册完成", + "navigationBarBackgroundColor": "#FFFFFF", + "navigationBarTextStyle": "black" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "家长学情", + "navigationBarBackgroundColor": "#FFFFFF", + "backgroundColor": "#F5F5F5" + }, + "uniIdRouter": {} +} diff --git a/pages/alerts/index.uvue b/pages/alerts/index.uvue new file mode 100644 index 0000000..d294c0c --- /dev/null +++ b/pages/alerts/index.uvue @@ -0,0 +1,68 @@ + + + + + diff --git a/pages/growth/index.uvue b/pages/growth/index.uvue new file mode 100644 index 0000000..8fa808f --- /dev/null +++ b/pages/growth/index.uvue @@ -0,0 +1,75 @@ + + + + + diff --git a/pages/index/index.uvue b/pages/index/index.uvue new file mode 100644 index 0000000..c320e5b --- /dev/null +++ b/pages/index/index.uvue @@ -0,0 +1,649 @@ + + + + + diff --git a/pages/login/forgot-password.uvue b/pages/login/forgot-password.uvue new file mode 100644 index 0000000..1396553 --- /dev/null +++ b/pages/login/forgot-password.uvue @@ -0,0 +1,360 @@ + + + + + diff --git a/pages/login/index.uvue b/pages/login/index.uvue new file mode 100644 index 0000000..dc4e8f8 --- /dev/null +++ b/pages/login/index.uvue @@ -0,0 +1,333 @@ + + + + + diff --git a/pages/login/verify-code.uvue b/pages/login/verify-code.uvue new file mode 100644 index 0000000..43fc28e --- /dev/null +++ b/pages/login/verify-code.uvue @@ -0,0 +1,230 @@ + + + + + diff --git a/pages/profile/index.uvue b/pages/profile/index.uvue new file mode 100644 index 0000000..24e0a6a --- /dev/null +++ b/pages/profile/index.uvue @@ -0,0 +1,159 @@ + + + + + diff --git a/pages/profile/verify-student.uvue b/pages/profile/verify-student.uvue new file mode 100644 index 0000000..c09474c --- /dev/null +++ b/pages/profile/verify-student.uvue @@ -0,0 +1,130 @@ + + + + + diff --git a/pages/register/complete.uvue b/pages/register/complete.uvue new file mode 100644 index 0000000..3da125a --- /dev/null +++ b/pages/register/complete.uvue @@ -0,0 +1,322 @@ + + + + + diff --git a/pages/register/index.uvue b/pages/register/index.uvue new file mode 100644 index 0000000..695c9ee --- /dev/null +++ b/pages/register/index.uvue @@ -0,0 +1,14 @@ + + + diff --git a/pages/register/input-student.uvue b/pages/register/input-student.uvue new file mode 100644 index 0000000..5167459 --- /dev/null +++ b/pages/register/input-student.uvue @@ -0,0 +1,546 @@ + + + + + diff --git a/pages/register/parent-account.uvue b/pages/register/parent-account.uvue new file mode 100644 index 0000000..4140214 --- /dev/null +++ b/pages/register/parent-account.uvue @@ -0,0 +1,496 @@ + + + + + diff --git a/pages/register/scan-student.uvue b/pages/register/scan-student.uvue new file mode 100644 index 0000000..8d0dd10 --- /dev/null +++ b/pages/register/scan-student.uvue @@ -0,0 +1,515 @@ + + + + + diff --git a/pages/study/index.uvue b/pages/study/index.uvue new file mode 100644 index 0000000..5ea7f0e --- /dev/null +++ b/pages/study/index.uvue @@ -0,0 +1,155 @@ + + + + + diff --git a/pages/tasks/index.uvue b/pages/tasks/index.uvue new file mode 100644 index 0000000..c7743b7 --- /dev/null +++ b/pages/tasks/index.uvue @@ -0,0 +1,85 @@ + + + + + diff --git a/pages/today-learning/index.uvue b/pages/today-learning/index.uvue new file mode 100644 index 0000000..d4c0815 --- /dev/null +++ b/pages/today-learning/index.uvue @@ -0,0 +1,147 @@ + + + + + diff --git a/pages/vocabulary/index.uvue b/pages/vocabulary/index.uvue new file mode 100644 index 0000000..8908e46 --- /dev/null +++ b/pages/vocabulary/index.uvue @@ -0,0 +1,92 @@ + + + + + diff --git a/platformConfig.json b/platformConfig.json new file mode 100644 index 0000000..896f669 --- /dev/null +++ b/platformConfig.json @@ -0,0 +1,7 @@ + +// 参考链接 https://doc.dcloud.net.cn/uni-app-x/tutorial/ls-plugin.html#setting +{ + "targets": [ + "APP-ANDROID" + ] +} \ No newline at end of file diff --git a/screenshots/study-report-modules.png b/screenshots/study-report-modules.png new file mode 100644 index 0000000..ad4ac16 Binary files /dev/null and b/screenshots/study-report-modules.png differ diff --git a/screenshots/vocabulary.png b/screenshots/vocabulary.png new file mode 100644 index 0000000..3eb39fe Binary files /dev/null and b/screenshots/vocabulary.png differ diff --git a/static/README.md b/static/README.md new file mode 100644 index 0000000..b987501 --- /dev/null +++ b/static/README.md @@ -0,0 +1,24 @@ +# 静态资源说明 + +## 需要添加的图片资源 + +请在 `static` 目录下添加以下图片: + +### 1. logo.png +- **用途**: 应用Logo,用于登录页、注册页等 +- **尺寸建议**: 256x256px 或更大 +- **格式**: PNG (支持透明背景) +- **路径**: `/static/logo.png` + +## 临时处理 + +如果暂时没有logo图片,可以: +1. 使用任意图片重命名为 `logo.png` 放到此目录 +2. 或者注释掉页面中的 `` 标签 + +## 其他资源 + +后续可以根据需要添加其他图片资源,如: +- 引导页图片 +- 默认头像 +- 占位图等 diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..b5771e2 Binary files /dev/null and b/static/logo.png differ diff --git a/store/home.ts b/store/home.ts new file mode 100644 index 0000000..8d7cc47 --- /dev/null +++ b/store/home.ts @@ -0,0 +1,24 @@ +import type { StudentAlert, StudentHomeMetrics, StudentHomeProfile } from '../api/student' + +// 仅在本次登录会话内缓存首页学生资料,切换一级页面时不重复请求。 +export const homeStudentProfile = ref(null) +export const homeStudentMetrics = ref(null) +export const homeStudentAlerts = ref(null) + +export function setHomeStudentProfile(profile: StudentHomeProfile) { + homeStudentProfile.value = profile +} + +export function setHomeStudentMetrics(metrics: StudentHomeMetrics) { + homeStudentMetrics.value = metrics +} + +export function setHomeStudentAlerts(alerts: StudentAlert[]) { + homeStudentAlerts.value = alerts +} + +export function clearHomeStudentProfile() { + homeStudentProfile.value = null + homeStudentMetrics.value = null + homeStudentAlerts.value = null +} diff --git a/store/registration.ts b/store/registration.ts new file mode 100644 index 0000000..42ec866 --- /dev/null +++ b/store/registration.ts @@ -0,0 +1,8 @@ +import type { StudentInfo } from '../api/student' + +// 仅在注册流程中保存待绑定学生,供扫码与手动输入页面共同使用。 +export const registrationStudents = ref([]) + +export function resetRegistrationStudents() { + registrationStudents.value.splice(0) +} diff --git a/store/user.ts b/store/user.ts new file mode 100644 index 0000000..14f720b --- /dev/null +++ b/store/user.ts @@ -0,0 +1,170 @@ +/** + * 用户状态管理 + * 使用 uni-app x 的响应式系统 + */ + +import type { UserInfo } from '../api/auth' +import { clearHomeStudentProfile } from './home' + +// 用户状态类型 +type UserState = { + userInfo: UserInfo | null + token: string + isLoggedIn: boolean + rememberPassword: boolean + rememberedPhone: string + rememberedPassword: string +} + +// 创建响应式用户状态 +const userState = reactive({ + userInfo: null, + token: '', + isLoggedIn: false, + rememberPassword: false, + rememberedPhone: '', + rememberedPassword: '' +}) + +/** + * 初始化用户状态 + * 从本地存储恢复登录状态和记住密码信息 + */ +export function initUserStore() { + // 恢复登录状态 + const token = uni.getStorageSync('token') as string + const userInfo = uni.getStorageSync('userInfo') as UserInfo | null + + if (token && userInfo) { + userState.token = token + userState.userInfo = userInfo + userState.isLoggedIn = true + } + + // 恢复记住密码信息 + const rememberInfo = uni.getStorageSync('rememberInfo') as any + if (rememberInfo && rememberInfo.remember) { + userState.rememberPassword = true + userState.rememberedPhone = rememberInfo.phone + userState.rememberedPassword = rememberInfo.password + } +} + +/** + * 设置用户信息 + */ +export function setUserInfo(userInfo: UserInfo) { + userState.userInfo = userInfo + userState.token = userInfo.token + userState.isLoggedIn = true + + // 保存到本地存储 + uni.setStorageSync('token', userInfo.token) + uni.setStorageSync('refreshToken', userInfo.refreshToken) + uni.setStorageSync('userInfo', userInfo) +} + +/** + * 保存记住密码信息 + */ +export function saveRememberInfo(phone: string, password: string, remember: boolean) { + userState.rememberPassword = remember + userState.rememberedPhone = phone + userState.rememberedPassword = password + + if (remember) { + uni.setStorageSync('rememberInfo', { + phone, + password, + remember: true + }) + } else { + uni.removeStorageSync('rememberInfo') + } +} + +/** + * 获取记住的密码信息 + */ +export function getRememberInfo(): { phone: string, password: string, remember: boolean } { + return { + phone: userState.rememberedPhone, + password: userState.rememberedPassword, + remember: userState.rememberPassword + } +} + +/** + * 清除用户状态 + */ +export function clearUserInfo() { + userState.userInfo = null + userState.token = '' + userState.isLoggedIn = false + + // 清除本地存储 + uni.removeStorageSync('token') + uni.removeStorageSync('refreshToken') + uni.removeStorageSync('userInfo') + clearHomeStudentProfile() +} + +/** + * 退出登录 + */ +export function logout() { + clearUserInfo() + + // 跳转到登录页 + uni.reLaunch({ + url: '/pages/login/index' + }) +} + +/** + * 获取用户信息 + */ +export function getUserInfo(): UserInfo | null { + return userState.userInfo +} + +/** + * 获取Token + */ +export function getToken(): string { + return userState.token +} + +/** + * 获取登录状态 + */ +export function getIsLoggedIn(): boolean { + return userState.isLoggedIn +} + +/** + * 更新Token + */ +export function updateToken(token: string, refreshToken: string) { + userState.token = token + uni.setStorageSync('token', token) + uni.setStorageSync('refreshToken', refreshToken) + + if (userState.userInfo !== null) { + userState.userInfo.token = token + userState.userInfo.refreshToken = refreshToken + uni.setStorageSync('userInfo', userState.userInfo) + } +} + +// 导出用户状态(只读) +export function useUserStore() { + return { + userInfo: computed(() => userState.userInfo), + token: computed(() => userState.token), + isLoggedIn: computed(() => userState.isLoggedIn), + rememberPassword: computed(() => userState.rememberPassword), + rememberedPhone: computed(() => userState.rememberedPhone), + rememberedPassword: computed(() => userState.rememberedPassword) + } +} diff --git a/uni.scss b/uni.scss new file mode 100644 index 0000000..b9249e9 --- /dev/null +++ b/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16px; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; diff --git a/uni_modules/uni-badge-view/changelog.md b/uni_modules/uni-badge-view/changelog.md new file mode 100644 index 0000000..ed5163c --- /dev/null +++ b/uni_modules/uni-badge-view/changelog.md @@ -0,0 +1,6 @@ +## 1.0.2(2026-05-18) +- 修复类名拼写错误 +## 1.0.1(2026-04-30) +- 更新文档 +## 1.0.0(2026-04-30) +- 支持 uni-badge-view 组件 diff --git a/uni_modules/uni-badge-view/components/uni-badge-view/uni-badge-view.uvue b/uni_modules/uni-badge-view/components/uni-badge-view/uni-badge-view.uvue new file mode 100644 index 0000000..fd1bb6c --- /dev/null +++ b/uni_modules/uni-badge-view/components/uni-badge-view/uni-badge-view.uvue @@ -0,0 +1,287 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/uni-badge-view/package.json b/uni_modules/uni-badge-view/package.json new file mode 100644 index 0000000..30b9a64 --- /dev/null +++ b/uni_modules/uni-badge-view/package.json @@ -0,0 +1,74 @@ +{ + "id": "uni-badge-view", + "displayName": "uni-badge-view 数字角标", + "version": "1.0.2", + "description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27844", + "keywords": [ + "badge", + "uni-ui", + "uniui", + "数字角标", + "徽章" +], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "^5.08", + "uni-app-x": "^5.08", + "uni-app": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "type": "component-vue", + "darkmode": "√", + "i18n": "√", + "widescreen": "√", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + }, + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + } + } + } +} diff --git a/uni_modules/uni-badge-view/readme.md b/uni_modules/uni-badge-view/readme.md new file mode 100644 index 0000000..3922376 --- /dev/null +++ b/uni_modules/uni-badge-view/readme.md @@ -0,0 +1,34 @@ +badge角标,可以用于显示红点、被红圈或其他颜色圈包裹的数字、文字。提醒用户此时有需要注意的新信息。 + +### 文件路径 +项目下有一个 /pages/uni-ui/badge/badge.uvue 页面,里面演示了badge组件的各种用法。 + +### 设计 +#### badge组件有多种用法: +1. 独立显示 + 如果没有slot内容,视为要显示独立的红点和数字,此时不是角标,就是普通的红点和圆背景数字。 + 在按钮或列表项的右侧,独立显示数字。也可以显示红点,但这种场景数字更多一些。 + +2. 作为一个容器组件,包含一个slot,在包裹内容的右上角显示一个角标view。 + badge组件包裹一个目标view时,badge组件内部的角标view的中心点位于目标view的右上角顶点。 + 此时角标view是通过transform位移到右上角的,实际内容已经超出了容器本身。 + 所以需要注意容器的父级的设置,留出超出容器的尺寸(即top和right边距需要留出角标view的高度和宽度的一半),避免被父级裁剪。 + +#### badge的text属性的用法: +在badge作为容器组件时,text属性的不同设置,可以代表不同的效果。具体见属性章节。 + +#### 样式自定义 +badge组件的badge-class和badge-style属性,直接作用在角标view上,可以修改角标view的样式。 + +角标view本质是一个text组件,可以覆盖前景、背景、位置等各种样式。 + +#### badge组件有如下属性: +- text + 类型为 string|null + * 值为"0"时,不显示角标;所以永远不会显示一个为0的角标。此时直接显示slot子内容,不包裹外层,减少层级,提高性能; + * 值为null或""时,代表一个长宽为8px的红色圆点; + * 否则显示为该文字内容的圆角红底圈套文字。 +- badge-class + 直接作用于角标view的class +- badge-style + 直接作用于角标view的style diff --git a/uni_modules/uni-collapse-x/changelog.md b/uni_modules/uni-collapse-x/changelog.md new file mode 100644 index 0000000..c1ffe2d --- /dev/null +++ b/uni_modules/uni-collapse-x/changelog.md @@ -0,0 +1,4 @@ +## 1.0.1(2026-05-14) +- 支持 content-wrap-class 定义内层容器样式 +## 1.0.0(2026-05-07) +- 支持 uni-collapse 组件 diff --git a/uni_modules/uni-collapse-x/components/common.ts b/uni_modules/uni-collapse-x/components/common.ts new file mode 100644 index 0000000..deec9a1 --- /dev/null +++ b/uni_modules/uni-collapse-x/components/common.ts @@ -0,0 +1,3 @@ +export const UNI_COLLAPSE_REGISTER_CHILD = 'uni-collapse-register-child' +export const UNI_COLLAPSE_UNREGISTER_CHILD = 'uni-collapse-unregister-child' +export const UNI_COLLAPSE_CHILD_TOGGLE = 'uni-collapse-child-toggle' diff --git a/uni_modules/uni-collapse-x/components/type.ts b/uni_modules/uni-collapse-x/components/type.ts new file mode 100644 index 0000000..7e875c7 --- /dev/null +++ b/uni_modules/uni-collapse-x/components/type.ts @@ -0,0 +1,6 @@ +export type ItemChildType = { + openCollapse?: (show: boolean) => void; + openOrClose: (show: boolean) => void; + is_open: Ref; + elId: string; +}; diff --git a/uni_modules/uni-collapse-x/components/uni-collapse-item/uni-collapse-item.uvue b/uni_modules/uni-collapse-x/components/uni-collapse-item/uni-collapse-item.uvue new file mode 100644 index 0000000..cb008c5 --- /dev/null +++ b/uni_modules/uni-collapse-x/components/uni-collapse-item/uni-collapse-item.uvue @@ -0,0 +1,739 @@ + + + + + diff --git a/uni_modules/uni-collapse-x/components/uni-collapse/uni-collapse.uvue b/uni_modules/uni-collapse-x/components/uni-collapse/uni-collapse.uvue new file mode 100644 index 0000000..c437c8f --- /dev/null +++ b/uni_modules/uni-collapse-x/components/uni-collapse/uni-collapse.uvue @@ -0,0 +1,129 @@ + + + diff --git a/uni_modules/uni-collapse-x/package.json b/uni_modules/uni-collapse-x/package.json new file mode 100644 index 0000000..10f02f1 --- /dev/null +++ b/uni_modules/uni-collapse-x/package.json @@ -0,0 +1,74 @@ +{ + "id": "uni-collapse-x", + "displayName": "uni-collapse 折叠面板", + "version": "1.0.1", + "description": "Collapse 折叠面板组件,可自定义折叠面板的展开和收起。", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27899", + "keywords": [ + "uni-ui", + "uniui", + "collapse", + "折叠面板", + "uni-app-x" +], + "engines": { + "HBuilderX": "", + "uni-app": "", + "uni-app-x": "^5.07" + }, + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "encrypt": [], + "dependencies": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-collapse-x/readme.md b/uni_modules/uni-collapse-x/readme.md new file mode 100644 index 0000000..a6702ed --- /dev/null +++ b/uni_modules/uni-collapse-x/readme.md @@ -0,0 +1,96 @@ +折叠面板组件,包含: + +- `uni-collapse`:父容器 +- `uni-collapse-item`:子面板 + +一个折叠面板组件可以包含若干uni-collapse-item。每个uni-collapse-item包括顶部标题栏和通过slot传入的内容区。 + +### 基本用法 + +```html + + + 内容 1 + + + 内容 2 + + +``` + +uni-collapse-item的标题栏的自定义: +- 标题栏容器可通过 `title-wrap-class` 自定义背景等容器样式。 +- 标题栏支持title属性,可通过title-class自定义文字样式。 +- 标题展开态可通过 `title-open-class` 自定义激活文字样式。组件默认使用透明度区分状态,便于外部继续覆盖文字颜色。 +- 标题禁用态可通过 `title-disabled-class` 自定义禁用文字样式。若同时存在展开态和禁用态,以禁用态为最终状态。 +- 内容外层容器可通过 `content-wrap-class` 自定义背景等容器样式。 +- 标题栏右边默认带有箭头,可通过arrow-class自定义箭头样式。该箭头为一个view包括2个直角边的边框,然后旋转而成。 +- 箭头展开态和禁用态可分别通过 `arrow-open-class`、`arrow-disabled-class` 覆盖。组件默认也使用透明度区分状态,便于外部继续覆盖箭头颜色。 +- 如果完全不想要默认包含的title和arrow,也可以传入一个名为title的具名插槽来替换。 + +### 具名插槽标题 + +```html + + + 自定义标题内容 + +``` + +### externalClass + +```html + + 内容区域 + +``` + +### Slots + +#### uni-collapse-item + +| 插槽名 | 说明 | 插槽参数 | +| --- | --- | --- | +| default | 面板内容 | - | +| title | 自定义标题区域 | `open: boolean`、`disabled: boolean` | + +### Events + +#### uni-collapse-item + +| 事件名 | 说明 | 回调参数 | +| --- | --- | --- | +| change | 面板展开状态变化时触发 | `open: boolean` | + +### Expose + +#### uni-collapse-item + +| 方法名 | 说明 | +| --- | --- | +| openCollapse(open: boolean) | 执行展开/收起,会遵循 `disabled` 和手风琴逻辑 | +| openOrClose(open: boolean) | 直接切换内容显示状态 | + +### 暗黑模式适配 + +建议通过组件 `class` 和 externalClass 组合传入暗色样式: + +- 用组件 `class` 统一控制根节点和内容区等外层视觉。 +- 用 `title-wrap-class`、`title-class`、`title-open-class`、`content-wrap-class`、`arrow-class`、`arrow-open-class` 等 externalClass 覆盖标题区、内容外层和箭头样式。 +- 默认展开态和禁用态主要通过透明度表达,减少颜色覆盖时的交叉冲突。 + +### 注意 +- 本组件在Android上需HBuilderX 5.09+。在低版本上箭头变颜色+旋转时,在Android上会消失 diff --git a/uni_modules/uni-combox-x/changelog.md b/uni_modules/uni-combox-x/changelog.md new file mode 100644 index 0000000..68ffac8 --- /dev/null +++ b/uni_modules/uni-combox-x/changelog.md @@ -0,0 +1,6 @@ +## 1.0.2 (2026-10-08) +- 修复安卓蒸汽模式会出现内容不显示的问题 +## 1.0.1(2026-08-28) +- 修复 web 运行报错的问题 +## 1.0.0(2026-08-28) +- 新增 uni-combox 组合框组件。 diff --git a/uni_modules/uni-combox-x/components/uni-combox/uni-combox.uvue b/uni_modules/uni-combox-x/components/uni-combox/uni-combox.uvue new file mode 100644 index 0000000..ab17343 --- /dev/null +++ b/uni_modules/uni-combox-x/components/uni-combox/uni-combox.uvue @@ -0,0 +1,660 @@ + + + + + diff --git a/uni_modules/uni-combox-x/package.json b/uni_modules/uni-combox-x/package.json new file mode 100644 index 0000000..68f7aab --- /dev/null +++ b/uni_modules/uni-combox-x/package.json @@ -0,0 +1,91 @@ +{ + "id": "uni-combox-x", + "displayName": "uni-combox 组合框", + "version": "1.0.2", + "description": "Combox 组合框组件,支持默认单选和通过 input 插槽启用可输可选模式。", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=29441", + "keywords": [ + "组合框", + "combox", + "uni-app-x" +], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "", + "uni-app": "", + "uni-app-x": "^5.31" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "type": "component-vue", + "darkmode": "√", + "i18n": "x", + "widescreen": "√", + "npmurl": "", + "vapor": "√" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": { + "extVersion": "1.0.2", + "minVersion": "" + }, + "chrome": { + "extVersion": "1.0.2", + "minVersion": "" + } + }, + "app": { + "android": { + "extVersion": "1.0.2", + "minVersion": "" + }, + "ios": { + "extVersion": "1.0.2", + "minVersion": "" + }, + "harmony": { + "extVersion": "1.0.2", + "minVersion": "" + } + }, + "mp": { + "weixin": { + "extVersion": "1.0.2", + "minVersion": "" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-combox-x/readme.md b/uni_modules/uni-combox-x/readme.md new file mode 100644 index 0000000..9e4b24b --- /dev/null +++ b/uni_modules/uni-combox-x/readme.md @@ -0,0 +1,122 @@ +uni-combox 组合框组件。 + +默认为单选模式,类似 select 组件,只能选择不能输入。通过具名插槽 `input` 传入 input 组件后,切换为可输入、可选择的组合模式。 + +### 基本用法 + +```html + +``` + +### options + +```html + +``` + +### modelValue / v-model + +```html + +``` + +### placeholder + +```html + +``` + +### 组合模式 + +```html + + + +``` + +输入时会打开候选浮层,并按 startsWith 规则过滤候选项。点击右侧箭头打开时展示完整候选列表,并高亮当前输入匹配项。 + +### 自定义样式 + +```html + +``` + +也可以单独传入任意一个样式类属性。 + +```html + + + + + +``` + +### 事件 + +```html + +``` + +### 浮层 + +候选浮层通过 `teleport` 渲染到页面层,不受组件自身尺寸裁剪。下方空间不足且上方空间更充足时,会自动向上展开。默认最大高度为 200px。 + +候选浮层内部使用 `scroll-view`。 + +### External Classes + +| 类名 | 说明 | +| --- | --- | +| arrow-class | 右侧箭头样式 | +| options-view-class | 候选浮层外层样式 | +| option-text-class | 候选项文字样式 | +| option-text-highlight-class | 候选项高亮样式 | +| result-text-class | 单选模式结果文字样式 | + +### Props + +| 属性名 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| options | Array\ | [] | 候选字符串数组 | +| modelValue | String | '' | 当前值,支持 v-model | +| placeholder | String | '' | 占位文字 | +| arrowClass | String | '' | 右侧箭头自定义样式类 | +| optionsViewClass | String | '' | 候选浮层外层自定义样式类 | +| optionTextClass | String | '' | 候选项文字自定义样式类 | +| optionTextHighlightClass | String | '' | 候选项高亮自定义样式类 | +| resultTextClass | String | '' | 单选模式结果文字自定义样式类 | + +### Events + +| 事件名 | 说明 | 回调参数 | +| --- | --- | --- | +| update:modelValue | 值更新时触发 | value: string | +| selectchange | 从候选项选中时触发 | value: string | + +### Expose + +| 方法名 | 说明 | +| --- | --- | +| setInputValue(value: string) | 设置当前值并打开过滤候选 | +| openOptions() | 打开候选浮层 | +| closeOptions() | 关闭候选浮层 | diff --git a/uni_modules/uni-drag-cell/changelog.md b/uni_modules/uni-drag-cell/changelog.md new file mode 100644 index 0000000..6ffb5ab --- /dev/null +++ b/uni_modules/uni-drag-cell/changelog.md @@ -0,0 +1,2 @@ +## 1.0.0(2026-04-30) +- 支持 uni-drag-cell 组件 diff --git a/uni_modules/uni-drag-cell/components/uni-drag-cell/uni-drag-cell.uvue b/uni_modules/uni-drag-cell/components/uni-drag-cell/uni-drag-cell.uvue new file mode 100644 index 0000000..2ca2940 --- /dev/null +++ b/uni_modules/uni-drag-cell/components/uni-drag-cell/uni-drag-cell.uvue @@ -0,0 +1,1263 @@ + + + + + + + + + + + + diff --git a/uni_modules/uni-drag-cell/package.json b/uni_modules/uni-drag-cell/package.json new file mode 100644 index 0000000..f15d1cd --- /dev/null +++ b/uni_modules/uni-drag-cell/package.json @@ -0,0 +1,73 @@ +{ + "id": "uni-drag-cell", + "displayName": "uni-drag-cell", + "version": "1.0.0", + "description": "可拖拽排序组件", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27845", + "keywords": [ + "uni-drag-cell", + "uni-app-x", + "uni-app x", + "组件" + ], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "^5.08", + "uni-app-x": "^5.08", + "uni-app": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-drag-cell/readme.md b/uni_modules/uni-drag-cell/readme.md new file mode 100644 index 0000000..919faaa --- /dev/null +++ b/uni_modules/uni-drag-cell/readme.md @@ -0,0 +1,331 @@ +`uni-drag-cell` 是一个基于 `uni-app x + uvue + UTS` 实现的可拖拽排序组件。 + +它适合这些场景: +- 标签排序 +- 宫格排序 +- 单列列表排序 +- 带删除能力的拖拽 +- 尾部追加“+”入口 +- 手柄拖拽排序 +- 九宫格图片选择和排序 +- 支付方式排序 + +组件通过 v-model 绑定了一个数组,通过插槽渲染绑定数组的内容,通过拖拽调整数组项的顺序。 + +组件还提供了show-close属性,显示底部删除条,拖到删除条即可删除。 + +组件还提供了show-append属性,显示尾部的添加插槽。该插槽不参与拖拽排序,始终在最后一个。(添加数组项的逻辑需自行实现) + +通过这些能力,本组件具备对数组数据排序、添加、删除的能力。 + +但本组件不适合超长虚拟列表。不具备拖动到顶部或底部后自动滚动列表的功能。 + +### 设计目标 + +这个组件的设计不是“拖一下就立刻改数组”,而是分成两个阶段: + +1. **拖拽预览阶段** + - 拖动过程中,通过 `transform` 动态调整视觉顺序 + - 其他 item 会根据碰撞结果做位移动画 + - 此时外部 `v-model` 还不会被正式改写 + +2. **拖拽提交阶段** + - 松手后,才正式修改内部数组顺序 + - 然后通过 `update:modelValue` 和 `change` 把最终结果抛给外部 + +这样做的好处是: +- 拖动过程更平滑 +- 外部响应式更新次数更少 +- 更容易支持删除条、追加入口、手柄拖拽等扩展能力 + +### 交互模式 + +组件目前支持两种进入拖拽态的方式:长按拖拽和手柄拖拽。 + +#### 1. 长按拖拽 + +默认模式。 + +- 长按某个 item 后进入拖拽态 +- 当前长按触发时间为 `350ms` +- 进入拖拽态后,item 会跟随手指/鼠标移动,并且会scale放大1.12倍。 +- 拖动过程中持续做碰撞检测,并实时预览排序结果 + +补充: +- 如果按下后移动距离超过组件内部阈值,会取消本次长按判定,避免页面滚动时误触发拖拽 + +适合: +- 标签排序 +- 宫格排序 +- 图片排序 + +#### 2. 手柄拖拽 + +当 `handleMode` 为 `true`,且同时提供 `handle` 插槽时,会启用手柄模式。 + +- 不再需要长按 +- 只有拖动手柄才会开始排序 +- 内容区本身可以继续点击、滚动、预览 + +适合: +- 设置项排序 +- 表单项排序 +- 内容区本身还有点击行为的列表 + +### 插槽 + +| 插槽名 | 插槽参数 | 说明 | +| --- | --- | --- | +| `default` | `{ item, index }` | 默认内容插槽,用于渲染每一个可排序项。组件不限制 item 的视觉形式,可以自由渲染标签、卡片、宫格、图片等内容。 | +| `append` | - | 列表末尾的固定入口。不参与拖拽排序,不会写入 `v-model`,不会被删除,适合放“新增按钮”“上传入口”“更多入口”等内容。 | +| `handle` | `{ item, index }` | 拖拽手柄插槽,仅在 `handleMode` 开启时生效。手柄由组件放在每个 item 的右上角,建议给内容区预留右侧空间,避免手柄遮挡正文。 | + +### 事件 + +| 事件名 | 回调参数 | 触发时机 | 说明 | +| --- | --- | --- | --- | +| `change` | 最新数组 | 排序完成后、删除完成后 | 最终结果事件。拖动中的视觉预览不会频繁触发 `change`。 | + +### 使用方式 + +#### 1. 基础标签排序 + +```vue + + + +``` + +适合: +- 标签拖拽 +- 轻量横向流式布局 + +说明: +- 默认使用长按触发拖拽 +- 当前长按触发时间为 `350ms` + +#### 2. 宫格排序 + +```vue + + + +``` + +适合: +- 应用图标 +- 功能入口宫格 +- 九宫格类布局 + +#### 3. 带删除条的拖拽 + +```vue + + + +``` + +交互: +- 长按进入拖拽 +- 拖到屏幕底部删除条 +- 删除条高亮后松手即删除 + +#### 4. 尾部追加一个固定入口 + +```vue + + + + +``` + +说明: +- `append` 是通用设计,不局限于图片场景 +- 拖拽排序时,`append` 不参与位置计算 + +#### 5. 九宫格图片选择 + +```vue + + + + +``` + +设计说明: +- 图片数组本身就是 `v-model` +- `append` 用来放“添加图片”入口 +- 当达到上限时,把 `showAppend` 设为 `false` + +#### 6. 手柄拖拽排序 + +```vue + + + + +``` + +说明: +- 手柄模式下,不需要长按 +- 当前实现中,手柄模式不会再把整个 item 放大 +- 这样可以避免单列场景下被父容器裁剪 + +### 组件内部设计说明 + +#### 1. 排序预览不是直接改数组 + +拖动过程中,组件内部维护的是“预览顺序”。 + +实现方式: +- 记录每个 item 的稳定 id +- 根据碰撞结果生成一份预览顺序 +- 通过 `transform` 让其他项移动到目标位置 +- 松手后再正式更新数组和 id 顺序 + +这样可以避免: +- 拖动中频繁改外部数组 +- 频繁触发父组件重渲染 +- item 身份丢失导致动画错乱 + +#### 2. 删除判定基于拖动内容区域 + +删除命中不是简单按手指坐标判断,而是优先按“拖动内容的真实边界”和“删除条边界”是否接触来判断。 + +这样做是为了避免: +- item 外层有 padding 时提前触发删除 +- 不同高度 item 在删除区表现不一致 +- web 与 app 坐标系不一致时出现明显偏移 + +#### 3. 追加项与删除条互不干扰 + +`append` 项的设计目标是“固定入口”,因此: +- 不参与碰撞排序 +- 不参与删除 +- 不写入绑定数组 + +#### 4. 手柄模式与长按模式共用同一套拖拽链路 + +也就是说: +- 排序逻辑相同 +- 删除逻辑相同 +- 只是“进入拖拽态”的入口不同 + +这样可以减少两套逻辑分叉导致的维护成本。 + +### 性能注意 + +拖拽时耗费性能的点主要有两个: + +#### 1. 拖动中的碰撞检测 + +每次 move 都会更新: +- 当前拖动偏移 +- 删除条命中状态 +- 目标悬停位置 + +这是拖拽组件的必要开销,当前实现已经把重测布局的频率控制在较低水平,只在必要时重新测量。 + +#### 2. 删除条命中时的实时矩形读取 + +当启用 `showClose` 时,拖动中会优先读取当前拖动内容的实时矩形,以保证删除命中准确。 + +这部分是“精度优先”的设计,开销可接受,但不建议把它用于超大数量、超复杂 item 的高频拖拽场景。 + +### 使用注意事项 + +#### 1. 建议 item 尺寸稳定 + +尤其是宫格模式下,建议每个 item 的宽高尽量稳定。 + +否则可能带来: +- 拖动命中感不一致 +- 排序预览观感不稳定 + +#### 2. 手柄模式下给内容区预留右侧空间 + +因为手柄是覆盖在 item 右上角的。 + +建议: +- 内容区右侧留白 +- 或者把手柄区域设计成视觉上独立的一块 + +#### 3. `append` 只是入口,不是数据项 + +这点非常重要。 + +不要指望: +- `append` 参与排序 +- `append` 进入 `v-model` +- `append` 被删除 + +如果你需要“真正的数据项”,请直接写进 `modelValue`。 + +#### 4. 外部不要在拖拽过程中频繁整体替换列表 + +建议: +- 由组件在松手后统一回传结果 +- 父组件收到结果后再更新自己的数据源 + +不建议: +- 拖动过程中再由父组件持续重建整个数组 + +#### 5. 不建议用于超大列表或虚拟列表场景 + +这个组件当前更适合: +- 小中型标签列表 +- 宫格 +- 表单排序 +- 九宫格图片选择 + +如果是超长列表、虚拟滚动列表,应该使用专门为大数据量设计的方案。 + +### 平台说明 + +#### Web + +- 支持 pointer / touch 交互 + +#### Android + +- 支持触摸拖拽 diff --git a/uni_modules/uni-fab-button/changelog.md b/uni_modules/uni-fab-button/changelog.md new file mode 100644 index 0000000..8d5b5a6 --- /dev/null +++ b/uni_modules/uni-fab-button/changelog.md @@ -0,0 +1,2 @@ +## 1.0.0(2026-04-30) +- 支持 uni-fab-button 组件 diff --git a/uni_modules/uni-fab-button/components/uni-fab-button/uni-fab-button.uvue b/uni_modules/uni-fab-button/components/uni-fab-button/uni-fab-button.uvue new file mode 100644 index 0000000..ca27cda --- /dev/null +++ b/uni_modules/uni-fab-button/components/uni-fab-button/uni-fab-button.uvue @@ -0,0 +1,152 @@ + + + diff --git a/uni_modules/uni-fab-button/package.json b/uni_modules/uni-fab-button/package.json new file mode 100644 index 0000000..a4b1644 --- /dev/null +++ b/uni_modules/uni-fab-button/package.json @@ -0,0 +1,73 @@ +{ + "id": "uni-fab-button", + "displayName": "uni-fab-button", + "version": "1.0.0", + "description": "uni-fab-button", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27846", + "keywords": [ + "uni-fab-button", + "uni-app-x", + "ui", + "组件" + ], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "^5.07", + "uni-app-x": "^5.07", + "uni-app": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-fab-button/readme.md b/uni_modules/uni-fab-button/readme.md new file mode 100644 index 0000000..e87bdee --- /dev/null +++ b/uni_modules/uni-fab-button/readme.md @@ -0,0 +1,14 @@ +悬浮加号按钮 + +本组件常用于页面中右下角悬浮的、里面有一个加号的圆形按钮。 + +组件的根view默认是一个圆形,蓝色背景(#007AFF),并带有阴影。使用组件时注意组件的父容器要留出阴影的空间,避免被裁剪。 + +组件的二级view是2个view交叉组成的加号。其中垂直的view是通过transform变换方向实现的。 + +组件的属性为 plus-class,它是一个externalClass,用于外部修改组件内部的、组成加号的2个view的样式。 + +组件中加号的默认样式为背景白色、粗细为2px、尺寸为40%。 + +### 注意 +- 鸿蒙平台按下fab-button不缩写的问题,需升级到HBuilderX 5.09+解决 diff --git a/uni_modules/uni-icons-x/changelog.md b/uni_modules/uni-icons-x/changelog.md new file mode 100644 index 0000000..5fe59ad --- /dev/null +++ b/uni_modules/uni-icons-x/changelog.md @@ -0,0 +1,4 @@ +## 1.0.0 (2026-10-16) +- 新增 uni-app x 和蒸汽模式支持 +- 新增组合式 API 实现 +- 调整 支持直接使用 `class` 或 `style` 自定义颜色、字号和字体 diff --git a/uni_modules/uni-icons-x/components/uni-icons/icons.uts b/uni_modules/uni-icons-x/components/uni-icons/icons.uts new file mode 100644 index 0000000..a864322 --- /dev/null +++ b/uni_modules/uni-icons-x/components/uni-icons/icons.uts @@ -0,0 +1,169 @@ +/** + * uni-icons 图标名称与 Unicode 字符映射表 + * @description 独立映射表,可在其它组件中直接 import 复用 + */ +export const uniIconsMap = { + 'arrow-down': '\ue6be', + 'arrow-left': '\ue6bc', + 'arrow-right': '\ue6bb', + 'arrow-up': '\ue6bd', + 'auth': '\ue6ab', + 'auth-filled': '\ue6cc', + 'back': '\ue6b9', + 'bars': '\ue627', + 'calendar': '\ue6a0', + 'calendar-filled': '\ue6c0', + 'camera': '\ue65a', + 'camera-filled': '\ue658', + 'cart': '\ue631', + 'cart-filled': '\ue6d0', + 'chat': '\ue65d', + 'chat-filled': '\ue659', + 'chatboxes': '\ue696', + 'chatboxes-filled': '\ue692', + 'chatbubble': '\ue697', + 'chatbubble-filled': '\ue694', + 'checkbox': '\ue62b', + 'checkbox-filled': '\ue62c', + 'checkmarkempty': '\ue65c', + 'circle': '\ue65b', + 'circle-filled': '\ue65e', + 'clear': '\ue66d', + 'close': '\ue673', + 'closeempty': '\ue66c', + 'cloud-download': '\ue647', + 'cloud-download-filled': '\ue646', + 'cloud-upload': '\ue645', + 'cloud-upload-filled': '\ue648', + 'color': '\ue6cf', + 'color-filled': '\ue6c9', + 'compose': '\ue67f', + 'contact': '\ue693', + 'contact-filled': '\ue695', + 'down': '\ue6b8', + 'bottom': '\ue6b8', + 'download': '\ue68d', + 'download-filled': '\ue681', + 'email': '\ue69e', + 'email-filled': '\ue69a', + 'eye': '\ue651', + 'eye-filled': '\ue66a', + 'eye-slash': '\ue6b3', + 'eye-slash-filled': '\ue6b4', + 'fire': '\ue6a1', + 'fire-filled': '\ue6c5', + 'flag': '\ue65f', + 'flag-filled': '\ue660', + 'folder-add': '\ue6a9', + 'folder-add-filled': '\ue6c8', + 'font': '\ue6a3', + 'forward': '\ue6ba', + 'gear': '\ue664', + 'gear-filled': '\ue661', + 'gift': '\ue6a4', + 'gift-filled': '\ue6c4', + 'hand-down': '\ue63d', + 'hand-down-filled': '\ue63c', + 'hand-up': '\ue63f', + 'hand-up-filled': '\ue63e', + 'headphones': '\ue630', + 'heart': '\ue639', + 'heart-filled': '\ue641', + 'help': '\ue679', + 'help-filled': '\ue674', + 'home': '\ue662', + 'home-filled': '\ue663', + 'image': '\ue670', + 'image-filled': '\ue678', + 'images': '\ue650', + 'images-filled': '\ue64b', + 'info': '\ue669', + 'info-filled': '\ue649', + 'left': '\ue6b7', + 'link': '\ue6a5', + 'list': '\ue644', + 'location': '\ue6ae', + 'location-filled': '\ue6af', + 'locked': '\ue66b', + 'locked-filled': '\ue668', + 'loop': '\ue633', + 'mail-open': '\ue643', + 'mail-open-filled': '\ue63a', + 'map': '\ue667', + 'map-filled': '\ue666', + 'map-pin': '\ue6ad', + 'map-pin-ellipse': '\ue6ac', + 'medal': '\ue6a2', + 'medal-filled': '\ue6c3', + 'mic': '\ue671', + 'mic-filled': '\ue677', + 'micoff': '\ue67e', + 'micoff-filled': '\ue6b0', + 'minus': '\ue66f', + 'minus-filled': '\ue67d', + 'more': '\ue64d', + 'more-filled': '\ue64e', + 'navigate': '\ue66e', + 'navigate-filled': '\ue67a', + 'notification': '\ue6a6', + 'notification-filled': '\ue6c1', + 'paperclip': '\ue652', + 'paperplane': '\ue672', + 'paperplane-filled': '\ue675', + 'person': '\ue699', + 'person-filled': '\ue69d', + 'personadd': '\ue69f', + 'personadd-filled': '\ue698', + 'personadd-filled-copy': '\ue6d1', + 'phone': '\ue69c', + 'phone-filled': '\ue69b', + 'plus': '\ue676', + 'plus-filled': '\ue6c7', + 'plusempty': '\ue67b', + 'pulldown': '\ue632', + 'pyq': '\ue682', + 'qq': '\ue680', + 'redo': '\ue64a', + 'redo-filled': '\ue655', + 'refresh': '\ue657', + 'refresh-filled': '\ue656', + 'refreshempty': '\ue6bf', + 'reload': '\ue6b2', + 'right': '\ue6b5', + 'scan': '\ue62a', + 'search': '\ue654', + 'settings': '\ue653', + 'settings-filled': '\ue6ce', + 'shop': '\ue62f', + 'shop-filled': '\ue6cd', + 'smallcircle': '\ue67c', + 'smallcircle-filled': '\ue665', + 'sound': '\ue684', + 'sound-filled': '\ue686', + 'spinner-cycle': '\ue68a', + 'staff': '\ue6a7', + 'staff-filled': '\ue6cb', + 'star': '\ue688', + 'star-filled': '\ue68f', + 'starhalf': '\ue683', + 'trash': '\ue687', + 'trash-filled': '\ue685', + 'tune': '\ue6aa', + 'tune-filled': '\ue6ca', + 'undo': '\ue64f', + 'undo-filled': '\ue64c', + 'up': '\ue6b6', + 'top': '\ue6b6', + 'upload': '\ue690', + 'upload-filled': '\ue68e', + 'videocam': '\ue68c', + 'videocam-filled': '\ue689', + 'vip': '\ue6a8', + 'vip-filled': '\ue6c6', + 'wallet': '\ue6b1', + 'wallet-filled': '\ue6c2', + 'weibo': '\ue68b', + 'weixin': '\ue691', +} + +export type IconsType = keyof typeof uniIconsMap diff --git a/uni_modules/uni-icons-x/components/uni-icons/uni-icons.uvue b/uni_modules/uni-icons-x/components/uni-icons/uni-icons.uvue new file mode 100644 index 0000000..aa09bf8 --- /dev/null +++ b/uni_modules/uni-icons-x/components/uni-icons/uni-icons.uvue @@ -0,0 +1,66 @@ + + + + + diff --git a/uni_modules/uni-icons-x/components/uni-icons/uniicons.ttf b/uni_modules/uni-icons-x/components/uni-icons/uniicons.ttf new file mode 100644 index 0000000..14696d0 Binary files /dev/null and b/uni_modules/uni-icons-x/components/uni-icons/uniicons.ttf differ diff --git a/uni_modules/uni-icons-x/package.json b/uni_modules/uni-icons-x/package.json new file mode 100644 index 0000000..214461a --- /dev/null +++ b/uni_modules/uni-icons-x/package.json @@ -0,0 +1,100 @@ +{ + "id": "uni-icons-x", + "displayName": "uni-icons 图标", + "version": "1.0.0", + "description": "适用于 uni-app x 的字体图标组件,可自定义颜色、大小和字体。", + "keywords": [ + "uni-ui-x", + "uni-app-x", + "icon", + "图标" + ], + "repository": "https://github.com/dcloudio/uni-ui-x", + "engines": { + "HBuilderX": "^5.0", + "uni-app-x": "^5.0", + "uni-app": "" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui-x", + "darkmode": "x", + "i18n": "x", + "widescreen": "x" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app": { + "vue": { + "vue2": "x", + "vue3": "x" + }, + "web": { + "safari": "-", + "chrome": "-" + }, + "app": { + "vue": "x", + "nvue": "x", + "android": "-", + "ios": "-", + "harmony": "-" + }, + "mp": { + "weixin": "-", + "alipay": "-", + "toutiao": "-", + "baidu": "-", + "kuaishou": "-", + "jd": "-", + "harmony": "-", + "qq": "-", + "lark": "-" + }, + "quickapp": { + "huawei": "-", + "union": "-" + } + }, + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-icons-x/readme.md b/uni_modules/uni-icons-x/readme.md new file mode 100644 index 0000000..31a1b0f --- /dev/null +++ b/uni_modules/uni-icons-x/readme.md @@ -0,0 +1 @@ +### [查看文档](https://doc.dcloud.net.cn/uni-app-x/component/uni-ui-x/uni-icons.html) diff --git a/uni_modules/uni-index-bar/changelog.md b/uni_modules/uni-index-bar/changelog.md new file mode 100644 index 0000000..a6ae3d2 --- /dev/null +++ b/uni_modules/uni-index-bar/changelog.md @@ -0,0 +1,4 @@ +## 1.0.1(2026-04-30) +- 更新文档 +## 1.0.0(2026-04-30) +- 支持 uni-index-bar 组件 diff --git a/uni_modules/uni-index-bar/components/uni-index-bar/uni-index-bar.uvue b/uni_modules/uni-index-bar/components/uni-index-bar/uni-index-bar.uvue new file mode 100644 index 0000000..82328cf --- /dev/null +++ b/uni_modules/uni-index-bar/components/uni-index-bar/uni-index-bar.uvue @@ -0,0 +1,384 @@ + + + + + diff --git a/uni_modules/uni-index-bar/package.json b/uni_modules/uni-index-bar/package.json new file mode 100644 index 0000000..dff0f87 --- /dev/null +++ b/uni_modules/uni-index-bar/package.json @@ -0,0 +1,75 @@ +{ + "id": "uni-index-bar", + "displayName": "uni-index-bar 索引栏", + "version": "1.0.1", + "description": "uni-index-bar 索引栏组件,用于显示索引栏,支持自定义索引栏的样式和内容。", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27847", + "keywords": [ + "uni-index-bar", + "索引栏", + "index-bar", + "uni-app-x", + "组件" + ], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "^5.08", + "uni-app-x": "^5.08", + "uni-app": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√", + "alipay": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-index-bar/readme.md b/uni_modules/uni-index-bar/readme.md new file mode 100644 index 0000000..eed8fe8 --- /dev/null +++ b/uni_modules/uni-index-bar/readme.md @@ -0,0 +1,98 @@ +### 介绍 + +本组件以竖排方式显示字母索引,常悬浮或固定于列表右侧,用户通过点击或滑动索引条上的字母索引,触发 `select` 事件。在该事件中通过代码驱动指定的列表滚动到该字母索引所在的位置。 + +本组件为绝对定位,默认垂直居中于父容器右侧。 + +本组件不包括 list-view 组件,需要配合 list-view 使用。list-view 中需要有对应索引的 list-item,且这些 list-item 需要有 id 属性,可以通过 `scroll-into-view` 属性或 `scrollTop` 属性跳转。 + +本组件包括一个指示器(indicator)子组件。当用户手指触摸在索引条上的某个字母时,在触摸的字母左边悬浮显示一个水滴形气泡,箭头指向当前触摸的字母。 + +### 基本用法 + +```html + + + + + + +``` + +```ts +const onSelect = (index: string) => { + // 处理索引选择,滚动列表到对应位置 +} +``` + +### 事件 + +| 事件名 | 说明 | 回调参数 | +|--------|------|----------| +| select | 选择索引时触发 | index: string - 当前选择的索引字符 | + +### 外部样式类 + +| 类名 | 说明 | +|------|------| +| indicator-view-class | 指示器容器样式,可自定义气泡背景色等 | +| indicator-text-class | 指示器文字样式,可自定义文字颜色等 | + +### 示例 + +#### 自定义索引 + +```html + +``` + +#### 自定义样式 + +```html + + +``` + +```css +.custom-indicator { + background-color: #007aff; +} +.custom-indicator-text { + color: #ffff00; +} +``` + +#### 配合 list-view 使用 + +```html + + + + + + +``` + +```ts +const indexList = cityGroups.map((g): string => g.index).join('\n') +const indexViewID = ref("") + +const onSelect = (index: string) => { + indexViewID.value = 'idx-' + index +} +``` diff --git a/uni_modules/uni-link-x/changelog.md b/uni_modules/uni-link-x/changelog.md new file mode 100644 index 0000000..8de647a --- /dev/null +++ b/uni_modules/uni-link-x/changelog.md @@ -0,0 +1,2 @@ +## 1.0.0(2026-05-07) +- 支持 uni-link 组件 diff --git a/uni_modules/uni-link-x/components/uni-link/uni-link.uvue b/uni_modules/uni-link-x/components/uni-link/uni-link.uvue new file mode 100644 index 0000000..a2d2e02 --- /dev/null +++ b/uni_modules/uni-link-x/components/uni-link/uni-link.uvue @@ -0,0 +1,362 @@ + + + + diff --git a/uni_modules/uni-link-x/package.json b/uni_modules/uni-link-x/package.json new file mode 100644 index 0000000..2fabe25 --- /dev/null +++ b/uni_modules/uni-link-x/package.json @@ -0,0 +1,76 @@ +{ + "id": "uni-link-x", + "displayName": "uni-link 链接", + "version": "1.0.0", + "description": "Link 链接组件,可自定义链接的打开方式。", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27898", + "keywords": [ + "uni-ui", + "uniui", + "link", + "链接", + "uni-app-x" +], + "engines": { + "HBuilderX": "", + "uni-app": "", + "uni-app-x": "^5.07" + }, + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [ + "uts-openSchema" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "√", + "aliyun": "√", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-link-x/pages/link-webview.uvue b/uni_modules/uni-link-x/pages/link-webview.uvue new file mode 100644 index 0000000..82c418c --- /dev/null +++ b/uni_modules/uni-link-x/pages/link-webview.uvue @@ -0,0 +1,28 @@ + + + diff --git a/uni_modules/uni-link-x/readme.md b/uni_modules/uni-link-x/readme.md new file mode 100644 index 0000000..e2f3d6e --- /dev/null +++ b/uni_modules/uni-link-x/readme.md @@ -0,0 +1,45 @@ +打开web链接组件。点击后通过内置或外部浏览器打开链接,加载 `href` 属性中配置的 URL。 + +- `target="_self"` 时,使用 `uni_modules/uni-link-x/pages/link-webview` 页面内的 `web-view` 打开链接。 + * 注意小程序需要将域名加入应用的域名白名单。 +- `target="_blank"` 时, + * APP平台调用手机浏览器打开链接 + * Web调用 `window.open(url, '_blank')` + * 小程序平台无法打开手机浏览器,此时会走到失败回退逻辑中 + + `target="_blank"`失败时,可以通过组件属性`blank-fallback`来控制失败后的回退处理策略,`blank-fallback`属性有3个值: + + 1. turn-self :使用_self方式打开。此值为默认值。也就是 `target="_blank"` 时,小程序下默认转到内置浏览器打开。 + 2. copy-link-modal :复制链接并弹框提醒。会复制URL到剪贴板中,并弹框提示用户在外部浏览器中打开。 + 3. none :不回退。会触发组件的error事件。 + +- 开发者也可以通过条件编译组合使用,比如APP平台使用`_black`,小程序平台使用`_self` + +常见失败原因: +- 安装本组件时未在项目的pages.json中注册组件的内置页面。(HBuilderX导入本组件到项目下时会弹框提醒注册内置页面) +- 小程序平台未将域名加入应用的域名白名单。 +- iOS运行基座未包括openSchema插件,仅在无xcode时运行会触发。打包其实不会发生此错误。 +- app平台,有些手机系统会弹框询问用户是否允许app打开浏览器,如果用户点了拒绝,这种情况没有失败回调。但有种判断方式是app的onHide生命周期是否触发,如果没有触发,那么意味着并没有启动系统浏览器。 + +### 基本用法 + +```html +uni-app x 文档 + +``` + +### Events + +| 事件名 | 说明 | 回调参数 | +| --- | --- | --- | +| error | 打开失败时触发 | `message: string` | + +### 依赖 + +`APP` 平台依赖 `uts-openSchema`。`uni-link` 已在 `uni_modules/uni-link-x/package.json` 中声明该依赖。 + +组件目录下有 `pages_init.json`,会向应用的 pages.json 中注册组件中自带的内置浏览器页面。如在HBuilderX中弹框询问是否将组件中的页面注册到项目pages.json中时,请选择同意。 + +### 示例页面 + +`/pages/uni-ui/link/link` diff --git a/uni_modules/uni-nav-bar-x/changelog.md b/uni_modules/uni-nav-bar-x/changelog.md new file mode 100644 index 0000000..8c6e0d5 --- /dev/null +++ b/uni_modules/uni-nav-bar-x/changelog.md @@ -0,0 +1,4 @@ +## 1.0.1(2026-05-20) +- 纠正类名拼写错误 +## 1.0.0(2026-05-07) +- 支持 uni-nav-bar 组件 diff --git a/uni_modules/uni-nav-bar-x/components/uni-nav-bar/uni-nav-bar.uvue b/uni_modules/uni-nav-bar-x/components/uni-nav-bar/uni-nav-bar.uvue new file mode 100644 index 0000000..6ed8459 --- /dev/null +++ b/uni_modules/uni-nav-bar-x/components/uni-nav-bar/uni-nav-bar.uvue @@ -0,0 +1,361 @@ + + + + + diff --git a/uni_modules/uni-nav-bar-x/package.json b/uni_modules/uni-nav-bar-x/package.json new file mode 100644 index 0000000..98946ee --- /dev/null +++ b/uni_modules/uni-nav-bar-x/package.json @@ -0,0 +1,73 @@ +{ + "id": "uni-nav-bar-x", + "displayName": "uni-nav-bar", + "version": "1.0.1", + "description": "uni-nav-bar", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27897", + "keywords": [ + "uni-nav-bar", + "uni-app-x", + "导航栏", + "组件" + ], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "^5.0", + "uni-app-x": "^5.07", + "uni-app": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-nav-bar-x/readme.md b/uni_modules/uni-nav-bar-x/readme.md new file mode 100644 index 0000000..fdfd813 --- /dev/null +++ b/uni_modules/uni-nav-bar-x/readme.md @@ -0,0 +1,63 @@ +自定义导航栏组件 + +在页面pages.json中关闭原生导航栏后,即使用`"style": {"navigationStyle": "custom"}`,可以使用本组件实现自定义导航栏。 + +同时注意在pages.json中配置页面style增加属性`disableScroll:true`,即禁止页面滚动。此时只有uni-nav-bar组件的下方才可以滚动。否则页面的回弹bounce效果会把自定义导航栏也拉下来。 + +本组件自动适配顶部安全区。用padding-top让出顶部状态栏的高度。除去状态栏高度后,本组件的高度为44px。 + +本组件左右两边默认各让出了6px的边距。也可以在left-class和right-class中自定义边距。 + +本组件分为left、mid、right 3个区域。 + +- left区域默认显示一个返回箭头,大小为44*44px。可以通过属性hideDefaultBack来隐藏,也可以传入一个slot name='left'来替代。可以通过left-class来修饰样式。 +- mid区域默认显示title属性设置的标题。也可以传入一个slot name='mid'来替代。其默认宽度为屏幕宽度-两边默认边距-left区域默认宽度-right区域默认宽度。可以通过mid-class来修饰样式。 +- right区域默认不显示内容,可以传入一个slot name='right'来显示自定义内容。right区域默认width也是44px,可以通过right-class来修饰样式。 + +支持属性: +- hideDefaultBack: 隐藏返回箭头 +- title: 通过属性方便设置标题。如果传入mid slot,则不生效 +- navigationBarTextStyle: 返回箭头和属性设置的标题,它们的颜色均由该属性控制,可选 white|black + +本组件默认没有背景色,即透明,会透显页面的背景色,开发者可通过组件的class自行设置背景色。 + +如果遇到顶部安全区避让失效的问题,可升级到 HBuilderX 5.25 版本。 + +在小程序端,如果需要规避右上角胶囊按钮,可以参考下方代码设置 margin-right 让出胶囊按钮的宽度和右侧间距。 + +```html + + + + + +``` diff --git a/uni_modules/uni-number-box-x/changelog.md b/uni_modules/uni-number-box-x/changelog.md new file mode 100644 index 0000000..cd4f8b5 --- /dev/null +++ b/uni_modules/uni-number-box-x/changelog.md @@ -0,0 +1,4 @@ +## 1.0.1(2026-05-07) +- 移除无用依赖 +## 1.0.0(2026-05-07) +- 支持 uni-number-box 组件 diff --git a/uni_modules/uni-number-box-x/components/uni-number-box/uni-number-box.uvue b/uni_modules/uni-number-box-x/components/uni-number-box/uni-number-box.uvue new file mode 100644 index 0000000..c014637 --- /dev/null +++ b/uni_modules/uni-number-box-x/components/uni-number-box/uni-number-box.uvue @@ -0,0 +1,802 @@ + + + + + diff --git a/uni_modules/uni-number-box-x/package.json b/uni_modules/uni-number-box-x/package.json new file mode 100644 index 0000000..4246d39 --- /dev/null +++ b/uni_modules/uni-number-box-x/package.json @@ -0,0 +1,74 @@ +{ + "id": "uni-number-box-x", + "displayName": "uni-number-box 数字输入框", + "version": "1.0.1", + "description": "NumberBox 带加减按钮的数字输入框组件,用户可以控制每次点击增加的数值,支持小数。", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27896", + "keywords": [ + "uni-ui", + "uniui", + "数字输入框", + "uni-app-x" +], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "", + "uni-app": "", + "uni-app-x": "^5.07" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [ + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-number-box-x/readme.md b/uni_modules/uni-number-box-x/readme.md new file mode 100644 index 0000000..6665c76 --- /dev/null +++ b/uni_modules/uni-number-box-x/readme.md @@ -0,0 +1,58 @@ +带加减按钮的数字输入框,常见于购物车商品数量选择。 + +### 基本用法 + +```html + +``` + +### 设置范围 + +```html + +``` + +### 设置步长 + +```html + + + + + +``` + +### 禁用状态 + +```html + +``` + +### 自定义样式 + +通过 `btn-class`、`input-class`、`icon-class` 自定义组件样式: + +```html + +``` + +```css +.custom-btn { + background-color: #007aff; +} +.custom-input { + background-color: #e8f4ff; + color: #007aff; +} +.custom-icon { + background-color: #fff; +} +``` + +### Events + +| 事件名 | 说明 | 回调参数 | +| --- | --- | --- | +| change | 值改变时触发 | value: number | +| focus | 输入框聚焦时触发 | event: UniInputFocusEvent | +| blur | 输入框失焦时触发 | event: UniInputBlurEvent | diff --git a/uni_modules/uni-rate-x/changelog.md b/uni_modules/uni-rate-x/changelog.md new file mode 100644 index 0000000..ad08e68 --- /dev/null +++ b/uni_modules/uni-rate-x/changelog.md @@ -0,0 +1,4 @@ +## 1.0.1(2026-05-11) +- 修复 starClass 和 starActiveClass 设置不生效的问题 +## 1.0.0(2026-05-07) +- 支持 uni-rate 组件 diff --git a/uni_modules/uni-rate-x/components/uni-rate/uni-rate.uvue b/uni_modules/uni-rate-x/components/uni-rate/uni-rate.uvue new file mode 100644 index 0000000..a28b1fd --- /dev/null +++ b/uni_modules/uni-rate-x/components/uni-rate/uni-rate.uvue @@ -0,0 +1,624 @@ + + + + + diff --git a/uni_modules/uni-rate-x/package.json b/uni_modules/uni-rate-x/package.json new file mode 100644 index 0000000..7401650 --- /dev/null +++ b/uni_modules/uni-rate-x/package.json @@ -0,0 +1,73 @@ +{ + "id": "uni-rate-x", + "displayName": "uni-rate 评分", + "version": "1.0.1", + "description": "Rate 评分组件,可自定义评分星星图标的大小、间隔、评分数。", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27900", + "keywords": [ + "uni-ui", + "uniui", + "评分", + "uni-app-x" +], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "", + "uni-app": "", + "uni-app-x": "^5.07" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-rate-x/readme.md b/uni_modules/uni-rate-x/readme.md new file mode 100644 index 0000000..fa71ff0 --- /dev/null +++ b/uni_modules/uni-rate-x/readme.md @@ -0,0 +1,32 @@ +评分组件,多用于购买商品后,对商品进行评价等场景 + +### 文件路径 +项目下有一个 /pages/uni-ui/rate/rate.uvue 页面,里面演示了rate组件的各种用法。 + +### 设计 + +rate组件是投票评分使用的组件。它使用unicode字符实现,通过修改字体的样式,实现各种效果。 + +它分2个版本,一个版本是高性能版本,只有一层,不显示灰星,仅显示亮星,亮星不支持小数点,不能点击。适于长列表中显示。 +另一个版本是普通版本,由背景灰星和前景亮星叠加而成,支持小数点,支持点击。 +这2个版本通过属性full来切换。 + +本组件支持v-model绑定响应式数据。 + +rate组件有如下属性: +- full + 是否显示全部5颗星。当黄色不够5星时,是否填充灰星以达到5星的固定长度。不填充灰星性能更高 +- readonly + 是否响应点击变化。full属性为false时不生效,即无灰星时一定只读 +- plain + 灰星是否镂空 +- star-class + 星星的样式 +- star-style + 星星的样式 +- star-active-class + 选中的星星的样式 +- star-active-style + 选中的星星的样式 + +rate组件有onChange事件,事件回调中会给出点击触发的新评分值。 diff --git a/uni_modules/uni-refresh-box/changelog.md b/uni_modules/uni-refresh-box/changelog.md new file mode 100644 index 0000000..47c2dd2 --- /dev/null +++ b/uni_modules/uni-refresh-box/changelog.md @@ -0,0 +1,2 @@ +## 1.0.0(2026-04-30) +- 支持 uni-refresh-box 组件 diff --git a/uni_modules/uni-refresh-box/components/uni-refresh-box/uni-refresh-box.uvue b/uni_modules/uni-refresh-box/components/uni-refresh-box/uni-refresh-box.uvue new file mode 100644 index 0000000..cb3edd0 --- /dev/null +++ b/uni_modules/uni-refresh-box/components/uni-refresh-box/uni-refresh-box.uvue @@ -0,0 +1,526 @@ + + + + + diff --git a/uni_modules/uni-refresh-box/package.json b/uni_modules/uni-refresh-box/package.json new file mode 100644 index 0000000..bf547bf --- /dev/null +++ b/uni_modules/uni-refresh-box/package.json @@ -0,0 +1,73 @@ +{ + "id": "uni-refresh-box", + "displayName": "uni-refresh-box", + "version": "1.0.0", + "description": "uni-refresh-box", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27848", + "keywords": [ + "uni-refresh-box", + "uni-app-x", + "uni-app x", + "组件" + ], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "^5.07", + "uni-app-x": "^5.07", + "uni-app": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-refresh-box/readme.md b/uni_modules/uni-refresh-box/readme.md new file mode 100644 index 0000000..a913370 --- /dev/null +++ b/uni_modules/uni-refresh-box/readme.md @@ -0,0 +1,80 @@ +自定义下拉刷新组件。 + +本组件封装了 scroll-view/list-view等滚动容器的下拉刷新功能,使用者只需在滚动容器中加入本组件,设置组件的属性和样式,即可实现自定义下拉刷新功能。 + +本组件自带一个全平台通用的下拉刷新样式,即左边一个 loading 圈,右边为下拉刷新相关的文字。也提供了各种自定义方法,包括文字、样式,甚至通过插槽传入完全不同的自定义下拉效果。 + +### 基本用法 + +```vue + + + +``` + +#### state 状态说明 + +| 值 | 说明 | +| :-- | :-- | +| 0 | 下拉中(未达到阈值) | +| 1 | 松手可刷新(已达到阈值) | +| 2 | 刷新中 | +| 3 | 刷新完成 | +| 4 | 归位中(不显示文字) | + +### Slots + +| 名称 | 说明 | 插槽参数 | +| :-- | :-- | :-- | +| loading | 自定义图标 | - | diff --git a/uni_modules/uni-tab-bar/changelog.md b/uni_modules/uni-tab-bar/changelog.md new file mode 100644 index 0000000..b2f5db9 --- /dev/null +++ b/uni_modules/uni-tab-bar/changelog.md @@ -0,0 +1,6 @@ +## 1.0.2(2026-07-16) +- 修复微信小程序滚动内容穿透底部 tab-bar +## 1.0.1(2026-07-07) +- 优化组件的宽度计算方式 +## 1.0.0(2026-04-30) +- 支持 uni-tab-bar 组件 diff --git a/uni_modules/uni-tab-bar/components/uni-tab-bar/uni-tab-bar.uvue b/uni_modules/uni-tab-bar/components/uni-tab-bar/uni-tab-bar.uvue new file mode 100644 index 0000000..4a3938b --- /dev/null +++ b/uni_modules/uni-tab-bar/components/uni-tab-bar/uni-tab-bar.uvue @@ -0,0 +1,139 @@ + + + + + diff --git a/uni_modules/uni-tab-bar/components/uni-tab-content/uni-tab-content.uvue b/uni_modules/uni-tab-bar/components/uni-tab-content/uni-tab-content.uvue new file mode 100644 index 0000000..cde6af7 --- /dev/null +++ b/uni_modules/uni-tab-bar/components/uni-tab-content/uni-tab-content.uvue @@ -0,0 +1,104 @@ + + + + + diff --git a/uni_modules/uni-tab-bar/components/uni-tab-item/uni-tab-item.uvue b/uni_modules/uni-tab-bar/components/uni-tab-item/uni-tab-item.uvue new file mode 100644 index 0000000..c6386e5 --- /dev/null +++ b/uni_modules/uni-tab-bar/components/uni-tab-item/uni-tab-item.uvue @@ -0,0 +1,187 @@ + + + + + diff --git a/uni_modules/uni-tab-bar/components/uni-tab-midbutton/uni-tab-midbutton.uvue b/uni_modules/uni-tab-bar/components/uni-tab-midbutton/uni-tab-midbutton.uvue new file mode 100644 index 0000000..5bbd3d8 --- /dev/null +++ b/uni_modules/uni-tab-bar/components/uni-tab-midbutton/uni-tab-midbutton.uvue @@ -0,0 +1,159 @@ + + + + + diff --git a/uni_modules/uni-tab-bar/components/uni-tab/uni-tab.uvue b/uni_modules/uni-tab-bar/components/uni-tab/uni-tab.uvue new file mode 100644 index 0000000..6d85fcf --- /dev/null +++ b/uni_modules/uni-tab-bar/components/uni-tab/uni-tab.uvue @@ -0,0 +1,222 @@ + + + + + diff --git a/uni_modules/uni-tab-bar/package.json b/uni_modules/uni-tab-bar/package.json new file mode 100644 index 0000000..5033ee7 --- /dev/null +++ b/uni_modules/uni-tab-bar/package.json @@ -0,0 +1,103 @@ +{ + "id": "uni-tab-bar", + "displayName": "uni-tab-bar 选项卡", + "version": "1.0.2", + "description": "底部选项卡组件集", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27849", + "examples": [ + { + "name": "tab-bar-dark", + "description": "暗色风格", + "url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-dark.uvue" + }, + { + "name": "tab-bar-custom", + "description": "自定义样式", + "url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-custom.uvue" + }, + { + "name": "tab-bar-blur", + "description": "半透明毛玻璃", + "url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-blur.uvue" + }, + { + "id": "uni-tab-midbutton", + "name": "tab-bar-midbutton", + "description": "中间按钮", + "url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-midbutton.uvue" + }, + { + "id": "uni-tab-midbutton", + "name": "tab-bar-midbutton-notch", + "description": "中间按钮镂空", + "url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-midbutton-notch.uvue" + } + ], + "keywords": [ + "uni-ui", + "uniui", + "自定义选项卡", + "uni-app-x", + "tab-bar" +], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "^5.07", + "uni-app": "", + "uni-app-x": "^5.07" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "√", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-tab-bar/readme.md b/uni_modules/uni-tab-bar/readme.md new file mode 100644 index 0000000..72ff30e --- /dev/null +++ b/uni_modules/uni-tab-bar/readme.md @@ -0,0 +1,93 @@ +自定义tab-bar组件, 包括一组前端组件: + +- `uni-tab`:最外层容器,负责激活索引、内容区高度和切换事件 +- `uni-tab-bar`:底部 tab 容器,负责底栏布局、安全区和中间按钮占位 +- `uni-tab-item`:普通 tab 项,负责插槽展示和 badge +- `uni-tab-midbutton`:中间悬浮按钮占位容器 +- `uni-tab-content`:上方内容区域容器,负责内容注册、懒渲染和显示切换 + +每个选项卡内容,不再是页面,而变成了uni-tab-content组件的子组件。 + +自定义tab-bar组件的使用方式是纯前端的、也是跨平台的。这与pages.json中的tabBar完全不同: +1. pages.json中的tabBar相关的api无法应用于自定义tab-bar,比如uni.switchTab、uni.showTabBar、uni.hideTabBar、uni.showTabBarRedDot、uni.hideTabBarRedDot、uni.setTabBarBadge、uni.removeTabBarBadge、uni.setTabBarStyle、uni.setTabBarItem。 +2. pages.json中的tabBar页面,生命周期仍然是页面生命周期。但自定义tab-bar的uni-tab-content内容不触发页面生命周期,只触发组件生命周期。 + +当然,自定义tab-bar组件拥有更强的功能、更灵活的控制方式。比如下凹式midbutton。在uni-app x的非小程序平台中,性能并不会比pages.json中的tabBar差。 + + +### 基础用法 + +```vue + +``` + +底栏主体高度默认为 `50px`。如果需要自定义,请在 `uni-tab` 上设置 `--uni-tab-bar-height` CSS 变量: + +```vue + + ... + +``` + +### 组件 API + +#### uni-tab + +##### 事件 + +| 事件名 | 返回参数 | 说明 | +| --- | --- | --- | +| `change` | `index: number` | 点击普通 tab 项切换后触发,返回新的激活索引 | + +#### uni-tab-bar + +无专用属性和API,可直接通过 `class` / `style` 自定义底栏外观。 + +#### uni-tab-content + +无专用属性和API,可直接通过 `class` / `style` 自定义外观。 + +多个选项卡内容揉在一个uni-tab中比较乱,一般推荐uni-tab-content下放一个子组件,子组件作为独立uvue文件。 + +各个uni-tab-content的子组件,在显示和隐藏的没有生命周期。 +如需监听显示/隐藏需要通过provide/inject方式从外层传入响应式变量activeIndex,然后在子组件中watch这个activeIndex。 + +### 使用注意事项 + +1. `uni-tab` 所在的页面不应该滚动,应该在pages.json中配置页面style为`disableScroll:true`。 +2. 屏幕变化适配,Android需HBuilderX 5.09+支持。之前版本组件里无法监听页面的onResize。 +3. `uni-tab-content` 与 `uni-tab-item` 的数量必须一致,顺序也必须一一对应,否则切换后的内容会错位。 +4. `uni-tab` 默认占满页面的宽度和高度,即 `style="flex: 1"` +5. 自定义底栏高度时,请在 `uni-tab` 上设置带长度单位的 `--uni-tab-bar-height` CSS 变量,例如 `--uni-tab-bar-height: 40px`。底栏和需要显式计算高度的内容区会共用该变量。 +6. 如果底栏存在镂空、下凹或透明边缘,需要让底部内容透出来,请在 `uni-tab` 上开启 `tab-content-height-full`,此时内容区会延伸到 `uni-tab-bar` 下方。 +7. `uni-tab-content` 采用“首次激活再渲染”的策略;第一次切入前不会创建实例,切走后只隐藏不销毁,适合保留 tab 内部状态。 +8. `uni-tab-item` 只负责普通 tab 项注册与展示,不提供单独点击事件;请统一监听 `uni-tab` 的 `change`。 +9. 使用 `uni-tab-midbutton` 时,中间按钮左右两侧的普通 `uni-tab-item` 数量需要相等,也就是普通项总数必须为偶数,才能保持居中均分布局。 +10. `uni-tab-midbutton` 只负责预留中间按钮位置,不参与激活索引计算;中间按钮点击行为需要在插槽内部自行处理。 +11. `badge-text` 传空字符串时显示红点,传 `'0'` 时不显示;如果需要自定义 badge 外观,请使用 `badge-class` 覆盖样式。 +12. `uni-tab-bar`、`uni-tab-item`、`uni-tab-content` 的根节点都支持直接挂 `class` / `style`,推荐把视觉差异放在页面侧处理,不要改组件内部逻辑。 +13. 微信小程序提供了一种webview方式渲染的底部tabBar方式,来进行tabBar自定义。这种方式仅微信小程序支持,与本组件无关。 diff --git a/uni_modules/uni-tab-bar/todo.md b/uni_modules/uni-tab-bar/todo.md new file mode 100644 index 0000000..dedeb77 --- /dev/null +++ b/uni_modules/uni-tab-bar/todo.md @@ -0,0 +1,10 @@ +## 中间凸起 +非下沉的,中间凸起的midbutton页面,缺少一个平滑的过渡曲线 + +## 左右滑 +Android常见,微信 +避免和页面内的内容冲突 + +## tab-bar高斯模糊 +等view支持高斯模糊的css后,补一个示例页面即可 + diff --git a/uni_modules/uni-time-format/changelog.md b/uni_modules/uni-time-format/changelog.md new file mode 100644 index 0000000..9b36732 --- /dev/null +++ b/uni_modules/uni-time-format/changelog.md @@ -0,0 +1,2 @@ +## 1.0.0(2026-04-30) +- 支持 uni-time-format 组件 diff --git a/uni_modules/uni-time-format/components/uni-time-format/uni-time-format.uvue b/uni_modules/uni-time-format/components/uni-time-format/uni-time-format.uvue new file mode 100644 index 0000000..028dba3 --- /dev/null +++ b/uni_modules/uni-time-format/components/uni-time-format/uni-time-format.uvue @@ -0,0 +1,290 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/uni-time-format/package.json b/uni_modules/uni-time-format/package.json new file mode 100644 index 0000000..cba8d9f --- /dev/null +++ b/uni_modules/uni-time-format/package.json @@ -0,0 +1,73 @@ +{ + "id": "uni-time-format", + "displayName": "uni-time-format 时间格式化", + "version": "1.0.0", + "description": "TimeFormat 时间格式化组件,可自定义时间格式化。", + "isExt": true, + "extUrl": "https://ext.dcloud.net.cn/plugin?id=27851", + "keywords": [ + "uni-ui", + "uniui", + "time-format", + "uni-app-x" + ], + "engines": { + "HBuilderX": "^5.07", + "uni-app-x": "^5.07", + "uni-app": "" + }, + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "type": "component-vue", + "darkmode": "x", + "i18n": "x", + "widescreen": "x", + "npmurl": "" + }, + "uni_modules": { + "encrypt": [], + "dependencies": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "√", + "chrome": "√" + }, + "app": { + "android": "√", + "ios": "√", + "harmony": "√" + }, + "mp": { + "weixin": "√" + } + } + } + } + } +} diff --git a/uni_modules/uni-time-format/readme.md b/uni_modules/uni-time-format/readme.md new file mode 100644 index 0000000..639d7a1 --- /dev/null +++ b/uni_modules/uni-time-format/readme.md @@ -0,0 +1,40 @@ +时间格式化展示组件,支持绝对时间与相对时间。 + +### 基本用法 + +```html + +``` + +### relative 规则 + +`format=relative` 按自然日输出: +- 今天内: +`< 1 分钟` 输出“刚刚” +`< 60 分钟` 输出“n分钟前” +`>= 60 分钟` 输出“n小时前” +- 昨天内:输出“昨天 HH:mm” +- 超过昨天:输出“YYYY-MM-DD HH:mm” +- 未来时间:直接输出“YYYY-MM-DD HH:mm” + +### 相对时间国际化 + +根据 `uni.getAppBaseInfo().appLanguage` 自动匹配: +- 简体中文(`zh`) +- 繁体中文(`zh-hant` / `zh-tw` / `zh-hk` / `zh-mo`) +- 英文(默认) +- 法文(`fr`) +- 拉丁文(`la`) + +法文、拉丁文使用短文案,避免长文本占位。 + +### 非法时间 + +当 `timestamp` 非法时,组件返回空字符串。 + +### 示例页面 + +`/pages/uni-ui/time-format/time-format` +示例页面包含两组相对时间演示: +- 输入框动态演示:输入 `yyyy-mm-dd hh:mm:ss`,实时展示对应相对时间 +- 固定分支演示:展示“刚刚 / 分钟前 / 小时前 / 昨天 HH:mm / 超过昨天”五种分支,且不随输入变化 diff --git a/uni_modules/uni-ui-x/changelog.md b/uni_modules/uni-ui-x/changelog.md new file mode 100644 index 0000000..0d827d4 --- /dev/null +++ b/uni_modules/uni-ui-x/changelog.md @@ -0,0 +1,19 @@ +## 1.0.6 (2026-10-08) +- uni-combox 修复安卓蒸汽模式会出现内容不显示的问题 +## 1.0.5 (2026-09-09) +- uni-tab-bar 优化组件的宽度计算方式 +- uni-tab-bar 修复微信小程序滚动内容穿透底部 tab-bar +## 1.0.4(2026-05-26) +- uni-badge-view 修复类名拼写错误 +- uni-nav-bar 纠正类名拼写错误 +## 1.0.3(2026-05-15) +- uni-collapse 支持 content-wrap-class 定义内层容器样式 +## 1.0.2(2026-05-12) +- uni-badge-view 更新文档 +- uni-index-bar 更新文档 +- uni-number-box 移除无用依赖 +- uni-rate 修复 starClass 和 starActiveClass 设置不生效的问题 +## 1.0.1(2026-05-07) +- 新增 uni-collapse、uni-link、uni-nav-bar、uni-number-box、uni-rate 组件 +## 1.0.0(2026-04-30) +- 支持 uni-ui-x 组件库 diff --git a/uni_modules/uni-ui-x/components/uni-ui-x/uni-ui-x.uvue b/uni_modules/uni-ui-x/components/uni-ui-x/uni-ui-x.uvue new file mode 100644 index 0000000..e69de29 diff --git a/uni_modules/uni-ui-x/package.json b/uni_modules/uni-ui-x/package.json new file mode 100644 index 0000000..1c0b4b7 --- /dev/null +++ b/uni_modules/uni-ui-x/package.json @@ -0,0 +1,106 @@ +{ + "id": "uni-ui-x", + "displayName": "uni-ui-x", + "version": "1.0.6", + "description": "uni-ui-x是DCloud为uni-app x提供的组件库", + "isExt": true, + "keywords": [ + "uni-ui-x", + "UI组件库", + "ui框架", + "ui库", + "uni-app-x" +], + "engines": { + "HBuilderX": "", + "uni-app-x": "^5.08", + "uni-app": "" + }, + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "type": "component-vue", + "darkmode": "√", + "i18n": "x", + "widescreen": "√", + "npmurl": "", + "vapor": "√" + }, + "uni_modules": { + "dependencies": [ + "uni-badge-view", + "uni-collapse-x", + "uni-drag-cell", + "uni-fab-button", + "uni-index-bar", + "uni-link-x", + "uni-nav-bar-x", + "uni-number-box-x", + "uni-rate-x", + "uni-refresh-box", + "uni-tab-bar", + "uni-time-format", + "uni-combox-x" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "x", + "aliyun": "x", + "alipay": "x" + }, + "client": { + "uni-app-x": { + "web": { + "safari": { + "extVersion": "1.0.3", + "minVersion": "" + }, + "chrome": { + "extVersion": "1.0.3", + "minVersion": "" + } + }, + "app": { + "android": { + "extVersion": "1.0.3", + "minVersion": "" + }, + "ios": { + "extVersion": "1.0.3", + "minVersion": "" + }, + "harmony": { + "extVersion": "1.0.3", + "minVersion": "" + } + }, + "mp": { + "weixin": { + "extVersion": "1.0.3", + "minVersion": "" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-ui-x/readme.md b/uni_modules/uni-ui-x/readme.md new file mode 100644 index 0000000..62c5fdc --- /dev/null +++ b/uni_modules/uni-ui-x/readme.md @@ -0,0 +1,127 @@ +## 项目介绍 + +uni-ui x,是DCloud为 uni-app x 提供的扩展组件库。[开源库地址](https://gitcode.com/dcloud/uni-ui-x/tree/alpha) + +uni-ui x 是全新设计的,与为uni-app做的uni-ui不同。 + +uni-ui x 是伴随着HBuilderX 5.08发版的。5.07版本上有部分组件在部分平台有兼容性问题,详见组件详情的兼容性表格。再往前的HBuilderX版本未测试。 + +## 特点 + +- **性能优先** + +uni-ui x 非常注重性能,在DOM层级控制、包体积、代码执行时间方面精益求精。 + +它了解 uni-app x 蒸汽模式的最佳实践,尽可能利用模板和样式的预编译特性来提升性能。当然它也支持非蒸汽模式。 + +- **合理的抽象** + +一个组件的抽象能力非常重要,如何在自定义性、易用性、性能之间尽可能三全其美。 + +很多组件为了满足使用者的需求,封装的属性越来越多,但组件使用者仍然在抱怨自定义性不足。 + +尤其是组件把样式封装在组件属性上,造成用户需要自定义样式时无限封装属性,并且这些属性的控制无法使用css变量。 + +uni-ui x 提供了完全的样式自定义能力,所有的样式定义都可以通过class设置,组件属性只控制功能逻辑。 + +* 组件的根节点样式,可以在使用组件时的组件class或style上设置。 +* 组件的子节点样式,可以通过[externalClass](https://doc.dcloud.net.cn/uni-app-x/css/common/style-isolation.html#external-class)设置样式。子组件开放自己的样式出来,外部可通过externalClass进行子组件的样式自定义。 + +组件使用者完全可以通过css自己控制所有样式。无需修改组件源码。加上css变量加持,灵活度远高于其他组件库。 + +使用者可以通过css变量完全自定义自己的主题,而不会被组件属性上不能写css变量困扰。 + +- **为AI设计** + +uni-ui x 优先代码而不是二进制文件。目前没有引入字体文件。这出于性能考虑,也出于AI友好度考虑。 + +以箭头为例,uni-ui x 中使用一个view配置左下边框并transform旋转来实现,这种设计性能比字体更好,且AI可以方便的理解这段代码的UI表现,可以在代码层面调整样式。 + +如果是字体图片,AI很难理解、生成、修改。 + +再举一个例子,uni-tab下沉midbutton,涉及一个贝塞尔曲线,此时没有使用图片,而是使用svg,同样是为了方便AI理解和修改。 + +- **中性** + +uni-ui x的风格是中性的,和uni内置组件、小程序内置组件一样,重视是开发者可以自定义成各种风格,而不是为开发者提供某套风格。 + +开发者可以通过自定义css,形成自己的风格,或者交给AI完成类似工作。 + +uni-ui x 没有写死的颜色和文字: + +* 没有写死的文字,不影响国际化。 +* 没有写死的颜色,不影响自定义主题。 + + +### uni-ui x 已支持的组件列表 + +| 组件名 | 组件说明 | +| --- | --- | +| uni-badge-view | [数字角标](https://ext.dcloud.net.cn/plugin?name=uni-badge-view) | +| uni-collapse | [折叠面板](https://ext.dcloud.net.cn/plugin?name=uni-collapse-x) | +| uni-drag-cell | [可拖拽排序组件](https://ext.dcloud.net.cn/plugin?name=uni-drag-cell) | +| uni-fab-button | [uni-fab-button](https://ext.dcloud.net.cn/plugin?name=uni-fab-button) | +| uni-index-bar | [uni-index-bar](https://ext.dcloud.net.cn/plugin?name=uni-index-bar) | +| uni-link | [链接](https://ext.dcloud.net.cn/plugin?name=uni-link-x) | +| uni-nav-bar | [uni-nav-bar](https://ext.dcloud.net.cn/plugin?name=uni-nav-bar-x) | +| uni-number-box | [数字输入框](https://ext.dcloud.net.cn/plugin?name=uni-number-box-x) | +| uni-rate | [评分](https://ext.dcloud.net.cn/plugin?name=uni-rate-x) | +| uni-refresh-box | [uni-refresh-box](https://ext.dcloud.net.cn/plugin?name=uni-refresh-box) | +| uni-tab | [选项卡](https://ext.dcloud.net.cn/plugin?name=uni-tab-bar) | +| uni-time-format | [时间格式化](https://ext.dcloud.net.cn/plugin?name=uni-time-format) | + + +## uni-ui的升级建议@uniuiupgrade + +为uni-app提供的uni-ui,升级到uni-app x时无法直接对照升级,因为组件名称、用法都有变化。 + +但升级时,老版uni-ui组件在uni-app x下同等功能的新组件是什么,这里说明下功能映射关系: + +|uni-ui组件 |中文名称 |在uni-app x下的方案 | +|-- |-- |-- | +|uni-badge |数字角标 |改用uni-badge-view | +|uni-calendar |日历 |见hello uni-app x里的模板中的日历页面 | +|uni-card |卡片 |无 | +|uni-collapse |折叠面板 |改用uni-collapse | +|uni-combox |组合框 |无 | +|uni-countdown |倒计时 |无 | +|uni-data-checkbox |数据选择器 |无 | +|uni-data-picker |数据驱动的picker选择器 |组件本身支持uni-app x | +|uni-dateformat |日期格式化 |改用uni-time-format | +|uni-datetime-picker |日期选择器 |内置组件picker支持mode=date | +|uni-drawer |抽屉 |改用内置组件page-container | +|uni-easyinput |增强输入框 |无 | +|uni-fab |悬浮按钮 |改用uni-fab-button | +|uni-fav |收藏按钮 |无 | +|uni-file-picker |文件选择上传 |无 | +|uni-forms |表单 |改用内置组件form | +|uni-goods-nav |商品导航 |无 | +|uni-grid |宫格 |无 | +|uni-group |分组 |无 | +|uni-icons |图标 |无 | +|uni-indexed-list |索引列表 |改用uni-index-bar | +|uni-link |超链接 |改用uni-link | +|uni-list |列表 |使用内置组件list | +|uni-load-more |加载更多 |使用内置组件loading | +|uni-nav-bar |自定义导航栏 |改用uni-nav-bar | +|uni-notice-bar |通告栏 |无 | +|uni-number-box |数字输入框 |改用uni-number-box | +|uni-pagination |分页器 |无 | +|uni-popup |弹出层 |改用内置组件page-container | +|uni-rate |评分 |改用uni-rate | +|uni-row |布局-行 |无 | +|uni-search-bar |搜索栏 |无 | +|uni-segmented-control|分段器 |无 | +|uni-steps |步骤条 |无 | +|uni-swipe-action |滑动操作 |参考hello uni-app x模板中可左滑删除长列表示例 | +|uni-swiper-dot |轮播图指示点 |改用内置组件swiper,通过externalClass控制指示点样式 | +|uni-table |表格 |推荐使用rich-text或web-view中的table。 | +|uni-tag |标签 |无 | +|uni-title |章节标题 |无 | +|uni-transition |过渡动画 |无 | + +标记为`无`的,大多比较简单,对于AI而言直接写相关UI比下载学习一个组件更合适。 + +## 交流群 + +有关 uni-ui-x 组件的任何意见或建议,欢迎通过 [uni-ui-x im 交流群](https://ext.dcloud.net.cn/publisher/start-session?pluginId=27852) 沟通交流。 diff --git a/uni_modules/uts-openSchema/changelog.md b/uni_modules/uts-openSchema/changelog.md new file mode 100644 index 0000000..5a1a2cc --- /dev/null +++ b/uni_modules/uts-openSchema/changelog.md @@ -0,0 +1,2 @@ +## 1.0.0(2026-04-30) +- 支持 uts-openSchema API diff --git a/uni_modules/uts-openSchema/package.json b/uni_modules/uts-openSchema/package.json new file mode 100644 index 0000000..65008f3 --- /dev/null +++ b/uni_modules/uts-openSchema/package.json @@ -0,0 +1,75 @@ +{ + "id": "uts-openSchema", + "displayName": "uts-openSchema", + "version": "1.0.0", + "description": "在 Android、iOS、HarmonyOS 上打开链接的 UTS API", + "keywords": [ + "uts-openSchema" + ], + "repository": "https://gitcode.com/dcloud/uni-ui-x", + "engines": { + "HBuilderX": "^4.0", + "uni-app-x": "^4.75" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "type": "uts", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "darkmode": "x", + "i18n": "x", + "widescreen": "x" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "√", + "aliyun": "√", + "alipay": "√" + }, + "client": { + "uni-app-x": { + "web": { + "safari": "x", + "chrome": "x" + }, + "app": { + "android": { + "extVersion": "1.0.0", + "minVersion": "21" + }, + "ios": { + "extVersion": "1.0.0", + "minVersion": "12" + }, + "harmony": { + "extVersion": "1.1.2", + "minVersion": "5.0.0" + } + }, + "mp": { + "weixin": "x" + } + } + } + } + } +} diff --git a/uni_modules/uts-openSchema/readme.md b/uni_modules/uts-openSchema/readme.md new file mode 100644 index 0000000..b31db45 --- /dev/null +++ b/uni_modules/uts-openSchema/readme.md @@ -0,0 +1,57 @@ +打开链接,支持: + +1. 打开外部 App +2. 使用浏览器打开链接 +3. 打开地图到指定地点 +4. ... + +## 使用 + +1. 安装此插件 +2. 在要使用的地方 `import` 导入 + ```ts + import { openSchema, canOpenURL } from '@/uni_modules/uts-openSchema' + ``` +3. 直接调用 `openSchema` 方法: + ```ts + // #ifdef UNI-APP-X + // 使用外部浏览器打开指定URL + openSchema('https://uniapp.dcloud.io/uni-app-x') + + // #ifdef APP-ANDROID + // Android 使用应用商店打开指定App + openSchema('market://details?id=com.tencent.mm') + + // Android 打开地图坐标 + // 可以先用canOpenURL判断是否安装了地图软件 + if (canOpenURL('androidamap://')) { + openSchema('androidamap://viewMap?sourceApplication=Hello%20uni-app&poiname=DCloud&lat=39.9631018208&lon=116.3406135236&dev=0') + } else { + console.log('未安装高德地图') + } + // #endif --> + + // #ifdef APP-IOS + // 打开 AppStore 到搜索页 + openSchema('itms-apps://search.itunes.apple.com//WebObjects//MZSearch.woa/wa/search?media=software<erm=') + + // 打开 iOS 地图坐标 + openSchema('http://maps.apple.com/?q=Mexican+Restaurant&sll=50.894967,4.341626&z=10&t=s') + // #endif --> + + // #endif --> + ``` + +### 参数 + +- openSchema(url: string) // `url`:要打开的链接 `必填` `不为空字符串` + +## 相关开发文档 + +[UTS 语法](https://uniapp.dcloud.net.cn/tutorial/syntax-uts.html) + +[UTS API插件](https://uniapp.dcloud.net.cn/plugin/uts-plugin.html) + +[UTS 组件插件](https://uniapp.dcloud.net.cn/plugin/uts-component.html) + +[Hello UTS](https://gitcode.net/dcloud/hello-uts) diff --git a/uni_modules/uts-openSchema/utssdk/app-android/config.json b/uni_modules/uts-openSchema/utssdk/app-android/config.json new file mode 100644 index 0000000..5defc8f --- /dev/null +++ b/uni_modules/uts-openSchema/utssdk/app-android/config.json @@ -0,0 +1,3 @@ +{ + "minSdkVersion": "21" +} diff --git a/uni_modules/uts-openSchema/utssdk/app-android/index.uts b/uni_modules/uts-openSchema/utssdk/app-android/index.uts new file mode 100644 index 0000000..ae2f7de --- /dev/null +++ b/uni_modules/uts-openSchema/utssdk/app-android/index.uts @@ -0,0 +1,27 @@ +import Intent from 'android.content.Intent' +import Uri from 'android.net.Uri' +import { OpenSchema, CanOpenURL } from '../interface.uts' + +export const openSchema : OpenSchema = function (url : string) { + if (canOpenURL(url)) { + const context = UTSAndroid.getUniActivity()! + const uri = Uri.parse(url) + const intent = new Intent(Intent.ACTION_VIEW, uri) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.setData(uri) + context.startActivity(intent) + } else { + console.error('[uts-openSchema] url param Error:', JSON.stringify(url)) + } +} + +export const canOpenURL : CanOpenURL = function (url : string) : boolean { + if (typeof url === 'string' && url.length > 0) { + const context = UTSAndroid.getUniActivity()! + const uri = Uri.parse(url) + const intent = new Intent(Intent.ACTION_VIEW, uri) + return intent.resolveActivity(context.packageManager) != null ? true : false + } else { + return false + } +} diff --git a/uni_modules/uts-openSchema/utssdk/app-harmony/index.uts b/uni_modules/uts-openSchema/utssdk/app-harmony/index.uts new file mode 100644 index 0000000..06eedc5 --- /dev/null +++ b/uni_modules/uts-openSchema/utssdk/app-harmony/index.uts @@ -0,0 +1,19 @@ +import { bundleManager, common } from '@kit.AbilityKit' +import OpenLinkOptions from '@ohos.app.ability.OpenLinkOptions' +import { getAbilityContext } from '@dcloudio/uni-runtime' +import { OpenSchema, CanOpenURL } from '../interface.uts' + +export const openSchema : OpenSchema = function (url : string) : void { + ;(getAbilityContext() as common.UIAbilityContext)?.openLink(url, { + appLinkingOnly: false + } as OpenLinkOptions) +} + +export const canOpenURL : CanOpenURL = function (url : string) : boolean { + try { + return bundleManager.canOpenLink(url) + } catch (error) { + console.error('[uts-openSchema] url param Error:', JSON.stringify(url)) + return false + } +} diff --git a/uni_modules/uts-openSchema/utssdk/app-ios/config.json b/uni_modules/uts-openSchema/utssdk/app-ios/config.json new file mode 100644 index 0000000..86b8f0b --- /dev/null +++ b/uni_modules/uts-openSchema/utssdk/app-ios/config.json @@ -0,0 +1,3 @@ +{ + "deploymentTarget": "12.0" +} diff --git a/uni_modules/uts-openSchema/utssdk/app-ios/index.uts b/uni_modules/uts-openSchema/utssdk/app-ios/index.uts new file mode 100644 index 0000000..9adaf48 --- /dev/null +++ b/uni_modules/uts-openSchema/utssdk/app-ios/index.uts @@ -0,0 +1,22 @@ +import { UIApplication } from 'UIKit' +import { URL } from 'Foundation' +import { OpenSchema, CanOpenURL } from '../interface.uts' + +export const openSchema : OpenSchema = function (url : string) : void { + if (canOpenURL(url)) { + let uri = new URL(string = url) + UIApplication.shared.open(uri!) + } else { + console.error('[uts-openSchema] url param Error: ', url) + } +} + +export const canOpenURL : CanOpenURL = function (url : string) : boolean { + if (typeof url == 'string' && url.length > 0) { + let uri = new URL(string = url) + if (uri != null && UIApplication.shared.canOpenURL(uri!)) { + return true + } + } + return false +} diff --git a/uni_modules/uts-openSchema/utssdk/interface.uts b/uni_modules/uts-openSchema/utssdk/interface.uts new file mode 100644 index 0000000..2b599ac --- /dev/null +++ b/uni_modules/uts-openSchema/utssdk/interface.uts @@ -0,0 +1,2 @@ +export type OpenSchema = (url : string) => void +export type CanOpenURL = (url : string) => boolean diff --git a/uni_modules/uts-openSchema/utssdk/web/index.uts b/uni_modules/uts-openSchema/utssdk/web/index.uts new file mode 100644 index 0000000..7410334 --- /dev/null +++ b/uni_modules/uts-openSchema/utssdk/web/index.uts @@ -0,0 +1,12 @@ +import { OpenSchema, CanOpenURL } from '../interface.uts' + +export const openSchema : OpenSchema = function (url : string) : void { + location.href = url +} + +export const canOpenURL : CanOpenURL = function (url : string) : boolean { + if (url != '') { + return true + } + return false +} diff --git a/utils/request.ts b/utils/request.ts new file mode 100644 index 0000000..82acfb4 --- /dev/null +++ b/utils/request.ts @@ -0,0 +1,471 @@ +/** + * 网络请求封装 + * 支持请求拦截、响应拦截、Loading显示、错误处理 + */ + +// 响应数据类型 +export type ApiResponse = { + code: number + message: string + data: T | null +} + +// 请求配置类型 +export type RequestConfig = { + url: string + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' + data?: any + header?: any + showLoading?: boolean + loadingText?: string + showError?: boolean +} + +// 基础URL配置 - 虚拟接口 +const BASE_URL = 'https://mock-api.example.com' + +// 请求超时时间 +const TIMEOUT = 10000 + +// 联调前统一使用测试响应,保证所有页面流程可完整验证。 +const USE_MOCK_API = true + +// Mock 接口模拟正常网络环境,响应延迟范围为 0 到 2 秒。 +const MOCK_RESPONSE_MAX_DELAY = 2000 + +// Loading引用计数 +let loadingCount = 0 + +/** + * 显示Loading + */ +function showLoading(text: string = '加载中...') { + loadingCount++ + if (loadingCount === 1) { + uni.showLoading({ + title: text, + mask: true + }) + } +} + +/** + * 隐藏Loading + */ +function hideLoading() { + loadingCount-- + if (loadingCount <= 0) { + loadingCount = 0 + uni.hideLoading() + } +} + +/** + * 获取Token + */ +function getToken(): string { + return uni.getStorageSync('token') as string ?? '' +} + +/** + * 请求拦截器 - 添加token等 + */ +function requestInterceptor(config: RequestConfig): RequestConfig { + // 添加完整URL + if (!config.url.startsWith('http')) { + config.url = BASE_URL + config.url + } + + // 添加请求头 + const token = getToken() + config.header = config.header ?? {} + config.header['Content-Type'] = 'application/json' + + if (token) { + config.header['Authorization'] = `Bearer ${token}` + } + + // 显示Loading + if (config.showLoading !== false) { + showLoading(config.loadingText) + } + + return config +} + +/** + * 响应拦截器 - 统一处理响应 + */ +function responseInterceptor(response: UniNamespace.RequestSuccessCallbackResult, config: RequestConfig): ApiResponse | null { + // 隐藏Loading + if (config.showLoading !== false) { + hideLoading() + } + + // HTTP状态码检查 + if (response.statusCode !== 200) { + handleError(response.statusCode, '网络请求失败', config.showError) + return null + } + + // 解析响应数据 + const result = response.data as ApiResponse + + // 业务状态码检查 + if (result.code !== 0) { + handleError(result.code, result.message, config.showError) + return null + } + + return result +} + +/** + * 错误处理 + */ +function handleError(code: number, message: string, showError: boolean = true) { + console.error(`[Request Error] Code: ${code}, Message: ${message}`) + + if (!showError) { + return + } + + // 根据错误码显示不同提示 + let errorMsg = message + + if (code === 401) { + errorMsg = '登录已过期,请重新登录' + // 清除token并跳转登录页 + uni.removeStorageSync('token') + uni.removeStorageSync('userInfo') + uni.reLaunch({ + url: '/pages/login/index' + }) + } else if (code === 403) { + errorMsg = '没有权限访问' + } else if (code === 404) { + errorMsg = '请求的资源不存在' + } else if (code === 500) { + errorMsg = '服务器错误,请稍后重试' + } + + uni.showToast({ + title: errorMsg, + icon: 'none', + duration: 2000 + }) +} + +/** + * 根据接口生成页面流程所需的测试数据。 + */ +function createMockResponse(config: RequestConfig): ApiResponse { + const requestData = (config.data ?? {}) as Record + let data: any = null + + if ( + config.url.includes('/api/auth/login') || + config.url.includes('/api/auth/login-by-code') || + config.url.includes('/api/auth/register') + ) { + data = { + userId: 'test-parent-001', + phone: requestData.phone ?? '13800000000', + nickname: requestData.parentName ?? '测试家长', + avatar: '', + token: 'test-token', + refreshToken: 'test-refresh-token' + } + } else if (config.url.includes('/api/auth/send-code')) { + data = { expireTime: 300 } + } else if (config.url.includes('/api/auth/forgot-password/verify')) { + data = { token: 'test-reset-token' } + } else if (config.url.includes('/api/auth/refresh-token')) { + data = { token: 'test-token', refreshToken: 'test-refresh-token' } + } else if ( + config.url.includes('/api/student/verify-qrcode') || + config.url.includes('/api/student/verify-account') + ) { + const studentAccount = requestData.account ?? 'test-student' + data = { + id: `test-student-${studentAccount}`, + name: requestData.name ?? '测试学生', + account: studentAccount, + avatar: '', + grade: '三年级', + class: '一班' + } + } else if (config.url.includes('/api/parent/check-phone')) { + data = { exists: false } + } else if (config.url.includes('/api/parent/students')) { + data = { + currentStudentId: 'test-student-001', + students: [ + { id: 'test-student-001', name: '张一诺', account: '20241001', grade: '初二', school: '实验中学', avatar: '' }, + { id: 'test-student-002', name: '张一鸣', account: '20241002', grade: '五年级', school: '实验小学', avatar: '' }, + { id: 'test-student-003', name: '张一宁', account: '20241003', grade: '高二', school: '实验中学', avatar: '' } + ] + } + } else if (config.url.includes('/api/parent/home-metrics')) { + const isSecondStudent = config.url.includes('studentId=test-student-002') + data = isSecondStudent ? { + studentId: 'test-student-002', onlineToday: true, checkedInToday: false, learningMinutesToday: 78, + accuracyToday: 88, learningDaysThisWeek: 4, pendingHomeworkCount: 2, updatedAt: '2026-10-28T08:30:00+08:00' + } : { + studentId: 'test-student-001', onlineToday: true, checkedInToday: true, learningMinutesToday: 155, + accuracyToday: 92, learningDaysThisWeek: 5, pendingHomeworkCount: 1, updatedAt: '2026-10-28T08:30:00+08:00' + } + } else if (config.url.includes('/api/parent/today-learning')) { + const isSecondStudent = config.url.includes('studentId=test-student-002') + data = isSecondStudent ? { + studentId: 'test-student-002', studentName: '张一鸣', dateText: '10月13日 周一', checkedIn: false, + learningMinutes: 78, answerMinutes: 56, answerCount: 42, completedPackages: 3, totalPackages: 6, masteredPoints: 8, + score: { accuracy: 84, yesterdayAccuracy: 81, change: 3 }, pointsTotal: 28, + pointDetails: [ + { id: 'point-201', label: '每日登录', points: 10 }, + { id: 'point-202', label: '每日提交作业', points: 8 }, + { id: 'point-203', label: '连续打卡', points: 6 }, + { id: 'point-204', label: '攻克知识点', points: 4 } + ], + courses: [ + { id: 'course-201', time: '08:30', subject: '语文', content: '古诗词诵读与默写', checkedIn: true }, + { id: 'course-202', time: '10:00', subject: '数学', content: '分数应用题练习', checkedIn: true }, + { id: 'course-203', time: '15:30', subject: '英语', content: 'Unit 4 单词听写', checkedIn: false } + ] + } : { + studentId: 'test-student-001', studentName: '张一诺', dateText: '10月13日 周一', checkedIn: true, + learningMinutes: 155, answerMinutes: 118, answerCount: 68, completedPackages: 5, totalPackages: 6, masteredPoints: 12, + score: { accuracy: 89, yesterdayAccuracy: 85, change: 4 }, pointsTotal: 42, + pointDetails: [ + { id: 'point-101', label: '每日登录', points: 10 }, + { id: 'point-102', label: '每日提交作业', points: 12 }, + { id: 'point-103', label: '连续打卡', points: 8 }, + { id: 'point-104', label: '攻克知识点', points: 12 } + ], + courses: [ + { id: 'course-101', time: '08:00', subject: '语文', content: '《背影》精读与批注', checkedIn: true }, + { id: 'course-102', time: '10:20', subject: '数学', content: '一元二次方程专项训练', checkedIn: true }, + { id: 'course-103', time: '14:00', subject: '英语', content: 'Unit 5 单词听写与跟读', checkedIn: true }, + { id: 'course-104', time: '19:30', subject: '物理', content: '声现象章节测试', checkedIn: false } + ] + } + } else if (config.url.includes('/api/parent/vocabulary')) { + const isSecondStudent = config.url.includes('studentId=test-student-002') + data = isSecondStudent ? { + studentId: 'test-student-002', studentName: '张一鸣', studentGrade: '五年级', studentClass: '二班', bookCount: 4, + books: [ + { id: 'vocab-201', name: '人教版八年级上册', subtitle: '总词量 620 词', totalWords: 620, learnedWords: 420, status: 'learning', statusText: '在学', levelText: '初中同步', color: 'green' }, + { id: 'vocab-202', name: '中考核心词汇 1600', subtitle: '总词量 1600 词', totalWords: 1600, learnedWords: 280, status: 'unfinished', statusText: '未学完', levelText: '中考冲刺', color: 'yellow' }, + { id: 'vocab-203', name: '人教版三年级起点·五年级上', subtitle: '总词量 380 词', totalWords: 380, learnedWords: 380, status: 'completed', statusText: '已学完', levelText: '小学基础', color: 'blue' }, + { id: 'vocab-204', name: '高中必修 5 核心词汇', subtitle: '总词量 1080 词', totalWords: 1080, learnedWords: 0, status: 'not_started', statusText: '未开始', levelText: '高中同步', color: 'gray' } + ] + } : { + studentId: 'test-student-001', studentName: '张一诺', studentGrade: '初二', studentClass: '3班', bookCount: 4, + books: [ + { id: 'vocab-101', name: '人教版八年级上册', subtitle: '总词量 620 词', totalWords: 620, learnedWords: 520, status: 'learning', statusText: '在学', levelText: '初中同步', color: 'green' }, + { id: 'vocab-102', name: '中考核心词汇 1600', subtitle: '总词量 1600 词', totalWords: 1600, learnedWords: 860, status: 'unfinished', statusText: '未学完', levelText: '中考冲刺', color: 'yellow' }, + { id: 'vocab-103', name: '人教版三年级起点·五年级上', subtitle: '总词量 380 词', totalWords: 380, learnedWords: 380, status: 'completed', statusText: '已学完', levelText: '小学基础', color: 'blue' }, + { id: 'vocab-104', name: '高中必修 5 核心词汇', subtitle: '总词量 1080 词', totalWords: 1080, learnedWords: 0, status: 'not_started', statusText: '未开始', levelText: '高中同步', color: 'gray' } + ] + } + } else if (config.url.includes('/api/parent/learning-tasks')) { + const isSecondStudent = config.url.includes('studentId=test-student-002') + data = isSecondStudent ? { + studentId: 'test-student-002', studentName: '张一鸣', studentGrade: '五年级', studentClass: '二班', pendingCount: 2, completedCount: 3, failedCount: 0, + groups: [ + { id: 'task-group-201', subject: '生物', unit: '预习', lesson: '第 3 课节', startText: '今天 14:00', endText: '14:40 开课', knowledgeCount: 0, note: '学生尚未进入课程,暂无学习句与知识点,进入课程后将自动生成', tasks: [] }, + { id: 'task-group-202', subject: '物理', unit: '复习', lesson: '第 4 课节', startText: '今天 16:00', endText: '16:40 开课', knowledgeCount: 3, note: '', tasks: [{ id: 'task-201', title: '声音的特性', status: 'success', statusText: '攻克成功', accuracyText: '正确率 92%', answerText: '答题时长 6 分钟', deadlineText: '今天 16:35 完成' }, { id: 'task-202', title: '回声与测距', status: 'in_progress', statusText: '攻克中', accuracyText: '正确率 78%', answerText: '答题时长 12 分钟', deadlineText: '今晚 21:00 截止' }, { id: 'task-203', title: '噪声的防治', status: 'failed', statusText: '攻克失败', accuracyText: '', answerText: '', deadlineText: '' }] } + ] + } : { + studentId: 'test-student-001', studentName: '张一诺', studentGrade: '初二', studentClass: '3班', pendingCount: 3, completedCount: 5, failedCount: 1, + groups: [{ id: 'task-group-101', subject: '生物', unit: '预习', lesson: '第 3 课节', startText: '今天 14:00', endText: '14:40 开课', knowledgeCount: 0, note: '学生尚未进入课程,暂无学习句与知识点,进入课程后将自动生成', tasks: [] }, { id: 'task-group-102', subject: '物理', unit: '复习', lesson: '第 4 课节', startText: '今天 16:00', endText: '16:40 开课', knowledgeCount: 3, note: '', tasks: [{ id: 'task-101', title: '声音的特性', status: 'success', statusText: '攻克成功', accuracyText: '正确率 92%', answerText: '答题时长 6 分钟', deadlineText: '今天 16:35 完成' }, { id: 'task-102', title: '回声与测距', status: 'in_progress', statusText: '攻克中', accuracyText: '正确率 78%', answerText: '答题时长 12 分钟', deadlineText: '今晚 21:00 截止' }, { id: 'task-103', title: '噪声的防治', status: 'failed', statusText: '攻克失败', accuracyText: '', answerText: '', deadlineText: '' }] }] + } + } else if (config.url.includes('/api/parent/growth')) { + const isSecondStudent = config.url.includes('studentId=test-student-002') + data = isSecondStudent ? { + studentId: 'test-student-002', studentName: '张一鸣', studentGrade: '五年级', studentClass: '二班', pointsBalance: 862, level: 4, rank: 128, streakDays: 3, totalPoints: 24, + records: [ + { id: 'growth-201', title: '每日登录', timeText: '今天 07:35', points: 10, icon: 'calendar' }, + { id: 'growth-202', title: '每日提交作业', timeText: '今天 19:22', points: 8, icon: 'assignment' }, + { id: 'growth-203', title: '连续打卡', timeText: '今天 07:40', points: 6, icon: 'streak' }, + { id: 'growth-204', title: '正确率≥80%', timeText: '昨天 20:05', points: 10, icon: 'accuracy' }, + { id: 'growth-205', title: '攻克知识点', timeText: '昨天 19:40', points: 8, icon: 'mastery' }, + { id: 'growth-206', title: '上传笔记', timeText: '10月12日 18:20', points: 5, icon: 'note' } + ] + } : { + studentId: 'test-student-001', studentName: '张一诺', studentGrade: '初二', studentClass: '3班', pointsBalance: 1268, level: 7, rank: 86, streakDays: 3, totalPoints: 42, + records: [ + { id: 'growth-101', title: '每日登录', timeText: '今天 07:35', points: 10, icon: 'calendar' }, + { id: 'growth-102', title: '每日提交作业', timeText: '今天 19:22', points: 12, icon: 'assignment' }, + { id: 'growth-103', title: '连续打卡', timeText: '今天 07:40', points: 8, icon: 'streak' }, + { id: 'growth-104', title: '正确率≥80%', timeText: '昨天 20:05', points: 10, icon: 'accuracy' }, + { id: 'growth-105', title: '攻克知识点', timeText: '昨天 19:40', points: 12, icon: 'mastery' }, + { id: 'growth-106', title: '上传笔记', timeText: '10月12日 18:20', points: 5, icon: 'note' } + ] + } + } else if (config.url.includes('/api/parent/study-report')) { + const isSecondStudent = config.url.includes('studentId=test-student-002') + const subjects = [ + { id: 'all', name: '全部科目' }, { id: 'math', name: '数学' }, { id: 'chinese', name: '语文' }, + { id: 'english', name: '英语' }, { id: 'physics', name: '物理' } + ] + const studentOneWeekActivity = { summary: '周六学习最投入,周四效率最高。', activeDays: 6, totalDurationText: '14小时20分', points: [{ label: '周一', studyMinutes: 0, plannedMinutes: 120, newMasteredCount: 0, studyHeight: 0, plannedHeight: 49, newMasteredHeight: 0 }, { label: '周二', studyMinutes: 0, plannedMinutes: 95, newMasteredCount: 0, studyHeight: 0, plannedHeight: 39, newMasteredHeight: 0 }, { label: '周三', studyMinutes: 142, plannedMinutes: 120, newMasteredCount: 2, studyHeight: 58, plannedHeight: 49, newMasteredHeight: 34 }, { label: '周四', studyMinutes: 88, plannedMinutes: 88, newMasteredCount: 2, studyHeight: 36, plannedHeight: 36, newMasteredHeight: 34 }, { label: '周五', studyMinutes: 0, plannedMinutes: 148, newMasteredCount: 0, studyHeight: 0, plannedHeight: 61, newMasteredHeight: 0 }, { label: '周六', studyMinutes: 178, plannedMinutes: 166, newMasteredCount: 1, studyHeight: 74, plannedHeight: 68, newMasteredHeight: 17 }, { label: '周日', studyMinutes: 0, plannedMinutes: 152, newMasteredCount: 0, studyHeight: 0, plannedHeight: 62, newMasteredHeight: 0 }] } + const studentOneMonthActivity = { summary: '本月学习节奏稳定,周末自主学习投入更高。', activeDays: 24, totalDurationText: '52小时40分', points: [{ label: '第1周', studyMinutes: 610, plannedMinutes: 650, newMasteredCount: 3, studyHeight: 52, plannedHeight: 56, newMasteredHeight: 24 }, { label: '第2周', studyMinutes: 690, plannedMinutes: 660, newMasteredCount: 4, studyHeight: 60, plannedHeight: 57, newMasteredHeight: 33 }, { label: '第3周', studyMinutes: 720, plannedMinutes: 680, newMasteredCount: 4, studyHeight: 64, plannedHeight: 59, newMasteredHeight: 33 }, { label: '第4周', studyMinutes: 780, plannedMinutes: 700, newMasteredCount: 3, studyHeight: 72, plannedHeight: 61, newMasteredHeight: 24 }] } + const studentOneWeekMasteries = [{ subjectName: '数学', masteredCount: 38, totalCount: 40, masteredPercent: 95 }, { subjectName: '语文', masteredCount: 26, totalCount: 28, masteredPercent: 93 }, { subjectName: '英语', masteredCount: 18, totalCount: 33, masteredPercent: 55 }, { subjectName: '物理', masteredCount: 9, totalCount: 18, masteredPercent: 50 }] + const studentOneMonthMasteries = [{ subjectName: '数学', masteredCount: 112, totalCount: 120, masteredPercent: 93 }, { subjectName: '语文', masteredCount: 84, totalCount: 92, masteredPercent: 91 }, { subjectName: '英语', masteredCount: 68, totalCount: 96, masteredPercent: 71 }, { subjectName: '物理', masteredCount: 42, totalCount: 68, masteredPercent: 62 }] + const studentTwoWeekActivity = { summary: '本周学习节奏平稳,周末阅读投入较多。', activeDays: 5, totalDurationText: '11小时10分', points: [{ label: '周一', studyMinutes: 82, plannedMinutes: 100, newMasteredCount: 1, studyHeight: 34, plannedHeight: 42, newMasteredHeight: 17 }, { label: '周二', studyMinutes: 0, plannedMinutes: 92, newMasteredCount: 0, studyHeight: 0, plannedHeight: 38, newMasteredHeight: 0 }, { label: '周三', studyMinutes: 110, plannedMinutes: 105, newMasteredCount: 1, studyHeight: 46, plannedHeight: 43, newMasteredHeight: 17 }, { label: '周四', studyMinutes: 96, plannedMinutes: 100, newMasteredCount: 1, studyHeight: 40, plannedHeight: 42, newMasteredHeight: 17 }, { label: '周五', studyMinutes: 0, plannedMinutes: 110, newMasteredCount: 0, studyHeight: 0, plannedHeight: 46, newMasteredHeight: 0 }, { label: '周六', studyMinutes: 152, plannedMinutes: 128, newMasteredCount: 2, studyHeight: 63, plannedHeight: 53, newMasteredHeight: 34 }, { label: '周日', studyMinutes: 130, plannedMinutes: 126, newMasteredCount: 1, studyHeight: 54, plannedHeight: 52, newMasteredHeight: 17 }] } + const studentTwoMonthActivity = { summary: '本月完成大部分学习计划,建议保持阅读习惯。', activeDays: 21, totalDurationText: '45小时30分', points: [{ label: '第1周', studyMinutes: 520, plannedMinutes: 600, newMasteredCount: 2, studyHeight: 44, plannedHeight: 51, newMasteredHeight: 17 }, { label: '第2周', studyMinutes: 590, plannedMinutes: 610, newMasteredCount: 3, studyHeight: 50, plannedHeight: 52, newMasteredHeight: 24 }, { label: '第3周', studyMinutes: 640, plannedMinutes: 620, newMasteredCount: 2, studyHeight: 55, plannedHeight: 53, newMasteredHeight: 17 }, { label: '第4周', studyMinutes: 670, plannedMinutes: 640, newMasteredCount: 3, studyHeight: 58, plannedHeight: 55, newMasteredHeight: 24 }] } + const studentTwoWeekMasteries = [{ subjectName: '数学', masteredCount: 32, totalCount: 40, masteredPercent: 80 }, { subjectName: '语文', masteredCount: 24, totalCount: 28, masteredPercent: 86 }, { subjectName: '英语', masteredCount: 16, totalCount: 30, masteredPercent: 53 }, { subjectName: '科学', masteredCount: 12, totalCount: 22, masteredPercent: 55 }] + const studentTwoMonthMasteries = [{ subjectName: '数学', masteredCount: 92, totalCount: 120, masteredPercent: 77 }, { subjectName: '语文', masteredCount: 76, totalCount: 92, masteredPercent: 83 }, { subjectName: '英语', masteredCount: 58, totalCount: 90, masteredPercent: 64 }, { subjectName: '科学', masteredCount: 38, totalCount: 68, masteredPercent: 56 }] + data = isSecondStudent ? { + studentId: 'test-student-002', studentName: '张一鸣', studentGrade: '五年级', studentClass: '二班', updatedAtText: '10月13日 08:30', subjects, + week: { + rangeText: '10月7日 - 10月13日', statusTitle: '本周学习状态良好', statusDescription: '本周学习完成度稳定,建议保持每天的阅读与口算练习。', + newMasteredPoints: 2, consecutiveDays: 8, averageAccuracy: 86, scaleLabels: ['120个', '116个', '112个', '108个'], + trends: [ + { subjectId: 'all', points: [{ label: '周一', value: 109, average: 110, valueHeight: 24, averageHeight: 30 }, { label: '周二', value: 110, average: 111, valueHeight: 30, averageHeight: 35 }, { label: '周三', value: 111, average: 111, valueHeight: 36, averageHeight: 35 }, { label: '周四', value: 112, average: 112, valueHeight: 42, averageHeight: 42 }, { label: '周五', value: 112, average: 113, valueHeight: 42, averageHeight: 48 }, { label: '周六', value: 114, average: 113, valueHeight: 54, averageHeight: 48 }, { label: '周日', value: 115, average: 114, valueHeight: 60, averageHeight: 54 }] }, + { subjectId: 'math', points: [{ label: '周一', value: 108, average: 109, valueHeight: 18, averageHeight: 24 }, { label: '周二', value: 109, average: 110, valueHeight: 24, averageHeight: 30 }, { label: '周三', value: 110, average: 110, valueHeight: 30, averageHeight: 30 }, { label: '周四', value: 111, average: 111, valueHeight: 36, averageHeight: 36 }, { label: '周五', value: 112, average: 112, valueHeight: 42, averageHeight: 42 }, { label: '周六', value: 113, average: 112, valueHeight: 48, averageHeight: 42 }, { label: '周日', value: 114, average: 113, valueHeight: 54, averageHeight: 48 }] } + ], activity: studentTwoWeekActivity, subjectMasteries: studentTwoWeekMasteries + }, + month: { + rangeText: '9月14日 - 10月13日', statusTitle: '本月学习状态稳步提升', statusDescription: '本月坚持学习,多个学科的知识点掌握情况有所提升。', + newMasteredPoints: 9, consecutiveDays: 8, averageAccuracy: 87, scaleLabels: ['120个', '116个', '112个', '108个'], + trends: [{ subjectId: 'all', points: [{ label: '第1周', value: 107, average: 108, valueHeight: 14, averageHeight: 18 }, { label: '第2周', value: 110, average: 109, valueHeight: 30, averageHeight: 24 }, { label: '第3周', value: 113, average: 111, valueHeight: 46, averageHeight: 36 }, { label: '第4周', value: 116, average: 113, valueHeight: 62, averageHeight: 48 }] }], activity: studentTwoMonthActivity, subjectMasteries: studentTwoMonthMasteries + } + } : { + studentId: 'test-student-001', studentName: '张一诺', studentGrade: '初二', studentClass: '3班', updatedAtText: '10月13日 08:30', subjects, + week: { + rangeText: '10月7日 - 10月13日', statusTitle: '本周学习状态良好', statusDescription: '本周状态良好,数学与英语逐渐稳定,物理薄弱题型进步明显。', + newMasteredPoints: 3, consecutiveDays: 12, averageAccuracy: 90, scaleLabels: ['120个', '116个', '112个', '108个'], + trends: [ + { subjectId: 'all', points: [{ label: '周一', value: 112, average: 111, valueHeight: 32, averageHeight: 26 }, { label: '周二', value: 112, average: 113, valueHeight: 32, averageHeight: 38 }, { label: '周三', value: 113, average: 113, valueHeight: 40, averageHeight: 38 }, { label: '周四', value: 114, average: 114, valueHeight: 48, averageHeight: 48 }, { label: '周五', value: 114, average: 114, valueHeight: 48, averageHeight: 48 }, { label: '周六', value: 116, average: 114, valueHeight: 64, averageHeight: 48 }, { label: '周日', value: 116, average: 115, valueHeight: 64, averageHeight: 56 }] }, + { subjectId: 'math', points: [{ label: '周一', value: 110, average: 110, valueHeight: 20, averageHeight: 20 }, { label: '周二', value: 111, average: 111, valueHeight: 28, averageHeight: 28 }, { label: '周三', value: 113, average: 112, valueHeight: 42, averageHeight: 35 }, { label: '周四', value: 114, average: 112, valueHeight: 49, averageHeight: 35 }, { label: '周五', value: 115, average: 113, valueHeight: 56, averageHeight: 42 }, { label: '周六', value: 116, average: 113, valueHeight: 63, averageHeight: 42 }, { label: '周日', value: 117, average: 114, valueHeight: 70, averageHeight: 49 }] } + ], activity: studentOneWeekActivity, subjectMasteries: studentOneWeekMasteries + }, + month: { + rangeText: '9月14日 - 10月13日', statusTitle: '本月学习状态持续向好', statusDescription: '本月学习计划完成度高,学科掌握情况保持上升趋势。', + newMasteredPoints: 14, consecutiveDays: 12, averageAccuracy: 89, scaleLabels: ['120个', '116个', '112个', '108个'], + trends: [{ subjectId: 'all', points: [{ label: '第1周', value: 108, average: 109, valueHeight: 16, averageHeight: 23 }, { label: '第2周', value: 111, average: 111, valueHeight: 38, averageHeight: 38 }, { label: '第3周', value: 114, average: 112, valueHeight: 53, averageHeight: 45 }, { label: '第4周', value: 116, average: 114, valueHeight: 68, averageHeight: 53 }] }], activity: studentOneMonthActivity, subjectMasteries: studentOneMonthMasteries + } + } + } else if (config.url.includes('/api/parent/alerts')) { + const isSecondStudent = config.url.includes('studentId=test-student-002') + const alerts = isSecondStudent ? [ + { id: 'alert-201', level: 'medium', title: '作业未完成', content: '英语《单元词汇练习》还有 2 题未完成', occurredAt: '2026-10-28T09:20:00+08:00', isRead: false }, + { id: 'alert-202', level: 'low', title: '学习时长提醒', content: '今日学习时长低于本周日均时长', occurredAt: '2026-10-28T08:40:00+08:00', isRead: true } + ] : [ + { id: 'alert-101', level: 'medium', title: '作业未完成', content: '物理《声现象章节测试》今晚 20:30 截止,尚未提交', occurredAt: '2026-10-28T09:30:00+08:00', isRead: false }, + { id: 'alert-102', level: 'high', title: '正确率下降', content: '今日数学正确率 78%,低于近 7 日均值 12%', occurredAt: '2026-10-28T09:10:00+08:00', isRead: false }, + { id: 'alert-103', level: 'high', title: '攻读失败', content: '数学《几何证明》已用满 2 次攻读机会,仍未完成', occurredAt: '2026-10-28T08:50:00+08:00', isRead: true }, + { id: 'alert-104', level: 'low', title: '上线提醒', content: '今日首次上线时间较平日延后 30 分钟', occurredAt: '2026-10-28T08:10:00+08:00', isRead: true } + ] + const list = config.url.includes('limit=3') ? alerts.slice(0, 3) : alerts + data = { list, pagination: { page: 1, pageSize: list.length, total: alerts.length, hasMore: false } } + } else if (config.url.includes('/api/parent/home-profile')) { + const isSecondStudent = config.url.includes('studentId=test-student-002') + data = isSecondStudent ? { + id: 'test-student-002', name: '张一鸣', account: '20241002', grade: '五年级', school: '实验小学', avatar: '', comprehensiveLevel: 'good' + } : { + id: 'test-student-001', name: '张一诺', account: '20241001', grade: '初二', school: '实验中学', avatar: '', comprehensiveLevel: 'excellent' + } + } + + return { + code: 0, + message: 'success', + data: data as T + } +} + +/** + * 通用请求方法 + */ +export function request(config: RequestConfig): Promise | null> { + return new Promise((resolve, reject) => { + // 请求拦截 + const finalConfig = requestInterceptor(config) + + if (USE_MOCK_API) { + const delay = Math.floor(Math.random() * (MOCK_RESPONSE_MAX_DELAY + 1)) + setTimeout(() => { + if (finalConfig.showLoading !== false) { + hideLoading() + } + resolve(createMockResponse(finalConfig)) + }, delay) + return + } + + // 发起请求 + uni.request({ + url: finalConfig.url, + method: finalConfig.method ?? 'GET', + data: finalConfig.data, + header: finalConfig.header, + timeout: TIMEOUT, + success: (res) => { + // 响应拦截 + const result = responseInterceptor(res, finalConfig) + resolve(result) + }, + fail: (err) => { + // 隐藏Loading + if (finalConfig.showLoading !== false) { + hideLoading() + } + + // 网络错误处理 + handleError(-1, err.errMsg ?? '网络连接失败', finalConfig.showError) + reject(err) + } + }) + }) +} + +/** + * GET请求 + */ +export function get(url: string, data?: any, config?: Partial): Promise | null> { + return request({ + url, + method: 'GET', + data, + ...config + }) +} + +/** + * POST请求 + */ +export function post(url: string, data?: any, config?: Partial): Promise | null> { + return request({ + url, + method: 'POST', + data, + ...config + }) +} + +/** + * PUT请求 + */ +export function put(url: string, data?: any, config?: Partial): Promise | null> { + return request({ + url, + method: 'PUT', + data, + ...config + }) +} + +/** + * DELETE请求 + */ +export function del(url: string, data?: any, config?: Partial): Promise | null> { + return request({ + url, + method: 'DELETE', + data, + ...config + }) +}