Appearance
二、框架核心组件
1. z-block 数据区块组件
z-block 是一个“区块容器组件”,用于按需加载数据或挂载子页面组件,支持懒加载、延迟加载、轮询刷新。
1.1 组件定位
数据块模式:传 url,组件进入视口后请求接口,把结果传给默认插槽渲染。
页面块模式:传 href,按路由路径找到对应页面组件并嵌入渲染。
1.2 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| url | String | undefined | 数据请求地址。存在时走接口加载模式。 |
| href | String | undefined | 路由路径。存在时走页面块模式(渲染路由组件)。 |
| params | Object | undefined | 请求参数或子页面参数。 |
| p | String | undefined | 预留字段(源码中未实际使用)。 |
| later | Number | 0 | 延迟首次加载(毫秒)。 |
| interval | Number | 0 | 轮询加载间隔(毫秒)。>0 时优先于 later。 |
| resources | String | Array | undefined | 进入视口后先加载的资源(JS/CSS)。 |
| lazy | Boolean | false/true | 是否等待数据加载完成后再渲染默认插槽。 |
1.3 事件
| 事件 | 参数 | 触发时机 |
|---|---|---|
| finish | (result, el) | url 请求成功后触发。 |
| fail | (error) | url 请求失败后触发。 |
1.4 插槽
| 插槽名 | 作用域参数 | 说明 |
|---|---|---|
| default | result | 数据模式下渲染内容,参数是接口返回结果。 |
渲染规则:
href 模式:忽略默认插槽,直接渲染匹配到的路由组件。
url 模式:当 visible=true 且(lazy=false 或 loaded=true)时才渲染默认插槽。
未满足条件时仅渲染空 <div> 占位。
1.5 使用示例
示例 A:标准数据块
xml
<z-block url="/api/employee/makeEmployeeBindLink" :params="params">
<template #default="data">
<z-copy label="复制链接" :value="data"
style="width:100%;margin-bottom:6px;text-align: center; width: 100%" />
<z-qr :value="data" height="160px" width="160px" />
<div style="text-align: center; width: 100%;">
二维码有效期十分钟
</div>
</template>
</z-block>示例 B:页面块模式(按路由组件嵌入)
java
<z-block
href="/orders/detail"
:params="{ id: orderId }"
/>示例 C:轮询刷新
java
<z-block
url="/api/task/status"
:params="{ taskId }"
:interval="5000"
@finish="onTick"
/>1.6 注意事项
params 监听是浅监听,建议替换对象引用触发更新,不要只改内部字段。
interval 会持续轮询,请避免过短间隔导致请求压力。
resources 仅在首次进入视口时加载一次。
href 是按 route.path 精确匹配,路径不一致会找不到目标组件。
finish/fail 只在 url 请求模式有意义。
lazy=true 时加载前只会渲染空容器,建议外层给最小高度避免布局跳动。
组件内部有节流(loadFc),高频参数变化不会每次都立刻发请求。
2. z-table 表格数据组件
z-table 是框架的核心数据表格组件,封装了:
远程分页加载
本地数据渲染
条件检索
列显示配置(筛选/顺序)
行选择跨页记忆
操作列渲染
全屏、刷新、Tab 过滤
2.1 组件定位
标准模式:按列配置自动渲染 el-table。
自定义模式:提供 default 插槽后,按行渲染你自己的布局。
数据来源支持:
url 远程接口
datasource 本地数据
都不传时会走 mock 数据(基于配置生成)。
2.2 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| url | String | - | 远程接口地址。 |
| columns | Array | - | 列定义。 |
| name | String | - | 表格唯一标识(用于列配置缓存 key)。 |
| entitys | Object | - | 字段实体配置,会与 configs 合并。 |
| maxPage | Number | 0 | 最大页保护,超过后回到第 1 页。 |
| title | String | - | 表头标题。 |
| rowKey | String | "id" | 行主键字段。 |
| modelValue | Array | - | 选中行的双向绑定值。 |
| condition | Array | - | 检索条件定义。 |
| slots | Object | - | 代码传入的插槽映射(与模板插槽合并)。 |
| tabs | Object | - | 头部 Tab 过滤配置。 |
| gutter | Number | - | 自定义模式下行间距。 |
| height | Number | 0 | 表格高度(可透传到 el-table)。 |
| datasource | Object | Array | - | 本地数据。数组直接渲染;对象支持 { list, total }。 |
| hideTitle | Boolean | false | 隐藏标题栏。 |
| params | Object | - | 请求参数。 |
| border | Boolean | true | 表格边框。 |
| showIndex | Boolean | false | 显示序号列。 |
| actionWidth | Number | 220 | 操作列宽度。 |
| size | Number | 20 | 每页数量(分页 pageSize)。 |
2.3 事件
| 事件名 | 说明 |
|---|---|
| finish(result:查询结果,params:查询参数) | 表格接口数据返回事件 |
2.4 插槽
| 插槽名 | 作用域参数 | 说明 |
|---|---|---|
| default | 启用“自定义模式”渲染整行内容。 | |
| action$ | scope(行作用域) | 标准模式下“操作列”内容。 |
| header$ | - | 表头检索区的自定义扩展区域。 |
| 列名同名插槽 | scope(行作用域) | 覆盖某一列单元格渲染。示例:#status="{ row }"。 |
说明:
列级插槽优先于默认列渲染。
slots prop 与模板插槽会合并。
2.5 公开能力(通过 ref 可调用)
常用方法:
reload():回到第一页并重新加载。
jump(page):跳转到指定页并加载。
on(keyOrObject, value?):设置检索条件并加载。
load():执行一次加载。
initData():按当前数据源初始化。
使用示例:
xml
<template>
<z-table
url="/do/select/customer"
:columns="[
{ label: '名称', name: 'title' },
{ label: '积分', name: 'points' },
]"
:size="2"
ref="tableRef"
>
<template #action$="{ row }">
<z-action
label="查看"
:data="row"
href="/task/detail"
@click="click"
/>
</template>
</z-table>
</template>
<script>
export default {
name: "p-3kpgc2ln",
setup() {
},
methods: {
finish(result, el) {
console.log(result);
console.log(el);
},
click() {
this.$refs.tableRef.on("title", "aa");
},
},
};
</script>注意:声明ref后,直接通过this.$refs.调用,无需再引入ref
2.6 columns 字段规范
columns 支持两种写法:
- 字符串写法
java
columns: ["id", "status", "createdAt"]会按 entitys 里同名字段自动补全配置。
- 对象写法
yaml
columns: [
{ name: "id", label: "ID", width: 120 },
{ name: "status", label: "状态", code: "order_status" },
{ name: "enabled", label: "启用", type: "switch", url: "/api/user/switch" }
]会与 entitys 同名配置合并(对象优先)。
列对象字段说明:
| 字段 | 类型 | 是否关键 | 用途 |
|---|---|---|---|
| name | String | 必填 | 字段名(对应 row 上的键)。 |
| label | String | 常用 | 列标题。 |
| width | Number/String | 可选 | 列宽。 |
| type | String | 可选 | 指定渲染类型。 |
| code | String | 可选 | 字典码,走 z-dict 显示。 |
| depend | String | 可选 | 关联展示,走 z-text。 |
| component | Component | 可选 | 自定义单元格组件(只读渲染)。 |
| url | String | 特殊 | type="switch" 时用于行内开关提交。 |
渲染优先级
单元格渲染顺序是:
如果有同名插槽(#<name>)→ 用插槽
否则如果有 component → 渲染自定义组件
否则如果 name === "id" → 用 z-copy
否则如果 type === "switch" → 用 z-form-item(支持 url)
否则如果 type 属于下列类型 → 用只读 z-form-item
否则如果有 code → z-dict
否则如果有 depend → z-text
否则显示原始值
“走只读 z-form-item”的类型列表(源码常量):
date
daterange
user
image
address
autoid
money
tenantUser
注意:
name 必须和后端返回字段一致,否则该列拿不到值。
switch 行内修改依赖 url,不配时只读展示。
字典列建议统一走 code,不要前后端重复维护文案。
写了 #default 插槽就进入自定义模式,columns 的单元格渲染规则会被绕过。
2.7 使用示例
示例 A:标准远程表格
sql
<z-table
url="/api/order/list"
:columns="[
{ label: '订单号', name: 'orderNo' },
{ label: '状态', name: 'status', code: 'order_status' },
{ label: '创建时间', name: 'createdAt', type: 'date' }
]"
:params="{ tenantId }"
:size="20"
:show-index="true"
@finish="onLoaded"
/>示例 B:行选择双向绑定
- 绑定 v-model 后,表格会开启选择逻辑,并在勾选变化时回传数组。
java
<z-table
v-model="selectedRows"
row-key="id"
url="/api/user/list"
:columns="columns"
/>示例 C:自定义操作列 + 列插槽
xml
<z-table url="/api/task/list" :columns="columns">
<template #status="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'info'">
{{ row.statusText }}
</el-tag>
</template>
<template #action$="{ row }">
<z-action label="查看" :data="row" href="/task/detail" />
</template>
</z-table>示例 D:自定义模式(整行自由布局)
xml
<z-table :datasource="list" :gutter="16">
<template #default="{ row, $index }">
<my-card :index="$index" :data="row" />
</template>
</z-table>2.8 注意事项
params 是浅监听,建议替换对象引用触发更新,不要只改对象内部字段。
当 default 插槽存在时会进入“自定义模式”,标准列渲染逻辑会被绕过。
若后端返回空页且当前页 > 1,组件会自动回退一页再请求。
列配置筛选会使用本地缓存(依赖 name/id 作为 key),name 建议稳定且唯一。
多选结果会做“跨页合并记忆”,依赖 rowKey;请确保 rowKey 唯一且稳定。
maxPage 配置不当会导致频繁跳回第一页(仅在大页数轮询场景建议使用)。
组件内部依赖 Element Plus(el-table、el-pagination、$loading)。
2.9 与 configs.jsx 配合使用 (由低代码生成,最常用)
configs.jsx:
sql
export default {
url: "/do/select/customer",
conditionLimit:null,
selectable: false,
showIndex: false,
compact: 220,
path: 'settings/customer',
title: "客户管理",
entitys: [{"name":"id","label":"编号","type":"search"},{"name":"creator","label":"创建人","type":"user"},{"name":"createGmt","label":"创建时间","type":"date"},{"name":"updateGmt","label":"更新时间","type":"date"},{"name":"title","label":"客户名称"},{"name":"level","label":"客户等级","code":"cfg_customerLevel"},{"name":"account","label":"所属账户","type":"search","depend":"account"},{"name":"beginDebt","label":"期初欠款","type":"money"},{"name":"receiveDebt","label":"应收欠款","type":"money"},{"name":"contract","label":"联系人"},{"name":"telephone","label":"联系电话"},{"name":"address","label":"客户地址","type":"address"},{"name":"detail","label":"详细地址"},{"name":"bank","label":"开户银行"},{"name":"bankNumber","label":"银行账号"},{"name":"tin","label":"税号"},{"name":"extra","label":"备注","type":"textarea"},{"name":"beginVerified","label":"期初收款","type":"money"},{"name":"stored","label":"储值金额","type":"money"},{"name":"points","label":"客户积分","type":"number"},{"name":"salesperson","label":"业务员","type":"search","depend":"salesperson"}],
columns: ["id","title","level","beginDebt","receiveDebt","stored","points","contract","telephone","createGmt","salesperson"],
condition: ["title","level","salesperson"],
slots: {
header$() {
return (
<>
<z-action p='fii5objw' label='新增客户' mode='dialog' fields={["title","level","beginDebt","contract","telephone","address","detail","bank","bankNumber","tin","extra","salesperson"]} rules={{"title":{"message":"请输入正确内容","required":true}}} br='beforePut' type='primary' url='/api/common/putCustomer' />
</>
)
},
action$({ row }) {
return (
<>
<z-action p='xx0y3a7q' label='编辑' mode='dialog' fields={["title","level","beginDebt","contract","telephone","address","detail","bank","bankNumber","tin","extra","salesperson"]} rules={{"title":{"message":"请输入正确内容","required":true}}} link data={row} url='/api/common/patchCustomer' />
<z-action p='lphjvw09' label='删除' mode='confirm' link data={row} url='/do/delete/customer' />
</>
)
}
}
}customer.vue:
xml
<template>
<z-table name="mg3h9mgo" :beforePut="beforePut"> </z-table>
</template>
<script>
import { provide } from "vue";
import configs from "./.lowcode/configs";
export default {
name: "p-mg3h9mgo",
setup() {
$.extend(configs, {
salesperson: { account: "account" },
level: { default: "1" },
});
provide("configs", configs);
},
methods: {
beforePut(formdata) {
if (formdata.beginDebt) {
formdata.receiveDebt = formdata.beginDebt;
}
},
},
};
</script>可以使用 $.extend(configs:Object, entitys:Object[])函数增加字段自定义属性
3. z-action 前后端交互组件
z-action 是框架里的统一“动作组件”,用来承载按钮点击后的行为:确认、弹窗表单、抽屉、气泡、事件派发、跳转等。
3.1 组件定位
一个 z-action 同时解决:
按钮展示(图标/文本/样式)
行操作数据注入(data)
动作弹层(confirm/dialog/drawer/popover)
提交逻辑(url + beforeSubmit)
与 z-table 联动刷新/事件派发
3.2 mode 行为
支持的模式(源码实际行为):
confirm
默认确认模式(桌面端是 el-popconfirm)。
portable 场景下会变成底部抽屉确认。
dialog
打开 el-dialog。
可渲染 fields 自动表单,或默认插槽内容。
drawer
打开 el-drawer。
可渲染 fields 自动表单,或默认插槽内容。
支持 direction(通过 $attrs 透传)。
emit
- 不弹层,直接向表格实例触发 cb 事件(如打印)。
blank
- 配合 href 时直接新窗口打开。
默认模式推导(未显式给 mode 时):
有 fields / 默认插槽 / href -> dialog
否则 -> confirm
portable 注入为 true 时,除了 confirm 外强制走 drawer
3.3 组件属性
| 属性名 | 说明 | 类型 | 默认值 | 枚举值 |
|---|---|---|---|---|
| id | 事件总线订阅 key($.emit(id, data) 可唤起)。 | String | - | - |
| label | 按钮显示名称 | String | - | - |
| icon | 图标(字符串用 z-icon,函数当组件渲染)。 | String | - | - |
| icon-size | 图标尺寸 | String | - | - |
| type | 展现样式 | Enum | primary | bubble/default/success/warning/danger |
| mode | 交互形式 | Enum | dialog | drawer/confirm |
| url | 提交到服务端的接口地址 | String | - | - |
| data | 初始化数据/from 接口的初始化参数 | String | - | - |
| from | 数据初始化接口 | String | - | - |
| href | 外链地址 | String | - | - |
| title | dialog/drawer/confirm 的标题 | String | - | - |
| size | 按钮的大小 | Enum | default | large/small - |
| rules | 表单的验证规则 | Array | - | - |
| fields | 表单字段配置 | Array | - | - |
| permission | 权限码 | String | - | - |
| beforeSubmit | 提交前拦截函数 | Function | - | - |
| beforeShow | dialog/drawer 显示前钩子函数 | Function | - | - |
| map | href 外链页面时,data 数据与子页面入参映射配置 | Object | - | - |
| cb | 回调事件名(emit 模式或提交后触发表格事件)。 | String | - | - |
| br | 从表格 $attrs[br] 取 beforeSubmit 方法名。 | String | - | - |
| later | 绑定事件/路由恢复延迟毫秒。 | Number | - | - |
| width | dialog/drawer 宽度 | String | Number | - | - |
| refresh | 非表格场景提交后是否 location.reload()。 | Boolean | false | - |
| sd | 批量模式:使用表格已选行逐条提交。 | Boolean | false | - |
| fixed | 与路由 query 同步的 key(支持直达恢复弹层)。 | String | - | - |
3.4 事件
| 事件 | 参数 | 时机 |
|---|---|---|
| finish | (result, payload) | 提交成功后触发。 |
| confirm | (result) | confirm 模式点击确认后触发。 |
| input | (visible) | 弹层开关变化时(兼容旧式 v-model)。 |
| close | (initData) | 关闭动作弹层时。 |
| change | (payload) | 提交前,数据处理完成后触发。 |
额外:mode='emit' 时会对表格实例触发 cb 事件(不是组件自身事件)。
3.5 插槽
#label
- 自定义触发器内容(替代默认按钮文案/图标)。
default
在 dialog/drawer 且未使用 fields 时,作为弹层主体内容。
作用域参数:initData(当前动作数据)。
action$
- 表单操作按钮插槽,替代弹窗的提交按钮
3.6 公开方法(ref 可调用)
show(data?, callback?):打开动作并注入数据。
close():关闭。
post(formData, autoClose=true):执行提交逻辑。
click(e):触发 show(内部使用)。
3.7 使用示例
1. 行删除确认
java
<z-action
label="删除"
mode="confirm"
:data="row"
url="/api/user/delete"
/>3.2 弹窗表单新增
java
<z-action
label="新增"
mode="dialog"
:fields="[{ name:'title', label:'名称' }]"
:rules="{ title:{ required:true, message:'必填' } }"
url="/api/user/add"
/>3.3 抽屉打开明细页
python
<z-action
label="明细"
mode="drawer"
href="/sales/salesDetail"
:data="row"
:map="{ salesId: 'id' }"
/>3.4 打印(事件派发)
java
<z-action
label="打印"
mode="emit"
cb="print"
:data="row"
link
/>3.5 批量提交(依赖表格勾选)
java
<z-action
label="批量审核"
:sd="true"
mode="confirm"
url="/api/order/review"
/>3.8 注意事项
mode='emit' 依赖表格上下文,通常放在 z-table 的 action$ 里用。
sd=true 必须先选中行,否则会报“请先选择要删除的项”。
fixed 会写入路由 query,用于恢复弹层状态。
beforeSubmit 返回 false 会中断提交。
fields 存在时默认由 z-form 承担提交;无 fields 时你可用默认插槽自定义。
refresh 仅在无表格上下文时影响是否整页刷新。
4. z-form 表单组件
z-form 是 Zen 内置的表单容器组件,负责字段渲染、校验、提交、重置,并可切换为普通表单/搜索表单/分步表单。
可以使用 $.extend(configs:Object, entitys:Object[])函数增加字段自定义属性
4.1 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| fields | Array | [] | 表单字段配置(通常来自 .lowcode/configs.jsx) |
| data | Object | {} | 表单初始值 |
| type | String | '' | 表单类型:normal/steped/search |
| span | Number | 6 | 默认栅格宽度(字段布局) |
| rules | Object | {} | 校验规则(会按字段转换为数组规则) |
| url | String | '' | 提交接口地址;有值时会走内置 post 提交 |
| readonly | Boolean | false | 只读模式 |
| beforeSubmit | Function | - | 提交前钩子:(formData, closable) => any |
| closable | Boolean | true | 提交后是否关闭(在弹层/动作场景下) |
| labelWidth | String/Number | '100px' | 标签宽度 |
4.2 事件
| 事件名 | 参数 | 触发时机 |
|---|---|---|
| finish | result | 提交成功后触发(独立使用 z-form 时) |
说明:如果 z-form 放在 z-action 内部,完成事件会优先发给外层动作组件($button.$emit('finish', data))。
4.3 插槽
| 插槽名 | 作用域参数 | 说明 |
|---|---|---|
| $ | formData | 动态插槽,替换同名 field |
| default | formData | 表单内容后追加自定义内容 |
| action$ | formData | 自定义操作区(在提交按钮基础上增加其他按钮) |
4.4 公开方法(通过 ref 调用)
validate():执行校验,返回校验结果
submit(e?):手动触发提交
reset():重置表单
close():关闭当前动作容器(若存在)
4.5 z-form-items 多个 field 组件
组件属性:
| 属性名 | 说明 | 类型 |
|---|---|---|
| title | 分组名称 | String |
| fields | 选项配置 | Array |
| value | 初始化数据 | Object |
| rules | 验证规则 | Array |
4.6 z-form-item 单个 field 组件
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| label | 字段标题 | String | - |
| name | 字段名称 | String | - |
| type | 表单类型 | enum | - |
| code | 字典 Code | String/Number | - |
| rules | 验证规则 | Array | - |
| tip | 验证规则 | String | - |
| value | 表单初始化值 | Object | 继承父级 form 的 data 属性 |
| default | 默认值 | Any | - |
| visible | 字段是否显示 | Boolean | Function(formdata) | - |
| readonly | 字段是否只读 | Boolean | Function(formdata) | - |
| onChange | 值变化事件 | Function(value,formData,name) | - |
4.7 表单type
| type 名 | 说明 | 其它属性 |
|---|---|---|
| input | 单行文本 | 微信端 scannable 属性支持扫码输入 |
| inputTag | 标签输入框 | - |
| password | 密码 | - |
| textarea | 多行文本 | - |
| date | 时间组件 | 参考element的DateTimePicker 日期时间选择器 |
| daterange | 时间范围 | 参考element的DateTimePicker 日期时间选择器 |
| number | 数字 | - |
| tel | 电话号码输入框 | - |
| attach | 附件 | - |
| checkbox | 多选 | - |
| radiobox | 单选 | - |
| color | 颜色 | - |
| image | 图片上传 | native:是否原生上传,支持微信上传接口自动识别 |
| map | 键值对象 | - |
| money | 金额 | - |
| moneyrange | 金额区间 | 自动映射成 ${name}Start 与 ${name}End 两个字段 |
| table | 表格输入 | fields:字段声明,noAction:无操作按钮,simple:boolean 简易模式,url:数据源 |
| select | 下拉框 | Any |
| search | 搜索下拉框 | depend:指定依赖表,tenant 指定租户字段名,以 title 字段为关键字 |
| switch | 开关 | Any |
| monaco | 代码编辑器 | Any |
| json | JSON 编辑器 | Any |
| $ | 应用全局组件 | Any |
1. search 属性
| 属性名 | 说明 | 类型 | 默认值 | 枚举值 |
|---|---|---|---|---|
| depend | 依赖表名 | String | - | - |
| tenant | 租户字段名 | String | - | - |
| account | 账户字段名 | String | - | - |
| app | 指定应用 | String | - | - |
4.2 switch属性
| 属性名 | 说明 | 类型 | 默认值 | 枚举值 |
|---|---|---|---|---|
| depend | 依赖表名 | String | - | - |
| tenant | 租户字段名 | String | - | - |
| account | 账户字段名 | String | - | - |
| app | 指定应用 | String | - | - |
4.3 money属性
| 属性名 | 说明 | 类型 | 默认值 | 枚举值 |
|---|---|---|---|---|
| allowMinus | 是否允许负数 | Boolean | false | - |
| unit | 币值单位 | String | 元 | - |
4.4 daterange属性
| 属性名 | 说明 | 类型 | 默认值 | 枚举值 |
|---|---|---|---|---|
| endKey | 结束时间字段名 | String | #{name}End | - |
4.8 使用示例
vue/src/pages/product/productEditor/.lowcode/configs.jsx:
sql
export default {
url: "",
conditionLimit:null,
selectable: false,
showIndex: false,
compact: 220,
path: 'product/productEditor',
title: "编辑商品",
entitys: [{"name":"id","label":"编号","type":"search"},{"name":"creator","label":"创建人","type":"user"},{"name":"createGmt","label":"创建时间","type":"date"},{"name":"updateGmt","label":"更新时间","type":"date"},{"name":"title","label":"商品名称"},{"name":"category","label":"商品分类","type":"category"},{"name":"brand","label":"商品品牌","type":"search","depend":"brand"},{"name":"images","label":"轮播图","type":"inputTag"},{"name":"mainImage","label":"主图","type":"image"},{"name":"description","label":"商品描述","type":"tiptap"},{"name":"status","label":"商品状态","code":"productStatus"},{"name":"skuConfig","label":"规格名","type":"inputTag"},{"name":"skuValue","label":"规格值","type":"inputTag"},{"name":"supplier","label":"供应商","type":"search","depend":"supplier"},{"name":"barcode","label":"条码"},{"name":"number","label":"商品编号"},{"name":"unit","label":"单位","type":"search","depend":"unit"},{"name":"location","label":"货位"},{"name":"stockWarning","label":"库存预警","type":"number"},{"name":"account","label":"所属账户","type":"search","depend":"account"},{"name":"price","label":"价格","type":"money"},{"name":"expriation","label":"保质期天数","type":"number"},{"name":"costPrice","label":"进货价","type":"money"},{"name":"wholesalePrice","label":"批发价","type":"money"},{"name":"isSku","label":"是否多规格","type":"switch"}],
columns: [],
condition: [],
slots: {
}
}vue/src/pages/product/productEditor/productEditor.vue
javascript
<template>
<el-tabs v-model="current" v-if="loaded">
<el-tab-pane label="基本信息" name="basic">
<z-form
:url="basicUrl"
:closable="false"
:data="productData"
:fields="basicFields"
@finish="addFinish"
:rules="rules"
/>
</el-tab-pane>
<el-tab-pane
:disabled="isNew"
label="商品规格"
lazy
name="models"
v-if="productData.isSku"
>
<z-form
url="/api/product/patchSku"
:closable="false"
:data="productData"
lazy
:fields="attrFields"
/>
</el-tab-pane>
<el-tab-pane :disabled="isNew" label="商品描述" lazy name="describe">
<z-form
url="/do/patch/product"
:closable="false"
:data="productData"
:fields="describeFields"
>
<template #description="formData">
<z-tiptap
:modelValue="formData.description"
style="margin-bottom: 20px; height: 600px"
@update:modelValue="update"
/>
</template>
</z-form>
</el-tab-pane>
</el-tabs>
</template>
<script>
import { markRaw } from "vue";
import configs from "./.lowcode/configs";
import SkuConfig from "./blocks/SkuConfig.vue";
import SkuValue from "./blocks/SkuValue.vue";
export default {
name: "p-jd4k5c6p",
inject: ["account"],
provide: { configs },
props: {
params: null,
},
computed: {
basicUrl() {
return this.isNew
? "/api/product/putProduct"
: "/api/product/patchProduct";
},
},
data() {
return {
current: "basic",
basicFields: [
"title",
"barcode",
"number",
"category",
{ name: "brand", account: "account" },
{ name: "unit", account: "account" },
{
name: "images",
type: "image",
limit: 5,
onChange: this.imageChange,
tip: "800px * 800px",
},
{
name: "mainImage",
type: "image",
multiple: false,
tip: "白底,800px * 800px",
},
{
label: "供应商",
name: "supplier",
type: "search",
depend: "supplier",
account: "account",
},
"location",
"stockWarning",
{ name: "expriation", label: "保质期天数", type: "number" },
{
name: "expirWarning",
label: "保质期预警天数",
type: "number",
},
{ name: "isSku", default: 1, visible: () => this.isNew },
],
attrFields: [
{ name: "skuConfig", component: markRaw(SkuConfig) },
{ name: "skuValue", component: markRaw(SkuValue) },
],
describeFields: [
{
name: "description",
tip: "宽900px,高度不限",
},
],
loaded: false,
isNew: true,
productData: { account: this.account },
rules: {
title: { required: true, message: "商品名称不能为空" },
},
};
},
async created() {
const { id } = this.params;
if (id) {
await this.loadDetail(id);
}
const config = await $.get({
url: "/api/setting/systemConfig",
data: { type: "pref" },
});
if (config != null && config.stockWarning != null)
this.productData.stockWarning = config.stockWarning;
this.loaded = true;
},
methods: {
update(value) {
this.productData.description = value;
},
imageChange(val, formData) {
console.log(formData);
if (val.length > 0) {
formData.mainImage = val[0].url;
}
},
async loadDetail(id) {
this.productData = await $.get({
url: "/do/get/product",
data: { id },
});
if (this.productData.status === 3) {
this.productData.status = 5;
}
this.isNew = false;
},
async addFinish(result) {
await this.loadDetail(result.id);
this.isNew = false;
},
},
};
</script>vue/src/pages/product/productEditor/blocks/SkuConfig.vue
javascript
<template>
<z-action label="添加规格" :fields="fields" :beforeSubmit="addProp" mode="popover" />
<div class="bg" v-if="props.length > 0">
<el-form-item v-for="(item, idx) in props" :key="item.pid" :value="item" :index="idx">
<template #label>
{{ item.title }}
<z-action icon="x" type="text" mode="confirm" title="确定删除该规格吗?" :beforeSubmit="() => removeProp(idx)" />
<z-icon v-if="idx > 0" value="arrowUp" class="remove hover" @click="() => move(idx, props)" />
</template>
<z-action v-if="item.isPic" type="default" size="small" label="规格属性" icon="plus" :fields="tagFieldsPic"
:beforeSubmit="(data) => addPropValue(data, item)" mode="popover" width="260px" />
<z-action v-else type="default" size="small" label="规格属性" icon="plus" :fields="tagFields"
:beforeSubmit="(data) => addPropValue(data, item)" mode="popover" width="260px" />
<SkuTag v-for="(tag, idx2) in item.list" @remove="() => remove(item, idx2)" :isPic="!!item.isPic"
:value="tag" :key="tag.vid" :idx="idx2" @move="() => move(idx2, item.list)" />
</el-form-item>
</div>
</template>
<script>
import SkuTag from './SkuTag.vue';
export default {
inheritAttrs: false,
components: { SkuTag },
props: {
modelValue: Array
},
data() {
return {
fields: [
{ label: '规格名', name: 'title' },
{ label: '是否图片', name: 'isPic', type: 'switch' }
],
tagFields: [
{ label: '规格属性', name: 'title' },
],
tagFieldsPic: [
{ label: '规格属性', name: 'title' },
{ label: '图片', name: 'picUrl', type: 'image' },
],
selected: null,
visible: false,
props: [],
};
},
async created() {
if (this.modelValue) {
this.props = this.modelValue
}
},
methods: {
remove(item, idx) {
item.list.splice(idx, 1);
this.update()
},
move(idx, parents) {
const item = parents.splice(idx, 1)
parents.splice(idx - 1, 0, item[0]);
},
removeProp(idx) {
if (this.props[idx].list.length > 0) {
return $.error('请先删除规格属性')
}
this.props.splice(idx, 1)
this.update()
},
addProp(data) {
data.pid = $.uid(8)
data.list = []
this.props.push(data)
},
addPropValue(data, parent) {
parent.list.push({ ...data, vid: $.uid(8) })
this.update()
return true
},
update() {
this.$emit("update:modelValue", this.props)
$.emit('skuConfigUpdate', this.props)
}
}
};
</script>
<style lang="scss" scoped>
.bg {
background: var(--a-fill-color);
margin-top: 8px;
padding: 8px;
border-radius: 4px;
width: 95%;
table {
width: 100%;
}
}
.propValueName {
display: inline-block;
white-space: normal;
width: 140px
}
:deep(.a-form-item__label) {
padding-right: 24px;
position: relative;
.remove {
display: none;
position: absolute;
top: 6px;
right: 10px;
}
&:hover .remove {
display: block;
}
}
:deep(.el-radio__input) {
vertical-align: top;
}
</style>vue/src/pages/product/productEditor/blocks/SkuValue.vue
javascript
<template>
<div class="bg">
<z-form-item :value="formData" :noAction="true" type="table" name="skuValue" :fields="fields" />
</div>
</template>
<script>
import SkuFunc from './skuFunc';
export default {
props: {
formData: Object
},
data() {
return {
fields: null,
fixedColumns: [
{ name: 'outId', label: '外部ID', width: '180px' },
{ name: 'weight', label: '净重(克)', type: 'number', width: '140px' },
{ name: 'status', label: '是否启用', type: 'switch', width: '100px' }
]
};
},
created() {
$.on('skuConfigUpdate', this.updateSku)
const skuConfig = this.formData['skuConfig']
if (skuConfig) {
this.updateSku(skuConfig)
} else {
this.fields = [...this.fixedColumns]
}
},
methods: {
updateSku(configs) {
const skuData = SkuFunc.createSku(configs)
const skuValue = skuData.map((sku) => {
const titles = []
configs.forEach(prop => {
titles.push(sku['p_' + prop.pid])
});
sku.title = titles.join(",")
// 更存量数据合并
const item = SkuFunc.skuData(sku, this.formData.skuValue || [], configs)
// 同步更新SKU标题
if (item) {
item.title = sku.title
return item
}
return sku
})
this.formData.skuValue = skuValue
const columns = configs.map((item) => {
return { name: "p_" + item.pid, label: item.title, type: 'text' }
})
this.fields = [...columns, ...this.fixedColumns]
},
//新增外部编码字段
blur(outId) {
if (/[^\w\.\/]/gi.test(outId)) {
return $.error("外部编码不支持输入中文和空格");
}
}
}
};
</script>
<style lang="scss" scoped>
.bg {
background: var(--a-fill-color);
padding: 8px;
border-radius: 4px;
width: 95%;
}
</style>