|
|
@@ -16,101 +16,107 @@
|
|
|
</view>
|
|
|
</uni-th>
|
|
|
</uni-tr>
|
|
|
-
|
|
|
-
|
|
|
+ <view v-if="loading" class="empty-state">
|
|
|
+ <uni-loading />
|
|
|
+ </view>
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <view v-if="!loading && tableData.length === 0" class="empty-state">
|
|
|
+
|
|
|
+ <cwg-empty-state v-if="!loading && tableData.length === 0" />
|
|
|
+ </view>
|
|
|
<!-- 表格主体 -->
|
|
|
+ <template v-else>
|
|
|
<template v-for="(row, rowIndex) in tableData" :key="rowIndex">
|
|
|
- <uni-tr>
|
|
|
- <!-- 数据列:根据设备类型动态渲染 -->
|
|
|
- <uni-td v-for="column in displayColumns" :key="column.prop" :align="column.align || 'center'"
|
|
|
+ <uni-tr>
|
|
|
+ <!-- 数据列:根据设备类型动态渲染 -->
|
|
|
+ <uni-td v-for="column in displayColumns" :key="column.prop" :align="column.align || 'center'"
|
|
|
:class="getCellClass(column, row)" :style="getCellStyle(column, row)"
|
|
|
@click="openRowDetail(row)">
|
|
|
- <template v-if="column.slot">
|
|
|
- <slot :name="column.slot" :row="row" :column="column" :index="rowIndex">
|
|
|
- {{ row[column.prop] }}
|
|
|
- </slot>
|
|
|
- </template>
|
|
|
- <uni-tag v-else-if="column.type === 'tag'" :text="formatTagText(row[column.prop], column)"
|
|
|
- :type="formatTagType(row[column.prop], column)" size="small" />
|
|
|
- <view v-else-if="column.type === 'file'">
|
|
|
- <cwg-file :path="row[column.prop]" />
|
|
|
- </view>
|
|
|
- <view v-else-if="column.type === 'note'">
|
|
|
- <text>{{ getNoteText(row, locale, userStore) }}</text>
|
|
|
- </view>
|
|
|
- <view v-else-if="column.type === 'action'" class="action-wrapper">
|
|
|
- <cwg-droplist v-if="getComputedMenuList(column.menuList, row).length > 0"
|
|
|
- :menuList="getComputedMenuList(column.menuList, row)" placement="bottom-end"
|
|
|
- @menuClick="(payload) => handleActionClick(payload, row)">
|
|
|
- <view class="action-trigger">
|
|
|
- <cwg-icon name="crm-ellipsis" :size="24" />
|
|
|
- </view>
|
|
|
- </cwg-droplist>
|
|
|
- </view>
|
|
|
- <template v-else-if="column.type === 'more'">
|
|
|
- <cwg-icon v-if="isMobile" name="crm-chevron-down" class="crm-chevron-down" :size="16" />
|
|
|
- </template>
|
|
|
-
|
|
|
- <template v-else>
|
|
|
- {{ formatCellValue(row[column.prop], column, row) }}
|
|
|
- </template>
|
|
|
- </uni-td>
|
|
|
- </uni-tr>
|
|
|
- <!-- 桌面端展开行(仅当非移动端且行展开时显示) -->
|
|
|
- <uni-tr v-if="!isMobile && expandedRows[rowIndex]" class="expand-row">
|
|
|
- <uni-td :colspan="columnSpan" class="expand-cell">
|
|
|
- <slot name="expand" :row="row" :rowIndex="rowIndex">
|
|
|
- <view class="default-expand-content">
|
|
|
- <text class="no-content">暂无展开内容</text>
|
|
|
- </view>
|
|
|
- </slot>
|
|
|
- </uni-td>
|
|
|
- </uni-tr>
|
|
|
+ <template v-if="column.slot">
|
|
|
+ <slot :name="column.slot" :row="row" :column="column" :index="rowIndex">
|
|
|
+ {{ row[column.prop] }}
|
|
|
+ </slot>
|
|
|
+ </template>
|
|
|
+ <uni-tag v-else-if="column.type === 'tag'" :text="formatTagText(row[column.prop], column)"
|
|
|
+ :type="formatTagType(row[column.prop], column)" size="small" />
|
|
|
+ <view v-else-if="column.type === 'file'">
|
|
|
+ <cwg-file :path="row[column.prop]" />
|
|
|
+ </view>
|
|
|
+ <view v-else-if="column.type === 'note'">
|
|
|
+ <text>{{ getNoteText(row, locale, userStore) }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="column.type === 'action'" class="action-wrapper">
|
|
|
+ <cwg-droplist v-if="getComputedMenuList(column.menuList, row).length > 0"
|
|
|
+ :menuList="getComputedMenuList(column.menuList, row)" placement="bottom-end"
|
|
|
+ @menuClick="(payload) => handleActionClick(payload, row)">
|
|
|
+ <view class="action-trigger">
|
|
|
+ <cwg-icon name="crm-ellipsis" :size="24" />
|
|
|
+ </view>
|
|
|
+ </cwg-droplist>
|
|
|
+ </view>
|
|
|
+ <template v-else-if="column.type === 'more'" >
|
|
|
+ <view class="morebox">
|
|
|
+ <cwg-icon v-if="isMobile" name="crm-chevron-down" class="crm-chevron-down" :size="16" />
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else>
|
|
|
+ {{ formatCellValue(row[column.prop], column, row) }}
|
|
|
+ </template>
|
|
|
+ </uni-td>
|
|
|
+ </uni-tr>
|
|
|
+ <!-- 桌面端展开行(仅当非移动端且行展开时显示) -->
|
|
|
+ <uni-tr v-if="!isMobile && expandedRows[rowIndex]" class="expand-row">
|
|
|
+ <uni-td :colspan="columnSpan" class="expand-cell">
|
|
|
+ <slot name="expand" :row="row" :rowIndex="rowIndex">
|
|
|
+ <view class="default-expand-content">
|
|
|
+ <text class="no-content">暂无展开内容</text>
|
|
|
+ </view>
|
|
|
+ </slot>
|
|
|
+ </uni-td>
|
|
|
+ </uni-tr>
|
|
|
</template>
|
|
|
|
|
|
<!-- 总计行 -->
|
|
|
<uni-tr v-if="showSummary && tableData.length !== 0" class="summary-row">
|
|
|
- <uni-td v-for="(column, index) in displayColumns" :key="'summary-' + column.prop"
|
|
|
- :align="column.align || 'center'" class="summary-cell"
|
|
|
- :style="getCellStyle(column, currentSummaryData)">
|
|
|
- <!-- 当有 summaryMethod 时,按照返回的数组直接渲染 -->
|
|
|
- <template v-if="props.summaryMethod">
|
|
|
- <text>{{ computedSummaryRow[index] !== undefined ? computedSummaryRow[index] : '' }}</text>
|
|
|
- </template>
|
|
|
- <template v-else>
|
|
|
- <!-- 特定列的总计自定义插槽,例如 #summary-volume="{ row }" -->
|
|
|
- <template v-if="$slots['summary-' + column.prop]">
|
|
|
- <slot :name="'summary-' + column.prop" :row="currentSummaryData" :column="column"
|
|
|
- :index="index"></slot>
|
|
|
- </template>
|
|
|
- <!-- 如果没有特定插槽,尝试常规渲染 -->
|
|
|
- <template v-else>
|
|
|
- <template
|
|
|
- v-if="currentSummaryData && currentSummaryData[column.prop] !== undefined && currentSummaryData[column.prop] !== null">
|
|
|
- <!-- 如果有常规插槽,并且总计数据里有该字段的值,则复用常规插槽渲染 -->
|
|
|
- <slot v-if="column.slot" :name="column.slot" :row="currentSummaryData"
|
|
|
- :column="column" :index="index">
|
|
|
- {{ currentSummaryData[column.prop] }}
|
|
|
- </slot>
|
|
|
- <!-- 否则使用格式化函数 -->
|
|
|
- <text v-else>{{ formatCellValue(currentSummaryData[column.prop], column,
|
|
|
- currentSummaryData) }}</text>
|
|
|
- </template>
|
|
|
- <!-- 如果总计数据里没有该字段的值,第一列显示总计文本,其他留空 -->
|
|
|
- <text v-else>{{ index === 0 ? summaryText : '' }}</text>
|
|
|
- </template>
|
|
|
+ <uni-td v-for="(column, index) in displayColumns" :key="'summary-' + column.prop"
|
|
|
+ :align="column.align || 'center'" class="summary-cell"
|
|
|
+ :style="getCellStyle(column, currentSummaryData)">
|
|
|
+ <!-- 当有 summaryMethod 时,按照返回的数组直接渲染 -->
|
|
|
+ <template v-if="props.summaryMethod">
|
|
|
+ <text>{{ computedSummaryRow[index] !== undefined ? computedSummaryRow[index] : '' }}</text>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <!-- 特定列的总计自定义插槽,例如 #summary-volume="{ row }" -->
|
|
|
+ <template v-if="$slots['summary-' + column.prop]">
|
|
|
+ <slot :name="'summary-' + column.prop" :row="currentSummaryData" :column="column"
|
|
|
+ :index="index"></slot>
|
|
|
+ </template>
|
|
|
+ <!-- 如果没有特定插槽,尝试常规渲染 -->
|
|
|
+ <template v-else>
|
|
|
+ <template
|
|
|
+ v-if="currentSummaryData && currentSummaryData[column.prop] !== undefined && currentSummaryData[column.prop] !== null">
|
|
|
+ <!-- 如果有常规插槽,并且总计数据里有该字段的值,则复用常规插槽渲染 -->
|
|
|
+ <slot v-if="column.slot" :name="column.slot" :row="currentSummaryData"
|
|
|
+ :column="column" :index="index">
|
|
|
+ {{ currentSummaryData[column.prop] }}
|
|
|
+ </slot>
|
|
|
+ <!-- 否则使用格式化函数 -->
|
|
|
+ <text v-else>{{ formatCellValue(currentSummaryData[column.prop], column,
|
|
|
+ currentSummaryData) }}</text>
|
|
|
</template>
|
|
|
- </uni-td>
|
|
|
+ <!-- 如果总计数据里没有该字段的值,第一列显示总计文本,其他留空 -->
|
|
|
+ <text v-else>{{ index === 0 ? summaryText : '' }}</text>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </uni-td>
|
|
|
</uni-tr>
|
|
|
+ </template>
|
|
|
+
|
|
|
</uni-table>
|
|
|
</view>
|
|
|
- <view class="table-loading-mask">
|
|
|
- <uni-loading v-if="loading" />
|
|
|
- </view>
|
|
|
- <!-- 空状态 -->
|
|
|
- <view v-if="!loading && tableData.length === 0" style="width: 100%;">
|
|
|
- <cwg-empty-state />
|
|
|
- </view>
|
|
|
+
|
|
|
+
|
|
|
<!-- 分页 -->
|
|
|
<view class="pagination-container" v-if="showPagination && tableData.length > 0">
|
|
|
<!-- <view class="pagination-info">
|
|
|
@@ -767,11 +773,13 @@ defineExpose({
|
|
|
|
|
|
.table-container {
|
|
|
width: 100%;
|
|
|
+ position: relative;
|
|
|
overflow-x: auto;
|
|
|
-webkit-overflow-scrolling: touch;
|
|
|
margin-top: px2rpx(20);
|
|
|
max-height: calc(100vh - 209px);
|
|
|
color: var(--color-slate-800);
|
|
|
+ box-shadow: none;
|
|
|
|
|
|
.action-wrapper {
|
|
|
display: flex;
|
|
|
@@ -805,7 +813,7 @@ defineExpose({
|
|
|
:deep(.uni-table-scroll) {
|
|
|
width: 100%;
|
|
|
max-height: calc(100vh - 375px);
|
|
|
- //min-height: 300px;
|
|
|
+ min-height: px2rpx(300);
|
|
|
overflow-y: auto;
|
|
|
overflow-x: auto;
|
|
|
|
|
|
@@ -815,27 +823,28 @@ defineExpose({
|
|
|
height: 4px !important;
|
|
|
display: block !important;
|
|
|
/* 强制在某些webkit浏览器中显示 */
|
|
|
- background-color: #ccc !important;
|
|
|
+ background-color:yellow !important;
|
|
|
}
|
|
|
|
|
|
&::-webkit-scrollbar-track {
|
|
|
- background-color: #ccc;
|
|
|
+ background-color: #faf3f3;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
&::-webkit-scrollbar-thumb {
|
|
|
- background-color:#ccc;
|
|
|
+ background-color:#6a6a6a;
|
|
|
border-radius: 4px;
|
|
|
|
|
|
&:hover {
|
|
|
- background-color: #ccc;
|
|
|
+ background-color: #6a6a6a;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
:deep(.uni-table) {
|
|
|
border-radius: 8px;
|
|
|
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
|
|
|
+ //box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
|
|
|
+ height: 100%;
|
|
|
|
|
|
.uni-table-th {
|
|
|
position: sticky;
|
|
|
@@ -899,15 +908,15 @@ defineExpose({
|
|
|
}
|
|
|
|
|
|
.summary-row {
|
|
|
- background-color: var(--bs-light-bg-subtle);
|
|
|
+ background-color: var(--table-th-color);
|
|
|
font-weight: 600;
|
|
|
|
|
|
.uni-table-td {
|
|
|
- border-top: 1px solid var(--bs-light-bg-subtle) !important;
|
|
|
+ border-top: 1px solid var(--table-th-color) !important;
|
|
|
position: sticky;
|
|
|
bottom: 0;
|
|
|
z-index: 99;
|
|
|
- background-color: var(--bs-light-bg-subtle);
|
|
|
+ background-color: var(--table-th-color);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1134,4 +1143,19 @@ defineExpose({
|
|
|
gap: px2rpx(10);
|
|
|
}
|
|
|
}
|
|
|
+.empty-state{
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 60%;
|
|
|
+ transform: translate(-50%, 0);
|
|
|
+}
|
|
|
+.morebox{
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
</style>
|