⚠️ 核心规则提示 本项目的核心强制规则(如接口规范、工具类替代、样式单位等)已提取至根目录下的
.cursorrules文件。 在进行代码转换时,AI 助手必须优先遵循 .cursorrules 中的指令。 本文档作为详细参考手册,提供具体的代码示例、复杂场景说明和完整的迁移背景。
以下规则是 .cursorrules 的补充说明,包含具体代码示例:
config/api 或相对路径引用接口。必须使用 @/pagesPersonal/service/... (分包) 或 @/service/... (主包)。
import service from ...)。import { updateAccountAuthList as updateAccountAuthListApi, backstageDelUserMember_V3 } from '@/pagesPersonal/service/patientManagement';7.8 接口封装调用规范。业务层调用 Service 方法时,必须使用解构获取返回值(如 let { resp, resData } = await api())。严禁只解构 { resp } 或假设接口直接返回数据对象。onLoad 解析参数时,JSON.parse 必须包裹在 try-catch 中,防止因字符截断导致的白屏。data-xxx 时,JS 中必须使用 e.currentTarget?.dataset?.xxx 安全访问,防止 undefined 报错。acceptCredit -> targetStatus)。rpx。必须全文批量替换为 upx。publicFn.getMember -> await useGetMember()util.getAuthorize -> common.getAuthorizeutils 导入 -> import { common } from '@/utils' (Common) / import icon from '@/utils/icon' (Icon)getState.js 和 pagesPatientFn.js 必须替换为 TS 版本:
import { getState, pagesPatientFn } from '@/uni-app-base/utils'uni-app-base/utils/getState.ts 和 uni-app-base/utils/pagesPatientFn.tsicon 是一个静态资源路径,必须使用 ref(icon) 包裹,防止在模板中直接引用导致的路径错误。变量名为 iconUrl,image 标签上有相关的 src 都要用 iconUrl 进行获取。
21→
22→8. 代码完整性:xbossTrack),严禁直接删除,必须在对应位置保留 TODO 注释并说明原因。pages.json。pages 数组保持不变,路径无需修改。subpackages 分包配置直接迁移到 pages.json 的 subPackages 字段。window 配置迁移至 pages.json 的 globalStyle。tabBar 配置迁移至 pages.json 的 tabBar。app.js 的逻辑迁移至 App.vue。globalData 建议迁移至 Vuex 或 Pinia 状态管理,或者直接挂载到 Vue.prototype / app.config.globalProperties。onLaunch, onShow, onHide 生命周期对应迁移到 App.vue。App.vue + 自定义 hook 完成启动逻辑(如版本检查、应用状态判断、登录、配置拉取、WebSocket 链接等),迁移时请将原 app.js 流程拆分为 Hook 并在 App.vue 中顺序调用。globalData:当前项目依旧使用 getApp().globalData 存储运行期上下文数据(如 accountInfo、hosId、channelId、hasWebsocket 等),迁移时优先复用此约定;如需长期状态请同步到 store。pages.json 添加路由(pages 或 subPackages)以确保可访问。components 文件夹下)且没有路由,必须在 pages.json 中定义。.wxml -> .vue (放在 <template> 标签内)。.wxss -> .vue (放在 <style> 标签内)。.js -> .vue (放在 <script> 标签内)。.json -> 删除。
navigationBarTitleText)迁移至 pages.json 的 style 字段。.json 文件,navigationBarTitleText 必须从该 .json 文件中获取并同步到 pages.json。usingComponents) 迁移至 pages.json (全局) 或 .vue 的 components 选项 (局部)。.wxss (公共样式) -> .scss。.js (工具/逻辑) -> .ts。@import '...common.wxss' -> @import '...common.scss')。.wxml, .wxss, .js, .json),只保留 .vue 文件(及必要的独立 .scss/.ts 工具文件)。Page({}) 替换为 export default {}。data: { ... } 替换为 data() { return { ... } }。this.setData({ key: value }) 替换为 key.value = value。
this.setData({ 'a.b': 1 }),需改为 a.value.b = 1。properties (组件) 替换为 props。
withDefaults,确保类型安全与默认值。
ts
const props = withDefaults(defineProps<{
userInfo?: any;
type?: string;
}>(), {
userInfo: () => ({}),
type: 'default'
});
this.selectComponent 调用的组件方法,在 <script setup> 中必须使用 defineExpose({ methodName }) 显式暴露,否则父组件无法调用。getCurrentPages() 获取页面栈(注意判空与类型转换)。生命周期映射:
onLoad -> onLoad (UniApp 保留页面生命周期)。onShow -> onShow。created, attached, detached (组件) -> created, mounted, beforeDestroy (Vue 生命周期)。当前项目页面常用模式(推荐按此迁移):
<script lang="ts" setup>
import { ref } from 'vue';
import { useOnLoad } from '@/hook';
// 使用 getApp() 读取运行时全局数据
const app = getApp();
const list = ref<any[]>([]);
useOnLoad(async (options) => {
// 迁移原 onLoad 逻辑至此,保留原字段处理
// 复杂逻辑请加简要中文注释
// 参数解析容错处理
if (options && options.myInfo) {
try {
const info = JSON.parse(decodeURIComponent(options.myInfo));
// 业务逻辑...
} catch (e) {
console.error('参数解析失败', e);
// 必要的兜底逻辑
}
}
});
</script>
wx:if, wx:elif, wx:else -> v-if, v-else-if, v-else。wx:for="{{list}}" -> v-for="(item, index) in list" :key="index"。wx:key="id" -> :key="item.id"。bindtap="handler" -> @click="handler"。catchtap="handler" -> @click.stop="handler"。bindinput -> @input。data-xxx="{{value}}" -> 建议改为函数传参 @click="handler(value)"。如果必须保留,使用 :data-xxx="value",在事件对象 e.currentTarget.dataset.xxx 中获取。const val = e.currentTarget?.dataset?.xxx;,避免因事件冒泡或元素结构变化导致 currentTarget 为空引发的报错。v-for 与 v-if 同时出现在同一元素会导致作用域问题(Vue 3 中 v-if 优先级更高),务必使用 <template v-for>...</template> 包裹并在内部元素上写 v-if,如:
vue
<template v-for="(item,index) in list" :key="index">
<view v-if="item?.IsShow == '1'">{{ item.MenuName }}</view>
</template>
列表截取展示:若需限制列表渲染数量(如仅展示前3条),禁止 v-for 与 v-if 混用(如 v-if="index < 3"),推荐在 v-for 中直接使用 .slice(0, n) 对数组进行切片,如 v-for="(item, index) in list.slice(0, 3)"。
深层属性访问需判空:对于 menuObj.Children[0].Children 等层级,需在模板中加入 v-if="menuObj?.Children?.[0]?.Children" 保护。
wx. API 可直接替换为 uni. (如 wx.request -> uni.request)。wx.getStorageSync -> uni.getStorageSync。wx.createSelectorQuery -> uni.createSelectorQuery().in(this) (注意必须加 .in(this) 以确保在组件内正确查找)。wx.navigateToMiniProgram -> uni.navigateToMiniProgram(当前项目中个别地方仍保留 wx 调用,迁移时统一改成 uni)。uni.getDeviceInfo,需容错回退 uni.getSystemInfoSync(),避免字符串方法调用报错(已在 uni_modules/mp-html/components/mp-html/parser.js 中处理)。utils/request.js 需要适配 uni.request,注意 header 和返回值的差异(uni-app 返回数组 [error, res] 或 Promise)。vite.config.js 配置了代理与环境变量,H5 开发走本地代理 /api -> VITE_APP_PROXY_API_BASE_URL。小程序端请直接使用后端绝对域名,或在运行时使用 useDomain() 获取域名拼接接口路径。domain 拼接与 url 处理(参考 pagesAdmin/article/detail/detail.vue 中的 :domain="domain")。upx(已对部分页面做了从 rpx 到 upx 的更正)。迁移时请保持一致。<style> 中的背景图引用建议使用绝对路径(/static/...)或网络路径,避免相对路径带来的构建问题。app.wxss 内容迁移至 App.vue 的 <style> 或单独的 common.css 并在 App.vue 中引入。xbossTrack 通过劫持 Page 和 App 构造器实现埋点,这在 UniApp (基于 Vue) 中会失效。
wrapper.js:不再使用 Page = ... 这种方式。使用 Global Mixin:在 main.js 中使用 Vue.mixin 全局混入生命周期逻辑。
// 示例思路
Vue.mixin({
onShow() {
// 调用 timeTracker 逻辑
},
methods: {
// 劫持 methods 需要在 beforeCreate 中遍历 this.$options.methods 进行包装
// 或者利用拦截器思路
}
})
catchtap 冒泡捕获。在 Vue 中,建议创建一个自定义指令 v-track 或者在全局点击事件中处理(但要注意 Vue 事件处理机制)。elementTracker 方法,并在需要的元素上显式绑定 @click="elementTracker($event)",或者重构为自动监听页面点击。preloadRule 配置。mp-html)。hook 管理页面加载与启动逻辑:useOnLoad((options)=>{ ... }):迁移原 onLoad 代码usePreserMember():预处理就诊人,迁移涉及患者缓存的逻辑publicFn.preserMember() 逻辑替换为 await usePreserMember()queryBaseMemberList_V3 获取就诊人列表逻辑若用于初始化或预加载,建议使用 usePreserMember() 替代useGetDefaultMember()、useIsToAuthPage():就诊人与授权判断逻辑useDomain():获取域名用于接口与资源拼接queryMemberCardList_V3 且入参只有 memberId 时,可替换成 hook 中的 queryMemberCardList_V3。App.vue 中通过多个 Hook 串联(版本检查、配置拉取、应用状态判断、登录、菜单初始化、WebSocket 等),迁移时按步骤落地:@kasite/uni-app-base/store 暴露的辅助方法:mapState({ token: 'token', currentUser: 'currentUser' })mapMutations({ setCurrentUser: 'setCurrentUser' })currentUser、memberList、token 等原字段的读写,不做字段名转换。uni.getStorageSync('token'|'openid'|'smallProOpenId')。必须通过 store.state.token、store.state.openId、store.state.smallProOpenId 获取(需先 import { useStore } from 'vuex' 并在 setup 中 const store = useStore())。utils/index.ts 暴露:export { common }(来自 @kasite/uni-app-base/utils)export const menuClick(e, _this, skipWay = 'navigateTo'):菜单点击统一入口,迁移时所有功能入口点击尽量走此方法,保留原 dataset 字段读取约定(data-item、data-item-parent)。getApp().globalData 多处字段(如 hosId、channelId、config.pageConfiguration),迁移时不要改动字段名。dataset 约定:
vue
<view :data-item="item" :data-item-parent="parent" @click="menuClickFn"></view>
const menuClickFn = (e) => { menuClick(e, proxy); }
uni_modules 下的组件可直接在模板中使用,无需显式 import 与 components 注册。mp-html(uni_modules/mp-html),在页面中直接写 <mp-html :content="html" :domain="domain" />。mp-html 的解析器已针对 system 进行了容错,迁移时无需额外处理。vite.config.js:
@ 指向项目根目录,请统一使用 @/... 路径/api -> VITE_APP_PROXY_API_BASE_URL(H5)useDomain() 构造完整 URLuni_modules/**/* 已排除env 目录(使用 loadEnv),迁移时如需新变量请按现有 .env 约定添加。upx;已有页面(如 homeSearch.vue、homePage.vue)已完成从 rpx 到 upx 更正。迁移时保持一致。style="{{...}}" 的 WXML 写法,改为 :style="..."。utils/publicFn.js)。统一从 utils/index.ts 引入已有工具方法。utils/index.ts 暴露的 getLocation,替换 publicFn.getLocation,保持原逻辑与字段不变。utils/index.ts 暴露的 menuClick,替换 publicFn.menuClick,保留 dataset 传参约定(data-item、data-item-parent)。import { common } from '@/utils';。严禁使用 import common from '@/utils/common' 或默认导入。import icon from '@/utils/icon';。严禁从 @/utils 导入(如 import { icon } from '@/utils' 是错误的)。import { publicFn } from '@/utils';。严禁使用 import publicFn from '@/utils/publicFn' 或默认导入。util.getAuthorize.call(proxy) 替换为 common.getAuthorize。util 对象。publicFn.getMember 必须替换为 await useGetMember()(需从 @/hook 导入)。publicFn.xxx 或 util.xxx 方法(如 countDown、formatTime 等),必须从 common或publicFn 中寻找对应方法替换(需 import { common, publicFn } from '@/utils')。utils/index.ts 或 @kasite/uni-app-base 提供的工具中引入,保持原字段不变,不做字段名转换。// 错误示例(严禁出现): // import {common} from '@/utils'; // 错误:common 需解构 // import { icon } from '@/utils'; // 错误:icon 未在 utils/index.ts 导出
### 7.8 接口封装调用规范(迁移需遵循)
- 目录结构:与 `pages/st1/service` 保持一致,建议按业务拆分为 `base/`、`home/`、`schemeDetail/` 等子目录,并在同级提供 `index.ts` 聚合导出。
- 统一封装:使用 `@kasite/uni-app-base` 提供的 `request` 与 `handle`。
- **必须使用 New 版方法**:`handle.promistHandleNew` 和 `handle.catchPromiseNew`。
- **严禁使用旧版**:`promistHandle` 和 `catchPromise`。
- **严禁引用不存在的 API 路径**:禁止使用 `config/api/...`,必须从 `@/pagesPersonal/service/...` 或各分包的 `service` 目录导入。
- **严禁依赖 api.ts 对象**:Service 层必须直接拼接 URL,严禁导入 `import api from '@/config/api'` 或使用 `api.Key` 形式。所有接口路径必须在 Service 文件内显式定义。
- **URL 拼接规范**:必须使用模板字符串 `${REQUEST_CONFIG.BASE_URL}wsgw/...` 进行拼接。
- **变量声明与 Handle 调用规范**:
- 必须使用 `let resp` (严禁 `const`)。
- 必须遵循 `let resp = handle.promistHandleNew(await request.doPost(...))` 结构 (注意 `await` 位置)。
- **返回值处理**:Service 层函数使用 `catchPromiseNew` 返回后,业务层调用**必须**使用解构获取 `resp` 和 `resData`。
- **标准写法**:`let { resp, resData } = await apiName(params);`
- **判断数据**:`if (common.isNotEmpty(resp)) { ... }`
- **命名冲突处理**:当导入的接口函数名与页面内的业务变量名冲突时,**必须**重命名页面内的局部变量。
- **串行调用与变量重命名**:当后续接口依赖前序接口结果时,应在 `if (common.isNotEmpty(resp))` 块内调用;为避免 `resp` 变量名冲突,后续接口调用**必须**使用解构重命名。
- **标准写法**:`let { resp: newName } = await nextApiName(params);`
- **参考已完成页面**:`pages/st1/business/tabbar/homePage/homePage.vue` 和 `pages/st1/service/base/index.ts`。
- 域名拼接:接口地址统一从 `REQUEST_CONFIG.BASE_URL` 获取。
- **Service 封装示例**:
```ts
import { REQUEST_CONFIG } from '@/config';
import { request, handle } from '@kasite/uni-app-base';
export const articleTypeListUrl = async (queryData: any) => {
let resp = handle.promistHandleNew(
await request.doPost(
`${REQUEST_CONFIG.BASE_URL}wsgw/article/type/List/callApiJSON.do`,
queryData
)
);
return handle.catchPromiseNew(resp, () => resp);
};
const getData = async () => {
let { resp, resData } = await articleTypeListUrl({
ChannelId: app.globalData.channelId
});
if (common.isNotEmpty(resp)) {
console.log('Data:', resp);
}
};
- 聚合导出:在 `service/index.ts` 中统一 `export * from './base';`。
- 引入路径:统一使用 `@` 别名。
### 7.9 页面实例与App实例获取规范
- **统一获取方式**:在 `<script setup>` 顶部统一获取 `proxy` 和 `app` 实例,严禁在函数内部重复调用 `getCurrentInstance()` 或 `getApp()`。
- **规范示例**:
```ts
<script lang="ts" setup>
import { getCurrentInstance } from 'vue';
// 1. 获取当前组件实例代理
const { proxy } = getCurrentInstance();
// 2. 获取 App 全局实例
const app = getApp();
// 业务逻辑中使用
const handleClick = () => {
// 使用 proxy
console.log(proxy);
// 使用 app
console.log(app.globalData);
}
</script>
let app = getApp()。getCurrentInstance()。this (setup 中 this 为 undefined),应使用 proxy。<template>
<view>
<view v-if="list.length === 0">暂无数据</view>
<view v-else v-for="(item, i) in list" :key="i" :data-item="item" @click="menuClickFn">
{{ item.MenuName }}
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, getCurrentInstance } from 'vue';
import { useOnLoad } from '@/hook';
import { common, menuClick } from '@/utils';
const { proxy } = getCurrentInstance() as any;
const app = getApp();
const list = ref<any[]>([]);
useOnLoad(async (options) => {
// 迁移 onLoad 逻辑,保持原字段
list.value = common.deepCopy(uni.getStorageSync('menuList') || []);
});
const menuClickFn = (e) => { menuClick(e, proxy); };
</script>
<template>
<view>
<view v-for="(n, i) in flowListRender" :key="i" @click="toggle(i)">{{ n.NodeTitle }}</view>
</view>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
const props = defineProps<{ flowList: any[]; pageType: string }>();
const flowListRender = ref<any[]>([]);
watch(() => props.flowList, (val) => {
// 保留原字段与处理逻辑
flowListRender.value = (val || []).map((it) => ({ ...it, ShowContInfo: true }));
}, { immediate: true });
const toggle = (i: number) => { flowListRender.value[i].ShowContInfo = !flowListRender.value[i].ShowContInfo; };
</script>
onLoad/onShow 逻辑接入 useOnLoad/生命周期,保留原字段与流程utils/index.ts 的 menuClick,保留 dataset 字段约定upx;背景图与资源路径按当前项目约定修正