Commit dcd21c31 authored by 贺世双's avatar 贺世双

新增异常上报

parent c02bee8e
...@@ -123,6 +123,27 @@ export function getOrderPodSingList(data) { ...@@ -123,6 +123,27 @@ export function getOrderPodSingList(data) {
return Request( `/api/entry/{mini}/m-action/getOrderPodSingList`,data ); return Request( `/api/entry/{mini}/m-action/getOrderPodSingList`,data );
} }
//查询一级异常信息
export function getLevelAbnormalAll(data) {
return Request( `/api/entry/{mini}/m-action/getLevelAbnormalAll`,data );
}
//查询费用明细项
export function getAbnormaCostBaseAll(data) {
return Request( `/api/entry/{mini}/m-action/getAbnormaCostBaseAll`,data );
}
//查询币种
export function getCurrencyAll(data) {
return Request( `/api/entry/{mini}/m-action/getCurrencyAll`,data );
}
//新增异常费用信息
export function saveAppAbnormalEvent(data) {
return Request( `/api/entry/{mini}/m-action/saveAppAbnormalEvent`,data );
}
// 查询货量明细 // 查询货量明细
// OM模式 // OM模式
export function OMOrderDetail(data) { export function OMOrderDetail(data) {
......
<template>
<view class="todo-remark">
<view class="remark-title">{{title}}</view>
<u-textarea v-model="value" :placeholder="isReadonly ? '' : placeholder" :count="!isReadonly" autoHeight
:disabled="isReadonly" maxlength="200" border="none" @blur="onBlur" holdKeyboard/>
</view>
</template>
<script>
export default {
name: "bs-todoRemark",
options: { styleIsolation: 'shared' },
props: {
title: {
type: String,
default: ""
},
placeholder: {
type: String,
default: ""
},
isReadonly: {
type: Boolean,
default: false
}
},
data() {
return {
value: ""
};
},
methods: {
onBlur(e) {
const { value } = e.detail
this.setValue(value)
},
//设置默认值
setValue(value) {
this.value = value
}
}
}
</script>
<style lang="scss">
.todo-remark {
font-size: 28rpx;
margin-top: 32rpx;
.remark-title {
color: #8C8C8C;
margin-bottom: 24rpx;
}
.u-textarea {
min-height: 150rpx;
background-color: #FAFAFA;
.u-textarea__field{
min-height: 120rpx;
}
}
}
</style>
...@@ -119,9 +119,9 @@ ...@@ -119,9 +119,9 @@
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
}, { }, {
"path": "signFor/signFor", "path": "abnormalEventDetail/abnormalEventDetail",
"style": { "style": {
"navigationBarTitleText": "", "navigationBarTitleText": "费用明细",
"enablePullDownRefresh": false, "enablePullDownRefresh": false,
"disableScroll": false "disableScroll": false
} }
...@@ -151,7 +151,8 @@ ...@@ -151,7 +151,8 @@
"enablePullDownRefresh": false, "enablePullDownRefresh": false,
"disableScroll": true "disableScroll": true
} }
}] }
]
}], }],
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "white", "navigationBarTextStyle": "white",
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
<view class="section_9"> <view class="section_9">
<u-button @click="warehouseShipment()" shape="circle" color="#2E75E6" text="仓库已发货"></u-button> <u-button @click="warehouseShipment()" shape="circle" color="#2E75E6" text="仓库已发货"></u-button>
<u-button @click="arrivalPort()" shape="circle" color="#2E75E6" text="进港(到目的地)" customStyle="margin-left: 20rpx;"></u-button> <u-button @click="arrivalPort()" shape="circle" color="#2E75E6" text="进港(到目的地)" customStyle="margin-left: 20rpx;"></u-button>
<u-button @click="onVisibleDate()" shape="circle" color="#2E75E6" customStyle="margin-left: 20rpx;" text="异常上报"></u-button> <u-button @click="onAbnormalEvents()" shape="circle" color="#2E75E6" customStyle="margin-left: 20rpx;" text="异常上报"></u-button>
</view> </view>
</view> </view>
<!-- <view class="box_4"> <!-- <view class="box_4">
...@@ -231,6 +231,22 @@ ...@@ -231,6 +231,22 @@
}) })
} }
}, },
//异常上报
onAbnormalEvents(){
if (this.message.length >0) {
console.log(this.message)
getApp().globalData.abnormalEvent = this.message[0]
uni.navigateTo({
url: `/subpkg/abnormalEvent/abnormalEvent`,
})
} else {
uni.showToast({
title: "请选择运单",
duration: 3000,
icon: 'error'
})
}
},
//到厂、仓库到货、进港提交表单数据 //到厂、仓库到货、进港提交表单数据
onSubmitForm(type) { onSubmitForm(type) {
const rows = this.choose; const rows = this.choose;
......
<template>
<view class="abnormalEvent-list content_box">
<view class="abnormalEvent-item">
<view v-if="eventList.order_base_no!=null && eventList.shipment_no==null">
<bs-customCell label="基础订单号 :" :value="eventList.order_base_no" labelCol="3" wrapCol="6" />
</view>
<view v-if="eventList.shipment_no!=null">
<bs-customCell label="运单号 :" :value="eventList.shipment_no" labelCol="3" wrapCol="6" />
</view>
<!-- 注册内容 -->
<u--form :model="formData" :rules="rules" ref="myForm" errorType='none'>
<view class="basic-info">
<u-form-item prop="one_level_abnormal" required label="一级异常" labelWidth="25%" borderBottom>
<view @click="showProcess">
<u-input border="none" readonly placeholder="请选择一级异常" :value="viewEnums[formData.one_level_abnormal]" />
</view>
</u-form-item>
<u-form-item prop="two_level_abnormal" label="二级异常" labelWidth="25%" borderBottom>
<u-input :disabledColor="'#ffffff'" border="none" v-model="formData.two_level_abnormal" />
</u-form-item>
<u-form-item prop="occur_location" label="发生地点" labelWidth="25%" borderBottom>
<u-input :disabledColor="'#ffffff'" border="none" v-model="formData.occur_location" />
</u-form-item>
<u-form-item prop="occur_time" label="发生时间" labelWidth="35%" borderBottom>
<view @click="onVisibleDate('occur_time')">
<u-input :disabledColor="'#ffffff'" disabled border="none" readonly placeholder="请选择发生时间"
:value="convertTime(formData.occur_time)" />
</view>
</u-form-item>
<u-form-item prop="abnormal_reason" label="异常原因" labelWidth="25%" borderBottom>
<u-input :disabledColor="'#ffffff'" border="none" v-model="formData.abnormal_reason" />
</u-form-item>
<u-form-item prop="person_liable" label="责任人" labelWidth="25%" borderBottom>
<u-input :disabledColor="'#ffffff'" border="none" v-model="formData.person_liable" />
</u-form-item>
</view>
<!-- 异常文件 -->
<view class="uploader-img">
<bs-uploader ref="myUploadImg" :required="requiredStatus"
uploadTitle="异常文件" />
</view>
<view class="module-title flex">
<text class="font_bolder">费用明细</text>
<view style="flex: 1;"></view>
<div style="display: flex; justify-content: flex-end;">
<u-button @click="onRateItemDatail()" style="width: 100px;" type="error" size="small">增加明细</u-button>
</div>
</view>
<view class="driverJobCertificate">
<zb-table :show-header="true" :columns="column" :stripe="true" :fit="false" @rowClick="rowClick"
@toggleRowSelection="toggleRowSelection" @toggleAllSelection="toggleAllSelection" :border="true"
@edit="buttonEdit" @dele="dele" :data="tableData" :key="index"></zb-table>
</view>
</u--form>
<!--查询一级异常-->
<u-popup :show="visible" @close="closeProcess">
<view class="select-container">
<view class="search-input flex_center">
<u-search searchIconSize="50" searchIconColor="#FFFFFF" placeholderColor='#757575' color="#606266"
:showAction="false" :placeholder="placeholder" v-model="searchValue" @clickIcon="onSearch" @search="onSearch" @clear="onSearchclear" />
</view>
<scroll-view scroll-y class="select-list">
<u-radio-group v-model="currentChecked" placement="column" iconPlacement="right">
<u-radio :customStyle="{margin: '22rpx 40rpx'}" :size="40" :iconSize="32" :labelSize="32"
labelColor="#262626" v-for="(item, index) in selectEnum" :key="index" :label="item.label"
:name="item.value" @change="onSelect(item.value)">
</u-radio>
</u-radio-group>
</scroll-view>
</view>
</u-popup>
</view>
<!-- 底部按钮 -->
<view class="operation-btn flex_sb">
<button class="backBtn common_btn" @click="onNavBack">返回</button>
<button class="submitBtn common_btn" @click="onSubmitForm">确认</button>
</view>
<!-- 类型选择动作面板 -->
<bs-selectPopup :popTitle="popTitle" :selectEnum="optionEnums" :visible="visible2" @onVisible="onVisible"
@onConfirm="onConfirmPicker" />
<!-- 时间选择 -->
<bs-datetimePicker ref="myTimePicker" :visible="showPopup" @onVisible="onVisibleDate"
@onSelsectDate="onSelsectDate" />
</view>
</template>
<script>
import {getLevelAbnormalAll, getAbnormaCostBaseAll ,
getCurrencyAll, saveAppAbnormalEvent} from '../../api/apiList.js'
export default {
name: 'abnormalEvent',
options: { styleIsolation: 'shared' },
data() {
return {
pageNum: 1, //页码
pageSize: 10, //每页条数
eventList:'',//头信息
tableData:[],
isEmpty: true, //是否为空
rows: [], //当前展开面板
formData: {
one_level_abnormal: '', //一级异常
two_level_abnormal: "",//二级异常
occur_location: "",//发生地点
occur_time: "",//发生时间
abnormal_reason: "",//异常原因
person_liable: "",//责任人
},
rules: {
'one_level_abnormal': { required: true },//一级异常
},
showPopup: false, //时间选择
visibleKey: '', //当前显示字段的key
optionEnums: [], //候选项枚举
popTitle: '', //弹窗标题
visible: false, //一级异常类型选择
viewEnums:[],//一级异常枚举视图
searchValue: "", //搜索值--一级异常
selectEnum:[],//接受返回值
currentChecked:'',
placeholder: "请输入搜索一级异常", //默认提示
column: [{
name: 'operation',
type: 'operation',
align: 'center',
label: '操作',
renders: [{
name: '编辑',
func: 'edit' // func 代表子元素点击的事件 父元素接收的事件 父元素 @edit
},
{
name: '删除',
type: 'warn',
func: "dele"
},
]
},
{
name: 'cost_classification_label',
label: '费用大类',
fixed: 'true',
},
{
name: 'rate_item',
label: '费用项'
},
{
name: 'money',
label: '金额'
}, {
name: 'currency',
label: '币种'
},{
name: 'remark',
label: '备注'
},{
name: 'files',
label: '文件'
},
],
}
},
onShow() {
const newData = [];
const data = getApp().globalData.abnormalEvent
const td = getApp().globalData.tableData;
this.eventList=data;
if (td) {
if (td.change) {
this.tableData.map(item => {
//费用项+币种是唯一的
if(item.rate_item!=td.rate_item && item.currency!=currency){
newData.push(item)
}
})
newData.push(td);
getApp().globalData.tableData = null
this.tableData=[]
this.tableData = newData;
} else {
this.tableData.push(td)
getApp().globalData.tableData = null
}
}
},
methods: {
dele(e) {
const index = e.key; // 找到元素 e 的索引
if (index !== -1) {
this.tableData.splice(index, 1); // 移除该元素
}
},
buttonEdit(e) {
getApp().globalData.tableData = e
this.onRateItemDatail();
},
//跳转费用明细新增
onRateItemDatail() {
getApp().globalData.all = this.tableData;//所有的明细
uni.navigateTo({
url: `/subpkg/abnormalEventDetail/abnormalEventDetail`,
})
},
//一级异常
showProcess() {this.visible = true },
//一级异常关闭弹出框
closeProcess() {
this.visible = false
this.selectEnum = []
this.searchValue =''
},
//一级异常输入查询
onSearch() {
this.pageNum = 1
this.onLevelAbnormal()
},
onSelect(value) {
this.formData.one_level_abnormal = value;
this.visible=false
},
//清除一级异常
onSearchclear(){
this.selectEnum = []
this.searchValue =''
},
//查询一级异常回调
onLevelAbnormal(type) {
const { pageNum, pageSize } = this
const executeBu=this.eventList.execute_bu
const orderType = this.eventList.order_type.substring(0,2);
let optionEnums = []
var searchValue = this.searchValue
let reqData = {
"args": {
pageNum,
pageSize,
executeBu,
orderType
}
}
if(this.searchValue){
reqData.args.restrictions = [{
"field": 'des',
"type": "like", //EQ精准匹配,LK模糊匹配
"value": this.searchValue
}]
}
let _this =this;
var setKey = 'viewEnums'
getLevelAbnormalAll(reqData).then(res=>{
uni.stopPullDownRefresh()
optionEnums = res.data.data.datas;
this.setViewEnums(optionEnums, setKey)
_this.selectEnum = optionEnums
})
},
// 提交表单数据
onSubmitForm() {
const rows = getApp().globalData.abnormalEvent;
if(this.formData.one_level_abnormal==null || this.formData.one_level_abnormal==""){
uni.showToast({
title: "一级异常不能为空",
icon: 'none',
duration: 2000
})
return;
}
this.formData.occur_time= this.formatDate(this.formData.occur_time)
var baseData = {
"aux": {
"rows":rows,
"addEvent":this.formData,
"costList": this.tableData,
},
}
//新增异常操作
saveAppAbnormalEvent(baseData).then(res => {
if (res.data.data.messageType === 'success') {
uni.showToast({
title: '操作成功!',
duration: 1000
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
})
},
//设置显示枚举
setViewEnums(optionEnums, key) {
let viewEnums = {}
optionEnums.map(item => {
viewEnums[item.value] = item.label
})
this[key] = viewEnums
},
//确认选择
onConfirmPicker(value) {
if (this.chooseType == 'cost_classification') {
var index = this.chooseIndex
this.orderList[0].cargoDetails[index].cost_classification = value
} else {
this.setFormData(value)
}
this.visible = false
this.visible2 = false
},
//页面展示时间戳转换为日期
convertTime(timestamp) {
if (!timestamp) return
const nowDate = new Date(timestamp)
var year = nowDate.getFullYear();
var month = nowDate.getMonth() + 1;
var date = nowDate.getDate();
return `${year}-${ month}-${date}`
},
//时间戳转换
formatDate(timestamp) {
if (!timestamp) return
const nowDate = new Date(timestamp)
var year = nowDate.getFullYear();
var month = nowDate.getMonth() < 10 ? '0' + (nowDate.getMonth() + 1) : nowDate.getMonth() + 1;
var date = nowDate.getDate();
const hour = nowDate.getHours();
const minute = nowDate.getMinutes()< 10 ? '0'+ nowDate.getMinutes() : nowDate.getMinutes() ;
var seconds = nowDate.getSeconds() < 10 ? '0' + nowDate.getSeconds() : nowDate.getSeconds();
if (this.dateType === 'date') {
return `${year}-${ month}-${date}`
} else {
return `${year}-${ month}-${date} ${hour}:${ minute}:${seconds}`
}
},
//设置formData
setFormData(value) {
const key = this.visibleKey
if (key.includes('.')) { //对象特殊处理
const keys = key.split('.')
this.formData[keys[0]][keys[1]] = value
} else {
this.formData[key] = value
}
},
// 时间选择
onSelsectDate(dateInfo) {
const {
timestamp
} = dateInfo
this.setFormData(timestamp)
this.showPopup = false
},
//关闭时间选择框
onVisibleDate(type) {
if (type) {
this.visibleKey = type
}
this.showPopup = !this.showPopup
},
}
}
</script>
<style lang="scss">
.abnormalEvent-list {
.module-title {
margin-top: 20rpx;
.vertical-separate {
height: 40rpx;
border-left: 7rpx solid #797979;
margin-right: 20rpx;
}
}
.select-container {
min-width: 70vw;
height: 50vh;
padding: 30rpx;
font-weight: 400;
.popTitle {
color: #8C8C8C;
font-size: 32rpx;
text-align: center;
margin-bottom: 44rpx;
}
.select-list{
height: 460rpx;
}
}
.new_cost{
background-color: coral;
height: 30px;
width: 100px;
border-radius: 5px;
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
margin-bottom: -10px;
}
.abnormalEvent-item {
padding: 32rpx;
margin-bottom: 24rpx;
background: #FFFFFF;
.detail-title {
font-size: 28rpx;
padding: 8rpx 0 30rpx;
border-bottom: 1rpx solid #E5E5E5;
.t-icon-order {
width: 50rpx;
height: 50rpx;
margin-right: 20rpx;
}
}
/deep/.custom-cell-row {
margin-top: 10rpx;
.custom-cell-label {
color: #8C8C8C;
}
}
.outer-frame {
margin-top: 32rpx;
border-radius: 16rpx;
border: 2rpx solid #F0F0F0;
overflow: hidden;
.foldPanel-title {
color: #2E75E6;
font-size: 28rpx;
padding-left: 20rpx;
height: 96rpx;
background: rgba(46, 117, 230, .1);
.foldPanel-right-icon {
margin-right: 20rpx;
}
}
/deep/.cargo-detail-table {
background: #FFFFFF;
padding-bottom: 10rpx;
.detail-row-title {
color: #8C8C8C;
background: #FAFAFA;
padding: 20rpx 0;
}
.detail-col-item {
margin: 20rpx;
.cargo-title {
font-size: 28rpx;
padding-left: 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
.snapshoot image {
margin: 20rpx 15rpx;
width: 140rpx;
height: 140rpx;
}
/deep/.basic-info,
/deep/.driving-license {
padding: 0 10rpx;
background-color: #ffffff;
.input-tip {
font-size: 28rpx;
color: #BFBFBF;
}
.u-form-item__body__left__content__label {
color: #8C8C8C;
font-size: 28rpx;
}
.u-form-item__body {
padding: 25rpx 0;
}
.u-icon {
margin-left: 20rpx;
}
.u-upload__wrap>view {
width: 100%;
}
.previewImages {
width: 100%;
.previewImg {
width: 100% !important;
height: 240rpx !important;
}
}
.uploadeBtn {
width: 100% !important;
height: 240rpx !important;
margin-bottom: 30rpx;
margin-left: 0 !important;
}
.orderTotal {
display: none;
}
}
.outer-frame {
border-radius: 16rpx;
border: 2rpx solid #F0F0F0;
overflow: hidden;
.foldPanel-title {
color: #2E75E6;
font-size: 28rpx;
padding-left: 20rpx;
height: 96rpx;
background: rgba(46, 117, 230, .1);
.foldPanel-right-icon {
margin-right: 20rpx;
}
}
/deep/.cargo-detail-table {
background: #FFFFFF;
padding-bottom: 10rpx;
.detail-row-title {
color: #8C8C8C;
background: #FAFAFA;
padding: 20rpx 0;
}
.detail-col-item {
margin: 20rpx;
.cargo-title {
font-size: 28rpx;
padding-left: 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.u-number-box {
justify-content: center !important;
}
//步进器相关
.u-number-box__input {
margin: 0 10rpx;
color: #333333 !important;
width: 60rpx !important;
height: 40rpx !important;
background-color: #fff !important;
}
.u-number-box__minus,
.u-number-box__plus {
width: 40rpx;
height: 40rpx !important;
border-radius: 6rpx;
border: 2rpx solid #C9C9C9;
}
.uicon-plus,
.uicon-minus {
font-size: 20rpx !important;
}
}
}
/deep/.basicInfo,
/deep/.driverLicense,
/deep/.driverJobCertificate {
padding: 0 40rpx;
background-color: #ffffff;
.input-tip {
font-size: 28rpx;
color: #BFBFBF;
}
.u-form-item__body__left__content__label {
color: #8C8C8C;
font-size: 28rpx;
}
.u-form-item__body {
padding: 40rpx 0;
}
.u-icon {
margin-left: 20rpx;
}
}
/deep/.driverLicense-uploader-img,
/deep/.driverJobCertificate {
.u-upload__wrap>view {
width: 100%;
}
.previewImages {
width: 100%;
.previewImg {
width: 100% !important;
height: 240rpx !important;
}
}
.uploadeBtn {
width: 100% !important;
height: 240rpx !important;
margin-bottom: 30rpx;
margin-left: 0 !important;
}
.orderTotal {
display: none;
}
}
}
</style>
<template>
<view class="abnormalEventDetail-list content_box">
<view class="abnormalEventDetail-item">
<!-- 注册内容 -->
<u--form :model="formData" :rules="rules" ref="myForm" errorType='none'>
<view class="basic-info">
<u-form-item prop="cost_classification" required label="费用大类" labelWidth="25%" borderBottom>
<view @click="onVisible('cost_classification')">
<u-input border="none" readonly placeholder="请选择费用大类" :value="ficationViewEnums[formData.cost_classification]" />
</view>
</u-form-item>
<u-form-item prop="rate_item" required label="费用项" labelWidth="25%" borderBottom>
<view @click="showProcess3">
<u-input border="none" readonly placeholder="请选择费用项" :value="rateViewEnums[formData.rate_item]" />
</view>
</u-form-item>
<u-form-item prop="money" required label="金额" labelWidth="25%" borderBottom>
<u-input :disabledColor="'#ffffff'" border="none" v-model="formData.money" />
</u-form-item>
<u-form-item prop="currency" required label="币种" labelWidth="25%" borderBottom>
<view @click="showProcess4">
<u-input border="none" readonly placeholder="请选择币种" :value="currencyViewEnums[formData.currency]" />
</view>
</u-form-item>
</view>
</u--form>
<!-- 异常文件 -->
<view class="reported-detail">
<!-- 备注信息 -->
<bs-todoRemark ref="myRemark" title="备注" />
<!-- 上传图片 -->
<view class="uploader-img">
<bs-uploader ref="myUploadImg" uploadTitle="拍照上传图片" />
</view>
</view>
<!--查询费用明细-->
<u-popup :show="visible3" @close="closeProcess3">
<view class="select-container">
<view class="search-input flex_center">
<u-search searchIconSize="50" searchIconColor="#FFFFFF" placeholderColor='#757575' color="#606266"
:showAction="false" :placeholder="placeholder3" v-model="searchValue3" @clickIcon="onSearch3" @search="onSearch3" @clear="onSearchclear3" />
</view>
<scroll-view scroll-y class="select-list">
<u-radio-group v-model="currentChecked" placement="column" iconPlacement="right">
<u-radio :customStyle="{margin: '22rpx 40rpx'}" :size="40" :iconSize="32" :labelSize="32"
labelColor="#262626" v-for="(item, index) in selectEnum3" :key="index" :label="item.label"
:name="item.value" @change="onSelect3(item.value, index)">
</u-radio>
</u-radio-group>
</scroll-view>
</view>
</u-popup>
<!--查询币种-->
<u-popup :show="visible4" @close="closeProcess4">
<view class="select-container">
<view class="search-input flex_center">
<u-search searchIconSize="50" searchIconColor="#FFFFFF" placeholderColor='#757575' color="#606266"
:showAction="false" :placeholder="placeholder4" v-model="searchValue4" @clickIcon="onSearch4" @search="onSearch4" @clear="onSearchclear4" />
</view>
<scroll-view scroll-y class="select-list">
<u-radio-group v-model="currentChecked" placement="column" iconPlacement="right">
<u-radio :customStyle="{margin: '22rpx 40rpx'}" :size="40" :iconSize="32" :labelSize="32"
labelColor="#262626" v-for="(item, index) in selectEnum4" :key="index" :label="item.label"
:name="item.value" @change="onSelect4(item.value, index)">
</u-radio>
</u-radio-group>
</scroll-view>
</view>
</u-popup>
</view>
<!-- 底部按钮 -->
<view class="operation-btn flex_sb">
<button class="backBtn common_btn" @click="onNavBack">返回</button>
<button class="submitBtn common_btn" @click="onSubmitForm">确认</button>
</view>
<!-- 类型选择动作面板 -->
<bs-selectPopup :popTitle="popTitle" :selectEnum="optionEnums" :visible="visible2" @onVisible="onVisible"
@onConfirm="onConfirmPicker" />
<!-- 时间选择 -->
<bs-datetimePicker ref="myTimePicker" :visible="showPopup" @onVisible="onVisibleDate"
@onSelsectDate="onSelsectDate" />
</view>
</template>
<script>
import {getAbnormaCostBaseAll ,
getCurrencyAll} from '../../api/apiList.js'
export default {
name: 'abnormalEventDetail',
options: { styleIsolation: 'shared' },
data() {
return {
pageNum: 1, //页码
pageSize: 10, //每页条数
eventList:'',//头信息
isEmpty: true, //是否为空
rows: [], //当前展开面板
formData: {
cost_classification: '', //费用大类
cost_classification_label: '',
rate_item: "",//费用项
money: "",//金额
currency: "",//币种
remark: "",//备注
files: "",//文件
},
rules: {
'cost_classification': { required: true },//费用大类
'rate_item': { required: true },//费用项
},
activeNames: [], //当前展开面板 names => orderIds
showPopup: false, //时间选择
visibleKey: '', //当前显示字段的key
optionEnums: [], //候选项枚举
popTitle: '', //弹窗标题
visible2:false,//费用大类
visible3:false,//费用项
visible4:false,//币种枚举
viewEnums:[],//一级异常枚举视图
ficationViewEnums:[],//费用大类项枚举
rateViewEnums:[],//费用项枚举
currencyViewEnums:[],//币种枚举
searchValue: "", //搜索值--一级异常
searchValue3: "", //搜索值--费用项
searchValue4:"",//币种
selectEnum:[],//接受返回值
selectEnum3:[],//接受返回值--费用项
selectEnum4:[],//币种
currentChecked:'',
placeholder3: "请输入搜索费用项", //默认提示
placeholder4: "请输入搜索币种", //默认提示
ficationViewEnums: {'cost_classification': ""},
rateViewEnums: {'rate_item': ""},
currencyViewEnums: {'currency': ""},
}
},
onLoad(options) {
const eventData = getApp().globalData.abnormalEvent
this.eventList=eventData;
let data = getApp().globalData.tableData;
if(data){
this.formData = data
this.ficationViewEnums[this.formData.cost_classification] = this.formData.cost_classification_label;
this.rateViewEnums[this.formData.rate_item] = this.formData.rate_item;
this.currencyViewEnums[this.formData.currency] = this.formData.currency;
this.formData.change = true
console.log(this.formData.change)
}
},
methods: {
//费用项
showProcess3(indexs) {
this.chooseIndex = indexs
this.visible3 = true
},
//费用项
showProcess4(indexs) {
this.chooseIndex = indexs
this.visible4 = true
},
//费用项关闭弹出框
closeProcess3() {
this.visible3 = false
this.selectEnum3 = []
this.searchValue3 =''
},
//币种关闭弹出框
closeProcess4() {
this.visible4 = false
this.selectEnum4 = []
this.searchValue4 =''
},
//费用项输入查询
onSearch3() {
this.pageNum = 1
this.onAbnormaCostBase()
},
//币种输入查询
onSearch4() {
this.pageNum = 1
this.onCurrencyAll()
},
onSelect3(value, index) {
this.formData.rate_item = value
this.visible3=false
},
onSelect4(value, index) {
this.formData.currency = value
this.visible4=false
},
//清除费用项
onSearchclear3(){
this.selectEnum3 = []
this.searchValue3 =''
},
//清除币种
onSearchclear4(){
this.selectEnum4 = []
this.searchValue4 =''
},
//查询费用项目回调
onAbnormaCostBase(type) {
const { pageNum, pageSize } = this
const executeBu=this.eventList.execute_bu
let optionEnums = []
var searchValue3 = this.searchValue3
let reqData = {
"args": {
pageNum,
pageSize,
executeBu
}
}
if(this.searchValue3){
reqData.args.restrictions = [{
"field": 'des',
"type": "like", //EQ精准匹配,LK模糊匹配
"value": this.searchValue3
}]
}
let _this =this;
var setKey = 'rateViewEnums'
getAbnormaCostBaseAll(reqData).then(res=>{
uni.stopPullDownRefresh()
optionEnums = res.data.data.datas;
this.setViewEnums(optionEnums, setKey)
_this.selectEnum3 = optionEnums
})
},
//查询币种
onCurrencyAll(type) {
const { pageNum, pageSize } = this
let optionEnums = []
var searchValue4 = this.searchValue4
let reqData = {
"args": { pageNum, pageSize}
}
if(this.searchValue4){
reqData.args.restrictions = [{
"field": 'code',
"type": "like", //EQ精准匹配,LK模糊匹配
"value": this.searchValue4
}]
}
let _this =this;
var setKey = 'currencyViewEnums'
getCurrencyAll(reqData).then(res=>{
uni.stopPullDownRefresh()
optionEnums = res.data.data.datas;
this.setViewEnums(optionEnums, setKey)
_this.selectEnum4 = optionEnums
})
},
//类型选择
async onVisible(type) {
if (this.visible2) {
this.visible2 = false
} else {
let optionEnums = []
let setKey = 'ficationViewEnums' //要设置的字段Key
if(type=='cost_classification'){
setKey = 'ficationViewEnums' //要设置的字段Key
optionEnums = [{ label: '基础费用', value: 'basic' },
{ label: '额外费用', value: 'addition' },
{ label: '异常费用', value: 'exception' },
{ label: '补差费用', value: 'difference' }]
}
this.setViewEnums(optionEnums, setKey)
this.optionEnums = optionEnums
this.visibleKey = type
this.visible2 = true
}
},
// 提交表单数据
onSubmitForm() {
const imgInstance = this.$refs.myUploadImg //获取图片上传实例
const remarkInstance = this.$refs.myRemark;//备注
this.formData.remark = remarkInstance.value;
imgInstance.uploadImage(images => {
})
const allData = getApp().globalData.all;//所有的明细
var totle=0;
if(allData.length>0){
allData.map(item => {
//费用项+币种是唯一的
if(item.rate_item==this.formData.rate_item && item.currency==this.formData.currency){
totle+=1;
}
})
if(totle>0){
uni.showToast({
title: "已存相同的费用项和币种,请合并相同费用项和币种!",
icon: 'none',
duration: 2000
})
return;
}
}
//验证金额
var money =uni.$u.test.number(this.formData.money)
if (!money) {
uni.showToast({
title: "请填写正确的金额",
icon: 'none',
duration: 2000
})
return;
}
if(this.formData.rate_item==null || this.formData.rate_item==""){
uni.showToast({
title: "费用项不能为空",
icon: 'none',
duration: 2000
})
return;
}
if(this.formData.currency==null || this.formData.currency==""){
uni.showToast({
title: "币种不能为空",
icon: 'none',
duration: 2000
})
return;
}
var cost = this.formData.cost_classification;
if(cost == 'basic'){
this.formData.cost_classification_label= '基础费用'
}else if(cost == 'addition'){
this.formData.cost_classification_label= '额外费用'
}else if(cost == 'exception'){
this.formData.cost_classification_label= '异常费用'
}else if(cost == 'difference'){
this.formData.cost_classification_label= '补差费用'
}
getApp().globalData.tableData = this.formData;
setTimeout(() => {
uni.navigateBack()
}, 1500)
},
//返回上一级页面
onNavBack() {
setTimeout(() => {
uni.navigateBack()
},
1500
)
},
//设置显示枚举
setViewEnums(optionEnums, key) {
let viewEnums = {}
optionEnums.map(item => {
viewEnums[item.value] = item.label
})
this[key] = viewEnums
},
//确认选择
onConfirmPicker(value) {
this.setFormData(value)
this.visible = false
this.visible2 = false
},
//页面展示时间戳转换为日期
convertTime(timestamp) {
if (!timestamp) return
const nowDate = new Date(timestamp)
var year = nowDate.getFullYear();
var month = nowDate.getMonth() + 1;
var date = nowDate.getDate();
return `${year}-${ month}-${date}`
},
//设置formData
setFormData(value) {
const key = this.visibleKey
if (key.includes('.')) { //对象特殊处理
const keys = key.split('.')
this.formData[keys[0]][keys[1]] = value
} else {
this.formData[key] = value
}
},
// 时间选择
onSelsectDate(dateInfo) {
const {
timestamp
} = dateInfo
this.setFormData(timestamp)
this.showPopup = false
},
//关闭时间选择框
onVisibleDate(type) {
if (type) {
this.visibleKey = type
}
this.showPopup = !this.showPopup
},
}
}
</script>
<style lang="scss">
.abnormalEventDetail-list {
.module-title {
margin-top: 20rpx;
//padding: 15rpx 0rpx 16rpx;
//background-color:#a2a2a2;
.vertical-separate {
height: 40rpx;
border-left: 7rpx solid #797979;
margin-right: 20rpx;
}
}
.select-container {
min-width: 70vw;
height: 50vh;
padding: 30rpx;
font-weight: 400;
.popTitle {
color: #8C8C8C;
font-size: 32rpx;
text-align: center;
margin-bottom: 44rpx;
}
.select-list{
height: 460rpx;
}
}
.new_cost{
background-color: coral;
height: 30px;
width: 100px;
border-radius: 5px;
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
margin-bottom: -10px;
}
.abnormalEventDetail-item {
padding: 32rpx;
margin-bottom: 24rpx;
background: #FFFFFF;
.detail-title {
font-size: 28rpx;
padding: 8rpx 0 30rpx;
border-bottom: 1rpx solid #E5E5E5;
.t-icon-order {
width: 50rpx;
height: 50rpx;
margin-right: 20rpx;
}
}
/deep/.custom-cell-row {
margin-top: 10rpx;
.custom-cell-label {
color: #8C8C8C;
}
}
.outer-frame {
margin-top: 32rpx;
border-radius: 16rpx;
border: 2rpx solid #F0F0F0;
overflow: hidden;
.foldPanel-title {
color: #2E75E6;
font-size: 28rpx;
padding-left: 20rpx;
height: 96rpx;
background: rgba(46, 117, 230, .1);
.foldPanel-right-icon {
margin-right: 20rpx;
}
}
/deep/.cargo-detail-table {
background: #FFFFFF;
padding-bottom: 10rpx;
.detail-row-title {
color: #8C8C8C;
background: #FAFAFA;
padding: 20rpx 0;
}
.detail-col-item {
margin: 20rpx;
.cargo-title {
font-size: 28rpx;
padding-left: 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
.snapshoot image {
margin: 20rpx 15rpx;
width: 140rpx;
height: 140rpx;
}
/deep/.basic-info,
/deep/.driving-license {
padding: 0 10rpx;
background-color: #ffffff;
.input-tip {
font-size: 28rpx;
color: #BFBFBF;
}
.u-form-item__body__left__content__label {
color: #8C8C8C;
font-size: 28rpx;
}
.u-form-item__body {
padding: 25rpx 0;
}
.u-icon {
margin-left: 20rpx;
}
.u-upload__wrap>view {
width: 100%;
}
.previewImages {
width: 100%;
.previewImg {
width: 100% !important;
height: 240rpx !important;
}
}
.uploadeBtn {
width: 100% !important;
height: 240rpx !important;
margin-bottom: 30rpx;
margin-left: 0 !important;
}
.orderTotal {
display: none;
}
}
.outer-frame {
border-radius: 16rpx;
border: 2rpx solid #F0F0F0;
overflow: hidden;
.foldPanel-title {
color: #2E75E6;
font-size: 28rpx;
padding-left: 20rpx;
height: 96rpx;
background: rgba(46, 117, 230, .1);
.foldPanel-right-icon {
margin-right: 20rpx;
}
}
/deep/.cargo-detail-table {
background: #FFFFFF;
padding-bottom: 10rpx;
.detail-row-title {
color: #8C8C8C;
background: #FAFAFA;
padding: 20rpx 0;
}
.detail-col-item {
margin: 20rpx;
.cargo-title {
font-size: 28rpx;
padding-left: 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.u-number-box {
justify-content: center !important;
}
//步进器相关
.u-number-box__input {
margin: 0 10rpx;
color: #333333 !important;
width: 60rpx !important;
height: 40rpx !important;
background-color: #fff !important;
}
.u-number-box__minus,
.u-number-box__plus {
width: 40rpx;
height: 40rpx !important;
border-radius: 6rpx;
border: 2rpx solid #C9C9C9;
}
.uicon-plus,
.uicon-minus {
font-size: 20rpx !important;
}
}
}
}
</style>
<template>
<view class="signFor content_box">
<view class="shipmentBriefInfo">
<bs-pageHeader-orderNo :shipmentNo="shipmentInfo.header.shipmentNo" />
<view class="primary-info routine_radius_card flex_center">
<view style="width: 100%;">
<bs-customCell label="货量 :" labelCol="2" wrapCol="10"
:value="shipmentInfo.header.shipOrders + '单/' +shipmentInfo.header.shipQty+ '件/' + shipmentInfo.header.shipWeight+'千克/'+ shipmentInfo.header.shipVolume+'方'" />
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="orders-total">
<view class="orderQty font_bolder">订单总数:{{orderList.length}}</view>
<scroll-view class="order-detail-list" scroll-y>
<u-empty mode="data" textSize='32' iconSize="160" text="暂无订单" v-if="!orderList.length" />
<u-checkbox-group placement="column" v-model="orderCheckeds">
<view class="order-detail-item" v-for="(item,index) in orderList" :key="index">
<view class="orderNo flex_sb">
<text>订单号 : {{item.orderNo}}</text>
<u-checkbox size='43' :disabled="item.disabled" iconSize='43' :name="item.id"/>
</view>
<u-line color="#F0F0F0" margin="20rpx 0" />
<view class="cargoQty">
<text>货量 :</text>
<text style="margin-left: 30rpx;">{{item.qty}}件 /</text>
<text style="margin-left: 20rpx;">{{item.weight}}千克 /</text>
<text style="margin-left: 20rpx;">{{item.volume}}</text>
</view>
<template v-if="orderCheckeds.some(itm => itm === item.id)">
<!-- 异常选择 -->
<view class="abnormal-select flex_sb flex_center">
<text>是否异常</text>
<switch :checked="item.abnormal" size="43" color="#EB2F48" style="transform:scale(0.8);width: 92rpx;" @change="onChange(item.id)"/>
</view>
<!-- 备注信息 -->
<view class="remark-content">
<u-textarea border="none" placeholder="请输入备注" v-model="item.content" holdKeyboard />
<!-- 图片上传 -->
<block>
<view class="uploader-imgList flex flex_wrap">
<view style="position: absolute;left: -14rpx;color: red;">
*
</view>
<view class="previewImages" v-for="(itm,idx) in item.images" :key="idx">
<image mode="aspectFill" :src="itm" @click="onPreviewImage(idx, item.images)"
class="previewImg" />
<u-icon name="close-circle-fill" class="clearIcon" size="30"
@click="deleteImage(idx, item)" />
</view>
<!-- <view v-if="!item.images.length" style=" width: 200rpx" /> -->
<template v-if="isIos">
<view class="uploadeBtn flex_col" @click="onUpLoad(item.id)">
<view class="t-icon t-icon-picture" />
<text style="margin-top:6rpx">上传照片</text>
<text class="font_bolder orderTotal">{{item.images.length}}/6)</text>
</view>
</template>
<template v-else>
<u-upload :fileList="item.images" :max-count="6" multiple :previewImage="false"
:sizeType="['compressed']" @afterRead="uploadAfterRead" :name="item.id">
<!-- 自定义上传按钮 -->
<view class="uploadeBtn flex_col">
<view class="t-icon t-icon-picture" />
<text style="margin-top:6rpx">上传照片</text>
<text class="font_bolder orderTotal">{{item.images.length}}/6)</text>
</view>
</u-upload>
</template>
</view>
</block>
</view>
</template>
</view>
</u-checkbox-group>
</scroll-view>
</view>
<!-- 底部按钮 -->
<view class="operation-btn flex_sb">
<button class="backBtn common_btn" @click="onNavBack">取消</button>
<button class="submitBtn common_btn" @click="onSubmit">确定</button>
</view>
</view>
</template>
<script>
import { signFor, uploadImg, ordermovementOM, ordermovementOR, ordermovementMASS} from '../../api/apiList'
const getUserLocation = require('../../mixins/getUserLocation')
const appInstance = getApp()
export default {
mixins: [getUserLocation],
data() {
return {
orderList: [], //订单列表
orderCheckeds: [], //已选中签收列表
isIos: false,
shipmentInfo: {},
shipmentMode: '', // 运输模式 OM、OR、MS: 大宗运输Mass
}
},
onLoad: function(options) {
let data = JSON.parse(options.data)
const { shipmentData } = data
this.shipmentInfo = shipmentData
const title = '纸质签收'
const { shipmentId, shipmentBizName } = appInstance.globalData.shipmentInfo
let shipmentMode = ''
switch (shipmentBizName) { //根据shipmentBizName 判断运输模式
case 'tm.MovementShipment':
case 'tm.SectionShipment': //暂时走OM逻辑
shipmentMode = 'OM'
break
case 'tm.MassShipment':
shipmentMode = 'MS'; //大宗运输
break
default:
shipmentMode = 'OR';
}
this.shipmentMode = shipmentMode
this.initData(shipmentId)
this.setNavTitle(title)
// #ifdef H5
const platform = uni.getSystemInfoSync().platform
if (platform === 'ios') {
// this.isIos = true
}
// #endif
this.getUserSetting()
},
methods: {
//设置页面头部标题
setNavTitle(title) {
uni.setNavigationBarTitle({
title: title,
})
},
//返回上一级页面
onNavBack() {
uni.navigateBack()
},
//初始化数据
initData(shipmentId) {
let newDatas = []
if (this.shipmentMode === 'MS') {
const reqData = { "aux": { "id": shipmentId } }
ordermovementMASS(reqData).then(res => {
const data = res.data.data.header
newDatas.push({
'id': data['massOrder.id'], //订单id
'orderNo': data['massOrder.orderNo'], //订单号
'qty': data['massOrder.qty'], //订单件数
'weight': data['massOrder.weight'], //订单重量
'volume': data['massOrder.volume'], //订单体积
'abnormal': false, //订单是否异常
'images': [], //订单图片
'content': '', //订单备注信息
'disabled': item.transportStatus === '80.POD' //已签收时禁用
})
this.orderList = [...newDatas]
})
} else {
const reqData = {
"args": {
"pageNum": 1,
"pageSize": 100,
"restrictions": [{
"field": "shipment.id",
"type": "EQ",
"value": shipmentId
}]
}
}
const isOM = this.shipmentMode === 'OM'
const requestFun = isOM ? ordermovementOM : ordermovementOR
requestFun(reqData).then(res => {
const datas = res.data.data.datas
datas.map(item => {
newDatas.push({
'id': item.id, //订单id
'orderNo': isOM ? item.movementNo : item.orderNo, //订单号
'qty': isOM ? item.shipQty : item.qty, //订单件数
'weight': isOM ? item.shipWeight : item.weight, //订单重量
'volume': isOM ? item.shipVolume : item.volume, //订单体积
'abnormal': false, //订单是否异常
'images': [], //订单图片
'content': '', //订单备注信息
'disabled': item.transportStatus === '80.POD' //已签收时禁用
})
})
this.orderList = [...newDatas]
})
}
},
// 确认
onSubmit() {
const _this = this
let {orderList, orderCheckeds} = _this
if (!orderCheckeds.length) { //校验是否存在可签收订单
this.customTost()
return
}
//校验签收订单图片是否上传
let flags = []
orderCheckeds.map(checkOrderId => {
let order = orderList.find(orderItem => orderItem.id === checkOrderId)
if (!order.images.length) {
flags.push(order.orderNo)
}
})
if(flags.length){
uni.showToast({
title: `订单${flags.join('|')}图片信息不能为空`,
icon: 'none',
duration: 3000
})
return
}
//调用接口签收订单
orderCheckeds.map(checkOrderId => {
let order = orderList.find(orderItem => orderItem.id === checkOrderId)
_this.uploadImages(order.images, imgs => {
_this.onSignFor(order, imgs) //签收选中订单
})
})
},
//订单签收
onSignFor(orderData, images) {
const { longitude, latitude, address, time } = this.locationInfo
const reqData = {
'aux': {
images: images,
content: orderData.content, //备注
abnormal: orderData.abnormal, //是否异常
eventSource: 'DriverApp',
longitude,
latitude,
address,
time
},
"args": {
"selectedIds": [orderData.id]
}
}
const signMode = { 'OM': 'OrderMovement', 'OR': 'SimpleOrder' }
signFor(signMode[this.shipmentMode], reqData).then(res => {
if (res.data.messageType === 'success') {
uni.showToast({
title: '操作成功',
duration: 3000
})
setTimeout(() => {
this.onNavBack()
}, 1500)
}
})
},
onChange(orderId){
const newList = this.orderList.map(item => {
if(item.id === orderId){
item.abnormal = !item.abnormal
}
return item
})
this.orderList = [...newList]
},
//图片上传 IOS兼容
onUpLoad(orderId) {
if (this.$jwx) {
const _this = this
const sourceType = this.isIos ? ['album'] : ['album', 'camera'] // 可以指定来源是相册还是相机, ios指定为仅相册
this.$jwx.chooseImage({
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: sourceType,
success: function(res) {
_this.onConvert(res.localIds)
_this.orderItemId = orderId
}
})
}
},
async onConvert(localIds) {
for (var i = 0; i < localIds.length; i++) {
await this.onReadImage(localIds[i]);
}
},
onReadImage(localId) {
const _this = this
return new Promise((resolve, reject) => {
_this.$jwx.getLocalImgData({
localId: localId, // 图片的localID
success: function(res) {
const localData = res.localData
let orderList = _this.orderList
orderList.forEach(order => {
if (order.id === _this.orderItemId) {
order.images = [...order.images, localData]
}
return
})
_this.orderList = [...orderList]
resolve('done!')
}
});
})
},
//上传图片
uploadImages(fileList, callback) {
let images = []
fileList.forEach(item => {
uploadImg(item).then(res => {
uni.hideLoading()
let data = JSON.parse(res).data
images.push(data.path)
if (images.length === fileList.length) {
return callback(images.join('|'))
}
}).catch(err => {
console.log(err);
})
})
},
// 自定义轻提示
customTost() {
uni.showToast({
title: '暂无可签收订单',
icon: 'none',
duration: 3000
})
},
// 上传图片回调
uploadAfterRead(info) {
const { file, name } = info
const newFile = file.map(item => item.url);
let orderList = this.orderList
orderList.forEach(order => {
if (order.id === name) {
order.images = [...order.images, ...newFile]
}
return
})
this.orderList = [...orderList]
},
// 预览图片回调
onPreviewImage(index, images) {
uni.previewImage({
current: index,
urls: images
})
},
// 删除已选图片
deleteImage(index, info) {
let orderList = this.orderList
uni.showModal({
title: '删除提示',
content: '确定删除本张图片吗?',
success: (res) => {
if (res.confirm) {
orderList.forEach(order => {
if (order.id === info.id) {
let images = [...order.images]
images.splice(index, 1)
order.images = [...images]
}
return
})
this.orderList = [...orderList]
} else if (res.cancel) {}
}
})
},
}
}
</script>
<style lang="scss">
.orders-total {
font-size: 28rpx;
background: #FAFAFA;
.orderQty {
padding: 26rpx 30rpx;
}
.order-detail-list {
height: 800rpx;
.order-detail-item {
width: 100vw;
margin-bottom: 35rpx;
padding: 30rpx;
background-color: #FFFFFF;
/deep/.orderNo {
color: #8C8C8C;
margin-bottom: 30rpx;
.u-checkbox__icon-wrap{
margin-right: 0;
}
}
.abnormal-select{
margin-top: 30rpx;
}
.remark-content {
margin-top: 30rpx;
padding: 16rpx;
background: #FAFAFA;
border-radius: 8px;
/deep/.u-textarea {
background: #FAFAFA;
}
.uploader-imgList {
padding-top: 20rpx;
position: relative;
.previewImages {
position: relative;
overflow: hidden;
.previewImg {
width: 180rpx;
height: 140rpx;
margin: 0 10rpx 20rpx;
}
/deep/.uicon-close-circle-fill {
width: 26rpx;
height: 26rpx;
border-radius: 50%;
background-color: #fff;
color: #D81E06 !important;
position: absolute;
top: 5rpx !important;
right: 15rpx;
}
}
.uploadeBtn {
position: relative;
color: #929292;
width: 180rpx;
height: 140rpx;
border: 2rpx dashed #D9D9D9;
margin: 0 0 20rpx 6rpx;
background-color: #fff;
justify-content: center;
align-items: center;
font-size: 24rpx;
.orderTotal {
color: #333333;
position: absolute;
right: -90rpx;
bottom: 0;
z-index: 999;
}
.t-icon-picture {
width: 50rpx;
height: 50rpx;
}
}
}
}
}
}
}
.primary-info {
flex-wrap: wrap;
.left-icon {
position: relative;
margin-right: 20rpx;
.leftIcon-text {
font-size: 28rpx;
display: inline-block;
position: absolute;
top: 5px;
left: 12px;
}
.t-icon {
width: 70rpx;
height: 70rpx;
}
}
/deep/.custom-cell-row {
width: 100%;
margin-top: 32rpx;
color: #8C8C8C;
}
}
</style>
## 1.2.1(2022-06-06)
- 修复 微信小程序存在无使用组件的问题
## 1.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-table](https://uniapp.dcloud.io/component/uniui/uni-table)
## 1.1.0(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.7(2021-07-08)
- 新增 uni-th 支持 date 日期筛选范围
## 1.0.6(2021-07-05)
- 新增 uni-th 支持 range 筛选范围
## 1.0.5(2021-06-28)
- 新增 uni-th 筛选功能
## 1.0.4(2021-05-12)
- 新增 示例地址
- 修复 示例项目缺少组件的Bug
## 1.0.3(2021-04-16)
- 新增 sortable 属性,是否开启单列排序
- 优化 表格多选逻辑
## 1.0.2(2021-03-22)
- uni-tr 添加 disabled 属性,用于 type=selection 时,设置某行是否可由全选按钮控制
## 1.0.1(2021-02-05)
- 调整为uni_modules目录规范
<template>
<view class="uni-table-scroll" :class="{ 'table--border': border, 'border-none': !noData }">
<!-- #ifdef H5 -->
<table class="uni-table" border="0" cellpadding="0" cellspacing="0" :class="{ 'table--stripe': stripe }" :style="{ 'min-width': minWidth + 'px' }">
<slot></slot>
<view v-if="noData" class="uni-table-loading">
<view class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</view>
</view>
<view v-if="loading" class="uni-table-mask" :class="{ 'empty-border': border }"><div class="uni-table--loader"></div></view>
</table>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="uni-table" :style="{ 'min-width': minWidth + 'px' }" :class="{ 'table--stripe': stripe }">
<slot></slot>
<view v-if="noData" class="uni-table-loading">
<view class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</view>
</view>
<view v-if="loading" class="uni-table-mask" :class="{ 'empty-border': border }"><div class="uni-table--loader"></div></view>
</view>
<!-- #endif -->
</view>
</template>
<script>
/**
* Table 表格
* @description 用于展示多条结构类似的数据
* @tutorial https://ext.dcloud.net.cn/plugin?id=3270
* @property {Boolean} border 是否带有纵向边框
* @property {Boolean} stripe 是否显示斑马线
* @property {Boolean} type 是否开启多选
* @property {String} emptyText 空数据时显示的文本内容
* @property {Boolean} loading 显示加载中
* @event {Function} selection-change 开启多选时,当选择项发生变化时会触发该事件
*/
export default {
name: 'uniTable',
options: {
virtualHost: true
},
emits:['selection-change'],
props: {
data: {
type: Array,
default() {
return []
}
},
// 是否有竖线
border: {
type: Boolean,
default: false
},
// 是否显示斑马线
stripe: {
type: Boolean,
default: false
},
// 多选
type: {
type: String,
default: ''
},
// 没有更多数据
emptyText: {
type: String,
default: '没有更多数据'
},
loading: {
type: Boolean,
default: false
},
rowKey: {
type: String,
default: ''
}
},
data() {
return {
noData: true,
minWidth: 0,
multiTableHeads: []
}
},
watch: {
loading(val) {},
data(newVal) {
let theadChildren = this.theadChildren
let rowspan = 1
if (this.theadChildren) {
rowspan = this.theadChildren.rowspan
}
// this.trChildren.length - rowspan
this.noData = false
// this.noData = newVal.length === 0
}
},
created() {
// 定义tr的实例数组
this.trChildren = []
this.thChildren = []
this.theadChildren = null
this.backData = []
this.backIndexData = []
},
methods: {
isNodata() {
let theadChildren = this.theadChildren
let rowspan = 1
if (this.theadChildren) {
rowspan = this.theadChildren.rowspan
}
this.noData = this.trChildren.length - rowspan <= 0
},
/**
* 选中所有
*/
selectionAll() {
let startIndex = 1
let theadChildren = this.theadChildren
if (!this.theadChildren) {
theadChildren = this.trChildren[0]
} else {
startIndex = theadChildren.rowspan - 1
}
let isHaveData = this.data && this.data.length.length > 0
theadChildren.checked = true
theadChildren.indeterminate = false
this.trChildren.forEach((item, index) => {
if (!item.disabled) {
item.checked = true
if (isHaveData && item.keyValue) {
const row = this.data.find(v => v[this.rowKey] === item.keyValue)
if (!this.backData.find(v => v[this.rowKey] === row[this.rowKey])) {
this.backData.push(row)
}
}
if (index > (startIndex - 1) && this.backIndexData.indexOf(index - startIndex) === -1) {
this.backIndexData.push(index - startIndex)
}
}
})
// this.backData = JSON.parse(JSON.stringify(this.data))
this.$emit('selection-change', {
detail: {
value: this.backData,
index: this.backIndexData
}
})
},
/**
* 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中)
*/
toggleRowSelection(row, selected) {
// if (!this.theadChildren) return
row = [].concat(row)
this.trChildren.forEach((item, index) => {
// if (item.keyValue) {
const select = row.findIndex(v => {
//
if (typeof v === 'number') {
return v === index - 1
} else {
return v[this.rowKey] === item.keyValue
}
})
let ischeck = item.checked
if (select !== -1) {
if (typeof selected === 'boolean') {
item.checked = selected
} else {
item.checked = !item.checked
}
if (ischeck !== item.checked) {
this.check(item.rowData||item, item.checked, item.rowData?item.keyValue:null, true)
}
}
// }
})
this.$emit('selection-change', {
detail: {
value: this.backData,
index:this.backIndexData
}
})
},
/**
* 用于多选表格,清空用户的选择
*/
clearSelection() {
let theadChildren = this.theadChildren
if (!this.theadChildren) {
theadChildren = this.trChildren[0]
}
// if (!this.theadChildren) return
theadChildren.checked = false
theadChildren.indeterminate = false
this.trChildren.forEach(item => {
// if (item.keyValue) {
item.checked = false
// }
})
this.backData = []
this.backIndexData = []
this.$emit('selection-change', {
detail: {
value: [],
index: []
}
})
},
/**
* 用于多选表格,切换所有行的选中状态
*/
toggleAllSelection() {
let list = []
let startIndex = 1
let theadChildren = this.theadChildren
if (!this.theadChildren) {
theadChildren = this.trChildren[0]
} else {
startIndex = theadChildren.rowspan - 1
}
this.trChildren.forEach((item, index) => {
if (!item.disabled) {
if (index > (startIndex - 1) ) {
list.push(index-startIndex)
}
}
})
this.toggleRowSelection(list)
},
/**
* 选中\取消选中
* @param {Object} child
* @param {Object} check
* @param {Object} rowValue
*/
check(child, check, keyValue, emit) {
let theadChildren = this.theadChildren
if (!this.theadChildren) {
theadChildren = this.trChildren[0]
}
let childDomIndex = this.trChildren.findIndex((item, index) => child === item)
if(childDomIndex < 0){
childDomIndex = this.data.findIndex(v=>v[this.rowKey] === keyValue) + 1
}
const dataLen = this.trChildren.filter(v => !v.disabled && v.keyValue).length
if (childDomIndex === 0) {
check ? this.selectionAll() : this.clearSelection()
return
}
if (check) {
if (keyValue) {
this.backData.push(child)
}
this.backIndexData.push(childDomIndex - 1)
} else {
const index = this.backData.findIndex(v => v[this.rowKey] === keyValue)
const idx = this.backIndexData.findIndex(item => item === childDomIndex - 1)
if (keyValue) {
this.backData.splice(index, 1)
}
this.backIndexData.splice(idx, 1)
}
const domCheckAll = this.trChildren.find((item, index) => index > 0 && !item.checked && !item.disabled)
if (!domCheckAll) {
theadChildren.indeterminate = false
theadChildren.checked = true
} else {
theadChildren.indeterminate = true
theadChildren.checked = false
}
if (this.backIndexData.length === 0) {
theadChildren.indeterminate = false
}
if (!emit) {
this.$emit('selection-change', {
detail: {
value: this.backData,
index: this.backIndexData
}
})
}
}
}
}
</script>
<style lang="scss">
$border-color: #ebeef5;
.uni-table-scroll {
width: 100%;
/* #ifndef APP-NVUE */
overflow-x: auto;
/* #endif */
}
.uni-table {
position: relative;
width: 100%;
border-radius: 5px;
// box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1);
background-color: #fff;
/* #ifndef APP-NVUE */
box-sizing: border-box;
display: table;
overflow-x: auto;
::v-deep .uni-table-tr:nth-child(n + 2) {
&:hover {
background-color: #f5f7fa;
}
}
::v-deep .uni-table-thead {
.uni-table-tr {
// background-color: #f5f7fa;
&:hover {
background-color:#fafafa;
}
}
}
/* #endif */
}
.table--border {
border: 1px $border-color solid;
border-right: none;
}
.border-none {
/* #ifndef APP-NVUE */
border-bottom: none;
/* #endif */
}
.table--stripe {
/* #ifndef APP-NVUE */
::v-deep .uni-table-tr:nth-child(2n + 3) {
background-color: #fafafa;
}
/* #endif */
}
/* 表格加载、无数据样式 */
.uni-table-loading {
position: relative;
/* #ifndef APP-NVUE */
display: table-row;
/* #endif */
height: 50px;
line-height: 50px;
overflow: hidden;
box-sizing: border-box;
}
.empty-border {
border-right: 1px $border-color solid;
}
.uni-table-text {
position: absolute;
right: 0;
left: 0;
text-align: center;
font-size: 14px;
color: #999;
}
.uni-table-mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(255, 255, 255, 0.8);
z-index: 99;
/* #ifndef APP-NVUE */
display: flex;
margin: auto;
transition: all 0.5s;
/* #endif */
justify-content: center;
align-items: center;
}
.uni-table--loader {
width: 30px;
height: 30px;
border: 2px solid #aaa;
// border-bottom-color: transparent;
border-radius: 50%;
/* #ifndef APP-NVUE */
animation: 2s uni-table--loader linear infinite;
/* #endif */
position: relative;
}
@keyframes uni-table--loader {
0% {
transform: rotate(360deg);
}
10% {
border-left-color: transparent;
}
20% {
border-bottom-color: transparent;
}
30% {
border-right-color: transparent;
}
40% {
border-top-color: transparent;
}
50% {
transform: rotate(0deg);
}
60% {
border-top-color: transparent;
}
70% {
border-left-color: transparent;
}
80% {
border-bottom-color: transparent;
}
90% {
border-right-color: transparent;
}
100% {
transform: rotate(-360deg);
}
}
</style>
<template>
<!-- #ifdef H5 -->
<tbody>
<slot></slot>
</tbody>
<!-- #endif -->
<!-- #ifndef H5 -->
<view><slot></slot></view>
<!-- #endif -->
</template>
<script>
export default {
name: 'uniBody',
options: {
virtualHost: true
},
data() {
return {
}
},
created() {},
methods: {}
}
</script>
<style>
</style>
<template>
<!-- #ifdef H5 -->
<td class="uni-table-td" :rowspan="rowspan" :colspan="colspan" :class="{'table--border':border}" :style="{width:width + 'px','text-align':align}">
<slot></slot>
</td>
<!-- #endif -->
<!-- #ifndef H5 -->
<!-- :class="{'table--border':border}" -->
<view class="uni-table-td" :class="{'table--border':border}" :style="{width:width + 'px','text-align':align}">
<slot></slot>
</view>
<!-- #endif -->
</template>
<script>
/**
* Td 单元格
* @description 表格中的标准单元格组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=3270
* @property {Number} align = [left|center|right] 单元格对齐方式
*/
export default {
name: 'uniTd',
options: {
virtualHost: true
},
props: {
width: {
type: [String, Number],
default: ''
},
align: {
type: String,
default: 'left'
},
rowspan: {
type: [Number,String],
default: 1
},
colspan: {
type: [Number,String],
default: 1
}
},
data() {
return {
border: false
};
},
created() {
this.root = this.getTable()
this.border = this.root.border
},
methods: {
/**
* 获取父元素实例
*/
getTable() {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== 'uniTable') {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
},
}
}
</script>
<style lang="scss">
$border-color:#EBEEF5;
.uni-table-td {
display: table-cell;
padding: 8px 10px;
font-size: 14px;
border-bottom: 1px $border-color solid;
font-weight: 400;
color: #606266;
line-height: 23px;
box-sizing: border-box;
}
.table--border {
border-right: 1px $border-color solid;
}
</style>
<template>
<view class="uni-filter-dropdown">
<view class="dropdown-btn" @click="onDropdown">
<view class="icon-select" :class="{active: canReset}" v-if="isSelect || isRange"></view>
<view class="icon-search" :class="{active: canReset}" v-if="isSearch">
<view class="icon-search-0"></view>
<view class="icon-search-1"></view>
</view>
<view class="icon-calendar" :class="{active: canReset}" v-if="isDate">
<view class="icon-calendar-0"></view>
<view class="icon-calendar-1"></view>
</view>
</view>
<view class="uni-dropdown-cover" v-if="isOpened" @click="handleClose"></view>
<view class="dropdown-popup dropdown-popup-right" v-if="isOpened" @click.stop>
<!-- select-->
<view v-if="isSelect" class="list">
<label class="flex-r a-i-c list-item" v-for="(item,index) in dataList" :key="index"
@click="onItemClick($event, index)">
<check-box class="check" :checked="item.checked" />
<view class="checklist-content">
<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
</view>
</label>
</view>
<view v-if="isSelect" class="flex-r opera-area">
<view class="flex-f btn btn-default" :class="{disable: !canReset}" @click="handleSelectReset">
{{resource.reset}}</view>
<view class="flex-f btn btn-submit" @click="handleSelectSubmit">{{resource.submit}}</view>
</view>
<!-- search -->
<view v-if="isSearch" class="search-area">
<input class="search-input" v-model="filterValue" />
</view>
<view v-if="isSearch" class="flex-r opera-area">
<view class="flex-f btn btn-submit" @click="handleSearchSubmit">{{resource.search}}</view>
<view class="flex-f btn btn-default" :class="{disable: !canReset}" @click="handleSearchReset">
{{resource.reset}}</view>
</view>
<!-- range -->
<view v-if="isRange">
<view class="input-label">{{resource.gt}}</view>
<input class="input" v-model="gtValue" />
<view class="input-label">{{resource.lt}}</view>
<input class="input" v-model="ltValue" />
</view>
<view v-if="isRange" class="flex-r opera-area">
<view class="flex-f btn btn-default" :class="{disable: !canReset}" @click="handleRangeReset">
{{resource.reset}}</view>
<view class="flex-f btn btn-submit" @click="handleRangeSubmit">{{resource.submit}}</view>
</view>
<!-- date -->
<view v-if="isDate">
<uni-datetime-picker ref="datetimepicker" :value="dateRange" type="datetimerange" return-type="timestamp" @change="datetimechange" @maskClick="timepickerclose">
<view></view>
</uni-datetime-picker>
</view>
</view>
</view>
</template>
<script>
import checkBox from '../uni-tr/table-checkbox.vue'
const resource = {
"reset": "重置",
"search": "搜索",
"submit": "确定",
"filter": "筛选",
"gt": "大于等于",
"lt": "小于等于",
"date": "日期范围"
}
const DropdownType = {
Select: "select",
Search: "search",
Range: "range",
Date: "date",
Timestamp: "timestamp"
}
export default {
name: 'FilterDropdown',
emits:['change'],
components: {
checkBox
},
options: {
virtualHost: true
},
props: {
filterType: {
type: String,
default: DropdownType.Select
},
filterData: {
type: Array,
default () {
return []
}
},
mode: {
type: String,
default: 'default'
},
map: {
type: Object,
default () {
return {
text: 'text',
value: 'value'
}
}
}
},
computed: {
canReset() {
if (this.isSearch) {
return this.filterValue.length > 0
}
if (this.isSelect) {
return this.checkedValues.length > 0
}
if (this.isRange) {
return (this.gtValue.length > 0 && this.ltValue.length > 0)
}
if (this.isDate) {
return this.dateSelect.length > 0
}
return false
},
isSelect() {
return this.filterType === DropdownType.Select
},
isSearch() {
return this.filterType === DropdownType.Search
},
isRange() {
return this.filterType === DropdownType.Range
},
isDate() {
return (this.filterType === DropdownType.Date || this.filterType === DropdownType.Timestamp)
}
},
watch: {
filterData(newVal) {
this._copyFilters()
},
indeterminate(newVal) {
this.isIndeterminate = newVal
}
},
data() {
return {
resource,
enabled: true,
isOpened: false,
dataList: [],
filterValue: '',
checkedValues: [],
gtValue: '',
ltValue: '',
dateRange: [],
dateSelect: []
};
},
created() {
this._copyFilters()
},
methods: {
_copyFilters() {
let dl = JSON.parse(JSON.stringify(this.filterData))
for (let i = 0; i < dl.length; i++) {
if (dl[i].checked === undefined) {
dl[i].checked = false
}
}
this.dataList = dl
},
openPopup() {
this.isOpened = true
if (this.isDate) {
this.$nextTick(() => {
if (!this.dateRange.length) {
this.resetDate()
}
this.$refs.datetimepicker.show()
})
}
},
closePopup() {
this.isOpened = false
},
handleClose(e) {
this.closePopup()
},
resetDate() {
let date = new Date()
let dateText = date.toISOString().split('T')[0]
this.dateRange = [dateText + ' 0:00:00', dateText + ' 23:59:59']
},
onDropdown(e) {
this.openPopup()
},
onItemClick(e, index) {
let items = this.dataList
let listItem = items[index]
if (listItem.checked === undefined) {
items[index].checked = true
} else {
items[index].checked = !listItem.checked
}
let checkvalues = []
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.checked) {
checkvalues.push(item.value)
}
}
this.checkedValues = checkvalues
},
datetimechange(e) {
this.closePopup()
this.dateRange = e
this.dateSelect = e
this.$emit('change', {
filterType: this.filterType,
filter: e
})
},
timepickerclose(e) {
this.closePopup()
},
handleSelectSubmit() {
this.closePopup()
this.$emit('change', {
filterType: this.filterType,
filter: this.checkedValues
})
},
handleSelectReset() {
if (!this.canReset) {
return;
}
var items = this.dataList
for (let i = 0; i < items.length; i++) {
let item = items[i]
this.$set(item, 'checked', false)
}
this.checkedValues = []
this.handleSelectSubmit()
},
handleSearchSubmit() {
this.closePopup()
this.$emit('change', {
filterType: this.filterType,
filter: this.filterValue
})
},
handleSearchReset() {
if (!this.canReset) {
return;
}
this.filterValue = ''
this.handleSearchSubmit()
},
handleRangeSubmit(isReset) {
this.closePopup()
this.$emit('change', {
filterType: this.filterType,
filter: isReset === true ? [] : [parseInt(this.gtValue), parseInt(this.ltValue)]
})
},
handleRangeReset() {
if (!this.canReset) {
return;
}
this.gtValue = ''
this.ltValue = ''
this.handleRangeSubmit(true)
}
}
}
</script>
<style lang="scss">
.flex-r {
display: flex;
flex-direction: row;
}
.flex-f {
flex: 1;
}
.a-i-c {
align-items: center;
}
.j-c-c {
justify-content: center;
}
.icon-select {
width: 14px;
height: 16px;
border: solid 6px transparent;
border-top: solid 6px #ddd;
border-bottom: none;
background-color: #ddd;
background-clip: content-box;
box-sizing: border-box;
}
.icon-select.active {
background-color: #1890ff;
border-top-color: #1890ff;
}
.icon-search {
width: 12px;
height: 16px;
position: relative;
}
.icon-search-0 {
border: 2px solid #ddd;
border-radius: 8px;
width: 7px;
height: 7px;
}
.icon-search-1 {
position: absolute;
top: 8px;
right: 0;
width: 1px;
height: 7px;
background-color: #ddd;
transform: rotate(-45deg);
}
.icon-search.active .icon-search-0 {
border-color: #1890ff;
}
.icon-search.active .icon-search-1 {
background-color: #1890ff;
}
.icon-calendar {
color: #ddd;
width: 14px;
height: 16px;
}
.icon-calendar-0 {
height: 4px;
margin-top: 3px;
margin-bottom: 1px;
background-color: #ddd;
border-radius: 2px 2px 1px 1px;
position: relative;
}
.icon-calendar-0:before, .icon-calendar-0:after {
content: '';
position: absolute;
top: -3px;
width: 4px;
height: 3px;
border-radius: 1px;
background-color: #ddd;
}
.icon-calendar-0:before {
left: 2px;
}
.icon-calendar-0:after {
right: 2px;
}
.icon-calendar-1 {
height: 9px;
background-color: #ddd;
border-radius: 1px 1px 2px 2px;
}
.icon-calendar.active {
color: #1890ff;
}
.icon-calendar.active .icon-calendar-0,
.icon-calendar.active .icon-calendar-1,
.icon-calendar.active .icon-calendar-0:before,
.icon-calendar.active .icon-calendar-0:after {
background-color: #1890ff;
}
.uni-filter-dropdown {
position: relative;
font-weight: normal;
}
.dropdown-popup {
position: absolute;
top: 100%;
background-color: #fff;
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
min-width: 150px;
z-index: 1000;
}
.dropdown-popup-left {
left: 0;
}
.dropdown-popup-right {
right: 0;
}
.uni-dropdown-cover {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: transparent;
z-index: 100;
}
.list {
margin-top: 5px;
margin-bottom: 5px;
}
.list-item {
padding: 5px 10px;
text-align: left;
}
.list-item:hover {
background-color: #f0f0f0;
}
.check {
margin-right: 5px;
}
.search-area {
padding: 10px;
}
.search-input {
font-size: 12px;
border: 1px solid #f0f0f0;
border-radius: 3px;
padding: 2px 5px;
min-width: 150px;
text-align: left;
}
.input-label {
margin: 10px 10px 5px 10px;
text-align: left;
}
.input {
font-size: 12px;
border: 1px solid #f0f0f0;
border-radius: 3px;
margin: 10px;
padding: 2px 5px;
min-width: 150px;
text-align: left;
}
.opera-area {
cursor: default;
border-top: 1px solid #ddd;
padding: 5px;
}
.opera-area .btn {
font-size: 12px;
border-radius: 3px;
margin: 5px;
padding: 4px 4px;
}
.btn-default {
border: 1px solid #ddd;
}
.btn-default.disable {
border-color: transparent;
}
.btn-submit {
background-color: #1890ff;
color: #ffffff;
}
</style>
<template>
<!-- #ifdef H5 -->
<th :rowspan="rowspan" :colspan="colspan" class="uni-table-th" :class="{ 'table--border': border }" :style="{ width: customWidth + 'px', 'text-align': align }">
<view class="uni-table-th-row">
<view class="uni-table-th-content" :style="{ 'justify-content': contentAlign }" @click="sort">
<slot></slot>
<view v-if="sortable" class="arrow-box">
<text class="arrow up" :class="{ active: ascending }" @click.stop="ascendingFn"></text>
<text class="arrow down" :class="{ active: descending }" @click.stop="descendingFn"></text>
</view>
</view>
<dropdown v-if="filterType || filterData.length" :filterData="filterData" :filterType="filterType" @change="ondropdown"></dropdown>
</view>
</th>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="uni-table-th" :class="{ 'table--border': border }" :style="{ width: customWidth + 'px', 'text-align': align }"><slot></slot></view>
<!-- #endif -->
</template>
<script>
// #ifdef H5
import dropdown from './filter-dropdown.vue'
// #endif
/**
* Th 表头
* @description 表格内的表头单元格组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=3270
* @property {Number | String} width 单元格宽度(支持纯数字、携带单位px或rpx)
* @property {Boolean} sortable 是否启用排序
* @property {Number} align = [left|center|right] 单元格对齐方式
* @value left 单元格文字左侧对齐
* @value center 单元格文字居中
* @value right 单元格文字右侧对齐
* @property {Array} filterData 筛选数据
* @property {String} filterType [search|select] 筛选类型
* @value search 关键字搜素
* @value select 条件选择
* @event {Function} sort-change 排序触发事件
*/
export default {
name: 'uniTh',
options: {
virtualHost: true
},
components: {
// #ifdef H5
dropdown
// #endif
},
emits:['sort-change','filter-change'],
props: {
width: {
type: [String, Number],
default: ''
},
align: {
type: String,
default: 'left'
},
rowspan: {
type: [Number, String],
default: 1
},
colspan: {
type: [Number, String],
default: 1
},
sortable: {
type: Boolean,
default: false
},
filterType: {
type: String,
default: ""
},
filterData: {
type: Array,
default () {
return []
}
}
},
data() {
return {
border: false,
ascending: false,
descending: false
}
},
computed: {
// 根据props中的width属性 自动匹配当前th的宽度(px)
customWidth(){
if(typeof this.width === 'number'){
return this.width
} else if(typeof this.width === 'string') {
let regexHaveUnitPx = new RegExp(/^[1-9][0-9]*px$/g)
let regexHaveUnitRpx = new RegExp(/^[1-9][0-9]*rpx$/g)
let regexHaveNotUnit = new RegExp(/^[1-9][0-9]*$/g)
if (this.width.match(regexHaveUnitPx) !== null) { // 携带了 px
return this.width.replace('px', '')
} else if (this.width.match(regexHaveUnitRpx) !== null) { // 携带了 rpx
let numberRpx = Number(this.width.replace('rpx', ''))
let widthCoe = uni.getSystemInfoSync().screenWidth / 750
return Math.round(numberRpx * widthCoe)
} else if (this.width.match(regexHaveNotUnit) !== null) { // 未携带 rpx或px 的纯数字 String
return this.width
} else { // 不符合格式
return ''
}
} else {
return ''
}
},
contentAlign() {
let align = 'left'
switch (this.align) {
case 'left':
align = 'flex-start'
break
case 'center':
align = 'center'
break
case 'right':
align = 'flex-end'
break
}
return align
}
},
created() {
this.root = this.getTable('uniTable')
this.rootTr = this.getTable('uniTr')
this.rootTr.minWidthUpdate(this.customWidth ? this.customWidth : 140)
this.border = this.root.border
this.root.thChildren.push(this)
},
methods: {
sort() {
if (!this.sortable) return
this.clearOther()
if (!this.ascending && !this.descending) {
this.ascending = true
this.$emit('sort-change', { order: 'ascending' })
return
}
if (this.ascending && !this.descending) {
this.ascending = false
this.descending = true
this.$emit('sort-change', { order: 'descending' })
return
}
if (!this.ascending && this.descending) {
this.ascending = false
this.descending = false
this.$emit('sort-change', { order: null })
}
},
ascendingFn() {
this.clearOther()
this.ascending = !this.ascending
this.descending = false
this.$emit('sort-change', { order: this.ascending ? 'ascending' : null })
},
descendingFn() {
this.clearOther()
this.descending = !this.descending
this.ascending = false
this.$emit('sort-change', { order: this.descending ? 'descending' : null })
},
clearOther() {
this.root.thChildren.map(item => {
if (item !== this) {
item.ascending = false
item.descending = false
}
return item
})
},
ondropdown(e) {
this.$emit("filter-change", e)
},
/**
* 获取父元素实例
*/
getTable(name) {
let parent = this.$parent
let parentName = parent.$options.name
while (parentName !== name) {
parent = parent.$parent
if (!parent) return false
parentName = parent.$options.name
}
return parent
}
}
}
</script>
<style lang="scss">
$border-color: #ebeef5;
.uni-table-th {
padding: 12px 10px;
/* #ifndef APP-NVUE */
display: table-cell;
box-sizing: border-box;
/* #endif */
font-size: 14px;
font-weight: bold;
color: #909399;
border-bottom: 1px $border-color solid;
}
.uni-table-th-row {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.table--border {
border-right: 1px $border-color solid;
}
.uni-table-th-content {
display: flex;
align-items: center;
flex: 1;
}
.arrow-box {
}
.arrow {
display: block;
position: relative;
width: 10px;
height: 8px;
// border: 1px red solid;
left: 5px;
overflow: hidden;
cursor: pointer;
}
.down {
top: 3px;
::after {
content: '';
width: 8px;
height: 8px;
position: absolute;
left: 2px;
top: -5px;
transform: rotate(45deg);
background-color: #ccc;
}
&.active {
::after {
background-color: #007aff;
}
}
}
.up {
::after {
content: '';
width: 8px;
height: 8px;
position: absolute;
left: 2px;
top: 5px;
transform: rotate(45deg);
background-color: #ccc;
}
&.active {
::after {
background-color: #007aff;
}
}
}
</style>
<template>
<!-- #ifdef H5 -->
<thead class="uni-table-thead">
<tr class="uni-table-tr">
<th :rowspan="rowspan" colspan="1" class="checkbox" :class="{ 'tr-table--border': border }">
<table-checkbox :indeterminate="indeterminate" :checked="checked" @checkboxSelected="checkboxSelected"></table-checkbox>
</th>
</tr>
<slot></slot>
</thead>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="uni-table-thead"><slot></slot></view>
<!-- #endif -->
</template>
<script>
import tableCheckbox from '../uni-tr/table-checkbox.vue'
export default {
name: 'uniThead',
components: {
tableCheckbox
},
options: {
virtualHost: true
},
data() {
return {
border: false,
selection: false,
rowspan: 1,
indeterminate: false,
checked: false
}
},
created() {
this.root = this.getTable()
// #ifdef H5
this.root.theadChildren = this
// #endif
this.border = this.root.border
this.selection = this.root.type
},
methods: {
init(self) {
this.rowspan++
},
checkboxSelected(e) {
this.indeterminate = false
const backIndexData = this.root.backIndexData
const data = this.root.trChildren.filter(v => !v.disabled && v.keyValue)
if (backIndexData.length === data.length) {
this.checked = false
this.root.clearSelection()
} else {
this.checked = true
this.root.selectionAll()
}
},
/**
* 获取父元素实例
*/
getTable(name = 'uniTable') {
let parent = this.$parent
let parentName = parent.$options.name
while (parentName !== name) {
parent = parent.$parent
if (!parent) return false
parentName = parent.$options.name
}
return parent
}
}
}
</script>
<style lang="scss">
$border-color: #ebeef5;
.uni-table-thead {
display: table-header-group;
}
.uni-table-tr {
/* #ifndef APP-NVUE */
display: table-row;
transition: all 0.3s;
box-sizing: border-box;
/* #endif */
border: 1px red solid;
background-color: #fafafa;
}
.checkbox {
padding: 0 8px;
width: 26px;
padding-left: 12px;
/* #ifndef APP-NVUE */
display: table-cell;
vertical-align: middle;
/* #endif */
color: #333;
font-weight: 500;
border-bottom: 1px $border-color solid;
font-size: 14px;
// text-align: center;
}
.tr-table--border {
border-right: 1px $border-color solid;
}
/* #ifndef APP-NVUE */
.uni-table-tr {
::v-deep .uni-table-th {
&.table--border:last-child {
// border-right: none;
}
}
::v-deep .uni-table-td {
&.table--border:last-child {
// border-right: none;
}
}
}
/* #endif */
</style>
<template>
<view class="uni-table-checkbox" @click="selected">
<view v-if="!indeterminate" class="checkbox__inner" :class="{'is-checked':isChecked,'is-disable':isDisabled}">
<view class="checkbox__inner-icon"></view>
</view>
<view v-else class="checkbox__inner checkbox--indeterminate">
<view class="checkbox__inner-icon"></view>
</view>
</view>
</template>
<script>
export default {
name: 'TableCheckbox',
emits:['checkboxSelected'],
props: {
indeterminate: {
type: Boolean,
default: false
},
checked: {
type: [Boolean,String],
default: false
},
disabled: {
type: Boolean,
default: false
},
index: {
type: Number,
default: -1
},
cellData: {
type: Object,
default () {
return {}
}
}
},
watch:{
checked(newVal){
if(typeof this.checked === 'boolean'){
this.isChecked = newVal
}else{
this.isChecked = true
}
},
indeterminate(newVal){
this.isIndeterminate = newVal
}
},
data() {
return {
isChecked: false,
isDisabled: false,
isIndeterminate:false
}
},
created() {
if(typeof this.checked === 'boolean'){
this.isChecked = this.checked
}
this.isDisabled = this.disabled
},
methods: {
selected() {
if (this.isDisabled) return
this.isIndeterminate = false
this.isChecked = !this.isChecked
this.$emit('checkboxSelected', {
checked: this.isChecked,
data: this.cellData
})
}
}
}
</script>
<style lang="scss">
$checked-color: #007aff;
$border-color: #DCDFE6;
$disable:0.4;
.uni-table-checkbox {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: relative;
margin: 5px 0;
cursor: pointer;
// 多选样式
.checkbox__inner {
/* #ifndef APP-NVUE */
flex-shrink: 0;
box-sizing: border-box;
/* #endif */
position: relative;
width: 16px;
height: 16px;
border: 1px solid $border-color;
border-radius: 2px;
background-color: #fff;
z-index: 1;
.checkbox__inner-icon {
position: absolute;
/* #ifdef APP-NVUE */
top: 2px;
/* #endif */
/* #ifndef APP-NVUE */
top: 2px;
/* #endif */
left: 5px;
height: 7px;
width: 3px;
border: 1px solid #fff;
border-left: 0;
border-top: 0;
opacity: 0;
transform-origin: center;
transform: rotate(45deg);
box-sizing: content-box;
}
&.checkbox--indeterminate {
border-color: $checked-color;
background-color: $checked-color;
.checkbox__inner-icon {
position: absolute;
opacity: 1;
transform: rotate(0deg);
height: 2px;
top: 0;
bottom: 0;
margin: auto;
left: 0px;
right: 0px;
bottom: 0;
width: auto;
border: none;
border-radius: 2px;
transform: scale(0.5);
background-color: #fff;
}
}
&:hover{
border-color: $checked-color;
}
// 禁用
&.is-disable {
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
background-color: #F2F6FC;
border-color: $border-color;
}
// 选中
&.is-checked {
border-color: $checked-color;
background-color: $checked-color;
.checkbox__inner-icon {
opacity: 1;
transform: rotate(45deg);
}
// 选中禁用
&.is-disable {
opacity: $disable;
}
}
}
}
</style>
<template>
<!-- #ifdef H5 -->
<tr class="uni-table-tr">
<th v-if="selection === 'selection' && ishead" class="checkbox" :class="{ 'tr-table--border': border }">
<table-checkbox :checked="checked" :indeterminate="indeterminate" :disabled="disabled" @checkboxSelected="checkboxSelected"></table-checkbox>
</th>
<slot></slot>
<!-- <uni-th class="th-fixed">123</uni-th> -->
</tr>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="uni-table-tr">
<view v-if="selection === 'selection' " class="checkbox" :class="{ 'tr-table--border': border }">
<table-checkbox :checked="checked" :indeterminate="indeterminate" :disabled="disabled" @checkboxSelected="checkboxSelected"></table-checkbox>
</view>
<slot></slot>
</view>
<!-- #endif -->
</template>
<script>
import tableCheckbox from './table-checkbox.vue'
/**
* Tr 表格行组件
* @description 表格行组件 仅包含 th,td 组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=
*/
export default {
name: 'uniTr',
components: { tableCheckbox },
props: {
disabled: {
type: Boolean,
default: false
},
keyValue: {
type: [String, Number],
default: ''
}
},
options: {
virtualHost: true
},
data() {
return {
value: false,
border: false,
selection: false,
widthThArr: [],
ishead: true,
checked: false,
indeterminate:false
}
},
created() {
this.root = this.getTable()
this.head = this.getTable('uniThead')
if (this.head) {
this.ishead = false
this.head.init(this)
}
this.border = this.root.border
this.selection = this.root.type
this.root.trChildren.push(this)
const rowData = this.root.data.find(v => v[this.root.rowKey] === this.keyValue)
if(rowData){
this.rowData = rowData
}
this.root.isNodata()
},
mounted() {
if (this.widthThArr.length > 0) {
const selectionWidth = this.selection === 'selection' ? 50 : 0
this.root.minWidth = this.widthThArr.reduce((a, b) => Number(a) + Number(b)) + selectionWidth
}
},
// #ifndef VUE3
destroyed() {
const index = this.root.trChildren.findIndex(i => i === this)
this.root.trChildren.splice(index, 1)
this.root.isNodata()
},
// #endif
// #ifdef VUE3
unmounted() {
const index = this.root.trChildren.findIndex(i => i === this)
this.root.trChildren.splice(index, 1)
this.root.isNodata()
},
// #endif
methods: {
minWidthUpdate(width) {
this.widthThArr.push(width)
},
// 选中
checkboxSelected(e) {
let rootData = this.root.data.find(v => v[this.root.rowKey] === this.keyValue)
this.checked = e.checked
this.root.check(rootData||this, e.checked,rootData? this.keyValue:null)
},
change(e) {
this.root.trChildren.forEach(item => {
if (item === this) {
this.root.check(this, e.detail.value.length > 0 ? true : false)
}
})
},
/**
* 获取父元素实例
*/
getTable(name = 'uniTable') {
let parent = this.$parent
let parentName = parent.$options.name
while (parentName !== name) {
parent = parent.$parent
if (!parent) return false
parentName = parent.$options.name
}
return parent
}
}
}
</script>
<style lang="scss">
$border-color: #ebeef5;
.uni-table-tr {
/* #ifndef APP-NVUE */
display: table-row;
transition: all 0.3s;
box-sizing: border-box;
/* #endif */
}
.checkbox {
padding: 0 8px;
width: 26px;
padding-left: 12px;
/* #ifndef APP-NVUE */
display: table-cell;
vertical-align: middle;
/* #endif */
color: #333;
font-weight: 500;
border-bottom: 1px $border-color solid;
font-size: 14px;
// text-align: center;
}
.tr-table--border {
border-right: 1px $border-color solid;
}
/* #ifndef APP-NVUE */
.uni-table-tr {
::v-deep .uni-table-th {
&.table--border:last-child {
// border-right: none;
}
}
::v-deep .uni-table-td {
&.table--border:last-child {
// border-right: none;
}
}
}
/* #endif */
</style>
{
"filter-dropdown.reset": "Reset",
"filter-dropdown.search": "Search",
"filter-dropdown.submit": "Submit",
"filter-dropdown.filter": "Filter",
"filter-dropdown.gt": "Greater or equal to",
"filter-dropdown.lt": "Less than or equal to",
"filter-dropdown.date": "Date"
}
{
"filter-dropdown.reset": "Reiniciar",
"filter-dropdown.search": "Búsqueda",
"filter-dropdown.submit": "Entregar",
"filter-dropdown.filter": "Filtrar",
"filter-dropdown.gt": "Mayor o igual a",
"filter-dropdown.lt": "Menos que o igual a",
"filter-dropdown.date": "Fecha"
}
{
"filter-dropdown.reset": "Réinitialiser",
"filter-dropdown.search": "Chercher",
"filter-dropdown.submit": "Soumettre",
"filter-dropdown.filter": "Filtre",
"filter-dropdown.gt": "Supérieur ou égal à",
"filter-dropdown.lt": "Inférieur ou égal à",
"filter-dropdown.date": "Date"
}
import en from './en.json'
import es from './es.json'
import fr from './fr.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
es,
fr,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}
{
"filter-dropdown.reset": "重置",
"filter-dropdown.search": "搜索",
"filter-dropdown.submit": "确定",
"filter-dropdown.filter": "筛选",
"filter-dropdown.gt": "大于等于",
"filter-dropdown.lt": "小于等于",
"filter-dropdown.date": "日期范围"
}
{
"filter-dropdown.reset": "重置",
"filter-dropdown.search": "搜索",
"filter-dropdown.submit": "確定",
"filter-dropdown.filter": "篩選",
"filter-dropdown.gt": "大於等於",
"filter-dropdown.lt": "小於等於",
"filter-dropdown.date": "日期範圍"
}
{
"id": "uni-table",
"displayName": "uni-table 表格",
"version": "1.2.1",
"description": "表格组件,多用于展示多条结构类似的数据,如",
"keywords": [
"uni-ui",
"uniui",
"table",
"表格"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": ["uni-scss","uni-datetime-picker"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "n",
"QQ": "y"
},
"快应用": {
"华为": "n",
"联盟": "n"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
\ No newline at end of file
## Table 表单
> 组件名:``uni-table``,代码块: `uTable`。
用于展示多条结构类似的数据
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-table)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
## 1.2.18(2023-06-01)
更新
## 1.2.16(2023-05-10)
增加单元格点击事件
完善文档
增加示例
## 1.2.15(2022-08-25)
优化部分细节
## 1.2.14(2022-04-25)
修改vue 3 报错小程序Generated an empty chunk: "uni_modules/zb-table/components/zb-table/js/util"
## 1.2.13(2022-04-22)
增加图片宽度设置
## 1.2.12(2022-04-22)
修复pc端 滚动条占位问题
## 1.2.11(2022-04-22)
增加多图片展示
## 1.2.10(2022-04-19)
版本解决冲突
## 1.2.9(2022-04-19)
暂时去掉多级表头...有着某些问题,正在修复中
## 1.1.9(2022-04-19)
暂时去掉多级表头...有着某些问题,正在修复中
## 1.1.23(2022-04-19)
暂时去掉多级表头,有着某些问题,修复中。。。
## 1.1.22(2022-04-19)
暂时去掉多级表头,存在某些问题 ,正在修复中
## 1.1.21(2022-03-29)
优化数字问题
## 1.1.20(2022-03-29)
优化按钮,可以自定义按钮,自定义添加class
## 1.1.19(2022-03-28)
进行优化加载
## 1.1.18(2022-03-28)
修复pc端滚动条问题
## 1.1.17(2022-03-25)
修改 数据回显的时候,全选框没有选中效果
## 1.1.16(2022-03-25)
新增:table属性 cell-style 修改单元格样式
## 1.1.15(2022-03-23)
fix:支付宝小程序上拉加载e.detail 没有值导致上拉加载失效 ,已修复
## 1.1.14(2022-03-23)
fix: 支付宝小程序左右无法滑动的问题
## 1.1.13(2022-03-21)
fix:英文宽度自适应问题
## 1.1.12(2022-03-20)
修改自适应宽度问题
## 1.1.11(2022-03-19)
增加上拉加载功能
## 1.1.10(2022-03-18)
修改合计不更新问题
## 1.1.9(2022-03-16)
优化css 样式
## 1.1.8(2022-03-16)
增加表尾合计行
## 1.1.7(2022-03-15)
修改css样式
## 1.1.6(2022-03-14)
进行代码优化
## 1.1.5(2022-03-12)
更新一个操作按钮的时候 报错问题,进行代码优化
## 1.1.4(2022-03-12)
增加图片统一高度
## 1.1.3(2022-03-12)
增加图片地址 ,并且图片支持预览功能
## 1.1.2(2022-03-11)
新增默认 是否选中功能,进行优化
## 1.1.1(2022-03-10)
新增单击事件
## 1.1.0(2022-03-10)
- 增加单击事件
## 1.1.0(2022-03-10)
- 增加checkbox功能 ,进行优化
## 1.0.11(2022-03-09)
- 修改小程序中排序问题
## 1.0.10(2022-03-09)
- 做了兼容性处理
## 1.0.8(2022-03-09)
- 进行优化滚动防止 多次计算
## 1.0.7(2022-03-09)
- 修改一些问题 新增过滤器
## 1.0.6(2022-03-08)
- 修改样式 按钮自适应宽度
## 1.0.5(2022-03-08)
- 新增按钮 修改问题
## 1.0.4(2022-03-04)
- 增加空占位符"--"
## 1.0.3(2022-03-02)
- 新增表格斑马纹配置、列宽配置、表头显示配置
## 1.0.2(2022-03-02)
- 新增排序功能,优化样式
## 1.0.1(2022-03-01)
- 可以传入动态数据,可以对左边列表进行是否固定首列
## 1.0.0(2022-03-01)
- 初始化
\ No newline at end of file
<template>
<view class="uni-table-checkbox" @click.stop="selected">
<view v-if="!indeterminate" class="checkbox__inner" :class="{'is-checked':isChecked,'is-disable':isDisabled}">
<view class="checkbox__inner-icon"></view>
</view>
<view v-else class="checkbox__inner checkbox--indeterminate">
<view class="checkbox__inner-icon"></view>
</view>
</view>
</template>
<script>
export default {
name: 'TableCheckbox',
emits:['checkboxSelected'],
props: {
indeterminate: {
type: Boolean,
default: false
},
checked: {
type: [Boolean,String],
default: false
},
disabled: {
type: Boolean,
default: false
},
index: {
type: Number,
default: -1
},
cellData: {
type: Object,
default () {
return {}
}
}
},
watch:{
checked(newVal){
if(typeof this.checked === 'boolean'){
this.isChecked = newVal
}else{
this.isChecked = true
}
},
indeterminate(newVal){
this.isIndeterminate = newVal
}
},
data() {
return {
isChecked: false,
isDisabled: false,
isIndeterminate:false
}
},
created() {
if(typeof this.checked === 'boolean'){
this.isChecked = this.checked
}
this.isDisabled = this.disabled
},
methods: {
selected() {
if (this.isDisabled) return
this.isIndeterminate = false
this.isChecked = !this.isChecked
console.log('===',this.indeterminate,this.isChecked)
this.$emit('checkboxSelected', {
checked: this.isChecked,
data: this.cellData
})
}
}
}
</script>
<style lang="scss">
$checked-color: #007aff;
$border-color: #DCDFE6;
$disable:0.4;
.uni-table-checkbox {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: relative;
margin: 5px 0;
cursor: pointer;
// 多选样式
.checkbox__inner {
/* #ifndef APP-NVUE */
flex-shrink: 0;
box-sizing: border-box;
/* #endif */
position: relative;
width: 16px;
height: 16px;
border: 1px solid $border-color;
border-radius: 2px;
background-color: #fff;
z-index: 1;
.checkbox__inner-icon {
position: absolute;
/* #ifdef APP-NVUE */
top: 2px;
/* #endif */
/* #ifndef APP-NVUE */
top: 2px;
/* #endif */
left: 5px;
height: 7px;
width: 3px;
border: 1px solid #fff;
border-left: 0;
border-top: 0;
opacity: 0;
transform-origin: center;
transform: rotate(45deg);
box-sizing: content-box;
}
&.checkbox--indeterminate {
border-color: $checked-color;
background-color: $checked-color;
.checkbox__inner-icon {
position: absolute;
opacity: 1;
transform: rotate(0deg);
height: 2px;
top: 0;
bottom: 0;
margin: auto;
left: 0px;
right: 0px;
bottom: 0;
width: auto;
border: none;
border-radius: 2px;
transform: scale(0.5);
background-color: #fff;
}
}
&:hover{
border-color: $checked-color;
}
// 禁用
&.is-disable {
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
background-color: #F2F6FC;
border-color: $border-color;
}
// 选中
&.is-checked {
border-color: $checked-color;
background-color: $checked-color;
.checkbox__inner-icon {
opacity: 1;
transform: rotate(45deg);
}
// 选中禁用
&.is-disable {
opacity: $disable;
}
}
}
}
</style>
<template>
<view class="table-h5-footer top-header-uni" :style="{paddingRight:`${scrollbarSize}px`}">
<scroll-view class="zb-table-headers"
@scroll="handleFooterTableScrollLeft"
scroll-x="true"
scroll-y="false"
id="tableFooterHeaders"
scroll-anchoring="true"
:scroll-left="headerFooterTableLeft"
style="padding-bottom: 0px;
background: #fafafa;height: 100%">
<view class="zb-table-fixed" >
<view class="zb-table-thead" style="position: relative;" >
<view class="item-tr">
<view
class="item-th"
:style="{
width:`${item.width?item.width:'100'}px`,
flex:index===transColumns.length-1?1:'none',
minWidth:`${item.width?item.width:'100'}px`,
borderRight:`${border?'1px solid #e8e8e8':''}`,
borderTop:`${border?'1px solid #e8e8e8':''}`,
textAlign:item.align||'left'
}"
v-for="(item,index) in transColumns" :key="index">
{{ sums[index] }}
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import summary from '../js/summary.js'
export default {
name:'table-footer',
mixins:[summary],
}
</script>
<style lang="scss" scoped>
.table-h5-footer {
background: #fafafa;
/*每个页面公共css */
scroll-view ::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
//第二种
::-webkit-scrollbar{
display: none;
}
.item-tr{
display: flex;
}
.item-th{
padding-left: 8px;
line-height: 39px;
height: 40px;
//display: flex;
//align-items: center;
box-sizing: border-box;
flex-shrink: 0;
width: 100px;
padding-right: 20px;
word-break: keep-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: break-word;
border-bottom: 1px solid #e8e8e8;
}
}
</style>
<template>
<view class="zb-table-header" style="display: flex;" >
<view class="item-tr" >
<view class='item-td'
:style="{
width:`${item.width?item.width:'100'}px`,
borderRight:`${border?'1px solid #e8e8e8':''}`,
textAlign:item.align||'left'
}"
:key="`15255966555${index}`"
v-for="(item,index) in fixedLeftColumns">
<template >
{{sums[index]}}
</template>
</view>
</view>
</view>
</template>
<script>
import summary from '../js/summary.js'
export default {
mixins:[summary]
}
</script>
<style lang="scss" scoped>
.zb-table-header {
overflow: hidden;
background: #fafafa;
.item-th{
padding-left: 8px;
line-height: 39px;
height: 40px;
//display: flex;
//align-items: center;
box-sizing: border-box;
}
}
.item-tr{
display: flex;
box-sizing: border-box;
}
.item-td{
flex-shrink: 0;
width: 100px;
padding-left: 8px;
height: 40px;
line-height: 40px;
padding-right: 20px;
box-sizing: border-box;
word-break: keep-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: break-word;
border-bottom: 1px solid #e8e8e8;
background: rgb(250, 250, 250);
}
</style>
<template>
<view class="zb-table-footer" style="height: 40px;">
<view class="zb-table-fixed" >
<view class="zb-table-thead" style="position: relative;" >
<view class="item-tr">
<view
:class="['item-th',index <fixedLeftColumns.length&&'zb-stick-side']"
:style="{
left:`${item.left}px`,
width:`${item.width?item.width:'100'}px`,
flex:index===transColumns.length-1?1:'none',
minWidth:`${item.width?item.width:'100'}px`,
borderRight:`${border?'1px solid #e8e8e8':''}`,
borderTop:`${border?'1px solid #e8e8e8':''}`,
textAlign:item.align||'left'
}"
v-for="(item,index) in transColumns" :key="index">
<template>
{{ sums[index]||item.emptyString }}
</template>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import summary from '../js/summary.js'
export default {
mixins:[summary]
}
</script>
<style lang="scss" scoped>
.zb-table-footer {
background: #fafafa;
width: fit-content;
min-width: 100%;
position: sticky;
bottom: 0;
z-index: 2;
.item-tr{
display: flex;
min-width: 100%;
}
.item-th{
padding-left: 8px;
line-height: 39px;
height: 40px;
//display: flex;
//align-items: center;
box-sizing: border-box;
flex-shrink: 0;
width: 100px;
padding-right: 20px;
word-break: keep-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: break-word;
border-bottom: 1px solid #e8e8e8;
}
.zb-table-fixed{
min-width: 100%;
}
.zb-stick-side{
position: sticky;
bottom:0 ;
left: 0;
z-index: 2;
//border-right: solid 1rpx #dbdbdb;
box-sizing: border-box;
background: #fafafa;
//box-shadow: 6px 0 6px -4px #ccc;
}
}
</style>
<template >
<view class="zb-load-more">
<image :src="base64Flower" style="" class="loading-custom-image"></image>
<text>正在加载中...</text>
</view>
</template>
<script>
const base64Flower = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAKlBMVEVHcEzDw8Ovr6+pqamUlJTCwsKenp61tbWxsbGysrLNzc2bm5u5ubmjo6MpovhuAAAACnRSTlMA/P79/sHDhiZS0DxZowAABBBJREFUWMPtl89rE0EUx7ctTXatB3MI1SWnDbUKPUgXqh4ED8Uf7KUVSm3ooVSpSii0Fn/gD4j4o+APiEoVmos9FO2celiqZVgwgaKHPQiCCkv+F99kM7Ozm5kxq1dfD91k9pPve9/3ZjbRNHHok/mKli4eIPNgSuRObuN9SqSEzM20iGnm0yIbqCuV7NSSSIV7uyPM6JMBYdeTOanh/QihJYZsUCSby+VkMj2AvOt0rAeQAwqE3lfKMZVlQCZk1QOCKkkVPadITCfIRNKxfoJI5+0OIFtJx14CMSg1mRSDko7VAfksRQzEbGYqxOJcVTWMCH2I1/IACNW0PWU2M8cmAVHtnH5mM1VRWtwKZjOd5JbF6s1IbaYqaotjNlPHgDAnlAizubTR6ovMYn052g/U5qcmOpi0WL8xTS/3IfSet5m8MEr5ajjF5le6dq/OJpobrdY0t3i9QgefWrxW9/1BLhk0E9m8FeUMhhXal499iD0eQRfDF+ts/tttORRerfp+oV7f4xJj82iUYm1Yzod+ZQEAlS/8mMBwKebVmCVp1f0JLS6zKd17+iwRKTARVg2SHtz3iEbBH+Q+U28zW2Jiza8Tjb1YFoYZMsJyjDqp3M9XBQdSdPLFdxEpvOB37JrHcmR/y9+LgoTlCFGZEa2sc6d4PGlweEa2JSVPoVm+IfGG3ZL037iV9oH+P+Jxc4HGVflNq1M0pivao/EopO4b/ojVCP9GjmiXOeS0DOn1o/iiccT4ORnyvBGF3yUywkQajW4Ti0SGuiy/wVSg/L8w+X/8Q+hvUx8Xd90z4oV5a1i88MbFWHz0WZZ1UrTwBGPX3Rat9AFiXRMRjoMdIdJLEOt2h7jrYOzgOamKZSWSNspOS0X8SAqRYmxRL7sg4eLzYmNehcxh3uoyud/BH2Udux4ywxFTc1xC7Mgf4vMhc5S+kSH3Y7yj+qpwIWSoPTVCOOPVthGx9FbGqrwFw6wSFxJr+17zeKcztt3u+2roAEVgUjDd+AHGuxHy2rZHaa8JMkTHEeyi85ANPO9j9BVuBRD2FY5LDMo/Sz/2hReqGIs/KiFin+CsPsYO/yvM3jL2vE8EbX7/Bf8ejtr2GLN65bioAdgLd8Bis/mD5GmP2qeqyo2ZwQEOtAjRIDH7mBKpUcMoApbZJ5UIxkEwxyMZyMxW/uKFvHCFR3SSmerHyDNQ2dF4JG6zIMpBgLfjSF9x1D6smFcYnGApjmSLICO3ecCDWrQ48geba9DI3STy2i7ax6WIB62fSyIZIiO3GFQqSURp8wCo7GhJBGwuSovJBNjb7kT6FPVnIa9qJ2Ko+l9mefGIdinaMp0yC1URYiwsdfNE45EuA5Cx9EhalfvN5s+UyItm81vaB3p4joniN+SCP7Qc1hblAAAAAElFTkSuQmCC';
export default {
data(){
return{
base64Flower
}
}
}
</script>
<style lang="scss" scoped>
.zb-load-more {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
z-index: 999;
background: white;
display: flex;
height: 40px;
flex-shrink: 0;
align-items: center;
justify-content: center;
.loading-custom-image{
color: #a4a4a4;
margin-right: 8rpx;
width: 24px;
height: 24px;
/* #ifndef APP-NVUE */
animation: loading-circle 1s linear infinite;
/* #endif */
}
@keyframes loading-circle {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
}
</style>
export default {
props:{
scrollbarSize:{
type:Number,
default:0
},
fixedLeftColumns:{
type:Array,
default:()=>[]
},
data:{
type:Array,
default:()=>[]
},
transColumns:{
type:Array,
default:()=>[]
},
border:{
type:Boolean,
default:false
},
showSummary:{
type:Boolean,
default:false
},
summaryMethod:{
type:Function
},
sumText:{
type:String,
default:'合计'
},
headerFooterTableLeft:{
type:Number,
default:0
},
handleFooterTableScrollLeft:Function,
},
data(){
return{
sums:[]
}
},
watch:{
'data':{
deep:true,
immediate:true,
handler(newValue,oldValue){
let sums = [];
if (this.summaryMethod) {
sums = this.summaryMethod({ columns: this.transColumns, data: this.data });
} else {
this.transColumns.forEach((column, index) => {
if (index === 0) {
sums[index] = this.sumText;
return;
}
const values = this.data.map(item => Number(item[column.name]));
const precisions = [];
let notNumber = true;
values.forEach(value => {
if (!isNaN(value)) {
notNumber = false;
let decimal = ('' + value).split('.')[1];
precisions.push(decimal ? decimal.length : 0);
}
});
const precision = Math.max.apply(null, precisions);
if (!notNumber) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return parseFloat((prev + curr).toFixed(Math.min(precision, 20)));
} else {
return prev;
}
}, 0);
} else {
sums[index] = '';
}
});
}
this.sums = sums
},
}
}
}
/**
* 获取滚动条宽度
*/
let cached = undefined;
export const getScrollbarSize = fresh => {
// #ifdef H5
if (fresh || cached === undefined) {
let inner = document.createElement("div");
let innerStyle = inner.style;
innerStyle.width = "100%";
innerStyle.height = "200px";
let outer = document.createElement("div");
let outerStyle = outer.style;
outerStyle.position = "absolute";
outerStyle.top = 0;
outerStyle.left = 0;
outerStyle.pointerEvents = "none";
outerStyle.width = "200px";
outerStyle.height = "150px";
outerStyle.visibility = "hidden";
outer.appendChild(inner);
document.body.appendChild(outer);
// 设置子元素超出部分隐藏
outerStyle.overflow = "hidden";
let width1 = inner.offsetWidth;
// 设置子元素超出部分滚动
outer.style.overflow = "scroll";
let width2 = inner.offsetWidth;
if (width1 === width2) {
width2 = outer.clientWidth;
}
document.body.removeChild(outer);
cached = width1 - width2;
}
//#endif
return cached;
};
<template>
<!-- #ifdef H5 || APP-PLUS -->
<view :class="['zb-table','zb-table-fixed-header',!border&&(bodyTableLeft>50||headerTableLeft>50)&&'scroll-left-fixed']">
<view class="zb-table-content" style="flex: 1">
<view class="zb-table-scroll" style="height: 100%;">
<template v-if="showHeader">
<view class="zb-table-header top-header-uni"
>
<scroll-view class="zb-table-headers"
@scroll="handleTableScrollLeft"
scroll-x="true"
scroll-y="false"
id="tableHeaders"
scroll-anchoring="true"
:scroll-left="headerTableLeft"
style="
height: 100%">
<view class="zb-table-fixed" >
<view class="zb-table-thead" style="position: relative;" >
<view class="item-tr">
<view
@click.stop="sortAction(item,index)"
class="item-th"
:style="[{
width:`${item.width?item.width:'100'}px`,
flex:index===transColumns.length-1?1:'none',
minWidth:`${item.width?item.width:'100'}px`,
borderRight:`${border?'1px solid #e8e8e8':''}`,
borderTop:`${border?'1px solid #e8e8e8':''}`,
textAlign:item.align||'left'
},getHeaderCellStyle(item,index)]"
v-for="(item,index) in transColumns" :key="index">
<template v-if="item.type==='selection'">
<view class="checkbox-item">
<tableCheckbox
:indeterminate="indeterminate" :checked="checkedAll" @checkboxSelected="checkboxSelectedAll"></tableCheckbox>
</view>
</template>
<template v-else>
{{ item.label }}
<view class="sorter-table" v-if="item.sorter">
<view :class="['sorter-table-icon',item.sorterMode==='_asc'&&`sorting${item.sorterMode||''}`]"></view>
<view :class="['sorter-table-icon',item.sorterMode==='_desc'&&`sorting${item.sorterMode||''}`]"></view>
</view>
</template>
</view>
<view
v-if="scrollbarSize"
class="item-th "
:style="{
borderTop:`${border?'1px solid #e8e8e8':''}`,
padding:0,
width:`${scrollbarSize}px`,
}">
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<template v-if="!data.length">
<view class="no-data">暂无数据~~</view>
</template>
<scroll-view
class="zb-table-body" ref="tableBody" scroll-x="true" scroll-y="true" id="tableBody"
:lower-threshold="40"
:upper-threshold="10"
@scrolltolower="scrolltolower"
@scrolltoupper="(e)=>debounce(scrollToLeft)(e)"
@scroll="handleBodyScroll" :scroll-left="bodyTableLeft" :scroll-top="bodyScrollTop"
:style=" `height: calc(100% - ${showSummary?80:40}px)`" >
<view class="zb-table-fixed">
<view class="zb-table-tbody">
<view :class="['item-tr',highlight&&isHighlight(item,index)?'current-row':'']"
@click.stop="rowClick(item,index)"
v-for="(item,index) in transData" :key="item.key" >
<view
:style="[{
width:`${ite.width?ite.width:'100'}px`,
flex:i===transColumns.length-1?1:'none',
minWidth:`${ite.width?ite.width:'100'}px`,
borderRight:`${border?'1px solid #e8e8e8':''}`,
textAlign:ite.align||'left',
},cellStyle&&getCellStyle(item,ite,index,i)]"
@click="cellClick(item,index,ite)"
:class="['item-td',stripe?(index % 2) != 0?'odd':'even':'']"
v-for="(ite,i) in transColumns" :key="i">
<template v-if="ite.type==='operation'">
<view style="display: flex;align-items: center;height: 100%">
<view
v-for="ren,ind in permission(item,ite.renders,index)"
:key="ind"
@click.stop="$emit(ren.func,item,index)"
:style="{
display:'flex',
alignItems: 'center',
marginRight:ite.renders.length>1?'8px':'0'
}">
<template v-if="ren.type==='custom'">
<view :class="ren.class||''" style="cursor: pointer">
{{ren.name}}
</view>
</template>
<template v-else>
<button
:class="ren.class||''"
:type="ren.type||'primary'" :size="ren.size||'mini'">{{ren.name}}</button>
</template>
</view>
</view>
</template>
<template v-else-if="ite.type==='selection'">
<view class="checkbox-item">
<tableCheckbox @checkboxSelected="(e)=>checkboxSelected(e,item)" :cellData="item" :checked="item.checked"/>
</view>
</template>
<template v-else-if="ite.type==='index'">
{{index+1}}
</template>
<template v-else-if="ite.type==='img'">
<view class="checkbox-item">
<template v-if="item[ite.name]">
<image
@click.stop="previewImage(item,item[ite.name],iImage)"
v-for="iImageTem,iImage in imgs(item[ite.name])"
:show-menu-by-longpress="false"
:key="iImage"
:src="iImageTem" style="width: 40px;height:30px; " mode="aspectFit"></image>
</template>
<text v-else>{{ite.emptyString}}</text>
</view>
</template>
<template v-else>
<!-- {{ ite.filters?itemFilter(item,ite):(item[ite.name]==null||item[ite.name]==='')?ite.emptyString:item[ite.name] }}-->
{{ ite.filters?itemFilter(item,ite):formatterAction(item,ite,index,i) }}
</template>
</view>
</view>
</view>
</view>
</scroll-view>
<table-h5-summary
:scrollbarSize="scrollbarSize"
:data="data"
:handleFooterTableScrollLeft="handleFooterTableScrollLeft"
:headerFooterTableLeft="headerFooterTableLeft"
v-if="showSummary"
:showSummary="showSummary"
:transColumns="transColumns"
:border="border"
:summary-method="summaryMethod"
:sumText="sumText"
:fixedLeftColumns="fixedLeftColumns"/>
</view>
<view class="zb-table-fixed-left"
v-if="isFixedLeft"
:style=" {height: `calc(100% - ${scrollbarSize}px)`}"
>
<template v-if="showHeader">
<view class="zb-table-header" style="display: flex">
<view class="item-tr"
style=""
@click.stop="rowClick(item,index)"
v-for="(item,index) in fixedLeftColumns" :key="index">
<view
:style="{
width:`${item.width?item.width:'100'}px`,
borderRight:`${border?'1px solid #e8e8e8':''}`,
borderTop:`${border?'1px solid #e8e8e8':''}`,
textAlign:item.align||'left'
}"
@click.stop="sortAction(item,index)"
class="item-th"
>
<template v-if="item.type==='selection'">
<view class="checkbox-item">
<tableCheckbox
:indeterminate="indeterminate" :checked="checkedAll" @checkboxSelected="checkboxSelectedAll"></tableCheckbox>
</view>
</template>
<template v-else>
{{ item.label }}
<view class="sorter-table" v-if="item.sorter">
<view :class="['sorter-table-icon',item.sorterMode==='_asc'&&`sorting${item.sorterMode||''}`]"></view>
<view :class="['sorter-table-icon',item.sorterMode==='_desc'&&`sorting${item.sorterMode||''}`]"></view>
</view>
</template>
</view>
</view>
</view>
</template>
<scroll-view
scroll-y="true"
id="leftTableFixed"
:upper-threshold="15"
@scrolltoupper="(e)=>scrollToFixedLeft(e)"
@scroll="leftFixedScrollAction"
:scroll-top="leftFiexScrollTop"
class="zb-table-body-inner"
:style=" `height: calc(100% - ${showSummary?80:40}px)`">
<view class="zb-table-fixed">
<view class="zb-table-tbody">
<view
:class="['item-tr',stripe?(i % 2) != 0?'odd':'even':'',highlight&&isHighlight(ite,i)?'current-row':'']"
v-for="(ite,i) in transData"
@click.stop="rowClick(ite,i)"
:key="ite.key"
style="">
<view class='item-td'
@click="cellClick(ite,index,item)"
:style="[{
width:`${item.width?item.width:'100'}px`,
borderRight:`${border?'1px solid #e8e8e8':''}`,
textAlign:item.align||'left'
},cellStyle&&getCellStyle(ite,item,i,index)]"
:key="index"
v-for="(item,index) in fixedLeftColumns">
<template v-if="item.type==='selection'">
<view class="checkbox-item">
<tableCheckbox @checkboxSelected="(e)=>checkboxSelected(e,ite)" :cellData="ite" :checked="ite.checked"/>
</view>
</template>
<template v-else-if="item.type==='index'">
{{i+1}}
</template>
<template v-else>
{{ite[item.name]||item.emptyString}}
</template>
</view>
</view>
</view>
</view>
</scroll-view>
<table-side-summary
:scrollbarSize="scrollbarSize"
v-if="showSummary&&!(scrollbarSize>0)"
:data="data"
:showSummary="showSummary"
:transColumns="transColumns"
:border="border"
:summary-method="summaryMethod"
:sumText="sumText"
:fixedLeftColumns="fixedLeftColumns"/>
</view>
</view>
<zb-load-more v-if="isLoadMore&&!completeLoading"/>
</view>
<!-- #endif -->
<!-- #ifndef H5 || APP-PLUS -->
<view class="zb-table-applet">
<view class="zb-table-content" style="white-space: nowrap">
<scroll-view
<!-- #ifdef MP-ALIPAY -->
@scroll="scrollAlipay"
<!-- #endif -->
@scrolltolower="scrolltolower"
<!-- #ifdef MP-ALIPAY -->
style=" height: 100%;overflow-x:scroll"
<!-- #endif -->
<!-- #ifndef MP-ALIPAY -->
style=" height: 100%"
<!-- #endif -->
scroll-y="true"
scroll-x="true">
<view class="zb-table-scroll" >
<template v-if="showHeader">
<view class="zb-table-header top-header-uni" style="">
<view class="zb-table-fixed" >
<view class="zb-table-thead" style="position: relative;" >
<view class="item-tr">
<view
@click.stop="sortAction(item,index)"
:class="['item-th',index <fixedLeftColumns.length&&'zb-stick-side']"
:style="{
left:`${item.left}px`,
width:`${item.width?item.width:'100'}px`,
flex:index===transColumns.length-1?1:'none',
minWidth:`${item.width?item.width:'100'}px`,
borderRight:`${border?'1px solid #e8e8e8':''}`,
borderTop:`${border?'1px solid #e8e8e8':''}`,
textAlign:item.align||'left'
}"
v-for="(item,index) in transColumns" :key="index">
<template v-if="item.type==='selection'">
<view class="checkbox-item">
<tableCheckbox
:indeterminate="indeterminate" :checked="checkedAll" @checkboxSelected="checkboxSelectedAll"></tableCheckbox>
</view>
</template>
<template v-else>
{{ item.label||'' }}
<view class="sorter-table" v-if="item.sorter">
<view :class="['sorter-table-icon',item.sorterMode==='_asc'&&`sorting${item.sorterMode||''}`]"></view>
<view :class="['sorter-table-icon',item.sorterMode==='_desc'&&`sorting${item.sorterMode||''}`]"></view>
</view>
</template>
</view>
</view>
</view>
</view>
</view>
</template>
<template v-if="!data.length">
<view class="no-data">暂无数据~~</view>
</template>
<view class="zb-table-fixed">
<view class="zb-table-tbody">
<view :class="['item-tr',highlight&&isHighlight(item,index)?'current-row':'']"
@click.stop="rowClick(item,index)"
v-for="(item,index) in transData" :key="item.key" >
<view
:style="[{
left:`${ite.left}px`,
width:`${ite.width?ite.width:'100'}px`,
flex:i===transColumns.length-1?1:'none',
minWidth:`${ite.width?ite.width:'100'}px`,
borderRight:`${border?'1px solid #e8e8e8':''}`,
textAlign:ite.align||'left',
},getCellStyle(item,ite,index,i)]"
@click="cellClick(item,index,ite)"
:class="['item-td', i <fixedLeftColumns.length&&'zb-stick-side',stripe?(index % 2) != 0?'odd':'even':'']"
v-for="(ite,i) in transColumns" :key="i">
<template v-if="ite.type==='operation'">
<view style="display: flex;align-items: center;height: 100%">
<view
v-for="ren,ind in permission(item,ite.renders,index)"
:key="ind"
@click.stop="$emit(ren.func,item,index)"
:style="{
display:'flex',
alignItems: 'center',
marginRight:ite.renders.length>1?'8px':'0'
}">
<template v-if="ren.type==='custom'">
<view :class="ren.class||''" style="cursor: pointer">
{{ren.name}}
</view>
</template>
<template v-else>
<button
:class="ren.class||''"
:type="ren.type||'primary'" :size="ren.size||'mini'">{{ren.name}}</button>
</template>
</view>
</view>
</template>
<template v-else-if="ite.type==='selection'">
<view class="checkbox-item">
<tableCheckbox @checkboxSelected="(e)=>checkboxSelected(e,item)" :cellData="item" :checked="item.checked"/>
</view>
</template>
<template v-else-if="ite.type==='img'">
<template v-if="item[ite.name]">
<view class="checkbox-item" @click.stop>
<image
@click.stop="previewImage(iImageTem,item[ite.name],iImage)"
v-for="iImageTem,iImage in imgs(item[ite.name])"
:show-menu-by-longpress="false"
:key="iImage"
:src="iImageTem" style="width: 40px;height:30px; " mode="aspectFit"></image>
</view>
</template>
<text v-else>{{ite.emptyString}}</text>
</template>
<template v-else-if="ite.type==='index'">
{{index+1}}
</template>
<template v-else>
<!-- {{ ite.filters?itemFilter(item,ite):(item[ite.name]==null||item[ite.name]==='')?ite.emptyString:item[ite.name] }}-->
{{ ite.filters?itemFilter(item,ite):formatterAction(item,ite,index,i) }}
</template>
</view>
</view>
</view>
</view>
<table-summary
v-if="showSummary"
:data="data"
:showSummary="showSummary"
:fixedLeftColumns="fixedLeftColumns"
:transColumns="transColumns"
:border="border"
:summary-method="summaryMethod"
:sumText="sumText"
/>
</view>
</scroll-view>
</view>
<zb-load-more v-if="isLoadMore&&!completeLoading"/>
</view>
<!-- #endif -->
</template>
<script>
import TableCheckbox from './components/table-checkbox.vue'
import TableSummary from "./components/table-summary.vue";
import TableSideSummary from "./components/table-side-summary.vue";
import TableH5Summary from './components/table-h5-summary'
import ZbLoadMore from './components/zb-load-more'
// #ifdef H5
import {getScrollbarSize} from "./js/util";
// #endif
export default {
components:{
TableCheckbox,
TableSummary,
TableSideSummary,
TableH5Summary,
ZbLoadMore
},
props:{
highlight:{
type:Boolean,
default:false
},
itemDate:{
type:Object,
default:()=>{}
},
columns:{
type:Array,
default:()=>[]
},
showSummary:{
type:Boolean,
default:false
},
isShowLoadMore:{
type:Boolean,
default:false
},
data:{
type:Array,
default:()=>[]
},
sumText:{
type:String,
default:'合计'
},
showHeader:{
type:Boolean,
default:true
},
border:{
type:Boolean,
default:false
},
stripe:{
type:Boolean,
default:true
},
fit:{
type:Boolean,
default:false
},
rowKey:[String, Function],
summaryMethod:Function,
pullUpLoading:Function,
formatter:Function,
cellStyle:Function,
cellHeaderStyle:Function,
permissionBtn:Function,
},
computed:{
loadMoreHeight(){
return this.isLoadMore?40:0
},
fixedLeftColumns(){
let arr = []
for(let i=0;i<this.columns.length;i++){
let item = this.columns[i]
if(item.fixed){
arr.push(item)
}else {
break
}
}
return arr
},
imgs(){
return (item)=>{
return typeof item==='string'?[item]:item
}
},
itemfilters(){
return(item,ite)=>{
if(item[ite.name]==null){
return ite.emptyString
}
return item[ite.name]
}
},
scrollbarSize(){
// #ifdef H5
return getScrollbarSize()
// #endif
// #ifndef H5
return 0
// #endif
},
isFixedLeft(){
if(!this.columns.length){
return false
}
if(!this.data.length){
return false
}
let [firstArr] = this.columns
return !!firstArr.fixed;
},
transColumns(){
if(this.fit){
this.columns.forEach(column=>{
if(column.type==="operation"&&column.renders){
let str = ''
column.renders.map((item)=>{
str+=item.name
})
column.width = this.getTextWidth(str)+column.renders.length*40
}else if(column.type==="img"){
}else if(column.type==="selection"){
}else{
let arr = [this.getTextWidth(column.label)]
this.data.forEach(data=>{
let str = (data[column.name]+'')
if(str==='undefined'){
arr.push(30)
}else{
let width = this.getTextWidth(str)
arr.push(width)
}
})
column.width = Math.max(...arr)+20
}
})
}
let number = 0
this.columns.forEach((item,index)=>{
if(item.type==="operation"&&item.renders&&!item.width){
let str = ''
item.renders.map((item)=>{
str+=item.name
})
item.width = this.getTextWidth(str)+item.renders.length*40
}
if(item.type==="img"){
if(!item.width){
let arr = []
let widImg = this.getTextWidth(item.label)
this.data.forEach(data=>{
if(data[item.name]){
let urls = typeof data[item.name]==='string'?[data[item.name]]:data[item.name]
arr.push(urls.length)
}
item.width = Math.max(...arr)*40+widImg
})
}
}
if(item.fixed){
if(index===0){
item.left = 0
number+=item.width
}else {
item.left = number
number+=item.width
}
}
item.emptyString = item.emptyString||'--'
})
return this.columns
},
transData(){
let flag = this.columns.some(item=>item.type==='selection')
this.data.forEach((item,index)=>{
if(flag){
if(item.checked){
if(!this.selectArr.length){
this.selectArr.push(item)
}
}
}
if(this.rowKey){
if(typeof this.rowKey==='function'){
item.key = Object.freeze(this.rowKey(item))||Date.now()
}else {
item.key = Object.freeze(item[this.rowKey])||Date.now()
}
}else {
item.key = index
}
})
if(flag){
if(this.data.length){
let le = this.data.filter(item=>item.checked).length
if(le){
if(le===this.data.length){
this.checkedAll = true
}else {
this.indeterminate = true
}
}else {
this.checkedAll = false
this.indeterminate = false
this.selectArr = []
}
}else {
this.checkedAll = false
this.indeterminate = false
this.selectArr = []
}
}
return this.data
},
isHighlight(){
return (item,index)=>{
if(this.rowKey){
return item.key === this.currentRow['key']
}else{
return index === this.currentRowIndex
}
}
},
getHeaderCellStyle() {
return (column, columnIndex,childIndex)=>{
const cellStyle = this.cellHeaderStyle;
if(typeof cellStyle==='function'){
return cellStyle({ column, columnIndex})
}
return {}
}
},
getCellStyle() {
return (row, column, rowIndex, columnIndex)=>{
const cellStyle = this.cellStyle;
if(typeof cellStyle==='function'){
return cellStyle({row, column, rowIndex, columnIndex})
}
return {}
}
},
},
data() {
return {
button:[],
alipayScrollTop:0,
alipayScrollOldTop:0,
alipayFlag:false,
bodyTableLeft:0,
headerTableLeft:0,
lastScrollLeft:0,
isLoadMore:false,
headerFooterTableLeft:0,
leftFiexScrollTop:0,
bodyScrollTop:0,
currentDriver:null,
currentDriver1:null,
bodyTime:null,
currentRowIndex:null,
currentRow: {},
bodyTime1:null,
headerTime:null,
debounceTime:null,
operation:{},
completedFlag:false,
selectArr:[],
indeterminate:false,
checkedAll:false,
completeLoading:false,
aliTime:null,
}
},
created(){
},
mounted(){
// setTimeout(()=>{
// uni.createSelectorQuery().in(this).select(".top-header-uni").boundingClientRect( data => {
// console.log('data=======',data)
// //data 可以打印data输出看详细数据,有很多数据信息
// var left = data.width;//表示元素宽度
// }).exec();
// },1000)
},
beforeDestroy(){
this.aliTime&&clearTimeout(this.aliTime)
this.debounceTime&&clearTimeout(this.debounceTime)
this.bodyTime1&&clearTimeout(this.bodyTime1)
this.bodyTime&&clearTimeout(this.bodyTime)
this.selectArr = []
this.indeterminate = false
this.checkedAll = false
},
methods: {
clearSelection(){
this.transData.forEach(item=>{
item.checked = false
})
this.selectArr = []
this.indeterminate = false
this.checkedAll = false
},
formatterAction(row,column,rowIndex,columnIndex){
if(column.formatter&&typeof this.formatter==='function'){
return this.formatter(row,column,rowIndex,columnIndex)
}
return (row[column.name]==null||row[column.name]==='')?column.emptyString:row[column.name]
},
permission(item,renders,index){
if(this.permissionBtn&&typeof this.permissionBtn==='function'){
return this.permissionBtn(item,renders,index)
}
return renders
},
pullUpCompleteLoading(type){
this.isLoadMore = false
if(type==='ok'){
this.completeLoading = true
}
},
scrollAlipay(e){
if(!this.alipayScrollOldTop){
this.alipayScrollOldTop = e.detail.scrollTop
}
this.aliTime&&clearTimeout(this.aliTime)
this.aliTime = setTimeout(()=>{
if(this.alipayFlag&&e.detail.scrollTop>this.alipayScrollOldTop){
this.pullLoad()
}
this.alipayFlag = false
this.alipayScrollOldTop = null
},500)
},
pullLoad(){
if(this.isShowLoadMore){
this.isLoadMore = true
this.$emit('pullUpLoading')
let that = this
this.pullUpLoading&&this.pullUpLoading.call(this.$parent.$parent, (type)=>{
that.isLoadMore = false
if(type==='ok'){
that.completeLoading=true
}
})
}
},
scrolltolower(e){
this.alipayFlag = true
if(e.detail.direction==='bottom'){
this.pullLoad()
}
// this.pullUpLoading.call(this.$parent)
},
previewImage(item,url,current){
let urls = typeof url==='string'?[url]:url
uni.previewImage({
current,
urls:urls
})
},
resetHighlight(){
this.currentRowIndex = null
this.currentRow = {}
},
cellClick(row,index,column){
this.$emit('cellClick',row,index,column)
},
rowClick(row,index){
if(this.highlight){
this.currentRowIndex = index
this.currentRow = row
this.$emit('currentChange',row,index)
}
this.$emit('rowClick',row,index)
},
checkboxSelectedAll(e){
this.indeterminate = false
if(e.checked){
this.selectArr = []
this.checkedAll = true
this.data.forEach(item=>{
// this.$set(item,'checked',true)
item.checked = true
this.selectArr.push(item)
})
}else{
this.checkedAll = false
this.data.forEach(item=>{
this.$set(item,'checked',false)
})
this.selectArr = []
}
// #ifndef H5 || APP-PLUS
this.$forceUpdate()
// #endif
this.$emit('toggleAllSelection',e.checked,this.selectArr)
},
checkboxSelected(e,item){
// #ifdef H5 || APP-PLUS
this.$set(item,'checked',e.checked)
// #endif
// #ifndef H5 || APP-PLUS
this.data.forEach(item=>{
if(item.key===e.data.key){
item.checked = e.checked
}
})
// #endif
item.checked = e.checked
e.data.checked = e.checked
if(e.checked){
this.selectArr.push(e.data)
}else{
this.selectArr = this.selectArr.filter(item=>item.key!==e.data.key)
}
if(this.selectArr.length===this.transData.length){
this.indeterminate = false
this.checkedAll = true
}else{
this.indeterminate = true
this.checkedAll = false
}
if(!this.selectArr.length){
this.checkedAll = false
this.indeterminate = false
}
// #ifndef H5 || APP-PLUS
this.$forceUpdate()
// #endif
this.$emit('toggleRowSelection',e.checked,this.selectArr)
},
itemFilter(item,ite){
if(ite.filters&&ite.name){
let key = item[ite.name]
return ite.filters[key]||''
}
return item[ite.name]||ite.emptyString
},
// 默认字体为微软雅黑 Microsoft YaHei,字体大小为 14px
getTextWidth(str) {
if(str.length<3){
return 40
}
let regx = /^[0-9]+.?[0-9]*$/
let flexWidth = 0
for (const char of str) {
if ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z')) {
// 如果是英文字符,为字符分配8个单位宽度
flexWidth += 10
} else if (char >= '\u4e00' && char <= '\u9fa5') {
// 如果是中文字符,为字符分配15个单位宽度
flexWidth += 15
} else if(regx.test(char)){
flexWidth += 9
}else {
// 其他种类字符,为字符分配8个单位宽度
flexWidth += 7
}
}
return flexWidth
},
width(item){
return `${item.width?item.width:'100'}px`
},
showStripe(index){
if(this.currentDriver)return
if(this.stripe){
return (index % 2) != 0?'odd':'even'
}else{
return ''
}
},
//验证字符串是否是数字
checkNumber(theObj) {
var reg = /^[0-9]+.?[0-9]*$/;
if (reg.test(theObj)) {
return true;
}
return false;
},
isDate(data){
if(isNaN(data)&&!isNaN(Date.parse(data))){
return true
}
return false
},
sortAction(item,index){
if(!item.sorter){return false}
this.$set(item,'sorterMode',item.sorterMode==='_asc'?'_desc':'_asc')
if(item.sorter==='custom'){
this.$emit('sort-change',item,item.sorterMode.replace('_',''),index)
}else {
this.sortData(item)
}
// #ifndef H5 || APP-PLUS
this.$forceUpdate()
// #endif
},
sortData(item){
let key = item.name
if(item.sorterMode==='_asc'){
this.data.sort((a,b)=>{
if(this.checkNumber(a[key])){
return a[key]-b[key]
}
if(this.isDate(a[key])){
let a1 = new Date(a[key]).getTime()
let b1 = new Date(b[key]).getTime()
return a1-b1
}
})
}else {
this.data.sort((a,b)=>{
if(this.checkNumber(a[key])){
return b[key]-a[key]
}
if(this.isDate(a[key])){
let a1 = new Date(a[key]).getTime()
let b1 = new Date(b[key]).getTime()
return b1-a1
}
})
}
},
throttle(method,delay=60){
let time = null
return (...args)=>{
if(!time){
time = setTimeout(()=>{
method(...args)
time = null;
},delay)
}
}
},
debounce(method,delay=1000){
return (...args)=>{
this.debounceTime&&clearTimeout(this.debounceTime)
this.debounceTime = setTimeout(()=>{
method(...args)
},delay)
}
},
handleBodyScroll(e){
if(this.currentDriver&&this.currentDriver!==e.currentTarget.id)return
this.currentDriver = e.currentTarget.id
this.headerTableLeft = e.detail.scrollLeft
this.headerFooterTableLeft = e.detail.scrollLeft
this.leftFiexScrollTop = e.detail.scrollTop
this.bodyTime&&clearTimeout(this.bodyTime)
this.bodyTime = setTimeout(()=>{
this.currentDriver=null
},200)
},
leftFixedScrollAction(e){
if(this.currentDriver&&this.currentDriver!==e.currentTarget.id)return
this.currentDriver = e.currentTarget.id
this.bodyScrollTop = e.detail.scrollTop
this.bodyTime&&clearTimeout(this.bodyTime)
this.bodyTime = setTimeout(()=>{
this.currentDriver=null
},200)
},
scrollToLeft(e){
if(this.currentDriver1&&this.currentDriver1!==e.currentTarget.id)return
this.currentDriver1 = e.currentTarget.id
if(e.detail.direction==='left'&&this.headerTableLeft<10){
this.headerTableLeft = 0
}else if(e.detail.direction==='top'&&this.leftFiexScrollTop<10){
this.leftFiexScrollTop = 0
}
this.bodyTime&&clearTimeout(this.bodyTime)
this.bodyTime = setTimeout(()=>{
this.currentDriver1=null
},200)
},
scrollToFixedLeft(e){
if(this.currentDriver1&&this.currentDriver1!==e.currentTarget.id)return
this.currentDriver1 = e.currentTarget.id
if(e.detail.direction==='top'&&this.bodyScrollTop<10){
this.bodyScrollTop = 0
}
this.bodyTime&&clearTimeout(this.bodyTime)
this.bodyTime = setTimeout(()=>{
this.currentDriver1=null
},200)
},
handleTableScrollLeft(e,type){
if(this.currentDriver&&this.currentDriver!==e.currentTarget.id)return
this.currentDriver = e.currentTarget.id
this.bodyTableLeft = e.detail.scrollLeft
this.headerFooterTableLeft = e.detail.scrollLeft
this.bodyTime&&clearTimeout(this.bodyTime)
this.bodyTime = setTimeout(()=>{
this.currentDriver=null
},200)
},
handleFooterTableScrollLeft(e){
if(this.currentDriver&&this.currentDriver!==e.currentTarget.id)return
this.currentDriver = e.currentTarget.id
this.bodyTableLeft = e.detail.scrollLeft
this.headerTableLeft = e.detail.scrollLeft
this.bodyTime&&clearTimeout(this.bodyTime)
this.bodyTime = setTimeout(()=>{
this.currentDriver=null
},200)
}
}
}
</script>
<style lang="scss">
.zb-table-fixed-left{
/*去除左边滚动条 */
scroll-view ::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
}
.zb-table-header{
///*去除头部滚动条 */
scroll-view ::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
}
</style>
<style lang="scss" scoped>
.sorter-table{
position: absolute;
right: 6px;
top:50%;
transform:translateY(-50%);
.sorter-table-icon{
width: 0;
height: 0;
color: #dcdcdc;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
}
.sorter-table-icon:first-child{
border-bottom: 5px solid currentColor;
}
.sorter-table-icon:last-child{
margin-top: 1.5px;
border-top: 5px solid currentColor;
}
.sorting_desc{
color: #2979ff;
}
.sorting_asc{
color: #2979ff;
}
}
.checkbox-item{
display: flex;align-items: center;justify-content: center;width: 100%;height: 100%
}
.no-data{
width: 100%;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px solid #e8e8e8;
}
.item-th{
position: relative;
flex-shrink: 0;
width: 100px;
overflow-wrap: break-word;
border-bottom: 1px solid #e8e8e8;
transition: background 0.3s;
padding-right: 20px;
word-break:keep-all; /* 不换行 */
white-space:nowrap; /* 不换行 */
overflow:hidden; /* 内容超出宽度时隐藏超出部分的内容 */
text-overflow:ellipsis; /* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用。*/
overflow-wrap: break-word;
}
.zb-table{
height: 100%;
overflow: hidden;
width: 100%;
display: flex;
flex-direction: column;
font-size: 12px;
position: relative;
.zb-table-content{
//height: 100%;
//flex: 1;
position: relative;
overflow: hidden;
}
.zb-table-fixed{
min-width: 100%;
}
.zb-table-body{
position: relative;
background: #fff;
transition: opacity 0.3s;
}
.item-tr{
display: flex;
//height: 41px;
}
.item-td{
flex-shrink: 0;
width: 100px;
padding-left: 8px;
height: 40px;
line-height: 40px;
padding-right: 20px;
box-sizing: border-box;
word-break:keep-all; /* 不换行 */
white-space:nowrap; /* 不换行 */
overflow:hidden; /* 内容超出宽度时隐藏超出部分的内容 */
text-overflow:ellipsis; /* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用。*/
overflow-wrap: break-word;
border-bottom: 1px solid #e8e8e8;
//transition: background 0.3s;
}
.zb-table-fixed-left .zb-table-header{
overflow-y: hidden;
}
.zb-table-header {
overflow: hidden;
background: #fafafa;
.item-th{
padding-left: 8px;
line-height: 39px;
height: 40px;
//display: flex;
//align-items: center;
box-sizing: border-box;
}
}
.zb-table-fixed-left .zb-table-fixed{
background: #fff;
}
.zb-table-fixed-right .zb-table-fixed{
background: #fff;
}
.zb-table-body-inner{
height: 100%;
// overflow: scroll;
}
.zb-table-fixed-left{
position: absolute;
top: 0;
z-index: 1;
overflow: hidden;
border-radius: 0;
height: 100%;
//transition: box-shadow 0.3s ease;
}
.odd{
background-color:rgba(249,249,249,0.6);
//height: 100%;
width: 100%;
}
.even{
background-color:white ;
//height: 100%;
width: 100%;
}
}
.scroll-left-fixed{
.zb-table-fixed-left {
left: 0;
box-shadow: 6px 0 6px -4px #ccc;
}
}
.zb-table-applet{
height: 100%;
//overflow: hidden;
width: 100%;
position: relative;
display: flex;
flex-direction: column;
font-size: 12px;
.zb-table-content{
//height: 100%;
flex: 1;
overflow: hidden;
position: relative;
}
.zb-table-fixed{
min-width: 100%;
width: fit-content;
}
.zb-table-body{
position: relative;
background: #fff;
transition: opacity 0.3s;
}
.item-tr{
display: flex;
//height: 41px;
}
.item-td{
flex-shrink: 0;
width: 100px;
padding-left: 8px;
height: 40px;
line-height: 40px;
padding-right:20px;
box-sizing: border-box;
word-break:keep-all; /* 不换行 */
white-space:nowrap; /* 不换行 */
overflow:hidden; /* 内容超出宽度时隐藏超出部分的内容 */
text-overflow:ellipsis; /* 当对象内文本溢出时显示省略标记(...) ;需与overflow:hidden;一起使用。*/
overflow-wrap: break-word;
border-bottom: 1px solid #e8e8e8;
//transition: background 0.3s;
}
.zb-table-header {
//overflow: hidden;
position: sticky;
top: 0;
z-index: 2;
//width: fit-content;
.item-th{
padding-left: 8px;
line-height: 39px;
height: 40px;
box-sizing: border-box;
background: #fafafa;
}
.zb-stick-side{
position: sticky;
top: 0;
left: 0;
z-index: 2;
//border-right: solid 1rpx #dbdbdb;
box-sizing: border-box;
background: #fafafa;
//box-shadow: 6px 0 6px -4px #ccc;
}
}
.zb-table-fixed-left .zb-table-fixed{
background: #fff;
}
.zb-table-fixed-right .zb-table-fixed{
background: #fff;
}
.zb-table-fixed-header .zb-table-body-inner{
height: 100%;
// overflow: scroll;
}
.zb-table-fixed-left{
position: absolute;
top: 0;
z-index: 1;
overflow: hidden;
border-radius: 0;
height: 100%;
//transition: box-shadow 0.3s ease;
}
.scroll-left-fixed{
.zb-table-fixed-left {
left: 0;
box-shadow: 6px 0 6px -4px #ccc;
}
}
.odd{
background-color:rgba(249,249,249,0.6);
//height: 100%;
width: 100%;
}
.even{
background-color:white ;
//height: 100%;
width: 100%;
}
.zb-table-tbody {
.zb-stick-side{
position: sticky;
left: 0;
z-index: 1;
box-sizing: border-box;
background:white;
//box-shadow: 6px 0 6px -2px #ccc;
}
.odd{
background:#f9f9f9;
//height: 100%;
width: 100%;
}
.even{
background:white ;
//height: 100%;
width: 100%;
}
}
.current-row{
.item-td{
background-color: #ecf5ff;
}
}
}
.current-row{
.item-td{
background-color: #ecf5ff;
}
}
.zb-table-header{
height: 40px;
}
.scrollPosition{
position: absolute;right: 0;top: 0;height: 100%;background: red;
z-index: 999;
}
</style>
{
"id": "zb-table",
"displayName": "zb-table(多功能表格)",
"version": "1.2.18",
"description": "表格组件 支持固定表头和首列、上拉加载更多、及固定多列,表格自适应内容,排序,多选checkbox、可点击删除,编辑、合计功能,兼容多端",
"keywords": [
"table",
"表格",
"固定表头、固定首列、多列",
"上拉加载更多、",
"排序、自适应列宽、多选checkbox、编辑、删除、按钮、合计"
],
"repository": "https://github.com/zouzhibin/zb-table.git",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
}
}
}
}
}
\ No newline at end of file
## 介绍
基于uni-app开发的一个普通的表格组件,功能有固定首列和表头、排序、操作按钮、
table 表格 固定表头、固定首列、多列 上拉加载更多、 排序、自适应列宽、多选checkbox、编辑、删除、按钮、合计
已用于生产环境
微信=》 19550102670 拉进群
## -- github 第一时间会更新到github,永远保持最新,有啥想法的可以提PR,共同开发 [地址](https://github.com/zouzhibin/zb-ui)
## 友情链接
### 在线预览点击 —— [企业级、通用型中后台前端解决方案 ](https://yuanzbz.gitee.io/vue-admin-perfect/#/login?redirect=/home)
### vue-admin-perfect —— [企业级、通用型中后台前端解决方案(基于vue3.0+TS+Element-Plus 最新版,同时支持电脑,手机,平板)](https://github.com/zouzhibin/vue-admin-perfect)
## table 属性
| 参数 | 说明 | 类型 | 可选值 | 默认值 |是否必须|
| ------ | ------ | ------ | ------ | ------ |------ |
| data | 显示的数据 | array |-- | -- |必须 |
| column | 显示的列数据 | array |-- | -- |必须 |
| stripe | 是否为斑马纹 table| boolean | - |false | 否 |
| fit | 列的宽度是否自撑开 | boolean |true,false | false |否 |
| show-header | 是否显示表头 | boolean |true,false | true |否 |
| cell-style | 单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有单元格设置一样的 Style。 | Function({row, column, rowIndex, columnIndex})/Object |-- | -- |否 |
| cell-header-style | 头部单元格的 style 的回调方法,也可以使用一个固定的 Object 为所有单元格设置一样的 Style。 | Function({ column, columnIndex})/Object |-- | -- |否 |
| formatter | colomn =》formatter 必须设置为true,才有作用,进行格式化数据,进行数据的转换 | Function({row, column, rowIndex, columnIndex})/Object |-- | -- |否 |
| border | 是否带有纵向边框 | boolean |true,false | true |否 |
| highlight | 是否要高亮当前行 | boolean |true,false | false |否 |
| show-summary | 是否在表尾显示合计行 | boolean |true,false | false |否 |
| sum-text | 合计行第一列的文本 | String |- | 合计 |否 |
| summary-method | 自定义的合计计算方法 | Function({ columns, data }) |- | - |否 |
| permissionBtn | 是否动态控制按钮的显示隐藏 | Function({ row, renders,index }) |- | - |否 |
| isShowLoadMore | 是否开启上拉加载 | boolean |true,false | false |否 |
| pullUpLoading | 开启上拉加载后的返回函数,接收参数done是函数,done(type),type为空代表还有数据,继续开启上拉加载,type='ok',代表结束上拉加载 | Function(done) |-- | -- |否 |
```
关闭上拉加载的方式1:pullUpLoading((done)=>{
done(type)
})
done 接收参数为 type ,type为空代表还有数据,可以继续加载,无数据的时候传入 'ok'代表结束
```
## table 事件
| 参数 | 说明 | 类型 | 可选值 | 默认值 |是否必须|
| ------ | ------ | ------ |--------------------------| ------ |------ |
| 事件名自定义 | 取决于type类型为operation的 renders参数里面 func 的参数名 | Function | (row,index)=>{} | -- |否 |
| sort-change | 取决于type类型为operation的 renders参数里面 func 的参数名 | Function | (column,model,index)=>{} | -- |否 |
| currentChange | 当表格的当前行发生变化的时候会触发该事件,如果要高亮当前行,请打开表格的 highlight属性,this.$refs.table.resetHighlight()清除选中 | Function | (row,index)=>{} | -- |否 |
| toggleRowSelection | 用于多选表格,切换某一行的选中状态,第一个参数代表选中状态,参数二代表选中的对象 | Function | (selected ,array)=>{} | -- |否 |
| toggleAllSelection | 用于多选表格,切换所有行的选中状态 ,第一个参数代表选中状态,参数二代表选中的对象| Function | (selected ,array)=>{} | -- |否 |
| rowClick | 单击某行 ,第一个参数代表选中对象,参数二代表选中的index| Function | (row ,index)=>{} | -- |否 |
| cellClick | 单击单元格 ,当某个单元格被点击时会触发该事件| Function | (row ,index,column)=>{} | -- |否 |
| pullUpLoading | 开启上拉加载后的返回函数,无参数| Function | -- |-- |否 |
```
关闭上拉加载的方式2:this.$refs.zbTable.pullUpCompleteLoading('ok')
接收参数为 type ,type为空代表还有数据,可以继续加载,无数据的时候传入 'ok'代表结束
```
## data 属性
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ------ | ------ | ------ | ------ | ------ |
| checked | 是否被勾选 | boolean |true,false | 无 |
## column 属性
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| ------ | ------ | ------ | ------ | ------ |
| name | 属性值 | string |-- | 无 |
| label | 显示的标题 | string |-- | 无 |
| width | 列的宽度 | number |-- | 100 |
| fixed | 列是否固定在左侧,true 表示固定在左侧 | boolean |true,false | true |
| sorter | 排序,当设置为custom的时候代表自定义排序,不会再触发默认排序,会触发table事件@sort-change,可以通过接口来进行排序 | boolean |true,false,'custom' | false |
| emptyString | 当值为空的时候默认显示的值 | string | | -- |
| filters | 对象过滤的选项,对象格式,对象中的元素需要有 key 和 value 属性。 | Object | {key:value} | -- |
| align | 对齐方式 | String | left/center/right | left |
| type | 为 operation 的时候代表为操作按钮,img的时候代表图片地址,index代表序列号 | string | operation,img,index | -- |
| renders | type 为operation的时候 必传 | Array | {name:'名字',func:"父元素接收事件名",type:"按钮的类型",size:"大小"} | -- |
```
type 为 operation 的时候代表为操作按钮
renders 代表传入的按钮 Array =>[
{
name:'编辑',
class:"", // 添加class
type:'primary',代表按钮的类型 type 为custom的时候自定义按钮 其他类型取决于uniapp buttom组件按钮
size:'mini',代表按钮的大小
func:'edit' // func 代表操作按钮点击的事件名字 父元素接收的事件 父元素 @edit
例如:// <zb-table @edit=""/>
}
]
```
## 示例
```
<zb-table
:show-header="true"
:columns="column"
:stripe="true"
:fit="false"
show-summary
sum-text="合计"
@rowClick="rowClick"
:summary-method="getSummaries"
@toggleRowSelection="toggleRowSelection"
@toggleAllSelection="toggleAllSelection"
:border="true"
@edit="buttonEdit"
@dele="dele"
:data="data"></zb-table>
```
## 数据格式
```
column:[
{ type:'selection', fixed:true,width:50 },
{ name: 'name', label: '姓名',fixed:false,width:80,emptyString:'--' },
{ name: 'age', label: '年纪',sorter:false,align:'right', },
{ name: 'sex', label: '性别',filters:{0:'男',1:'女'}},
{ name: 'img', label: '图片',type:"img" },
{ name: 'address', label: '地址' },
{ name: 'date', label: '日期',sorter:true },
{ name: 'province', label: '省份' },
{ name: 'city', label: '城市' },
{ name: 'zip', label: '邮编' },
{ name: 'operation', type:'operation',label: '操作',renders:[
{
name:'编辑',
func:'edit' // func 代表子元素点击的事件 父元素接收的事件 父元素 @edit
},
{
name:'删除',
type:'warn',
func:"dele"
},
]},
],
data:[
{
date: '2016-05-02',
name: '王小虎1',
province: '上海',
sex:'男',
age:'18',
img:"https://img1.baidu.com/it/u=300787145,1214060415&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500",
city: '普陀区',
address: '上海市普',
zip: 200333
},
{
date: '2016-05-02',
name: '王小虎2',
province: '上海',
sex:'男',
age:'18',
city: '普陀区',
address: '上海市普',
zip: 200333
},
{
date: '2016-05-02',
name: '王小虎3',
province: '上海',
sex:'男',
age:'18',
city: '普陀区',
address: '上海市普',
zip: 200333
},
{
date: '2016-05-02',
name: '王小虎4',
province: '上海',
sex:'男',
age:'18',
city: '普陀区',
address: '上海市普',
zip: 200333
},
{
date: '2016-05-02',
name: '王小虎5',
province: '上海',
sex:'男',
age:'18',
city: '普陀区',
address: '上海市普',
zip: 200333
}
]
```
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment