feat(oa): add cc read filters batch mark and unread styles

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-12 16:34:39 +08:00
1 parent ecdf73fe23
commit 06b1468bf1
3 files changed
+271 -15

No files matched your search

+9 -2
View File
@@ -34,9 +34,10 @@
class="oa-task-card__btn"
:class="{
'is-primary': !!act.primary,
'is-danger': !!act.danger && !act.primary
'is-danger': !!act.danger && !act.primary,
'is-disabled': !!act.disabled
}"
@click.stop="$emit('action', act)"
@click.stop="onActionClick(act)"
>
{{ act.label }}
</view>
@@ -63,6 +64,12 @@
const t = String(this.statusTone || 'info').toLowerCase()
return TONES.includes(t) ? t : 'info'
}
},
methods: {
onActionClick(act) {
if (!act || act.disabled) return
this.$emit('action', act)
}
}
}
</script>
+257 -13
View File
@@ -19,6 +19,13 @@
<text class="search-clear__x">×</text>
</view>
</view>
<view
class="manage-btn"
:class="{ 'is-active': manageMode }"
@click="toggleManage"
>
{{ manageMode ? '完成' : '管理' }}
</view>
</view>
<view class="toolbar-row toolbar-row--sub">
<view class="search-box" :class="{ 'is-focus': initiatorFocused }">
@@ -51,6 +58,17 @@
</view>
</view>
<view v-if="manageMode && selectedIds.length" class="batch-bar">
<text class="batch-bar__hint">已选 {{ selectedIds.length }} 项</text>
<view
class="batch-bar__btn"
:class="{ 'is-disabled': batchReadDisabled }"
@click="batchMarkRead"
>
批量已读
</view>
</view>
<view v-if="loading && !list.length" class="state state-loading">
<view class="pulse-dots" aria-hidden="true">
<view class="pulse-dot" />
@@ -63,18 +81,34 @@
<view v-else-if="!list.length" class="oa-task-list__empty">暂无抄送</view>
<view v-else class="oa-task-list">
<OaTaskCard
<view
v-for="row in list"
:key="row.id"
:title="cardTitle(row)"
:status-text="statusText(row.instanceStatus)"
:status-tone="statusTone(row.instanceStatus)"
:unread="!row.isRead"
:meta-lines="metaLines(row)"
:actions="cardActions"
@open="openView(row)"
@action="openView(row)"
/>
class="cc-row"
:class="{ 'is-manage': manageMode, 'is-selected': isSelected(row.id) }"
>
<view
v-if="manageMode"
class="cc-check"
@click.stop="toggleSelect(row.id)"
>
<view class="cc-check__box" :class="{ on: isSelected(row.id) }">
<text v-if="isSelected(row.id)" class="cc-check__mark">✓</text>
</view>
</view>
<view class="cc-card-wrap">
<OaTaskCard
:title="cardTitle(row)"
:status-text="statusText(row.instanceStatus)"
:status-tone="statusTone(row.instanceStatus)"
:unread="!row.isRead"
:meta-lines="metaLines(row)"
:actions="cardActionsFor(row)"
@open="onCardOpen(row)"
@action="onCardAction($event, row)"
/>
</view>
</view>
<view v-if="loadingMore" class="oa-task-list__end">加载更多…</view>
<view v-else-if="!hasMore" class="oa-task-list__end">已经到底了</view>
</view>
@@ -83,7 +117,11 @@
<script>
import OaTaskCard from '../../../components/OaTaskCard.vue'
import { fetchMarkOaCcTaskRead, fetchOaTasks } from '../../../utils/oa-api.js'
import {
fetchBatchMarkOaCcTasksRead,
fetchMarkOaCcTaskRead,
fetchOaTasks
} from '../../../utils/oa-api.js'
import { forceRelogin, getToken } from '../../../utils/auth.js'
import { stashOaDetailTitle } from '../../../utils/oa-h5.js'
@@ -116,14 +154,26 @@
loading: false,
loadingMore: false,
hasMore: true,
manageMode: false,
selectedIds: [],
openingId: '',
markingId: '',
batchMarking: false,
_searchTimer: null,
readTabs: [
{ value: 'unread', label: '未读' },
{ value: 'read', label: '已读' },
{ value: 'all', label: '全部' }
],
cardActions: [{ key: 'view', label: '查看', primary: true }]
]
}
},
computed: {
batchReadDisabled() {
const ids = this.selectedIds || []
if (!ids.length) return true
return !this.list.some(
(r) => r && ids.indexOf(r.id) >= 0 && !r.isRead
)
}
},
onShow() {
@@ -180,6 +230,20 @@
}
]
},
cardActionsFor(row) {
if (this.manageMode) return []
return [
{ key: 'view', label: '查看', primary: true },
{
key: 'read',
label: '已读',
disabled: !!(row && row.isRead)
}
]
},
isSelected(id) {
return this.selectedIds.indexOf(id) >= 0
},
scheduleSearch() {
if (this._searchTimer) clearTimeout(this._searchTimer)
this._searchTimer = setTimeout(() => {
@@ -219,6 +283,73 @@
this.readStatus = value
this.loadList(true)
},
toggleManage() {
this.manageMode = !this.manageMode
if (!this.manageMode) this.selectedIds = []
},
toggleSelect(id) {
if (!id) return
const i = this.selectedIds.indexOf(id)
if (i >= 0) {
this.selectedIds = this.selectedIds.filter((x) => x !== id)
} else {
this.selectedIds = this.selectedIds.concat([id])
}
},
onCardOpen(row) {
if (this.manageMode) {
this.toggleSelect(row && row.id)
return
}
this.openView(row)
},
onCardAction(act, row) {
if (!act || !row) return
if (act.key === 'view') this.openView(row)
else if (act.key === 'read') this.markOneRead(row)
},
async markOneRead(row) {
if (!row || row.isRead || this.markingId) return
const taskId = String(row.id || '').trim()
if (!taskId) return
this.markingId = taskId
try {
await fetchMarkOaCcTaskRead(taskId)
uni.showToast({ title: '已标记已读', icon: 'success' })
await this.loadList(true)
} catch (e) {
uni.showToast({
title: (e && e.message) || '标记已读失败',
icon: 'none'
})
} finally {
this.markingId = ''
}
},
async batchMarkRead() {
if (this.batchReadDisabled || this.batchMarking) return
const selected = this.selectedIds || []
const ids = this.list
.filter((r) => r && selected.indexOf(r.id) >= 0 && !r.isRead)
.map((r) => String(r.id || '').trim())
.filter(Boolean)
if (!ids.length) return
this.batchMarking = true
try {
await fetchBatchMarkOaCcTasksRead(ids)
uni.showToast({ title: '已标记已读', icon: 'success' })
this.selectedIds = []
this.manageMode = false
await this.loadList(true)
} catch (e) {
uni.showToast({
title: (e && e.message) || '批量已读失败',
icon: 'none'
})
} finally {
this.batchMarking = false
}
},
async openView(row) {
if (!row || this.openingId) return
const instanceId = String(row.instanceId || '').trim()
@@ -261,6 +392,7 @@
this.page = 1
this.hasMore = true
this.loading = true
this.selectedIds = []
} else {
this.loadingMore = true
}
@@ -400,6 +532,28 @@
margin-top: -2rpx;
}
.manage-btn {
flex-shrink: 0;
min-width: 96rpx;
height: 72rpx;
padding: 0 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
font-weight: 600;
color: #166534;
box-sizing: border-box;
}
.manage-btn.is-active {
color: #64748b;
}
.manage-btn:active {
opacity: 0.72;
}
.read-tabs {
display: flex;
align-items: center;
@@ -433,6 +587,96 @@
opacity: 0.78;
}
.batch-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
margin: 16rpx 24rpx 0;
padding: 20rpx 24rpx;
border-radius: 16rpx;
background: #fff;
border: 1rpx solid #d8f3e5;
box-sizing: border-box;
}
.batch-bar__hint {
font-size: 26rpx;
color: #5f7a6d;
}
.batch-bar__btn {
min-height: 64rpx;
padding: 0 28rpx;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 14rpx;
font-size: 26rpx;
font-weight: 600;
color: #166534;
background: #ecfdf5;
border: 1rpx solid #a7f3d0;
box-sizing: border-box;
}
.batch-bar__btn.is-disabled {
opacity: 0.45;
pointer-events: none;
}
.batch-bar__btn:active {
opacity: 0.78;
}
.cc-row {
display: flex;
align-items: stretch;
gap: 0;
}
.cc-row.is-manage .cc-card-wrap {
flex: 1;
min-width: 0;
}
.cc-row.is-selected .cc-card-wrap :deep(.oa-task-card) {
border-color: rgba(34, 197, 94, 0.45);
box-shadow: 0 8rpx 32rpx rgba(22, 163, 74, 0.12);
}
.cc-check {
display: flex;
align-items: flex-start;
padding: 40rpx 0 0 4rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
.cc-check__box {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #cbd5e1;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.cc-check__box.on {
border-color: #22c55e;
background: #22c55e;
}
.cc-check__mark {
font-size: 22rpx;
line-height: 1;
color: #fff;
font-weight: 700;
}
.state {
padding: 80rpx 24rpx;
text-align: center;
@@ -166,3 +166,8 @@
background: rgba(239, 68, 68, 0.06);
border-color: rgba(239, 68, 68, 0.35);
}
.oa-task-card__btn.is-disabled {
opacity: 0.45;
pointer-events: none;
}