4.7 KiB
4.7 KiB
参考:前端框架差异对照
前端 UI 适配时加载此文档。 核心原则:逐行阅读理解后修改,不要机械式查找替换。
script 部分差异
| CRM 原版 | shanxia2.0 等价方式 | 说明 |
|---|---|---|
getCurrentInstance() + proxy |
删除 | shanxia2.0 不使用 proxy |
proxy?.useDict('xxx') |
useDictTag('xxx') 或 dictStore.getLabel() |
字典获取 |
proxy?.$modal.msgSuccess(msg) |
ElMessage.success(msg) |
成功提示 |
proxy?.$modal.msgWarning(msg) |
ElMessage.warning(msg) |
警告提示 |
proxy?.$modal.msgError(msg) |
ElMessage.error(msg) |
错误提示 |
proxy?.$modal.confirm(msg) |
ElMessageBox.confirm(msg, '提示', { type: 'warning' }) |
确认框 |
import { ElMessage } from 'element-plus' |
删除 | 自动导入 |
import { ref, reactive } from 'vue' |
删除 | 自动导入 |
request({ url, method, data }) |
request.post('/web/crm/xxx', data) |
API 调用 |
hasPermi(['crm:xxx:add']) |
v-auth="'add'" |
权限 |
API 响应数据处理(重要)
request.post 已在拦截器中解包 res.data.data,返回值直接是 API 的 data 字段内容。
但由于加密/非加密场景差异,返回结构不完全一致,必须使用兼容写法:
// ✅ 正确:兼容写法
const res = await fetchXxxList(params)
const data = (res as any)?.data || res || {}
list.value = data.list || data.rows || []
pagination.total = data.total ?? 0
// ❌ 错误:多套一层 .data
const res = await fetchXxxList(params)
const list = res.data?.list // undefined!
下拉选择器加载用户列表:
pageSize上限为 100(后端Query(le=100)),传1000会 422- 用
pageSize: 100+filterable搜索过滤 - 选项字段为
label/value(fetchUserOptions),勿用原 CRM 的nickName/userId
Excel / Blob 导出(重要)
CRM 列表「导出」走 responseType: 'blob',已在 src/utils/http/index.ts 集中适配:
// api/crm/xxx/index.ts
export function exportXxx(query?: XxxQuery) {
return request.post({
url: `${BASE}/export`,
data: mapListQuery(query),
responseType: 'blob',
showErrorMessage: false
})
}
// 页面 handleExport
const res = await exportXxx(params)
const blob = res instanceof Blob ? res : new Blob([res as BlobPart], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
// ... createObjectURL + link.click()
- 拦截器对 xlsx 不做 JSON
code校验;对 JSON 错误体(如缺少 openpyxl)仍会解析并抛错 - 后端字典列已由
crm_export_columns.py+excel_export.py翻译,前端无需再转 label
查看详情(SystemViewDialog)
| CRM 原版 | shanxia2.0 |
|---|---|
<el-drawer> 宽抽屉 + Tab |
<SystemViewDialog>(参考 clue/detailDrawer.vue) |
抽屉内 <dict-tag> |
ViewField + tagType + dictStore.getLabel() |
打开详情前:await dictStore.loadAllDict()(避免字典 code 直出)。
template 部分差异
| CRM 原版 | shanxia2.0 等价方式 | 说明 |
|---|---|---|
<el-drawer> |
<el-dialog> |
禁止抽屉,统一弹窗 |
<el-drawer size="80%"> |
<el-dialog width="960px"> |
固定宽度 |
<dict-tag :options="xxx" :value="val"/> |
<el-tag :type="getTagType(val)" size="small">{{ getLabel(val) }}</el-tag> |
字典展示 |
<right-toolbar> |
<div class="list-toolbar"><div class="list-toolbar-row"> |
工具栏 |
label-width="120px" |
label-position="top" |
表单标签 |
<el-row><el-col :span="12"> 栅格 |
.dialog-form-layout + .form-grid-2 |
表单布局 |
| RuoYi 上传组件 | MinIO fetchUploadAttachment |
文件上传 |
弹窗标准模板
<el-dialog v-model="innerVisible" :title="isEdit ? '编辑' : '新增'" width="960px"
destroy-on-close align-center :close-on-click-modal="false"
style="backdrop-filter: none" class="dialog-form-layout">
<el-form ref="formRef" :model="formData" :rules="formRules"
label-position="top" scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }">
<div class="form-grid-2">
<!-- 表单项 -->
</div>
</el-form>
<template #footer>
<el-button @click="innerVisible = false"><el-icon><Close /></el-icon>取消</el-button>
<el-button type="primary" @click="handleSubmit"><el-icon><Check /></el-icon>确定</el-button>
</template>
</el-dialog>
style 部分差异
- UnoCSS 工具类 → Tailwind CSS 等价类
- 复杂样式用 SCSS
- 使用门户标准 CSS 类:
dialog-form-layout、form-grid-2、form-col-span-2
详细对照规则
完整的 1044 行替换规则见 docs/crm-frontend-migration-guide.md。