Files
tuanwei-uniapp/pages/mine/manuscripts.vue
2026-07-20 11:56:02 +08:00

423 lines
9.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<scroll-view scroll-y class="page-scroll">
<view class="hero-card">
<view class="hero-title">我的稿件</view>
<view class="hero-desc">汇总当前账号创建或参与的稿件记录可查看状态和内容摘要</view>
<view class="hero-stats">
<view class="stat-item">
<view class="stat-value">{{ manuscripts.length }}</view>
<view class="stat-label">稿件总数</view>
</view>
<view class="stat-item">
<view class="stat-value">{{ publishedCount }}</view>
<view class="stat-label">已通过/发布</view>
</view>
<view class="stat-item">
<view class="stat-value">{{ pendingCount }}</view>
<view class="stat-label">待处理</view>
</view>
</view>
</view>
<view class="toolbar-card">
<uv-input
v-model="keyword"
class="search-input"
placeholder="搜索稿件标题或内容"
placeholder-style="color: #c0c4cc;"
:maxlength="-1"
/>
<view class="toolbar-actions">
<view class="toolbar-btn" @tap="loadData()">刷新</view>
<view class="toolbar-btn toolbar-btn--primary" @tap="goCreate">去创作</view>
</view>
</view>
<view v-if="loading" class="state-card">
<view class="state-title">正在加载稿件...</view>
<view class="state-desc">请稍候正在同步当前账号的稿件记录</view>
</view>
<view v-else-if="errorText" class="state-card state-card--error">
<view class="state-title">加载失败</view>
<view class="state-desc">{{ errorText }}</view>
</view>
<view v-else-if="!filteredList.length" class="state-card">
<view class="state-title">暂无稿件</view>
<view class="state-desc">当前账号还没有可展示的稿件可前往文稿工作台开始生成</view>
</view>
<view v-else class="card-list">
<view v-for="item in filteredList" :key="item.id || item.title" class="data-card">
<view class="card-top">
<view class="card-main">
<view class="card-title">{{ item.title || '未命名稿件' }}</view>
<view class="card-subtitle">
创建时间{{ formatDateTime(item.createTime) }} · 更新时间{{ formatDateTime(item.updateTime) }}
</view>
</view>
<view class="status-tag" :class="`status-tag--${getStatusMeta(item).tone}`">
{{ getStatusMeta(item).text }}
</view>
</view>
<view v-if="item.cover" class="cover-wrap">
<image class="cover-image" :src="item.cover" mode="aspectFill" />
</view>
<view class="content-preview">{{ getContentPreview(item.content) }}</view>
<view class="card-actions">
<view class="card-action" @tap="toggleExpand(item)">{{ expandedId === item.id ? '收起内容' : '查看内容' }}</view>
<view class="card-action" @tap="copyTitle(item.title)">复制标题</view>
</view>
<view v-if="expandedId === item.id" class="content-detail">{{ item.content || '暂无正文内容' }}</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import {
formatDateTime,
getCurrentUser,
getManuscriptStatusMeta,
listManuscript
} from './service'
export default {
data() {
return {
loading: false,
errorText: '',
keyword: '',
expandedId: null,
manuscripts: []
}
},
computed: {
filteredList() {
const keyword = String(this.keyword || '').trim().toLowerCase()
if (!keyword) {
return this.manuscripts
}
return this.manuscripts.filter((item) => {
return (
String(item.title || '').toLowerCase().includes(keyword) ||
String(item.content || '').toLowerCase().includes(keyword)
)
})
},
publishedCount() {
return this.manuscripts.filter((item) => {
const text = this.getStatusMeta(item).text
return text === '通过' || text === '已发布'
}).length
},
pendingCount() {
return this.manuscripts.length - this.publishedCount
}
},
onShow() {
this.loadData()
},
onPullDownRefresh() {
this.loadData(true)
},
methods: {
formatDateTime,
getStatusMeta(item) {
return getManuscriptStatusMeta(item)
},
getContentPreview(content) {
const text = String(content || '').replace(/\s+/g, ' ').trim()
return text ? `${text.slice(0, 90)}${text.length > 90 ? '...' : ''}` : '暂无内容摘要'
},
async loadData(fromPullDown = false) {
this.loading = true
this.errorText = ''
try {
const user = getCurrentUser()
const result = await listManuscript()
const list = Array.isArray(result) ? result : []
this.manuscripts = list
.filter((item) => !user.userId || Number(item.userId) === Number(user.userId))
.sort((left, right) => {
const leftTime = new Date(String((left.updateTime || left.createTime || '')).replace(/-/g, '/')).getTime()
const rightTime = new Date(String((right.updateTime || right.createTime || '')).replace(/-/g, '/')).getTime()
return (Number.isNaN(rightTime) ? 0 : rightTime) - (Number.isNaN(leftTime) ? 0 : leftTime)
})
} catch (error) {
this.errorText = (error && error.message) || '稿件加载失败'
} finally {
this.loading = false
if (fromPullDown) {
uni.stopPullDownRefresh()
}
}
},
toggleExpand(item) {
this.expandedId = this.expandedId === item.id ? null : item.id
},
copyTitle(title) {
if (!title) {
uni.showToast({
title: '暂无标题可复制',
icon: 'none'
})
return
}
uni.setClipboardData({
data: title,
success: () => {
uni.showToast({
title: '标题已复制',
icon: 'none'
})
}
})
},
goCreate() {
uni.navigateTo({
url: '/pages/manuscript/index'
})
}
}
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background:
radial-gradient(circle at top center, rgba(240, 122, 61, 0.1), transparent 30%),
linear-gradient(180deg, #fff8f2 0%, #f8f2ec 100%);
}
.page-scroll {
height: 100vh;
padding: 24rpx 24rpx 40rpx;
}
.hero-card,
.toolbar-card,
.state-card,
.data-card {
border-radius: 30rpx;
background: rgba(255, 255, 255, 0.94);
border: 1rpx solid rgba(240, 122, 61, 0.08);
box-shadow: 0 10rpx 28rpx rgba(76, 49, 35, 0.08);
}
.hero-card {
padding: 32rpx 28rpx;
background: linear-gradient(145deg, #7c2f04 0%, #c8641f 56%, #f29a4b 100%);
color: #fffaf5;
}
.hero-title {
font-size: 40rpx;
font-weight: 700;
}
.hero-desc {
margin-top: 14rpx;
font-size: 24rpx;
line-height: 1.7;
color: rgba(255, 250, 245, 0.86);
}
.hero-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16rpx;
margin-top: 24rpx;
}
.stat-item {
padding: 18rpx 16rpx;
border-radius: 22rpx;
background: rgba(255, 255, 255, 0.12);
text-align: center;
}
.stat-value {
font-size: 32rpx;
font-weight: 700;
}
.stat-label {
margin-top: 6rpx;
font-size: 22rpx;
color: rgba(255, 250, 245, 0.8);
}
.toolbar-card {
margin-top: 24rpx;
padding: 22rpx;
}
:deep(.search-input.uv-input) {
width: 100%;
min-height: 84rpx;
padding: 0 22rpx;
border-radius: 12rpx;
border: 1px solid #dcdfe6;
background: #fff;
box-sizing: border-box;
}
:deep(.search-input.uv-input .uv-input__content__field-wrapper__field) {
height: 84rpx;
min-height: 84rpx;
font-size: 28rpx;
line-height: 84rpx;
color: #303133;
}
.toolbar-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16rpx;
margin-top: 18rpx;
}
.toolbar-btn {
height: 84rpx;
line-height: 84rpx;
border-radius: 22rpx;
background: #edf6ff;
color: #0f7dd1;
font-size: 26rpx;
font-weight: 700;
text-align: center;
}
.toolbar-btn--primary {
background: linear-gradient(135deg, #0f7dd1 0%, #1496f2 100%);
color: #fff;
}
.state-card {
margin-top: 24rpx;
padding: 28rpx 24rpx;
}
.state-card--error {
border-color: rgba(20, 150, 242, 0.16);
}
.state-title {
font-size: 30rpx;
font-weight: 700;
color: #20252c;
}
.state-desc {
margin-top: 12rpx;
font-size: 24rpx;
line-height: 1.75;
color: #786e69;
}
.card-list {
display: flex;
flex-direction: column;
gap: 18rpx;
margin-top: 24rpx;
}
.data-card {
padding: 24rpx;
}
.card-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16rpx;
}
.card-main {
flex: 1;
min-width: 0;
}
.card-title {
font-size: 30rpx;
font-weight: 700;
color: #22272f;
}
.card-subtitle {
margin-top: 8rpx;
font-size: 22rpx;
line-height: 1.6;
color: #8b817c;
}
.status-tag {
padding: 10rpx 16rpx;
border-radius: 999rpx;
font-size: 20rpx;
font-weight: 700;
}
.status-tag--success {
background: #eef9f1;
color: #1f8f49;
}
.status-tag--danger {
background: #fff1f0;
color: #d4380d;
}
.status-tag--warning {
background: #fff8e6;
color: #ad7b00;
}
.status-tag--default {
background: #f5f5f5;
color: #8b817c;
}
.cover-wrap {
margin-top: 18rpx;
}
.cover-image {
width: 100%;
height: 260rpx;
border-radius: 24rpx;
}
.content-preview,
.content-detail {
margin-top: 18rpx;
font-size: 24rpx;
line-height: 1.8;
color: #5d544f;
word-break: break-word;
white-space: pre-wrap;
}
.content-detail {
padding: 20rpx;
border-radius: 22rpx;
background: #fbf7f3;
}
.card-actions {
display: flex;
gap: 14rpx;
margin-top: 18rpx;
}
.card-action {
padding: 10rpx 18rpx;
border-radius: 999rpx;
background: rgba(240, 122, 61, 0.08);
color: #c8641f;
font-size: 22rpx;
}
</style>