729 lines
20 KiB
Vue
729 lines
20 KiB
Vue
<template>
|
||
<div class="article-admin-page">
|
||
<div class="page-title-wrap">
|
||
<h1>企业动态</h1>
|
||
</div>
|
||
|
||
<div class="stats-grid">
|
||
<div
|
||
v-for="item in statsCards"
|
||
:key="item.type"
|
||
class="stats-card"
|
||
>
|
||
<div class="stats-icon" :class="item.iconClass">
|
||
<i :class="item.icon"></i>
|
||
</div>
|
||
<div>
|
||
<div class="stats-label">{{ item.label }}</div>
|
||
<div class="stats-value">{{ item.count }} 篇</div>
|
||
<div class="stats-read">阅读 {{ item.views }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="filter-card">
|
||
<el-form :model="queryForm" inline class="filter-form">
|
||
<el-form-item label="文章类型">
|
||
<el-select v-model="queryForm.type" size="small" clearable placeholder="全部类型" @change="handleSearch">
|
||
<el-option label="企业动态" value="企业动态" />
|
||
<el-option label="产品动态" value="产品动态" />
|
||
<el-option label="行业洞察" value="行业洞察" />
|
||
<el-option label="活动资讯" value="活动资讯" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="状态">
|
||
<el-select v-model="queryForm.status" size="small" clearable placeholder="全部状态" @change="handleSearch">
|
||
<el-option label="已发布" value="published" />
|
||
<el-option label="草稿" value="draft" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-input
|
||
v-model.trim="queryForm.keyword"
|
||
size="small"
|
||
clearable
|
||
placeholder="搜索文章标题..."
|
||
@keyup.enter.native="handleSearch"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" size="small" icon="el-icon-search" @click="handleSearch">搜索</el-button>
|
||
<el-button type="primary" size="small" icon="el-icon-plus" @click="openArticleEditor">新建文章</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<div class="table-card">
|
||
<el-table
|
||
:data="pagedArticles"
|
||
v-loading="loading"
|
||
class="article-table"
|
||
style="width: 100%"
|
||
empty-text="暂无文章,点击“新建文章”开始创作"
|
||
>
|
||
<el-table-column label="序号" width="80">
|
||
<template slot-scope="scope">
|
||
{{ getRowNo(scope.$index) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
prop="title"
|
||
label="标题"
|
||
min-width="240"
|
||
align="left"
|
||
header-align="left"
|
||
class-name="title-column"
|
||
label-class-name="title-column-header"
|
||
show-overflow-tooltip
|
||
>
|
||
<template slot-scope="scope">
|
||
<span class="article-title">{{ scope.row.title }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="类型" width="120">
|
||
<template slot-scope="scope">
|
||
<span class="type-tag" :class="getTypeClass(scope.row.type)">{{ scope.row.type }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" width="110">
|
||
<template slot-scope="scope">
|
||
<span class="status-tag" :class="scope.row.status === 'published' ? 'is-published' : 'is-draft'">
|
||
<i></i>{{ scope.row.status === 'published' ? '已发布' : '草稿' }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="updateTime" label="创建时间" width="170" />
|
||
<el-table-column prop="publishTime" label="发布时间" width="140" />
|
||
<el-table-column label="阅读数据" width="130">
|
||
<template slot-scope="scope">
|
||
<span v-if="scope.row.status === 'published'" class="views-text">
|
||
<i class="el-icon-view"></i>{{ scope.row.views }}
|
||
</span>
|
||
<span v-else class="empty-read">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180" fixed="right">
|
||
<template slot-scope="scope">
|
||
<el-button
|
||
type="text"
|
||
size="mini"
|
||
:disabled="scope.row.status === 'published'"
|
||
@click="editArticle(scope.row)"
|
||
>
|
||
编辑
|
||
</el-button>
|
||
<el-button
|
||
v-if="scope.row.status === 'published'"
|
||
type="text"
|
||
size="mini"
|
||
class="warning-text"
|
||
@click="unpublishArticle(scope.row)"
|
||
>
|
||
下架
|
||
</el-button>
|
||
<el-button
|
||
v-else
|
||
type="text"
|
||
size="mini"
|
||
class="success-text"
|
||
@click="publishArticle(scope.row)"
|
||
>
|
||
发布
|
||
</el-button>
|
||
<el-button
|
||
type="text"
|
||
size="mini"
|
||
class="danger-text"
|
||
:disabled="scope.row.status === 'published'"
|
||
@click="deleteArticle(scope.row)"
|
||
>
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<div class="table-pagination">
|
||
<span class="pagination-info">显示 {{ pageStart }}-{{ pageEnd }} 条,共 {{ total }} 条</span>
|
||
<el-pagination
|
||
background
|
||
layout="prev, pager, next, jumper"
|
||
:current-page.sync="page"
|
||
:page-size="pageSize"
|
||
:total="total"
|
||
@current-change="handlePageChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<article-editor-dialog
|
||
:visible.sync="editorVisible"
|
||
:article="currentArticle"
|
||
@save="handleSaveArticle"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import ArticleEditorDialog from './ArticleEditorDialog.vue'
|
||
import {
|
||
reqAddNews,
|
||
reqDeleteNews,
|
||
reqEditNews,
|
||
reqNewsListAdmin,
|
||
reqPublishNews,
|
||
reqUnpublishNews
|
||
} from '@/api/newsapi/newsapi'
|
||
export default {
|
||
name: 'NewsLog',
|
||
components: { ArticleEditorDialog },
|
||
data() {
|
||
return {
|
||
page: 1,
|
||
pageSize: 10,
|
||
total: 0,
|
||
loading: false,
|
||
editorVisible: false,
|
||
currentArticle: null,
|
||
queryForm: {
|
||
type: '',
|
||
status: '',
|
||
keyword: ''
|
||
},
|
||
articleList: []
|
||
}
|
||
},
|
||
computed: {
|
||
filteredArticles() {
|
||
return this.articleList
|
||
},
|
||
pagedArticles() {
|
||
return this.articleList
|
||
},
|
||
pageStart() {
|
||
if (!this.total) return 0
|
||
return (this.page - 1) * this.pageSize + 1
|
||
},
|
||
pageEnd() {
|
||
if (!this.total) return 0
|
||
return Math.min(this.page * this.pageSize, this.total)
|
||
},
|
||
statsCards() {
|
||
const map = {
|
||
企业动态: { label: '企业动态', type: '企业动态', icon: 'el-icon-document', iconClass: 'blue' },
|
||
产品动态: { label: '产品动态', type: '产品动态', icon: 'el-icon-box', iconClass: 'green' },
|
||
行业洞察: { label: '行业洞察', type: '行业洞察', icon: 'el-icon-data-analysis', iconClass: 'amber' },
|
||
活动资讯: { label: '活动资讯', type: '活动资讯', icon: 'el-icon-date', iconClass: 'purple' }
|
||
}
|
||
return Object.values(map).map(item => {
|
||
const list = this.articleList.filter(article => article.type === item.type)
|
||
return {
|
||
...item,
|
||
count: list.length,
|
||
views: list.reduce((total, article) => total + Number(article.views || 0), 0)
|
||
}
|
||
})
|
||
}
|
||
},
|
||
created() {
|
||
// 页面创建后立即拉取第一页文章列表。
|
||
this.getArticleList()
|
||
},
|
||
methods: {
|
||
getRowNo(index) {
|
||
// 根据当前分页计算表格序号。
|
||
return (this.page - 1) * this.pageSize + index + 1
|
||
},
|
||
getTypeClass(type) {
|
||
// 按文章类型返回对应的标签样式类。
|
||
const classMap = {
|
||
企业动态: 'type-blue',
|
||
产品动态: 'type-green',
|
||
行业洞察: 'type-amber',
|
||
活动资讯: 'type-violet'
|
||
}
|
||
return classMap[type] || 'type-blue'
|
||
},
|
||
handleSearch() {
|
||
// 筛选条件变化后从第一页重新查询。
|
||
this.page = 1
|
||
this.getArticleList()
|
||
},
|
||
handlePageChange(page) {
|
||
// 用户切换分页后更新页码并重新拉取数据。
|
||
this.page = page
|
||
this.getArticleList()
|
||
},
|
||
openArticleEditor() {
|
||
// 清空当前文章,打开新建弹窗。
|
||
this.currentArticle = null
|
||
this.editorVisible = true
|
||
},
|
||
editArticle(row) {
|
||
// 编辑回显直接读取当前表格行数据,不再请求详情接口。
|
||
this.currentArticle = { ...row }
|
||
this.editorVisible = true
|
||
},
|
||
getApiStatus(status) {
|
||
// 页面状态转成接口需要的状态值:1 上架,0 草稿/下架。
|
||
return String(status) === '1' || status === 'published' ? '1' : '0'
|
||
},
|
||
getViewStatus(status) {
|
||
// 接口状态转成页面使用的状态枚举。
|
||
return String(status) === '1' || status === 'published' ? 'published' : 'draft'
|
||
},
|
||
isSuccess(res) {
|
||
// 兼容不同接口的成功标识。
|
||
return res && (res.status === true || res.status === 'true' || res.code === 200)
|
||
},
|
||
buildQueryParams() {
|
||
// 组装后台列表查询参数。
|
||
return {
|
||
url_link: window.location.href,
|
||
current_page: String(this.page),
|
||
page_size: String(this.pageSize),
|
||
title: this.queryForm.keyword.trim(),
|
||
article_type: this.queryForm.type,
|
||
status: this.queryForm.status ? this.getApiStatus(this.queryForm.status) : ''
|
||
}
|
||
},
|
||
normalizeArticle(row) {
|
||
// 将接口字段统一转换成页面表格和编辑弹窗使用的字段。
|
||
return {
|
||
id: row.id,
|
||
title: row.title || '',
|
||
summary: row.summary || '',
|
||
type: row.article_type || row.type || '企业动态',
|
||
status: this.getViewStatus(row.status),
|
||
publishTime: row.publish_time || row.publishTime || '',
|
||
updateTime: row.update_time || row.updateTime || '',
|
||
views: row.read_count || row.views || 0,
|
||
content: row.content || '',
|
||
coverUrl: row.cover_img || row.coverUrl || '',
|
||
coverName: row.cover_name || row.coverName || ''
|
||
}
|
||
},
|
||
getResponseList(res) {
|
||
// 兼容接口可能返回的多种列表结构。
|
||
if (Array.isArray(res.data)) return res.data
|
||
if (res.data && Array.isArray(res.data.data)) return res.data.data
|
||
if (res.data && Array.isArray(res.data.list)) return res.data.list
|
||
if (Array.isArray(res.list)) return res.list
|
||
return []
|
||
},
|
||
getResponseTotal(res, list) {
|
||
// 兼容接口可能返回的多种总数字段。
|
||
const data = res.data && !Array.isArray(res.data) ? res.data : {}
|
||
const pagination = res.pagination || data.pagination || data.page || {}
|
||
return Number(
|
||
res.total ||
|
||
res.total_count ||
|
||
data.total ||
|
||
data.total_count ||
|
||
pagination.total ||
|
||
list.length
|
||
)
|
||
},
|
||
buildArticlePayload(article) {
|
||
// 将编辑弹窗字段转换成新增/编辑接口需要的字段。
|
||
return {
|
||
...(article.id ? { id: article.id } : {}),
|
||
url_link: window.location.href,
|
||
title: article.title,
|
||
article_type: article.type,
|
||
summary: article.summary,
|
||
update_time: article.update_time || article.updateTime,
|
||
publish_time: article.publish_time || article.publishTime,
|
||
cover_img: article.coverUrl,
|
||
content: article.content,
|
||
status: this.getApiStatus(article.status)
|
||
}
|
||
},
|
||
async confirmAction(title, message, type = 'warning', confirmButtonText = '确定') {
|
||
// 操作前弹出二次确认,用户取消时直接终止后续请求。
|
||
const isDanger = type === 'error'
|
||
const isSuccess = type === 'success'
|
||
try {
|
||
await this.$confirm(message, title, {
|
||
confirmButtonText,
|
||
cancelButtonText: '取消',
|
||
type,
|
||
customClass: `news-confirm-box ${isDanger ? 'news-confirm-box--danger' : isSuccess ? 'news-confirm-box--success' : 'news-confirm-box--warning'}`,
|
||
confirmButtonClass: `news-confirm-btn news-confirm-btn--confirm ${isDanger ? 'is-danger' : isSuccess ? 'is-success' : ''}`,
|
||
cancelButtonClass: 'news-confirm-btn news-confirm-btn--cancel'
|
||
})
|
||
return true
|
||
} catch (error) {
|
||
return false
|
||
}
|
||
},
|
||
async getArticleList() {
|
||
// 开启表格 loading,防止用户误以为页面没有响应。
|
||
this.loading = true
|
||
try {
|
||
// 按当前筛选条件和分页请求后台文章列表。
|
||
const res = await reqNewsListAdmin(this.buildQueryParams())
|
||
if (this.isSuccess(res)) {
|
||
// 请求成功后解析列表数据。
|
||
const list = this.getResponseList(res)
|
||
// 将接口数据映射成页面展示字段。
|
||
this.articleList = list.map(item => this.normalizeArticle(item))
|
||
// 读取接口总数,用于分页器和底部统计。
|
||
this.total = this.getResponseTotal(res, list)
|
||
return
|
||
}
|
||
// 接口返回失败时清空列表,避免展示旧数据。
|
||
this.articleList = []
|
||
this.total = 0
|
||
} catch (error) {
|
||
// 请求异常时清空列表并提示用户。
|
||
this.articleList = []
|
||
this.total = 0
|
||
this.$message.error('文章列表加载失败')
|
||
} finally {
|
||
// 无论成功失败都关闭 loading。
|
||
this.loading = false
|
||
}
|
||
},
|
||
async handleSaveArticle(article) {
|
||
// 根据是否存在 id 判断是编辑还是新增。
|
||
const request = article.id ? reqEditNews : reqAddNews
|
||
try {
|
||
// 调用新增或编辑接口保存文章。
|
||
const res = await request(this.buildArticlePayload(article))
|
||
if (!this.isSuccess(res)) {
|
||
// 接口明确返回失败时展示后端错误信息。
|
||
this.$message.error((res && (res.message || res.msg)) || '保存失败')
|
||
return
|
||
}
|
||
// 保存成功后提示,并刷新列表展示最新数据。
|
||
this.$message.success(this.getApiStatus(article.status) === '1' ? '发布成功' : '草稿已保存')
|
||
this.getArticleList()
|
||
} catch (error) {
|
||
// 网络或程序异常时提示保存失败。
|
||
this.$message.error('保存失败')
|
||
}
|
||
},
|
||
async publishArticle(row) {
|
||
// 上架前先二次确认,避免误操作。
|
||
const confirmed = await this.confirmAction('确认发布企业动态文章?', '请确认该文章不包含敏感词汇。', 'success', '确定发布')
|
||
if (!confirmed) return
|
||
try {
|
||
// 调用上架接口。
|
||
const res = await reqPublishNews({ id: row.id })
|
||
if (!this.isSuccess(res)) {
|
||
// 接口返回失败时展示错误并停止刷新。
|
||
this.$message.error((res && (res.message || res.msg)) || '发布失败')
|
||
return
|
||
}
|
||
// 上架成功后刷新列表。
|
||
this.$message.success('发布成功')
|
||
this.getArticleList()
|
||
} catch (error) {
|
||
// 请求异常时提示用户。
|
||
this.$message.error('发布失败')
|
||
}
|
||
},
|
||
async unpublishArticle(row) {
|
||
// 下架前先二次确认,避免误操作。
|
||
const confirmed = await this.confirmAction('确定要下架这篇文章吗?', '下架后将在企业动态页面隐藏。')
|
||
if (!confirmed) return
|
||
try {
|
||
// 调用下架接口。
|
||
const res = await reqUnpublishNews({ id: row.id })
|
||
if (!this.isSuccess(res)) {
|
||
// 接口返回失败时展示错误并停止刷新。
|
||
this.$message.error((res && (res.message || res.msg)) || '下架失败')
|
||
return
|
||
}
|
||
// 下架成功后刷新列表。
|
||
this.$message.success('下架成功')
|
||
this.getArticleList()
|
||
} catch (error) {
|
||
// 请求异常时提示用户。
|
||
this.$message.error('下架失败')
|
||
}
|
||
},
|
||
async deleteArticle(row) {
|
||
// 删除前先二次确认,避免误删文章。
|
||
const confirmed = await this.confirmAction('确认删除', `删除后不可恢复,确定要删除「${row.title}」吗?`, 'error')
|
||
if (!confirmed) return
|
||
try {
|
||
// 调用删除接口。
|
||
const res = await reqDeleteNews({ id: row.id })
|
||
if (!this.isSuccess(res)) {
|
||
// 接口返回失败时展示错误并停止刷新。
|
||
this.$message.error((res && (res.message || res.msg)) || '删除失败')
|
||
return
|
||
}
|
||
// 删除当前页最后一条时,自动回到上一页。
|
||
if (this.articleList.length === 1 && this.page > 1) this.page -= 1
|
||
// 删除成功后刷新列表。
|
||
this.$message.success('删除成功')
|
||
this.getArticleList()
|
||
} catch (error) {
|
||
// 请求异常时提示用户。
|
||
this.$message.error('删除失败')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.article-admin-page {
|
||
min-height: calc(100vh - 84px);
|
||
padding: 24px;
|
||
background: #f3f7ff;
|
||
}
|
||
|
||
.page-title-wrap {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.page-title-wrap h1 {
|
||
margin: 0;
|
||
color: #1f2937;
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.stats-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 16px;
|
||
background: #fff;
|
||
border: 1px solid #edf0f5;
|
||
border-radius: 14px;
|
||
}
|
||
|
||
.stats-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 10px;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.stats-icon.blue {
|
||
color: #3b82f6;
|
||
background: #eff6ff;
|
||
}
|
||
|
||
.stats-icon.green {
|
||
color: #10b981;
|
||
background: #ecfdf5;
|
||
}
|
||
|
||
.stats-icon.amber {
|
||
color: #f59e0b;
|
||
background: #fffbeb;
|
||
}
|
||
|
||
.stats-icon.purple {
|
||
color: #8b5cf6;
|
||
background: #f5f3ff;
|
||
}
|
||
|
||
.stats-label,
|
||
.stats-read {
|
||
color: #9ca3af;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.stats-value {
|
||
margin: 3px 0;
|
||
color: #1f2937;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.filter-card,
|
||
.table-card {
|
||
background: #fff;
|
||
border: 1px solid #edf0f5;
|
||
border-radius: 14px;
|
||
}
|
||
|
||
.filter-card {
|
||
padding: 16px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.filter-form {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-form ::v-deep .el-form-item {
|
||
margin-right: 14px;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.filter-form ::v-deep .el-input,
|
||
.filter-form ::v-deep .el-select {
|
||
width: 170px;
|
||
}
|
||
|
||
.filter-form ::v-deep .el-input__inner {
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.filter-form ::v-deep .el-button--small {
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.table-card {
|
||
overflow: hidden;
|
||
}
|
||
|
||
.article-table ::v-deep th {
|
||
background: #f8fafc;
|
||
color: #6b7280;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.article-table ::v-deep .title-column .cell,
|
||
.article-table ::v-deep .title-column-header .cell {
|
||
justify-content: flex-start;
|
||
/* text-align: left !important; */
|
||
}
|
||
|
||
.article-title {
|
||
display: block;
|
||
width: 100%;
|
||
color: #1f2937;
|
||
font-weight: 600;
|
||
text-align: left;
|
||
}
|
||
|
||
.type-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.type-blue {
|
||
color: #2563eb;
|
||
background: #eff6ff;
|
||
}
|
||
|
||
.type-green {
|
||
color: #059669;
|
||
background: #ecfdf5;
|
||
}
|
||
|
||
.type-amber {
|
||
color: #d97706;
|
||
background: #fffbeb;
|
||
}
|
||
|
||
.type-violet {
|
||
color: #db2777;
|
||
background: #fdf2f8;
|
||
}
|
||
|
||
.status-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.status-tag i {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.status-tag.is-published {
|
||
color: #16a34a;
|
||
}
|
||
|
||
.status-tag.is-published i {
|
||
background: #22c55e;
|
||
}
|
||
|
||
.status-tag.is-draft {
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.status-tag.is-draft i {
|
||
background: #d1d5db;
|
||
}
|
||
|
||
.views-text {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
color: #6b7280;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.empty-read {
|
||
color: #d1d5db;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.success-text {
|
||
color: #16a34a;
|
||
}
|
||
|
||
.warning-text {
|
||
color: #f59e0b;
|
||
}
|
||
|
||
.danger-text {
|
||
color: #f87171;
|
||
}
|
||
|
||
.table-pagination {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 14px 20px;
|
||
border-top: 1px solid #edf0f5;
|
||
}
|
||
|
||
.pagination-info {
|
||
color: #6b7280;
|
||
font-size: 14px;
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.stats-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.article-admin-page {
|
||
padding: 12px;
|
||
}
|
||
|
||
.stats-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.table-pagination {
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
align-items: flex-start;
|
||
}
|
||
}
|
||
</style> |