|
|
|
|
@ -0,0 +1,551 @@
|
|
|
|
|
<template>
|
|
|
|
|
<view class="page-wrap profile-bg weekly-brief-page">
|
|
|
|
|
<view class="card intro-card">
|
|
|
|
|
<text class="intro-title">AI 科技成果周报</text>
|
|
|
|
|
<text class="intro-desc">基于爬虫入库的论文与资讯,按周自动生成简报。可选择本周或历史自然周。</text>
|
|
|
|
|
<view class="generate-row">
|
|
|
|
|
<view class="picker-wrap">
|
|
|
|
|
<picker
|
|
|
|
|
class="picker-field"
|
|
|
|
|
:range="weekOptionLabels"
|
|
|
|
|
:value="weekOptionIndex"
|
|
|
|
|
@change="onWeekChange"
|
|
|
|
|
>
|
|
|
|
|
<view class="select">{{ weekOptionLabels[weekOptionIndex] || '选择统计周' }}</view>
|
|
|
|
|
</picker>
|
|
|
|
|
</view>
|
|
|
|
|
<button class="btn btn-primary generate-btn" :loading="generating" @tap="onGenerate">生成简报</button>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view class="card list-card">
|
|
|
|
|
<text class="section-title">历史简报</text>
|
|
|
|
|
<view v-if="loading && !items.length" class="empty-hint">加载中...</view>
|
|
|
|
|
<view v-else-if="!items.length" class="empty-hint">暂无周报,请先生成</view>
|
|
|
|
|
<view v-else class="brief-list">
|
|
|
|
|
<view v-for="item in items" :key="item.id" class="brief-item" @tap="openDetail(item.id)">
|
|
|
|
|
<text class="brief-title">{{ item.title }}</text>
|
|
|
|
|
<text class="brief-meta">{{ formatWeekRange(item.week_start, item.week_end) }}</text>
|
|
|
|
|
<text class="brief-meta">
|
|
|
|
|
论文 {{ item.stats?.papers_count ?? 0 }} · 资讯 {{ item.stats?.news_count ?? 0 }} ·
|
|
|
|
|
{{ formatGeneratedAt(item.generated_at) }}
|
|
|
|
|
</text>
|
|
|
|
|
<view class="brief-actions" @tap.stop>
|
|
|
|
|
<button
|
|
|
|
|
v-if="item.has_docx"
|
|
|
|
|
class="action-btn"
|
|
|
|
|
size="mini"
|
|
|
|
|
@tap="downloadDocx(item)"
|
|
|
|
|
>
|
|
|
|
|
下载 Word
|
|
|
|
|
</button>
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="hasMore" class="load-more-wrap">
|
|
|
|
|
<button class="load-more-btn" :loading="loadingMore" @tap="loadMore">加载更多</button>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view v-if="detailVisible" class="detail-mask" @tap="closeDetail">
|
|
|
|
|
<view class="detail-panel" @tap.stop>
|
|
|
|
|
<view class="detail-head">
|
|
|
|
|
<text class="detail-title">{{ detail?.title || 'AI 科技成果周报' }}</text>
|
|
|
|
|
<text class="detail-close" @tap="closeDetail">×</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="detail" class="detail-meta-row">
|
|
|
|
|
<text>{{ formatWeekRange(detail.week_start, detail.week_end) }}</text>
|
|
|
|
|
<text>论文 {{ detail.stats?.papers_count ?? 0 }}</text>
|
|
|
|
|
<text>资讯 {{ detail.stats?.news_count ?? 0 }}</text>
|
|
|
|
|
<text v-if="detail.stats?.teachers_count != null">老师 {{ detail.stats.teachers_count }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<scroll-view v-if="detail" scroll-y class="detail-scroll">
|
|
|
|
|
<text class="detail-content">{{ detail.content || '暂无简报正文,请重新生成' }}</text>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
<view v-else class="detail-loading">加载中...</view>
|
|
|
|
|
<view class="detail-footer">
|
|
|
|
|
<button class="footer-btn btn" :disabled="!detail?.content" @tap="copyContent">复制内容</button>
|
|
|
|
|
<button
|
|
|
|
|
class="footer-btn btn primary"
|
|
|
|
|
:disabled="!detail?.has_docx"
|
|
|
|
|
@tap="downloadDocx(detail)"
|
|
|
|
|
>
|
|
|
|
|
下载 Word
|
|
|
|
|
</button>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import { getShareAppMessage, getShareTimelineMessage } from '@/utils/page-share-handlers'
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
onShareAppMessage: getShareAppMessage,
|
|
|
|
|
onShareTimeline: getShareTimelineMessage,
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, ref } from 'vue'
|
|
|
|
|
import { onShow } from '@dcloudio/uni-app'
|
|
|
|
|
import {
|
|
|
|
|
weeklyBriefApi,
|
|
|
|
|
type WeeklyBriefDetail,
|
|
|
|
|
type WeeklyBriefRow,
|
|
|
|
|
type WeeklyBriefWeekOption,
|
|
|
|
|
} from '@/api/weekly-brief'
|
|
|
|
|
import { useUserStore } from '@/stores/user'
|
|
|
|
|
|
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const loadingMore = ref(false)
|
|
|
|
|
const generating = ref(false)
|
|
|
|
|
const items = ref<WeeklyBriefRow[]>([])
|
|
|
|
|
const page = ref(1)
|
|
|
|
|
const lastPage = ref(1)
|
|
|
|
|
const weekOptions = ref<WeeklyBriefWeekOption[]>([])
|
|
|
|
|
const weekOptionIndex = ref(0)
|
|
|
|
|
const weekOffset = ref(0)
|
|
|
|
|
|
|
|
|
|
const detailVisible = ref(false)
|
|
|
|
|
const detail = ref<WeeklyBriefDetail | null>(null)
|
|
|
|
|
|
|
|
|
|
const weekOptionLabels = computed(() =>
|
|
|
|
|
weekOptions.value.map(
|
|
|
|
|
(opt) => `${opt.label}(${opt.week_start} ~ ${opt.week_end})`,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const hasMore = computed(() => page.value < lastPage.value)
|
|
|
|
|
|
|
|
|
|
function formatWeekRange(start?: string | null, end?: string | null) {
|
|
|
|
|
if (!start || !end) return '—'
|
|
|
|
|
return `${start} 至 ${end}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatGeneratedAt(iso?: string | null) {
|
|
|
|
|
if (!iso) return '—'
|
|
|
|
|
const d = new Date(iso)
|
|
|
|
|
if (Number.isNaN(d.getTime())) return '—'
|
|
|
|
|
const pad = (n: number) => String(n).padStart(2, '0')
|
|
|
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadWeekOptions() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await weeklyBriefApi.weekOptions()
|
|
|
|
|
weekOptions.value = res.items || []
|
|
|
|
|
if (
|
|
|
|
|
weekOptions.value.length &&
|
|
|
|
|
!weekOptions.value.some((opt) => opt.offset === weekOffset.value)
|
|
|
|
|
) {
|
|
|
|
|
weekOffset.value = weekOptions.value[0].offset
|
|
|
|
|
weekOptionIndex.value = 0
|
|
|
|
|
} else {
|
|
|
|
|
weekOptionIndex.value = Math.max(
|
|
|
|
|
0,
|
|
|
|
|
weekOptions.value.findIndex((opt) => opt.offset === weekOffset.value),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
weekOptions.value = []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadBriefs(reset = false) {
|
|
|
|
|
if (reset) {
|
|
|
|
|
page.value = 1
|
|
|
|
|
items.value = []
|
|
|
|
|
}
|
|
|
|
|
const isFirstPage = page.value === 1
|
|
|
|
|
if (isFirstPage) {
|
|
|
|
|
loading.value = true
|
|
|
|
|
} else {
|
|
|
|
|
loadingMore.value = true
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const res = await weeklyBriefApi.list({ page: page.value, page_size: 10 })
|
|
|
|
|
items.value = reset ? res.items : [...items.value, ...res.items]
|
|
|
|
|
lastPage.value = res.meta.last_page
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (reset) items.value = []
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: error instanceof Error ? error.message : '加载失败',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
})
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
loadingMore.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadMore() {
|
|
|
|
|
if (!hasMore.value || loadingMore.value) return
|
|
|
|
|
page.value += 1
|
|
|
|
|
await loadBriefs()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onWeekChange(event: UniHelper.PickerChangeEvent) {
|
|
|
|
|
weekOptionIndex.value = Number(event.detail.value)
|
|
|
|
|
weekOffset.value = weekOptions.value[weekOptionIndex.value]?.offset ?? 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onGenerate() {
|
|
|
|
|
generating.value = true
|
|
|
|
|
try {
|
|
|
|
|
const brief = await weeklyBriefApi.generate({ week_offset: weekOffset.value })
|
|
|
|
|
uni.showToast({ title: '周报已生成', icon: 'none' })
|
|
|
|
|
await loadBriefs(true)
|
|
|
|
|
await openDetail(brief.id)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: error instanceof Error ? error.message : '周报生成失败',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
})
|
|
|
|
|
} finally {
|
|
|
|
|
generating.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function openDetail(id: number) {
|
|
|
|
|
detailVisible.value = true
|
|
|
|
|
detail.value = null
|
|
|
|
|
try {
|
|
|
|
|
detail.value = await weeklyBriefApi.detail(id)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
detailVisible.value = false
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: error instanceof Error ? error.message : '加载周报失败',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeDetail() {
|
|
|
|
|
detailVisible.value = false
|
|
|
|
|
detail.value = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function copyContent() {
|
|
|
|
|
const text = detail.value?.content?.trim()
|
|
|
|
|
if (!text) {
|
|
|
|
|
uni.showToast({ title: '暂无简报正文', icon: 'none' })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
uni.setClipboardData({
|
|
|
|
|
data: text,
|
|
|
|
|
success: () => uni.showToast({ title: '已复制', icon: 'none' }),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function downloadDocx(brief?: WeeklyBriefRow | WeeklyBriefDetail | null) {
|
|
|
|
|
if (!brief?.id || !brief.has_docx) return
|
|
|
|
|
const filename = `高校科技成果周报_${brief.week_start}_${brief.week_end}.docx`
|
|
|
|
|
try {
|
|
|
|
|
await weeklyBriefApi.downloadDocx(brief.id, filename)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title: error instanceof Error ? error.message : '下载失败',
|
|
|
|
|
icon: 'none',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onShow(async () => {
|
|
|
|
|
if (!userStore.isLoggedIn) {
|
|
|
|
|
uni.navigateTo({ url: '/subpkg/login/index' })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await userStore.fetchMe()
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
if (!userStore.isStaff) {
|
|
|
|
|
uni.showToast({ title: '无权使用论文周报', icon: 'none' })
|
|
|
|
|
setTimeout(() => uni.navigateBack(), 1200)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
await loadWeekOptions()
|
|
|
|
|
await loadBriefs(true)
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
@import '@/styles/page.scss';
|
|
|
|
|
@import '@/styles/card.scss';
|
|
|
|
|
@import '@/styles/tokens.scss';
|
|
|
|
|
|
|
|
|
|
.weekly-brief-page {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.intro-card,
|
|
|
|
|
.list-card {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
margin-bottom: $section-gap;
|
|
|
|
|
padding: 32rpx;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.intro-title {
|
|
|
|
|
display: block;
|
|
|
|
|
color: #111827;
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.intro-desc {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-top: 12rpx;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.generate-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
margin-top: 24rpx;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.picker-wrap {
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.picker-field {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.select {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
min-height: 84rpx;
|
|
|
|
|
padding: 16rpx 24rpx;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
border: 1px solid #d6dde8;
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
background: #fff;
|
|
|
|
|
color: #111827;
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.generate-btn {
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-height: 88rpx;
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.generate-btn::after {
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
color: #111827;
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.empty-hint {
|
|
|
|
|
padding: 40rpx 0;
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.brief-item {
|
|
|
|
|
position: relative;
|
|
|
|
|
padding: 24rpx 56rpx 24rpx 0;
|
|
|
|
|
border-bottom: 1px solid #eef2f6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.brief-item:last-child {
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.brief-title {
|
|
|
|
|
display: block;
|
|
|
|
|
color: #111827;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.brief-meta {
|
|
|
|
|
display: block;
|
|
|
|
|
margin-top: 8rpx;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.brief-actions {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 50%;
|
|
|
|
|
right: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8rpx;
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0 16rpx;
|
|
|
|
|
color: #244e98;
|
|
|
|
|
font-size: 22rpx;
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
background: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-btn::after {
|
|
|
|
|
border: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.arrow {
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
font-size: 36rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.load-more-wrap {
|
|
|
|
|
padding-top: 16rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.load-more-btn {
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-height: 72rpx;
|
|
|
|
|
color: #244e98;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.load-more-btn::after {
|
|
|
|
|
border: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-mask {
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
background: rgba(15, 23, 42, 0.35);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-panel {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-height: 82vh;
|
|
|
|
|
padding: 28rpx 28rpx calc(28rpx + env(safe-area-inset-bottom));
|
|
|
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-head {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-title {
|
|
|
|
|
flex: 1;
|
|
|
|
|
color: #111827;
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-close {
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
font-size: 44rpx;
|
|
|
|
|
line-height: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-meta-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
margin-top: 16rpx;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-scroll {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
max-height: 52vh;
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-content {
|
|
|
|
|
display: block;
|
|
|
|
|
color: #374151;
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-loading {
|
|
|
|
|
padding: 48rpx 0;
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-footer {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
margin-top: 24rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.footer-btn {
|
|
|
|
|
flex: 1;
|
|
|
|
|
height: 80rpx;
|
|
|
|
|
min-height: 80rpx;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
color: #374151;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
line-height: 1;
|
|
|
|
|
background: #f3f4f6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.footer-btn.primary {
|
|
|
|
|
color: #fff;
|
|
|
|
|
background: #244e98;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.footer-btn::after {
|
|
|
|
|
border: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.footer-btn[disabled] {
|
|
|
|
|
opacity: 0.55;
|
|
|
|
|
}
|
|
|
|
|
</style>
|