背诵-----------------------------

2025-12.8

复制代码
view    → 所有块级元素(div, section, article, header, footer...)
text    → 所有文本元素(span, p, h1-h6, label...)
image   → 所有图片
input   → 表单输入
button  → 按钮
scroll-view → 滚动区域

商品列表

// 定义组件

组件 {

// 响应式数据存储

数据 {

配置项 {

获取数据接口: 初始为空字符串 (后续动态赋值)

删除数据接口: 初始为空字符串 (后续动态赋值)

编辑页面路径: '/pages/goods/edit' (固定路径)

菜单ID: 'f7a907fb-b79e-4fee-a512-7482ff796236' (唯一标识)

}

列表数据: 空数组 (用于存储从API获取的商品列表)

}

// 计算属性区 (当前为空)

计算属性 {

}

// 下拉刷新事件处理器

下拉刷新事件 {

  1. 调用搜索框组件的搜索方法 (触发数据更新)

  2. 设置500毫秒延时

  3. 延时结束后停止下拉刷新动画

}

// 组件创建时初始化

创建生命周期 {

  1. 将获取数据接口指向全局API的商品查询方法

  2. 将删除数据接口指向全局API的商品删除方法

}

// 显示页面时触发

显示生命周期 {

  1. 调用搜索框组件的搜索方法 (确保显示最新数据)

}

// 方法区 (当前为空)

方法 {

}

}

商品编辑

定义组件 {

//属性

属性: {

title: 字符串 // 标题

},

//响应式数据存储

数据() {

return{

状态: {

操作类型: 'add' // 当前操作类型:add/update

},

表单配置选项: [

{

标题: '商品状态',

键: 'status',

值: 1,

类型: 开关,

必填: true

},

],

规格表头: [

{ 标题: '序号', 键: 'sort', 类型: 数字输入 }

],

附文件列表: [],

规格列表: [],

选择器页面参数: { a: 1 },

编辑参数: {},

API配置: {

编辑接口: '',

新增接口: '',

更新接口: '',

删除接口: ''

}

}

},

//组件创建时初始化

创建时() {

// 初始化API配置

设置API配置.编辑接口 = 商品API.编辑

设置API配置.新增接口 = 商品API.新增

设置API配置.更新接口 = 商品API.更新

设置API配置.删除接口 = 商品API.删除

},

//加载页面时触发

页面加载时(参数) {

如果 参数.id 存在 {

显示加载中('数据加载中...')

调用编辑方法(参数.id)

}

},

//方法

方法: {

删除商品() {

显示确认弹窗({

'删除提示',

'确定要删除当前数据吗?',

success: (e) => {

如果 用户确认 {

发送GET请求(删除接口, {

id: 编辑参数.id

}) .then(data => {

如果 成功 {

显示成功提示({

'删除成功!'

})

延迟1秒后 返回上一页

}

}).catch(e => {})

}

}

});

},

编辑商品(商品ID) {

设置操作类型为'update'

发送GET请求(编辑接口, {

id: 商品ID

}).then (data=> {

如果 数据存在 {

保存编辑参数 = 返回的数据

  1. 回填表单选项

遍历 表单选项 {

如果 数据中有对应字段 {

设置选项值 = 数据中的值

1.特殊处理供应商:供应商未选择时,不回显

如果 选项是供应商 {

检查供应商ID是否无效 {

清空供应商选项

}

}

}

}

  1. 回填附件文件

文件列表 = 处理文件数据(数据.files) {

映射每个文件为 {

原文件属性,

url: 优先使用原url,无则尝试从path/saveName构造,

状态: 'success'

}

}

  1. 回填规格列表

规格列表 = 处理规格数据(数据.specList) {

映射每个规格为 {

原规格属性,

images: 处理规格图片(规格.files), // 类似文件处理

qty: 转换为数字,

subAmount: 转换为数字

}

}

}

隐藏加载中

}

},

取消返回() {

返回上一页

},

保存商品() {

如果 表单验证通过 {

确定要使用的API = 编辑参数.id存在 ? 更新接口 : 新增接口

构建请求参数 = {}

如果 是更新 {

请求参数 = 复制编辑参数

}

  1. 收集表单数据

遍历 表单选项 {

如果 必填或有值 {

请求参数[字段键] = 选项值

1.特殊处理选择器类型(分类、单位、供应商)

如果 字段是选择器类型 {

从数组中取第一个值

}

}

}

  1. 处理规格数据

如果 规格列表存在且非空 {

请求参数.specList = 映射规格列表 {

转换每个规格为 {

除images外的规格属性,

files: 处理图片数据(规格.images), // 前端images转后端files

sort: 规格序号或索引+1

}

}

}

  1. 处理附件文件

如果 文件列表存在 {

请求参数.files = 映射文件列表 {

处理文件ID: 只保留有效的GUID格式ID

}

}

  1. 提交保存

显示加载中('保存中...')

发送POST请求(对应API, 请求参数) {

隐藏加载中

显示成功提示('保存成功!')

延迟1秒后返回上一页

}

}

},

表单选项变化回调(新选项) {

更新表单选项 = 新选项

},

添加规格行() {

新序号 = 当前规格列表长度 + 1

添加新规格到列表({

sort: 新序号,

name: '',

purchasePrice: 0,

tradePrice: 0,

vipPrice: 0,

remark: '',

images: [],

qty: 0,

subAmount: 0

})

},

规格变化回调(事件) {

如果 事件.索引存在 {

更新规格列表[索引] = 事件.数据项

}

},

删除规格({索引}) {

第1行:onSpecDelete({ index }) {

函数定义:名为 onSpecDelete,接收一个包含 index 属性的对象作为参数

第2行:if (!Array.isArray(this.specList)) return;

条件判断:如果 this.specList 不是数组类型

则执行:直接返回,结束函数执行

第3行:const row = this.specList[index];

变量声明:从 this.specList 数组中获取索引为 index 的元素

赋值给变量 row

第4行:const guidRe = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;

变量声明:创建一个正则表达式对象 guidRe

模式说明:匹配GUID/UUID格式字符串

格式要求:8位十六进制-4位十六进制-4位十六进制-4位十六进制-12位十六进制

第5行:// 后端确定主键是 id,这里只用 row.id

注释:说明后端确认的主键字段名是 "id"

第6行:const serverIdCandidate = row?.id;

变量声明:获取 row 对象的 id 属性值

使用可选链操作符:如果 row 为 null 或 undefined,返回 undefined

赋值给变量 serverIdCandidate

第7行:const hasServerId = typeof serverIdCandidate === 'string' && guidRe.test(serverIdCandidate);

变量声明:判断是否有有效的服务器ID

条件1:serverIdCandidate 的数据类型是字符串

条件2:serverIdCandidate 符合 guidRe 正则表达式模式

两个条件都为真时:hasServerId = true

否则:hasServerId = false

第8行:空行(代码分隔)

第9行:// 把本地列表里这条删除,并把后续序号重排(保持 sort 从1开始递增)

注释:说明接下来定义的函数功能

第10行:const removeLocally = () => {

函数声明:定义一个名为 removeLocally 的箭头函数

该函数没有参数

第11行:this.specList.splice(index, 1);

数组操作:从 this.specList 数组中删除一个元素

删除位置:索引为 index 的位置

删除数量:1 个元素

第12行:this.specList = this.specList.map((s, i) => ({ ...s, sort: i + 1 }));

数组操作:重新赋值 this.specList

使用 map 方法:遍历数组中的每个元素

参数说明:s 表示当前元素,i 表示当前索引

操作:为每个元素创建一个新对象

  1. 展开原对象的所有属性:...s

  2. 添加或覆盖 sort 属性:sort: i + 1

  3. i + 1 表示从1开始的连续序号

第13行:};

函数定义结束

第14行:空行(逻辑分隔)

第15行:if (hasServerId) {

条件判断:如果 hasServerId 为 true

则执行:第16-33行的代码块

第16行:// 后端删除:POST /App/Goods/DeleteSpec

注释:说明接下来执行后端删除操作

第17行:uni.showLoading({ title: '删除规格中...', mask: true });

调用函数:显示加载提示框

参数说明:

title: '删除规格中...' 提示文字

mask: true 显示遮罩层,阻止用户操作

第18行:空行

第19行:// 后端要求:URL 后面带上 id, /App/Goods/DeleteSpec?id=xxxx

注释:说明URL构建规则

第20行:// POST 方法,拼到 URL 上

注释:说明HTTP方法

第21行:const url = `{this.api.goods.deleteSpec}?id=${encodeURIComponent(serverIdCandidate)}`;

变量声明:构建请求URL

字符串模板:{this.api.goods.deleteSpec} 获取API基础地址

查询参数:?id=${encodeURIComponent(serverIdCandidate)}

encodeURIComponent:对 serverIdCandidate 进行URL编码

第22行:this.$http.Post(url, {})

调用函数:发送HTTP POST请求

参数1:url 请求地址

参数2:{} 空对象作为请求体

第23行:.then(() => {

Promise 成功回调:当HTTP请求成功时执行

开始定义回调函数

第24行:uni.hideLoading();

调用函数:隐藏加载提示框

第25行:uni.showToast({ title: '规格已删除', icon: 'success', duration: 800 });

调用函数:显示成功提示

参数说明:

title: '规格已删除' 提示文字

icon: 'success' 成功图标

duration: 800 显示800毫秒后自动消失

第26行:removeLocally();

调用函数:执行本地删除操作(第10-13行定义的函数)

第27行:})

Promise 成功回调函数结束

第28行:.catch(() => {

Promise 失败回调:当HTTP请求失败时执行

开始定义回调函数

第29行:uni.hideLoading();

调用函数:隐藏加载提示框

第30行:// 如果后端删除失败,不改动本地列表

注释:说明失败时的处理原则

第31行:uni.showToast({ title: '删除失败', icon: 'error', duration: 1000 });

调用函数:显示错误提示

参数说明:

title: '删除失败' 提示文字

icon: 'error' 错误图标

duration: 1000 显示1000毫秒后自动消失

第32行:});

Promise 失败回调函数结束

第33行:}

if 语句代码块结束

第34行:else {

else 分支:当 hasServerId 为 false 时执行

第35行:// 没有合法id(说明还没保存到服务器),直接本地删除即可

注释:说明else分支的逻辑

第36行:removeLocally();

调用函数:执行本地删除操作

第37行:}

else 分支代码块结束

第38行:}

函数 onSpecDelete 定义结束

}

}

}

列表子组件

template

第1行:<template>

开始模板部分

第2行: <div class="table-u" :style="{height: tableHeigth+'px'}">

根div元素,类名table-u,动态绑定样式高度为tableHeight像素

第3行: <u-list @scrolltolower="scrolltolower" style="width: 100%;overflow-y: auto;height: 100%;" :height="tableHeigth+'px'"

使用u-list组件,绑定滚动到底部事件,设置宽度100%,垂直滚动,高度100%,动态高度绑定

第4行: refresherEnabled :refresherTriggered="refresherTriggered" @onRefresh="onRefresh">

启用下拉刷新,绑定刷新触发状态和刷新事件

第5行: <u-list-item v-for="(item, index) in list" :key="index">

遍历list数组,为每个元素创建u-list-item,key使用索引index

第6行: <!-- 主战 -->

注释:主战模式(type=1)

第7行: <template v-if="type===1">

条件渲染:当type等于1时显示此模板

第8行: <div @click="listItemClick(item)">

点击事件,触发listItemClick方法,传入当前item

第9行: <div class="content active" :class="[status.multiple&&item.check?'check':'']">

div元素,类名content和active,动态类名:当status.multiple为真且item.check为真时添加check类

第10行: <!-- 内容1 -->

注释:内容1区域

第11行: <div class="content1 ">

类名为content1的div

第12行: <div class="df-jb-ac f30 emphasis-font" style="padding:0 30rpx;">

flex布局(两端对齐,垂直居中),字体大小30,强调字体,内边距0 30rpx

第13行: <div class="df-js-ac" v-for="(header,index) in firstHeaders">

遍历firstHeaders数组,每个元素创建div,flex布局(主轴起始对齐,垂直居中)

第14行: <checkbox v-if="status.multiple&&index===0" :checked="item.check" color="#0388FF" />

条件渲染复选框:当status.multiple为真且是第一个表头时显示,选中状态绑定item.check,颜色蓝色

第15行: <div>{{item[header.aName]||''}}</div>

显示item对象中header.aName属性的值,如果为空则显示空字符串

第16行: </div>

结束firstHeaders遍历

第17行: </div>

结束content1的flex容器

第18行: </div>

结束content1区域

第19行: <!-- 内容2 -->

注释:内容2区域

第20行: <div class="f28 content2">

字体大小28,类名content2

第21行: <div class="df-js-ac content2-item" v-for="(header,index) in headers" style="flex-wrap: wrap">

遍历headers数组,每个元素创建div,flex布局,允许换行

第22行: <!-- 名 -->

注释:字段名称

第23行: <div class="content-font">{{header.description}}:</div>

显示表头描述,添加冒号

第24行: <!-- 值 -->

注释:字段值

第25行: <div v-if="header.aName==='status'" class="content-font2" style="flex: 1;">{{item[header.aName]|Status}}</div>

条件渲染:当字段名为status时,显示格式化后的状态值(使用Status过滤器)

第26行: <div v-else class="content-font2" style="flex: 1;">{{item[header.aName]||''}}</div>

否则显示字段值,如果为空显示空字符串

第27行: </div>

结束headers遍历

第28行: </div>

结束content2区域

第29行: <!-- 内容3 -->

注释:内容3区域

第30行: <div class="content3 f30">

类名content3,字体大小30

第31行: <div class="df-fc-jc-ac content3-item " v-for="(header,index) in lastHeaders">

遍历lastHeaders数组,每个元素创建div,flex列布局,主轴和交叉轴居中

第32行: <!-- 值 -->

注释:字段值

第33行: <div v-if="header.aName==='status'" class="df-jc-ac primary-font">{{item[header.aName]|Status}}</div>

条件渲染:当字段名为status时,显示格式化后的状态值

第34行: <div v-else class="df-jc-ac primary-font">{{item[header.aName]||'无'}}</div>

否则显示字段值,如果为空显示"无"

第35行: <!-- 名 -->

注释:字段名称

第36行: <div class="df-jc-ac content-font">{{header.description}}</div>

显示表头描述

第37行: </div>

结束lastHeaders遍历

第38行: </div>

结束content3区域

第39行: </div>

结束最内层div

第40行: </div>

结束点击事件div

第41行: </template>

结束type=1模板

第42行: <!-- 简易横拉 -->

注释:简易横拉模式(type=2)

第43行: <template v-if="type===2">

条件渲染:当type等于2时显示此模板

第44行: <!-- <u-swipe-action> -->

注释:侧滑操作组件(被注释)

第45行: <u-swipe-action-item :ref="'actionItemRef'+index" :options="options" :disabled="type2Disabled" @click="(v)=>{swipeClick(v,item)}">

侧滑操作项,动态ref绑定,操作选项绑定options,禁用状态绑定type2Disabled,点击事件

第46行: <div class="cell f28 df-jb-ac active1" @click="listItemClick(item)">

div元素,类名cell、f28、df-jb-ac、active1,点击事件

第47行: <div>{{item.name}}</div>

显示item.name

第48行: <u-icon name="arrow-right" color="#6d6d6d" size="16" style="margin-right: 10rpx;"></u-icon>

右侧箭头图标,颜色灰色,大小16,右边距10rpx

第49行: </div>

结束cell div

第50行: </u-swipe-action-item>

结束侧滑操作项

第51行: <!-- </u-swipe-action> -->

注释:结束侧滑操作组件

第52行: </template>

结束type=2模板

第53行: <!-- 简易多选 -->

注释:简易多选模式(type=3)

第54行: <template v-if="type===3">

条件渲染:当type等于3时显示此模板

第55行:

空行

第56行: <!-- 自定义展示 -->

注释:自定义展示

第57行: <template v-if="initHeaders&&initHeaders.length">

条件渲染:当initHeaders存在且长度大于0时

第58行: <div @click="listItemClick(item)">

点击事件div

第59行: <div class="content active" :class="[item.check?'check':'']">

div元素,类名content和active,动态类名:item.check为真时添加check类

第60行: <div class="content1 ">

类名content1

第61行: <div class="df-jb-ac f30 emphasis-font" style="padding:0 30rpx;">

flex布局,字体大小30,强调字体,内边距

第62行: <div class="df-js-ac" v-for="(header,index) in firstHeaders">

遍历firstHeaders

第63行: <checkbox v-if="index===0" :checked="item.check" color="#0388FF" />

条件渲染复选框:第一个表头时显示

第64行: <div>{{item[header.aName]||''}}</div>

显示字段值

第65行: </div>

结束firstHeaders遍历

第66行: </div>

结束flex容器

第67行: </div>

结束content1

第68行: <div class="f28 content2">

字体大小28,类名content2

第69行: <div class="df-js-ac content2-item" v-for="(header,index2) in headers" style="flex-wrap: wrap">

遍历headers数组(使用index2避免冲突)

第70行: <div class="content-font">{{header.description}}:</div>

显示表头描述

第71行:

空行

第72行: <div v-if="header.inputType==='number'" class="content-font2" style="width: 120rpx;border-bottom:1rpx solid #e0e0e0;;">

条件渲染:当inputType为number时,显示数字输入框容器

第73行: <u--input v-model="item[header.aName]" slot="value" placeholder=" " border="none" maxlength="11" inputAlign="right"

输入框组件,双向绑定item[header.aName],无占位符,无边框,最大长度11,右对齐

第74行: type="number" style=""></u--input>

输入类型为数字

第75行: </div>

结束数字输入框容器

第76行: <div v-else class="content-font2" style="flex: 1;">{{item[header.aName]||''}}</div>

否则显示普通文本

第77行:

空行

第78行: </div>

结束headers遍历

第79行: </div>

结束content2

第80行: </div>

结束动态类名div

第81行: </div>

结束点击事件div

第82行: </template>

结束initHeaders条件渲染

第83行: <template v-else>

否则(initHeaders不存在或为空)

第84行: <div class="cell f28 df-jb-ac active1" :class="[checkbox&&item.check?'check':'']" @click="listItemClick(item)">

div元素,类名cell、f28、df-jb-ac、active1,动态类名:checkbox为真且item.check为真时添加check类,点击事件

第85行: <div>

内层div

第86行: <div>{{item.name||item.label}}</div>

显示item.name或item.label

第87行: <!-- zyy:如果有规格(specName),在商品的名称下方追加显示 -->

注释:规格信息显示

第88行: <div v-if="item.specName" style="color: #6d6d6d;margin-top: 6rpx;font-size: 24rpx;">规格:{{item.specName}}</div>

条件渲染:当有specName时显示规格信息

第89行: </div>

结束内层div

第90行:

空行

第91行: <div class="df-je-ac">

flex布局,主轴结束对齐,垂直居中

第92行: <!-- zyy:不同接口的"库存/未出库数量"字段名不一致。这里统一用方法读取:优先取 availableStock,其次 notOutQty/stockQuantity,最后用 qty。 -->

注释:库存显示逻辑说明

第93行: <div v-if="getStock(item) !== null" style="margin-right: 20rpx;color: #0388FF;">库存:{{getStock(item)}}</div>

条件渲染:当getStock返回值不为null时显示库存信息

第94行: <!-- <u-icon name="arrow-right" color="#6d6d6d" size="16" style="margin-right: 10rpx;"></u-icon> -->

注释:箭头图标(被注释)

第95行: <checkbox v-if="checkbox" :checked="item.check" color="#0388FF" />

条件渲染复选框:当checkbox为真时显示

第96行: </div>

结束flex容器

第97行:

空行

第98行: </div>

结束cell div

第99行: </template>

结束else模板

第100行:

空行

第101行: <template v-if="item.childrenOpen" v-for="item2 in item.children">

条件渲染+遍历:当item.childrenOpen为真时,遍历item.children

第102行: <div class="cell f28 df-jb-ac active1" :class="[checkbox&&item2.check?'check':'']" @click="listItemClick(item2)"

二级子项div,样式类似主项,左边距50rpx

第103行: style="margin-left: 50rpx;">

内联样式:左边距50rpx

第104行: <!-- zyy:子级同样支持显示规格 -->

注释:子级规格显示

第105行: <div>

内层div

第106行: <div>{{item2.name||item2.label}}</div>

显示名称

第107行: <div v-if="item2.specName" style="color: #6d6d6d;margin-top: 6rpx;font-size: 24rpx;">规格:{{item2.specName}}</div>

条件渲染规格信息

第108行: </div>

结束内层div

第109行:

空行

第110行: <checkbox v-if="checkbox" :checked="item2.check" color="#0388FF" />

条件渲染复选框

第111行: </div>

结束二级子项div

第112行:

空行

第113行: <template v-if="item2.childrenOpen" v-for="item3 in item2.children">

条件渲染+遍历:当item2.childrenOpen为真时,遍历item2.children

第114行: <template v-if="checkbox">

条件渲染:当checkbox为真时

第115行: <!-- @click="listItemClick(item3)" -->

注释:点击事件(被注释)

第116行: <div class="cell f28 df-jb-ac active1" :class="[checkbox&&item3.check?'check':'']" style="margin-left: 100rpx;">

三级子项div,左边距100rpx

第117行: <div>

内层div

第118行: <div>{{item3.name||item3.label}}</div>

显示名称

第119行: <div v-if="item3.specName" style="color: #6d6d6d;margin-top: 6rpx;font-size: 24rpx;">规格:{{item3.specName}}</div>

条件渲染规格信息

第120行: </div>

结束内层div

第121行: <!-- <checkbox v-if="checkbox" :checked="item3.check" color="#0388FF" /> -->

注释:复选框(被注释)

第122行: </div>

结束三级子项div

第123行: </template>

结束checkbox条件渲染

第124行: <template v-else>

否则

第125行: <div class="cell f28 df-jb-ac active1" :class="[checkbox&&item3.check?'check':'']" @click="listItemClick(item3)"

三级子项div,带点击事件

第126行: style="margin-left: 100rpx;">

左边距100rpx

第127行: <div>

内层div

第128行: <div>{{item3.name||item3.label}}</div>

显示名称

第129行: <div v-if="item3.specName" style="color: #6d6d6d;margin-top: 6rpx;font-size: 24rpx;">规格:{{item3.specName}}</div>

条件渲染规格信息

第130行: </div>

结束内层div

第131行: <!-- <checkbox v-if="checkbox" :checked="item3.check" color="#0388FF" /> -->

注释:复选框(被注释)

第132行: </div>

结束三级子项div

第133行:

空行

第134行: </template>

结束else模板

第135行: </template>

结束item2.childrenOpen遍历

第136行: </template>

结束item.childrenOpen遍历

第137行:

空行

第138行:

空行

第139行:

空行

第140行: </template>

结束type=3模板

第141行: <!-- 用户管理界面 -->

注释:用户管理模式(type=4)

第142行: <template v-if="type===4">

条件渲染:当type等于4时

第143行: <div class="cell f28 df-jb-ac active1" @click="listItemClick(item)" style="padding: 16rpx 30rpx;">

div元素,类名cell、f28、df-jb-ac、active1,点击事件,内边距16rpx 30rpx

第144行:

空行

第145行:

空行

第146行: <div class="df-jb-ac">

flex布局,两端对齐,垂直居中

第147行: <div style="margin-right: 20rpx;">

div元素,右边距20rpx

第148行:

空行

第149行: <template v-if="item.userFile&&item.userFile.length">

条件渲染:当userFile存在且长度大于0时

第150行: <u-avatar class="avatar-img" :src="item.userFile[0].path" size="100rpx"></u-avatar>

头像组件,使用userFile第一个元素的path作为图片源,大小100rpx

第151行: </template>

结束条件渲染

第152行: <u-avatar v-else class="avatar-img" :src="'https://dev.stall.lkxlkf.com/ca/icons/default/default-icon.png'"

否则显示默认头像,使用默认图片URL

第153行: size="100rpx"></u-avatar>

头像大小100rpx

第154行: <!-- <open-data type="userAvatarUrl" style="width: 100rpx;height: 100rpx;margin-right: 20rpx;border-radius: 5pt;" -->

注释:open-data组件(被注释)

第155行: default-avatar="https://dev.stall.lkxlkf.com/ca/icons/default/default-icon.png"></open-data> -->

注释结束

第156行: </div>

结束头像容器div

第157行: <div>

右侧信息div

第158行: <div>{{item.realName}}</div>

显示真实姓名

第159行: <div style="color: #e2a10e;">{{item.roleName}}</div>

显示角色名称,颜色黄色

第160行: </div>

结束右侧信息div

第161行: </div>

结束flex容器

第162行:

空行

第163行:

空行

第164行: <div class="df-je-ac">

flex布局,主轴结束对齐,垂直居中

第165行: <div style="color: #6d6d6d;">设置</div>

"设置"文字,颜色灰色

第166行: <u-icon name="arrow-right" color="#6d6d6d" size="16" style="margin-right: 10rpx;"></u-icon>

右侧箭头图标

第167行: </div>

结束flex容器

第168行: </div>

结束type=4主div

第169行: </template>

结束type=4模板

第170行: </u-list-item>

结束u-list-item

第171行:

空行

第172行: <u-loading-page v-if="list.length===0&&status.getLoading"></u-loading-page>

条件渲染加载页:当列表为空且正在加载时显示

第173行: <u-empty v-if="list.length===0&&!status.getLoading" mode="list" marginTop="40"> </u-empty>

条件渲染空状态:当列表为空且不在加载时显示

第174行: <div v-if="addPath" style="text-align: center;color:#0388FF;margin-bottom: 30px;" :style="{marginTop:list.length===0?'50px':'0px'}"

条件渲染新增按钮:当addPath存在时显示,文本居中,颜色蓝色,动态上边距

第175行: @click="toAddPath">前往新增</div>

点击事件触发toAddPath方法,显示文字"前往新增"

第176行:

空行

第177行: </u-list>

结束u-list组件

第178行: <div class="else-heigth"></div>

占位div,类名else-heigth

第179行:

空行

第180行:

空行

第181行: <image v-if="editPath" class="add-button active" src="../../static/images/add.png" @click="toEdit('')"></image>

条件渲染添加按钮图片:当editPath存在时显示,点击触发toEdit方法

第182行: <!-- <u-empty mode="list"></u-empty> -->

注释:空状态组件(被注释)

第183行: </div>

结束根div

第184行:

空行

第185行:</template>

结束模板部分

script
第一部分:Props 定义(第1-74行)

第1-2行:export default {

导出默认组件配置对象

第3-4行: props: {

开始定义props(组件接收的属性)

第5-8行: nameTemplate: { type: String, default: '' },

属性:nameTemplate,字符串类型,默认值空字符串

第9-12行: type: { type: Number, default: 1 },

属性:type,数字类型,默认值1

第13-16行: editPath: { type: String, default: '' },

属性:editPath,字符串类型,默认值空字符串

第17-24行: options: { type: Array, default: () => [{ text: '删除', style: { backgroundColor: '#f56c6c' } }] },

属性:options,数组类型,默认返回包含删除操作的数组

第25-28行: optionsDisabled: { type: Boolean, default: true },

属性:optionsDisabled,布尔类型,默认值true

第29-32行: api: { type: String, default: () => '' },

属性:api,字符串类型,默认值空字符串

第33-36行: method: { type: String, default: () => 'Post' },

属性:method,字符串类型,默认值'Post'

第37-40行: deleteApi: { type: String, default: () => '' },

属性:deleteApi,字符串类型,默认值空字符串

第41-44行: checkbox: { type: Boolean, default: false },

属性:checkbox,布尔类型,默认值false

第45-48行: checkbox1: { type: Boolean, default: false },

属性:checkbox1,布尔类型,默认值false

第49-52行: menuId: { type: String, default: () => '' },

属性:menuId,字符串类型,默认值空字符串

第53-57行: // menuIdEdit 专用于"跳转到编辑页"携带的菜单ID

注释:menuIdEdit的用途说明

第58-61行: menuIdEdit: { type: String, default: () => '' },

属性:menuIdEdit,字符串类型,默认值空字符串

第62-66行: // 是否仅在跳转编辑页时传递 id(不传 vouchecode/menuId)

注释:passOnlyId的用途说明

第67-70行: passOnlyId: { type: Boolean, default: false },

属性:passOnlyId,布尔类型,默认值false

第71-74行: type2Disabled: { type: Boolean, default: false },

属性:type2Disabled,布尔类型,默认值false

第75-78行: addPath: { type: String, default: () => '' },

属性:addPath,字符串类型,默认值空字符串

第79-82行: title: { type: String, default: () => '' },

属性:title,字符串类型,默认值空字符串

第83-86行: appendGetPms: { type: Object, default: () => {} },

属性:appendGetPms,对象类型,默认空对象

第87-90行: dontAdd: { type: Boolean, default: false },

属性:dontAdd,布尔类型,默认值false

第91-94行: // zyy:需要隐藏经理/店员/内勤/收货人/联系人等人员列。

注释:supplierExcludeDescriptions的用途说明

第95-98行: supplierExcludeDescriptions: { type: Array, default: () => [] },

属性:supplierExcludeDescriptions,数组类型,默认空数组

第99-102行: initHeaders: { type: Array, default: () => [] },

属性:initHeaders,数组类型,默认空数组

第103-106行: // 默认选中的ID列表。用于再次进入多选页时自动勾选之前的选择。

注释:defaultCheckedIds的用途说明

第107-110行: defaultCheckedIds: { type: Array, default: () => [] },

属性:defaultCheckedIds,数组类型,默认空数组

第111-114行: // 默认的抵扣金额映射,键是行的 id,值是该行的抵扣金额

注释:defaultDeductMap的用途说明

第115-118行: defaultDeductMap: { type: Object, default: () => ({}) },

属性:defaultDeductMap,对象类型,默认空对象

第119-122行: // 表格数据过滤方法

注释:tableDataFilter的用途说明

第123-126行: tableDataFilter: { type: Function, default: null },

属性:tableDataFilter,函数类型,默认null

第127-130行: //内容一过滤

注释:firstHeaderKeys的用途说明

第131-134行: firstHeaderKeys: { type: Array, default: () => [] },

属性:firstHeaderKeys,数组类型,默认空数组

第135-138行: //内容三过滤

注释:lastHeaderKeys的用途说明

第139-142行: lastHeaderKeys: { type: Array, default: () => [] },

属性:lastHeaderKeys,数组类型,默认空数组

第143行: },

结束props定义

第二部分:Data 定义(第144-187行)

第144-146行: data() {

开始定义组件数据

第147行: return {

返回数据对象

第148-149行: componentsName: 'tableU',

数据:componentsName,字符串'tableU'

第150-154行: status: { heigthInitializeLoading: false, multiple: false, getLoading: false },

数据:status对象,包含三个布尔值

第155行: list: [],

数据:list,空数组

第156行: headers_: [],

数据:headers_,空数组

第157行: firstHeaders: [],

数据:firstHeaders,空数组

第158行: headers: [],

数据:headers,空数组

第159行: lastHeaders: [],

数据:lastHeaders,空数组

第160-171行: urls: [ "https://uviewui.com/album/1.jpg", ... "https://uviewui.com/album/10.jpg", ],

数据:urls,包含10个图片URL的数组

第172行: tableHeigth: 660,

数据:tableHeigth,数字660

第173行: refresherTriggered: false,

数据:refresherTriggered,布尔值false

第174-177行: getPms: { pageNo: 1, pageSize: 20 },

数据:getPms对象,包含页码和每页大小

第178行: maxPageNo: 0,

数据:maxPageNo,数字0

第179行: lastGetPms: {},

数据:lastGetPms,空对象

第180行: checkbox1Item: {}

数据:checkbox1Item,空对象

第181行: }

结束数据返回

第182行: },

结束data函数

第三部分:生命周期和监听器(第183-195行)

第183-185行: created() {

创建完成生命周期钩子

第186行:

空行

第187行: },

结束created

第188-190行: mounted() {

挂载完成生命周期钩子

第191-193行: setTimeout(() => { this.heightInit(); }, 0)

延迟0毫秒后调用heightInit方法

第194行: },

结束mounted

第195-197行: watch: {

开始定义监听器

第198-200行: initHeaders() { this.initHeader(); }

监听initHeaders变化,调用initHeader方法

第201行: },

结束watch

第四部分:Methods 方法(第202-618行)

initHeader 方法(第203-218行)

第202行: methods: {

开始定义方法

第203-205行: initHeader() {

方法:initHeader

第206行: console.log('initHeader')

控制台输出

第207-217行: if (this.initHeaders) {

let hs = [...this.initHeaders];

this.headers_ = hs;

this.firstHeaders = hs.slice(0, 2);

if (this.isLastHeaders) {

this.headers = hs.slice(2, -3);

this.lastHeaders = hs.slice(-3);

} else {

this.headers = hs.slice(2);

}

this.setSearchBox();

}

如果initHeaders存在:复制数组,设置表头分段,调用setSearchBox

第218行: },

结束initHeader

getStock 方法(第219-229行)

第219-221行: getStock(item) {

方法:getStock,接收item参数

第222行: if (!item || typeof item !== 'object') return null;

如果item无效返回null

第223行: const v = (item.availableStock ?? item.notOutQty ?? item.stockQuantity ?? item.qty);

获取库存值,按优先级查找字段

第224行: if (v === undefined || v === null || v === '') return null;

如果值为空返回null

第225-228行: // 如果是字符串数字,转为数字显示;否则原样返回

const n = Number(v);

return Number.isNaN(n) ? v : n;

尝试转换为数字,失败则返回原值

第229行: },

结束getStock

toAddPath 方法(第230-237行)

第230-232行: toAddPath() {

方法:toAddPath

第233-236行: uni.navigateTo({ url: this.addPath, complete: (v) => {} });

跳转到addPath指定的页面

第237行: },

结束toAddPath

swipeClick 方法(第238-270行)

第238-240行: swipeClick(v, item) {

方法:swipeClick,接收v和item参数

第241-242行: if (this.options[v.index].text === '删除') {

如果点击的操作文本是"删除"

第243-244行: if (this.deleteApi) {

如果deleteApi存在

第245-268行: uni.showModal({

title: '删除提示',

content: '确定要删除当前数据吗?',

success: (e) => {

if (e.confirm) {

this.$http.Get(this.deleteApi, { id: item.id }).then(data => {

if (data) {

uni.showToast({ title: '删除成功!', icon: 'success', duration: 1000 })

setTimeout(() => { this.getList(); }, 1000)

}

}).catch(e => {})

}

}

});

显示确认对话框,确认后调用删除接口,成功则提示并刷新列表

第269行: }

结束if

第270行: },

结束swipeClick

getList 方法(第271-510行) - 太长,分段解释:

第271-284行:参数处理

第271-273行: getList(getPms = {}) {

方法:getList,接收getPms参数,默认空对象

第274-280行: if (Object.keys(getPms).length !== 0) {

this.lastGetPms = { ...getPms }

} else {

getPms = { ...this.lastGetPms }

}

如果有新参数则保存,否则使用上次参数

第281-283行: return new Promise((resolve, reject) => {

返回Promise对象

第285-302行:模拟数据模式

第285-287行: if (!this.api) {

如果api不存在

第288-295行: this.list = [];

for (let i = 0; i < 5; i++) {

this.list.push({

check: false,

url: this.urls[uni.$u.random(0, this.urls.length - 1)],

});

}

resolve();

return

创建模拟数据并返回

第296行: }

结束if

第297-302行:准备请求参数

第297-303行: let pms = { ...this.getPms, ...getPms, idMenuDTO: this.menuId, ...this.appendGetPms };

合并所有参数

第304-508行:完整数据处理流程(由于太长,只总结关键点)

发送请求(第304-306行)

数据规范化(第307-340行):处理不同格式的响应数据

表头处理(第341-409行):

过滤不需要的列(supplierExcludeDescriptions)

排序处理(firstHeaderKeys优先,lastHeaderKeys末尾)

默认排序逻辑(时间/日期/备注列处理)

分段表头(firstHeaders, headers, lastHeaders)

行数据处理(第410-490行):

递归关闭子级展开

名称兼容处理(goodsName, label)

名称模板渲染

子级展开状态初始化

ID字段统一映射

地址字段统一映射

时间字段清理

回显逻辑(defaultCheckedIds)

自定义数据过滤(tableDataFilter)

数据更新逻辑(第491-507行):

第一页直接覆盖

后续页智能合并(避免重复)

关闭侧滑操作

计算总页数

设置搜索框

更新状态

其他方法简要说明:

multipleChange 方法(第511-513行):切换多选模式

listItemClick 方法(第514-551行):处理列表项点击,支持单选、多选、跳转

toEdit 方法(第552-578行):跳转到编辑页面,参数处理

onRefresh 方法(第579-590行):下拉刷新处理

heightInit 方法(第591-628行):初始化表格高度,考虑屏幕尺寸和其他元素

setTotal 方法(第629-640行):通知合计组件更新总数

setSearchBox 方法(第641-652行):通知搜索组件更新排序选项

scrolltolower 方法(第653-658行):滚动到底部加载更多

第659-660行:

第659行: }

结束methods

第660行: }

结束组件导出

组件功能总结:

这是一个高度可配置的表格组件,主要功能包括:

  1. 四种显示模式:通过type切换

  2. 数据获取:支持API请求和模拟数据

  3. 智能表头:动态分段、排序、过滤

  4. 多级数据:支持三级嵌套结构

  5. 多选功能:单选、多选、树形选择

  6. 编辑跳转:灵活的参数传递

  7. 下拉刷新:支持刷新和加载更多

  8. 高度自适应:根据屏幕计算高度

  9. 组件联动:与搜索框、合计组件联动

  10. 数据回显:支持选中状态回显

组件代码量较大,但结构清晰,每个方法职责明确,是一个功能完备的业务表格组件。

相关推荐
梨子同志2 小时前
Node.js 事件循环(Event Loop)
前端
Risk Actuary2 小时前
磁道优化分布的一道题
linux·运维·服务器
没有bug.的程序员2 小时前
AOT 与 GraalVM Native Image 深度解析
java·jvm·测试工具·aot·gc·gc调优·graalvm native
JS_GGbond2 小时前
Vue3 组件入门:像搭乐高一样玩转前端!
前端·vue.js
SakuraOnTheWay2 小时前
拆解一个由 setTimeout 引发的“页面假死”悬案
前端·javascript
渔_2 小时前
【已解决】uni-textarea 无法绑定 v-model / 数据不回显?换原生 textarea 一招搞定!
前端
小胖霞2 小时前
vite+ts+monorepo从0搭建vue3组件库(二):项目搭建
前端·vue.js·前端工程化
JS_GGbond2 小时前
Vue中级冒险:3-4周成为组件沟通大师 🚀
前端·vue.js
登山者2 小时前
npm发布报错急救手册:快速解决2FA与令牌问题
前端·npm