|
@@ -0,0 +1,729 @@
|
|
|
+<template>
|
|
|
+ <div class="news-list">
|
|
|
+ <el-card class="box-card" style="flex: 1; display: flex; flex-direction: column">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <div class="header-title">
|
|
|
+ <el-icon><Collection /></el-icon>
|
|
|
+ <span>资讯管理</span>
|
|
|
+ </div>
|
|
|
+ <div class="actions">
|
|
|
+ <el-button type="primary" :icon="Refresh" @click="fetchNews">刷新</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Search form -->
|
|
|
+ <div class="search-container">
|
|
|
+ <el-form :model="searchForm" label-width="100px" class="search-form">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-form-item label="资讯发布日期">
|
|
|
+ <div class="dateRange">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="searchForm.beginTime"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择日期"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabledDate="startPickerOptions"
|
|
|
+ />
|
|
|
+ <span>至</span>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="searchForm.endTime"
|
|
|
+ type="date"
|
|
|
+ placeholder="选择日期"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabledDate="endPickerOptions"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-form-item label="关键字">
|
|
|
+ <el-input v-model="searchForm.key" placeholder="请输入关键字" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-form-item label="分类">
|
|
|
+ <el-select
|
|
|
+ v-model="searchForm.categoryId"
|
|
|
+ placeholder="请选择分类"
|
|
|
+ clearable
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="category in categories"
|
|
|
+ :key="category.id"
|
|
|
+ :label="category.name"
|
|
|
+ :value="category.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6" class="search-actions">
|
|
|
+ <el-form-item label=" ">
|
|
|
+ <el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
|
+ <el-button @click="resetSearch">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- News table -->
|
|
|
+ <el-table
|
|
|
+ ref="newsTableRef"
|
|
|
+ :data="newsList"
|
|
|
+ style="width: 100%; flex: 1"
|
|
|
+ v-loading="loading"
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ border
|
|
|
+ stripe
|
|
|
+ highlight-current-row
|
|
|
+ :header-cell-style="{ background: '#f8f9fa', color: '#606266' }"
|
|
|
+ :row-style="{ height: '55px' }"
|
|
|
+ :cell-style="{ padding: '8px' }"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="55"></el-table-column>
|
|
|
+ <el-table-column label="序号" width="80">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ (pagination.currentPage - 1) * pagination.pageSize + scope.$index + 1 }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="title" label="标题" min-width="200">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-link :href="scope.row.articleUrl" target="_blank" :underline="false">
|
|
|
+ {{ scope.row.title }}
|
|
|
+ </el-link>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="sourceName" label="来源名称" width="150"></el-table-column>
|
|
|
+ <el-table-column label="来源类型" width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag :type="scope.row.sourceType === 1 ? 'primary' : 'success'">
|
|
|
+ {{ scope.row.sourceType === 1 ? '网站' : '公众号' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="categoryName" label="分类" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-select
|
|
|
+ v-if="scope.row.isEditing"
|
|
|
+ v-model="scope.row.categoryId"
|
|
|
+ placeholder="请选择分类"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="category in categories"
|
|
|
+ :key="category.id"
|
|
|
+ :label="category.name"
|
|
|
+ :value="category.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <el-tag v-else>{{ scope.row.categoryName }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="digest" label="摘要" min-width="300">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-input
|
|
|
+ v-if="scope.row.isEditing"
|
|
|
+ v-model="scope.row.digest"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ <span v-else>{{ scope.row.digest }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="publicTime" label="发布时间" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ formatDate(scope.row.publicTime) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="180" fixed="right">
|
|
|
+ <template #default="scope">
|
|
|
+ <div v-if="!scope.row.isEditing">
|
|
|
+ <el-button size="small" @click="startEditing(scope.row)">编辑</el-button>
|
|
|
+ <el-button size="small" type="danger" @click="deleteNews(scope.row)">删除</el-button>
|
|
|
+ </div>
|
|
|
+ <div v-else>
|
|
|
+ <el-button size="small" type="success" @click="saveNewsField(scope.row)"
|
|
|
+ >保存</el-button
|
|
|
+ >
|
|
|
+ <el-button size="small" @click="cancelEditing(scope.row)">取消</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- Pagination and selection info -->
|
|
|
+ <div class="pagination-container">
|
|
|
+ <div class="pagination-and-selection">
|
|
|
+ <div>
|
|
|
+ <div class="selection-info-bottom" v-if="selectedNews.length > 0">
|
|
|
+ <div class="selection-text">已选择 {{ selectedNews.length }} 项</div>
|
|
|
+ <div class="selection-actions">
|
|
|
+ <el-button type="primary" size="small" @click="showCreateReportDialog = true"
|
|
|
+ >创建报告</el-button
|
|
|
+ >
|
|
|
+ <el-button size="small" @click="showAddToReportDialog = true"
|
|
|
+ >加入已有报告</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="pagination.currentPage"
|
|
|
+ v-model:page-size="pagination.pageSize"
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
+ :total="pagination.total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- Floating selection indicator -->
|
|
|
+ <div
|
|
|
+ class="floating-selection-indicator"
|
|
|
+ v-if="selectedNews.length > 0"
|
|
|
+ @click="showSelectedNews = true"
|
|
|
+ >
|
|
|
+ <el-badge :value="selectedNews.length" :max="99" type="primary">
|
|
|
+ <el-button type="primary" :icon="Collection" circle></el-button>
|
|
|
+ </el-badge>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Selected news dialog -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="showSelectedNews"
|
|
|
+ title="已选择的资讯"
|
|
|
+ width="80%"
|
|
|
+ class="selected-news-dialog"
|
|
|
+ >
|
|
|
+ <el-table :data="selectedNews" border stripe max-height="400">
|
|
|
+ <el-table-column type="index" label="#" width="60"></el-table-column>
|
|
|
+ <el-table-column prop="title" label="标题" min-width="200">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-link :href="scope.row.articleUrl" target="_blank" :underline="false">
|
|
|
+ {{ scope.row.title }}
|
|
|
+ </el-link>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="sourceName" label="来源" width="120"></el-table-column>
|
|
|
+ <el-table-column prop="categoryName" label="分类" width="120"></el-table-column>
|
|
|
+ <el-table-column prop="publicTime" label="发布时间" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ formatDate(scope.row.publicTime) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button type="danger" size="small" @click="removeFromSelection(scope.row)">
|
|
|
+ 移除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="selected-news-actions">
|
|
|
+ <el-button type="primary" @click="showCreateReportDialog = true">创建报告</el-button>
|
|
|
+ <el-button @click="showAddToReportDialog = true">加入已有报告</el-button>
|
|
|
+ <el-button @click="clearSelection">清空选择</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- Create Report Dialog -->
|
|
|
+ <el-dialog v-model="showCreateReportDialog" title="创建报告" width="500px">
|
|
|
+ <el-form :model="newReport" label-width="80px">
|
|
|
+ <el-form-item label="报告名称">
|
|
|
+ <el-input v-model="newReport.reportName" placeholder="请输入报告名称" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="showCreateReportDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="createReport">创建</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- Add to Report Dialog -->
|
|
|
+ <el-dialog v-model="showAddToReportDialog" title="加入已有报告" width="500px">
|
|
|
+ <el-form label-width="80px">
|
|
|
+ <el-form-item label="选择报告">
|
|
|
+ <el-select v-model="selectedReportId" placeholder="请选择报告">
|
|
|
+ <el-option
|
|
|
+ v-for="report in reports"
|
|
|
+ :key="report.reportId"
|
|
|
+ :label="report.reportName"
|
|
|
+ :value="report.reportId"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="showAddToReportDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="addToReport">添加</el-button>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, onMounted, reactive, nextTick } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import { Collection, Refresh } from '@element-plus/icons-vue'
|
|
|
+import type { NewsItem, Category, Report } from '@/types'
|
|
|
+import { format } from 'date-fns'
|
|
|
+import type { TableInstance } from 'element-plus'
|
|
|
+
|
|
|
+// Extend NewsItem type to include editing state
|
|
|
+interface EditableNewsItem extends NewsItem {
|
|
|
+ isEditing?: boolean
|
|
|
+ originalData?: {
|
|
|
+ categoryId: number
|
|
|
+ categoryName: string
|
|
|
+ digest: string
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// State
|
|
|
+const newsList = ref<EditableNewsItem[]>([])
|
|
|
+const categories = ref<Category[]>([])
|
|
|
+const reports = ref<Report[]>([])
|
|
|
+const loading = ref(false)
|
|
|
+const selectedNews = ref<EditableNewsItem[]>([])
|
|
|
+const showCreateReportDialog = ref(false)
|
|
|
+const showAddToReportDialog = ref(false)
|
|
|
+const showSelectedNews = ref(false)
|
|
|
+const selectedReportId = ref<number | null>(null)
|
|
|
+const newsTableRef = ref<TableInstance>()
|
|
|
+
|
|
|
+// Search form
|
|
|
+const searchForm = reactive({
|
|
|
+ beginTime: '',
|
|
|
+ endTime: '',
|
|
|
+ key: '',
|
|
|
+ categoryId: null as number | null,
|
|
|
+})
|
|
|
+
|
|
|
+const newReport = ref({
|
|
|
+ reportName: '',
|
|
|
+})
|
|
|
+
|
|
|
+const pagination = ref({
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0,
|
|
|
+})
|
|
|
+
|
|
|
+// 禁用结束日期的可选范围(不能早于开始日期)
|
|
|
+const startPickerOptions = (time) => {
|
|
|
+ if (searchForm.endTime) {
|
|
|
+ return time.getTime() > new Date(searchForm.endTime).getTime()
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+// 禁用开始日期的可选范围(不能晚于结束日期)
|
|
|
+const endPickerOptions = (time) => {
|
|
|
+ if (searchForm.beginTime) {
|
|
|
+ return time.getTime() < new Date(searchForm.beginTime).getTime()
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+// Import API functions
|
|
|
+import { newsApi, categoryApi, reportApi } from '@/services/api'
|
|
|
+
|
|
|
+// Fetch news with search filters
|
|
|
+const fetchNews = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ // 构造搜索参数
|
|
|
+ const params: any = {
|
|
|
+ pageNum: pagination.value.currentPage,
|
|
|
+ pageSize: pagination.value.pageSize,
|
|
|
+ ...searchForm,
|
|
|
+ }
|
|
|
+
|
|
|
+ const response = await newsApi.getNews(params)
|
|
|
+ // 为每条资讯添加编辑状态
|
|
|
+ newsList.value = response.data.data.map((news) => ({
|
|
|
+ ...news,
|
|
|
+ isEditing: false,
|
|
|
+ }))
|
|
|
+ pagination.value.total = response.data.total
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('获取资讯列表失败')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleSearch = () => {
|
|
|
+ // 重置到第一页
|
|
|
+ pagination.value.currentPage = 1
|
|
|
+ fetchNews()
|
|
|
+}
|
|
|
+
|
|
|
+const resetSearch = () => {
|
|
|
+ // 重置搜索表单
|
|
|
+ searchForm.beginTime = ''
|
|
|
+ searchForm.endTime = ''
|
|
|
+ searchForm.key = ''
|
|
|
+ searchForm.categoryId = null
|
|
|
+ // 重置到第一页
|
|
|
+ pagination.value.currentPage = 1
|
|
|
+ fetchNews()
|
|
|
+}
|
|
|
+
|
|
|
+const fetchCategories = async () => {
|
|
|
+ try {
|
|
|
+ // 获取所有分类,不分页
|
|
|
+ const response = await categoryApi.getCategories({})
|
|
|
+ categories.value = response.data.data
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('获取分类列表失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const fetchReports = async () => {
|
|
|
+ try {
|
|
|
+ const response = await reportApi.getReports({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 1000,
|
|
|
+ })
|
|
|
+ reports.value = response.data.data
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('获取报告列表失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const saveNewsField = async (news: EditableNewsItem, field?: string) => {
|
|
|
+ try {
|
|
|
+ // 如果没有指定字段,保存所有字段
|
|
|
+ const updates: Partial<NewsItem> = field
|
|
|
+ ? { [field]: news[field as keyof NewsItem] }
|
|
|
+ : { categoryId: news.categoryId, digest: news.digest, articleId: news.articleId }
|
|
|
+
|
|
|
+ // 更新资讯
|
|
|
+ await newsApi.updateNews(updates)
|
|
|
+ ElMessage.success('资讯更新成功')
|
|
|
+
|
|
|
+ // 如果是保存所有字段,则退出编辑模式
|
|
|
+ if (!field) {
|
|
|
+ news.isEditing = false
|
|
|
+ // 更新本地列表中的分类名称
|
|
|
+ const category = categories.value.find((c) => c.id === news.categoryId)
|
|
|
+ if (category) {
|
|
|
+ news.categoryName = category.name
|
|
|
+ }
|
|
|
+ // 清除原始数据
|
|
|
+ news.originalData = undefined
|
|
|
+ // 重新获取数据以确保一致性
|
|
|
+ fetchNews()
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('更新资讯失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const deleteNews = async (news: EditableNewsItem) => {
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm('确定要删除该资讯吗?', '提示', {
|
|
|
+ type: 'warning',
|
|
|
+ })
|
|
|
+ const params: Record<string, any> = {
|
|
|
+ articleIds: [news.articleId],
|
|
|
+ }
|
|
|
+ await newsApi.deleteNews(params)
|
|
|
+ ElMessage.success('资讯删除成功')
|
|
|
+ fetchNews()
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ ElMessage.error('删除资讯失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const createReport = async () => {
|
|
|
+ try {
|
|
|
+ if (!newReport.value.reportName) {
|
|
|
+ ElMessage.warning('请输入报告名称')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const params = {
|
|
|
+ articleIds: selectedNews.value.map((news) => news.articleId),
|
|
|
+ reportName: newReport.value.reportName,
|
|
|
+ }
|
|
|
+ await reportApi.createReport(params)
|
|
|
+ ElMessage.success('报告创建成功')
|
|
|
+ showCreateReportDialog.value = false
|
|
|
+ newReport.value.reportName = ''
|
|
|
+ fetchReports()
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('创建报告失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const addToReport = async () => {
|
|
|
+ try {
|
|
|
+ if (!selectedReportId.value) {
|
|
|
+ ElMessage.warning('请选择报告')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const params = {
|
|
|
+ articleIds: selectedNews.value.map((news) => news.articleId),
|
|
|
+ reportId: selectedReportId.value,
|
|
|
+ }
|
|
|
+ await reportApi.addNewsToReport(params)
|
|
|
+ ElMessage.success('资讯已添加到报告')
|
|
|
+ showAddToReportDialog.value = false
|
|
|
+ selectedReportId.value = null
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('添加到报告失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Remove a news item from selection
|
|
|
+const removeFromSelection = (news: EditableNewsItem) => {
|
|
|
+ const index = selectedNews.value.findIndex((item) => item.articleId === news.articleId)
|
|
|
+ if (index > -1) {
|
|
|
+ selectedNews.value.splice(index, 1)
|
|
|
+ newsTableRef.value?.toggleRowSelection(news, false)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Clear all selections
|
|
|
+const clearSelection = async () => {
|
|
|
+ selectedNews.value = []
|
|
|
+ // Also clear selection in the table
|
|
|
+ newsTableRef.value?.clearSelection()
|
|
|
+}
|
|
|
+
|
|
|
+// Event handlers
|
|
|
+const handleSelectionChange = (selection: EditableNewsItem[]) => {
|
|
|
+ selectedNews.value = selection
|
|
|
+}
|
|
|
+
|
|
|
+const handleSizeChange = (val: number) => {
|
|
|
+ pagination.value.pageSize = val
|
|
|
+ fetchNews()
|
|
|
+}
|
|
|
+
|
|
|
+const handleCurrentChange = (val: number) => {
|
|
|
+ pagination.value.currentPage = val
|
|
|
+ fetchNews()
|
|
|
+}
|
|
|
+
|
|
|
+const startEditing = (news: EditableNewsItem) => {
|
|
|
+ // 保存原始数据用于取消编辑
|
|
|
+ news.originalData = {
|
|
|
+ categoryId: news.categoryId,
|
|
|
+ categoryName: news.categoryName,
|
|
|
+ digest: news.digest,
|
|
|
+ }
|
|
|
+ news.isEditing = true
|
|
|
+}
|
|
|
+
|
|
|
+const cancelEditing = (news: EditableNewsItem) => {
|
|
|
+ // 恢复原始数据
|
|
|
+ if (news.originalData) {
|
|
|
+ news.categoryId = news.originalData.categoryId
|
|
|
+ news.categoryName = news.originalData.categoryName
|
|
|
+ news.digest = news.originalData.digest
|
|
|
+ }
|
|
|
+ news.isEditing = false
|
|
|
+}
|
|
|
+
|
|
|
+const formatDate = (date: Date, formatStr = 'yyyy-MM-dd') => {
|
|
|
+ return format(new Date(date), formatStr)
|
|
|
+}
|
|
|
+
|
|
|
+// Lifecycle
|
|
|
+onMounted(() => {
|
|
|
+ fetchNews()
|
|
|
+ fetchCategories()
|
|
|
+ fetchReports()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.news-list {
|
|
|
+ padding: 20px;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+.dateRange {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.dateRange span {
|
|
|
+ padding: 0 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.box-card {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.box-card :deep(.el-card__header) {
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ padding: 15px 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.box-card :deep(.el-card__body) {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ padding: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.header-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.header-title .el-icon {
|
|
|
+ margin-right: 10px;
|
|
|
+ font-size: 20px;
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.search-container {
|
|
|
+ background-color: #fff;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 10px 15px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-form {
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.search-actions {
|
|
|
+ text-align: right;
|
|
|
+ padding-top: 5px;
|
|
|
+}
|
|
|
+
|
|
|
+.box-card :deep(.el-table) {
|
|
|
+ flex: 1;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.box-card :deep(.el-table__header th) {
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ color: #606266;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.box-card :deep(.el-table tr) {
|
|
|
+ height: 45px;
|
|
|
+}
|
|
|
+
|
|
|
+.pagination-container {
|
|
|
+ margin-top: 12px;
|
|
|
+ padding: 8px 0;
|
|
|
+ background-color: #fff;
|
|
|
+ border-top: 1px solid #ebeef5;
|
|
|
+ border-radius: 0 0 4px 4px;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.pagination-and-selection {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.selection-info-bottom {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ background-color: #e8f4ff;
|
|
|
+ padding: 5px 15px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #c3e0ff;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.selection-text {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #409eff;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.selection-actions {
|
|
|
+ margin-left: 5px;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.selected-news-actions {
|
|
|
+ margin: 20px 0;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* Floating selection indicator */
|
|
|
+.floating-selection-indicator {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 200px;
|
|
|
+ right: 50px;
|
|
|
+ z-index: 1000;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.floating-selection-indicator :deep(.el-badge__content) {
|
|
|
+ transform: translateY(-50%) translateX(100%) scale(1) !important;
|
|
|
+ top: 0;
|
|
|
+ right: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .floating-selection-indicator {
|
|
|
+ bottom: 20px;
|
|
|
+ right: 20px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Selected news dialog */
|
|
|
+.selected-news-dialog :deep(.el-dialog__body) {
|
|
|
+ padding: 15px 20px;
|
|
|
+}
|
|
|
+</style>
|