ワンクリックで
app-coding
Triflow App 移动端开发规范。包括 UniApp + Wot Design Uni + Alova 请求、API 路径前缀、z-paging 分页、UnoCSS 样式、跨端兼容性等项目约定。在编写或修改 triflow-app 代码时必须遵循。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Triflow App 移动端开发规范。包括 UniApp + Wot Design Uni + Alova 请求、API 路径前缀、z-paging 分页、UnoCSS 样式、跨端兼容性等项目约定。在编写或修改 triflow-app 代码时必须遵循。
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Triflow 代码审查规范。对当前代码变更或指定文件/模块进行全面合规性检查,覆盖后端 Java、Web 前端、App 移动端的命名、架构、类型安全、数据库、API 等规范。使用此 skill 审查代码是否符合项目约定。
Triflow 后端 Java 开发规范。包括 MyBatis-Flex 查询、实体与 DTO/VO 定义、分层架构、数据库命名、异常处理、数据转换等项目约定。在编写或修改 triflow-server 代码时必须遵循。
Triflow Web 前端开发规范。包括 Vue 3.5 + Element Plus + TypeScript 组件编写、API 类型定义、Pinia 状态管理、VXE Table、样式规范等项目约定。在编写或修改 triflow-web 代码时必须遵循。
SOC 職業分類に基づく
| name | app-coding |
| description | Triflow App 移动端开发规范。包括 UniApp + Wot Design Uni + Alova 请求、API 路径前缀、z-paging 分页、UnoCSS 样式、跨端兼容性等项目约定。在编写或修改 triflow-app 代码时必须遵循。 |
本 Skill 定义了 Triflow App(移动端)的代码编写规范,所有代码变更必须遵守。
| 技术 | 版本 | 用途 | 文档 |
|---|---|---|---|
| Unibest | 4.3.x | 项目模板 | unibest.tech |
| Wot Design Uni | 1.14.x | UI 组件库 | wot-ui.cn |
| UniApp | 3.0.x (Vite) | 跨端框架 | uniapp.dcloud.net.cn |
| Vue | 3.4+ | UI 框架 | - |
| TypeScript | 5.8+ | 类型系统 | - |
| Pinia | 2.0.x | 状态管理 | - |
| UnoCSS | 66.x | 原子化 CSS | unocss.dev |
| Alova | 3.x | 请求库 | alova.js.org |
| z-paging | 2.8.x | 分页组件 | z-paging.zxlee.cn |
| pnpm | 10.0.0+ | 包管理器 | - |
wd-xxx 组件triflow-app/src/
├── api/ # API 接口定义
├── components/ # 公共组件
├── hooks/ # 组合式函数
├── http/ # Alova 请求配置
├── layouts/ # 布局组件
├── pages/ # 页面(文件路由)
├── service/ # 业务服务
├── static/ # 静态资源
├── store/ # Pinia 状态
├── style/ # 全局样式
├── tabbar/ # TabBar 页面
├── types/ # 类型定义
└── utils/ # 工具函数
pages/
├── index/index.vue # 首页
├── user/
│ ├── profile/index.vue # /pages/user/profile/index
│ ├── settings/index.vue
│ └── components/ # 模块私有组件(不生成路由)
└── order/
├── list/index.vue
└── detail/index.vue
TabBar 页面放在 tabbar/ 目录下。
# env/.env.development
VITE_SERVER_BASEURL=http://127.0.0.1:7100
注意:环境变量是
VITE_SERVER_BASEURL,不包含模块前缀。
/base/ 前缀// ✅ 正确 - 显式包含 /base 前缀
export function login(data: LoginDTO) {
return http.post<LoginVO>('/base/auth/login', data)
// 实际请求: http://127.0.0.1:7100/base/auth/login
}
export function sendSmsCode(phone: string, type: string) {
return http.post<void>('/base/public/sms/send', { phone, type })
}
// ❌ 错误 - 缺少模块前缀
export function login(data: LoginDTO) {
return http.post<LoginVO>('/auth/login', data) // ❌ 会请求 /auth/login
}
| 功能 | API 路径 |
|---|---|
| 登录 | /base/auth/login |
| 注册 | /base/auth/register |
| 用户信息 | /base/auth/user/info |
| 发送短信 | /base/public/sms/send |
| 验证码 | /base/public/captcha/image |
| CMS 文本 | /base/public/text/{key} |
| 开关状态 | /base/public/switch/{key} |
<script setup lang="ts">
// ==================== Props ====================
interface Props {
product: ProductVO
showPrice?: boolean
mode?: 'card' | 'list'
}
const props = withDefaults(defineProps<Props>(), {
showPrice: true,
mode: 'card',
})
// ==================== Emits ====================
const emit = defineEmits<{
(e: 'click', product: ProductVO): void
}>()
// ==================== Methods ====================
function handleClick() {
emit('click', props.product)
}
</script>
<template>
<view class="product-card" :class="[`product-card--${mode}`]" @click="handleClick">
<image class="product-card__image" :src="product.image" mode="aspectFill" lazy-load />
<view class="product-card__info">
<text class="product-card__title">{{ product.name }}</text>
<text v-if="showPrice" class="product-card__price">{{ product.price }}</text>
</view>
</view>
</template>
| 类型 | 规范 | 示例 |
|---|---|---|
| 组件文件 | kebab-case.vue | product-card.vue |
| 页面目录 | kebab-case | user-center/ |
| 变量/函数 | camelCase | handleClick |
| 常量 | UPPER_SNAKE_CASE | MAX_COUNT |
| 类型/接口 | PascalCase | ProductVO |
已配置自动导入,直接在模板中使用 wd-xxx:
<wd-button type="primary" @click="handleClick">主要按钮</wd-button>
<wd-input v-model="username" label="用户名" placeholder="请输入" />
<wd-cell title="设置" is-link @click="goSettings" />
<wd-toast />
常用组件:wd-button、wd-input、wd-picker、wd-cell、wd-toast、wd-message-box、wd-popup、wd-form、wd-tabs、wd-swiper
详细组件使用、已封装公共组件列表见 COMPONENTS.md
// http/index.ts
export const alovaInstance = createAlova({
baseURL: import.meta.env.VITE_SERVER_BASEURL,
...AdapterUniapp(),
timeout: 30000,
beforeRequest(method) {
const userStore = useUserStore()
if (userStore.token) {
method.config.headers.Authorization = `Bearer ${userStore.token}`
}
},
responded: {
onSuccess: async (response) => {
const data = response.data
if (data.code !== 0) {
uni.showToast({ title: data.message || '请求失败', icon: 'none' })
throw new Error(data.message)
}
return data.data
},
},
})
// api/user.ts
import { alovaInstance } from '@/http'
export interface UserVO {
id: number
nickname: string
avatar: string
phone: string
}
export function login(data: LoginDTO) {
return alovaInstance.Post<LoginVO>('/base/auth/login/sms', data) // ✅ 包含 /base/
}
export function getUserInfo() {
return alovaInstance.Get<UserVO>('/base/auth/user/info') // ✅ 包含 /base/
}
<script setup lang="ts">
import { useRequest } from 'alova/client'
import { getUserInfo } from '@/api/user'
// 自动请求
const { data: userInfo, loading } = useRequest(getUserInfo)
// 手动请求
const { send: fetchUser } = useRequest(getUserInfo, { immediate: false })
</script>
export const useUserStore = defineStore(
'user',
() => {
const token = ref<string>('')
const userInfo = ref<UserVO | null>(null)
const isLoggedIn = computed(() => !!token.value)
function setToken(newToken: string) { token.value = newToken }
function logout() { token.value = ''; userInfo.value = null }
return { token, userInfo, isLoggedIn, setToken, logout }
},
{
persist: {
storage: { getItem: uni.getStorageSync, setItem: uni.setStorageSync },
},
},
)
使用时:storeToRefs 解构响应式数据,actions 可直接解构。
使用 z-paging 组件实现下拉刷新 + 上拉加载分页列表。核心模式:@query 回调中请求数据,通过 pagingRef.value?.complete(list) 通知组件。
完整 z-paging 模板和配置示例见 COMPONENTS.md §5
<view class="flex items-center justify-between p-4 bg-white rounded-lg">
<text class="text-lg font-bold text-gray-800">标题</text>
</view>
| 单位 | 使用场景 |
|---|---|
rpx | 推荐,自动适配不同屏幕 |
px | 边框、小图标 |
% | 弹性布局 |
.product-card {
&__image { width: 100%; height: 340rpx; }
&__title { font-size: 28rpx; }
&--list { display: flex; width: 100%; }
}
<!-- 推荐:route 块 -->
<route lang="json">
{
"style": { "navigationBarTitleText": "用户中心" },
"needLogin": true
}
</route>
v-if 而非 v-show(小程序中 v-show 行为可能不一致)#ifdef / #endif<!-- #ifdef MP-WEIXIN -->
<button open-type="chooseAvatar" @chooseavatar="handleChooseAvatar">选择头像</button>
<!-- #endif -->
<!-- #ifdef H5 -->
<button @click="h5Upload">上传头像</button>
<!-- #endif -->
| CSS 属性 | 替代方案 |
|---|---|
backdrop-filter | 纯色半透明 rgba() |
gap (flex/grid) | margin 间距 |
aspect-ratio | padding-top 百分比 |
@font-face 自定义字体 | 系统字体栈 |
详细小程序兼容性问题和解决方案见 MINIPROGRAM.md
Alova 响应拦截器统一处理 code === 401,自动清除登录态并跳转登录页。
app.config.errorHandler = (err, instance, info) => {
console.error('全局错误:', err)
reportError(err)
}
| 组件 | 位置 | 用途 |
|---|---|---|
ImageUpload | components/image-upload/ | 单图/多图上传,支持 OSS 直传 |
CmsText | components/cms-text/ | 根据 textKey 展示 CMS 文本内容 |
AccessControl | components/access-control/ | 根据权限控制元素显示/隐藏 |
组件详细 Props 和使用示例见 COMPONENTS.md
调用后端 /base/public/switch 接口查询开关状态,使用 SwitchKey 枚举,带 5 分钟本地缓存。
规则:
getSwitchStatus / getBatchSwitchStatusSwitchKey 枚举,禁止硬编码字符串false完整 API 签名、SwitchKey 枚举列表和使用示例见 COMPONENTS.md §4