skill 边界
uni-spec负责页面开发规范、样式规范、通用页面约定tm-ui-components负责 tm-ui 组件参数/事件/插槽/ref 与源码映射- 需要查组件 API 时优先查看
.agents/skills/tm-ui-components/references/components/*.md, 涉及行为差异再核对src/uni_modules/tm-ui/components/
编码规范
- 响应式与自动导入:使用 Vue Macros 的 reactivity transform(
$ref、$computed等);vue 相关的类型和 API 如ref、computed等都无需导入,直接使用即可,因为vite.config.mts配置了自动导入AutoImport - 书写格式:当
[ ]代表的是值时中间有空格,例如:const arr = [ 1, 2, 3 ];、const arr = [ ];,{ }也同理;函数定义和调用时参数统一换行,vue 组件属性也统一换行 - 模板排版:空白行代码缩进要保持和上一行一致,方便后续添加代码;大块标签之间要留空行;结构标签上面写上注释
- 代码检查:使用
pnpm typecheck;tsconfig_tc.json才是正确的类型检查配置,tsconfig.json只是为了编辑器提示
form表单
tm-form-item组件中如果不需要校验必填则必须添加:required="false"属性, 否则会默认校验必填, 例:
<!-- 编码 -->
<tm-form-item
label="编码"
name="code"
:required="false"
>
<CustomInput
v-model="usr_input.code"
placeholder="请输入 编码"
></CustomInput>
</tm-form-item>
常用类库
dayjs- 处理日期时间, 可直接使用, 无需引入decimal.js- 处理精确小数计算, 金额计算, 可直接使用, 无需引入numeral- 数字格式化, 可直接使用, 无需引入
常用开发技巧
本地静态 Icon
- 当前 uni 仓库已在
uno.config.ts和uno_uni.config.ts中通过 UnoCSSpresetIcons注册了iconfontcollection,会自动读取src/assets/iconfont/{icon_name}.svg - 对这类本地单色 svg 图标, 优先使用
un-i="iconfont-图标名"挂在view/text等普通节点上,不要再写成image + src,也不要用new URL(...svg, import.meta.url)去手动引资源 - 图标颜色、尺寸直接用 UnoCSS 原子属性控制,例如
un-text="[#f08b6a]"、un-w="5"、un-h="5" - 仅当资源本身需要保留原始多色效果、渐变、位图展示时,才继续使用
image标签 - 可参考
src/pages/product/Detail.vue的收藏按钮写法
<view
un-i="iconfont-favorite_service"
un-w="5"
un-h="5"
un-text="[#f08b6a]"
></view>
富文本展示
- 像
detail这类富文本字段, 在当前仓库里通常保存的是 oss/tmpfile 附件 id, 不是 html 正文 - 移动端展示时不要直接把字段值传给组件, 应先通过
getDownloadUrl({ id, inline: "1" })拿到下载地址, 再用uni.request拉取 html 字符串 - 模板中使用
tm-html渲染拉取回来的 html 内容, 可参考src/pages/product/Detail.vue
<view
v-if="detail_html"
>
<tm-html
:value="detail_html"
></tm-html>
</view>
async function loadDetailHtml(
detail?: string,
) {
detail_html = "";
if (!detail) {
return;
}
const url = getDownloadUrl({
id: detail,
inline: "1",
});
const res = await uni.request({
url,
method: "GET",
});
detail_html = typeof res.data === "string"
? res.data
: String(res.data || "");
}
页面参数接收
- 使用
onLoad生命周期钩子接收页面跳转传递的参数,禁止使用getCurrentPages()方式获取参数 onLoad的回调参数query包含页面 URL 中的查询参数- 所有页面级初始化逻辑(包括但不限于
initFrame())都应放在onLoad内部调用,而非直接在setup中调用
onLoad(async (query?: AnyObject) => {
const some_param = query?.some_param;
if (some_param) {
search.some_field = decodeURIComponent(some_param);
}
await initFrame();
});
- 表单通常会有
let inited = $ref(false);标记是否初始化完成, 避免在初始化前触发表单变更事件 is_form_hydrating则表示表单是否正在数据回填- 如果表单存在由其他字段派生但最终仍会持久化的字段,
watch中必须区分初始化回填和用户编辑; 初始化阶段不要自动回写派生字段, 避免首屏展示值与数据库值不一致, 或用户未编辑就隐式改库 - 异步初始化的表单/筛选页, 输入类组件优先透传
:page-inited="inited"; 在初始化完成前隐藏 placeholder, 避免先出现空提示再被默认值或缓存值覆盖
watch(
() => dyn_page_field_input,
() => {
if (!inited || is_form_hydrating) {
return;
}
// 处理表单变更逻辑
},
{
deep: true,
},
);
CustomInput 数值输入
CustomInput的type="decimal"/type="number"主要控制键盘或原生输入类型, 不会自动把v-model转成Decimal/number- 需要
Decimal实例时必须显式传:is-decimal="true"; 需要数字时传:is-number="true" - 金额、积分、数量这类字段, 只要后续要调用
lte、gt、eq等Decimal方法, 状态就必须保持Decimal, 不要只写type="decimal" CustomInput默认isHideZero=true, 如果0需要在输入框里真实展示出来, 要补上:is-hide-zero="false"
<CustomInput
v-model="recharge_amount"
type="decimal"
:is-decimal="true"
inputmode="decimal"
:is-hide-zero="false"
placeholder="请输入充值金额"
></CustomInput>
页面刷新与事件约定
- 跨页面刷新优先用
uni.$emit/uni.$on, 事件名统一写成页面路径语义, 例如"/pages/order/List:refresh"、"/pages/product/List:category_id_selected" - 绑定事件前先
uni.$off一次同名监听, 销毁时再uni.$off, 避免页面反复进入后重复注册 - 真正通过路由打开的页面优先在
onLoad/onUnload中完成初始化和解绑; 被 tab 页直接import复用的“页面型组件”可以使用onMounted/onUnmounted - 详情页、弹窗页如果既可能被路由打开, 也可能被父组件拿
ref触发刷新, 统一暴露refresh()方法给外部调用
const refresh_event_name = "/pages/order/List:refresh";
onLoad(async () => {
uni.$off(refresh_event_name, onPageRefresh);
uni.$on(refresh_event_name, onPageRefresh);
await initFrame();
});
onUnload(() => {
uni.$off(refresh_event_name, onPageRefresh);
});
async function initOrRefresh() {
if (!inited) {
await initFrame();
} else {
await onRefresh();
}
}
defineExpose({
refresh: initOrRefresh,
});
页面开发样式规范
字体与颜色约定
- 当前 uni 仓库默认字号基线来自
src/assets/style/common.scss:--font-size: 32rpx,约等于16px--font-size-sm: 28rpx,约等于14px
- 使用
un-text="数字"时,不要把数字直接当成移动端常规字号;在当前 UnoCSS 配置下可近似理解为数字 * 4 = px,例如un-text="6"约等于24px,对普通列表正文通常明显偏大 - 移动端页面正文、列表项、说明文案优先贴近默认基线:
- 常规正文优先使用继承默认字号,或
un-text="4"左右 - 次级说明文案优先使用
un-text="3.5"或接近14px的大小 - 分组标题、卡片标题通常控制在
un-text="4.5"到un-text="5",除非是页面主标题,不要轻易使用un-text="6"及以上
- 常规正文优先使用继承默认字号,或
- 先根据信息层级控制字号,再决定字重;不要靠过大的字号去撑出“重点感”
- 颜色优先使用 UnoCSS 标准色阶,例如
gray-200、gray-400、gray-700、red-500,尽量避免随手写零散十六进制颜色 - 只有在品牌色、业务状态色、设计稿明确指定时,才使用自定义颜色,例如
un-text="[#24324a]" - 灰色文案优先从标准灰阶中选择:
- 弱提示/分隔信息优先
gray-400 - 常规次级文案优先
gray-500或gray-600 - 重要正文优先
gray-700,不要用过浅灰色影响可读性
- 弱提示/分隔信息优先
<!-- ❌ 正文过大, 颜色也不够规范 -->
<view
un-text="6 [#24324a]"
un-font="700"
>
今日食堂菜单
</view>
<!-- ✅ 更接近当前 uni 仓库的移动端字号基线 -->
<view
un-text="5 gray-700"
un-font="700"
>
今日食堂菜单
</view>
<view
un-text="4 gray-700"
>
午餐 A: 红烧排骨 + 清炒时蔬 + 紫菜蛋花汤
</view>
<view
un-text="3.5 gray-500"
>
限时供应, 售完即止
</view>
CSS - Attributify Mode
基础用法
将冗长的 class 拆分为语义化属性:
<!-- ❌ 传统方式 -->
<tm-button class="bg-blue-400 hover:bg-blue-500 text-sm text-white font-mono py-2 px-4 rounded border-2">
Button
</tm-button>
<!-- ✅ Attributify -->
<tm-button
un-bg="blue-400 hover:blue-500"
un-text="sm white"
un-font="mono"
un-p="y-2 x-4"
un-border="2 rounded"
>
Button
</tm-button>
自引用前缀 ~
当属性名与值相同时使用:
<!-- border border-red → un-border="~ red" -->
<tm-button
un-border="~ red"
>
Button
</tm-button>
<!-- flex flex-col → un-flex="~ col" -->
<view
un-flex="~ col"
></view>
无值属性
无参数工具类直接作为属性:
<view
un-rounded
un-truncate
un-italic
></view>
原则
- 相同前缀合并:
text-sm text-white→un-text="sm white" - 自引用用
~:flex flex-col→un-flex="~ col" - 无参数用无值属性:
rounded→un-rounded - 单边边框如果同时写
solid/颜色, 必须先用0清零其它边框:un-border="0 b-1 solid [#f0f2f5]", 不要写成un-border="b-1 solid [#f0f2f5]"
自动引入的变量和组件
由 unplugin-auto-import 自动生成,无需手动维护。配置在 vite.config.mts 的 AutoImport({imports:[]}) 中
全局变量
src/typings/auto-imports.d.ts 中的变量可直接使用:
// 如 dayjs、ref、computed 等
const now = dayjs().format('YYYY-MM-DD');
uni 中的 API 也可直接使用, 在 src/typings/uni.d.ts 中定义:
const res = await uni.navigateTo({
url: "/pages/index/index",
});
全局组件
src/typings/components2.d.ts 中的组件可直接在模板中使用:
<tm-button>Click Me</tm-button>