463 lines
12 KiB
Vue
463 lines
12 KiB
Vue
<template>
|
|
<div class="admin-content">
|
|
<div class="dashboard-header">
|
|
<div class="header-left">
|
|
<h2 class="title">{{ $t('admin.content.title') }}</h2>
|
|
<p class="subtitle">Manage and review content items</p>
|
|
</div>
|
|
<div class="header-actions">
|
|
<el-button type="primary" :icon="Plus" @click="dialogVisible = true">
|
|
{{ $t('admin.content.add') }}
|
|
</el-button>
|
|
<el-button :icon="Refresh" @click="refresh">
|
|
{{ $t('admin.content.refresh') }}
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 筛选和搜索 -->
|
|
<el-card class="filter-card" shadow="never">
|
|
<div class="filters">
|
|
<el-row :gutter="16">
|
|
<el-col :span="6">
|
|
<el-input
|
|
v-model="filters.keyword"
|
|
:placeholder="$t('admin.content.search')"
|
|
clearable
|
|
:prefix-icon="Search"
|
|
/>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<el-select
|
|
v-model="filters.status"
|
|
:placeholder="$t('admin.content.status')"
|
|
clearable
|
|
style="width: 100%"
|
|
>
|
|
<el-option
|
|
v-for="status in statusOptions"
|
|
:key="status.value"
|
|
:label="status.label"
|
|
:value="status.value"
|
|
/>
|
|
</el-select>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<el-select
|
|
v-model="filters.type"
|
|
:placeholder="$t('admin.content.type')"
|
|
clearable
|
|
style="width: 100%"
|
|
>
|
|
<el-option
|
|
v-for="type in typeOptions"
|
|
:key="type.value"
|
|
:label="type.label"
|
|
:value="type.value"
|
|
/>
|
|
</el-select>
|
|
</el-col>
|
|
<el-col :span="6">
|
|
<el-date-picker
|
|
v-model="filters.dateRange"
|
|
type="daterange"
|
|
range-separator="-"
|
|
:start-placeholder="$t('admin.common.startDate')"
|
|
:end-placeholder="$t('admin.common.endDate')"
|
|
style="width: 100%"
|
|
/>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<el-button type="primary" :icon="Search" @click="search">
|
|
{{ $t('admin.common.search') }}
|
|
</el-button>
|
|
<el-button @click="resetFilters">{{ $t('admin.common.reset') }}</el-button>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</el-card>
|
|
|
|
<!-- 内容列表 -->
|
|
<el-card class="table-card" shadow="never">
|
|
<!-- stripe -->
|
|
<el-table :data="tableData" style="width: 100%" >
|
|
<el-table-column prop="id" label="ID" width="80" />
|
|
<el-table-column prop="title" :label="$t('admin.content.title')" min-width="200" />
|
|
<el-table-column prop="author" :label="$t('admin.content.author')" width="120" />
|
|
<el-table-column prop="type" :label="$t('admin.content.type')" width="100">
|
|
<template #default="{ row }">
|
|
<el-tag :type="getTypeTagType(row.type)">
|
|
{{ $t(`admin.content.typeOptions.${row.type}`) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="status" :label="$t('admin.content.status')" width="100">
|
|
<template #default="{ row }">
|
|
<el-tag :type="getStatusTagType(row.status)">
|
|
{{ $t(`admin.content.statusOptions.${row.status}`) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="publishDate" :label="$t('admin.content.publishDate')" width="120" />
|
|
<el-table-column prop="views" :label="$t('admin.content.views')" width="80" />
|
|
<el-table-column :label="$t('admin.content.actions')" width="200" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button link type="primary" :icon="View" @click="handleView(row)">
|
|
{{ $t('admin.content.view') }}
|
|
</el-button>
|
|
<el-button link type="primary" :icon="Edit" @click="handleEdit(row)">
|
|
{{ $t('admin.content.edit') }}
|
|
</el-button>
|
|
<el-button link type="danger" :icon="Delete" @click="handleDelete(row)">
|
|
{{ $t('admin.content.delete') }}
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<!-- 分页 -->
|
|
<div class="pagination-wrapper">
|
|
<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>
|
|
</el-card>
|
|
|
|
<!-- 添加/编辑对话框 -->
|
|
<el-dialog
|
|
v-model="dialogVisible"
|
|
:title="isEditing ? $t('admin.content.edit') : $t('admin.content.add')"
|
|
width="600px"
|
|
>
|
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
|
<el-form-item :label="$t('admin.content.title')" prop="title">
|
|
<el-input v-model="form.title" />
|
|
</el-form-item>
|
|
<el-form-item :label="$t('admin.content.type')" prop="type">
|
|
<el-select v-model="form.type" style="width: 100%">
|
|
<el-option
|
|
v-for="type in typeOptions"
|
|
:key="type.value"
|
|
:label="type.label"
|
|
:value="type.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item :label="$t('admin.content.status')" prop="status">
|
|
<el-select v-model="form.status" style="width: 100%">
|
|
<el-option
|
|
v-for="status in statusOptions"
|
|
:key="status.value"
|
|
:label="status.label"
|
|
:value="status.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item :label="$t('admin.content.author')" prop="author">
|
|
<el-input v-model="form.author" />
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<span class="dialog-footer">
|
|
<el-button @click="dialogVisible = false">{{ $t('admin.common.cancel') }}</el-button>
|
|
<el-button type="primary" @click="handleSubmit">{{ $t('admin.common.save') }}</el-button>
|
|
</span>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { Plus, Search, Edit, Delete, View, Refresh } from '@element-plus/icons-vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
const { t } = useI18n()
|
|
|
|
// 数据
|
|
const tableData = ref([
|
|
{
|
|
id: 1,
|
|
title: 'How to use Vue3 effectively',
|
|
author: 'John Doe',
|
|
type: 'article',
|
|
status: 'published',
|
|
publishDate: '2024-01-15',
|
|
views: 1250
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Beautiful sunset photography',
|
|
author: 'Jane Smith',
|
|
type: 'image',
|
|
status: 'pending',
|
|
publishDate: '2024-01-14',
|
|
views: 890
|
|
},
|
|
{
|
|
id: 3,
|
|
title: 'JavaScript ES6 Features Tutorial',
|
|
author: 'Bob Johnson',
|
|
type: 'video',
|
|
status: 'draft',
|
|
publishDate: '2024-01-13',
|
|
views: 567
|
|
}
|
|
])
|
|
|
|
const filters = reactive({
|
|
keyword: '',
|
|
status: '',
|
|
type: '',
|
|
dateRange: []
|
|
})
|
|
|
|
const pagination = reactive({
|
|
currentPage: 1,
|
|
pageSize: 10,
|
|
total: 3
|
|
})
|
|
|
|
const form = reactive({
|
|
title: '',
|
|
type: '',
|
|
status: '',
|
|
author: ''
|
|
})
|
|
|
|
const dialogVisible = ref(false)
|
|
const isEditing = ref(false)
|
|
const formRef = ref()
|
|
|
|
const statusOptions = [
|
|
{ value: 'published', label: t('admin.content.statusOptions.published') },
|
|
{ value: 'pending', label: t('admin.content.statusOptions.pending') },
|
|
{ value: 'draft', label: t('admin.content.statusOptions.draft') },
|
|
{ value: 'rejected', label: t('admin.content.statusOptions.rejected') }
|
|
]
|
|
|
|
const typeOptions = [
|
|
{ value: 'article', label: t('admin.content.typeOptions.article') },
|
|
{ value: 'image', label: t('admin.content.typeOptions.image') },
|
|
{ value: 'video', label: t('admin.content.typeOptions.video') }
|
|
]
|
|
|
|
const rules = {
|
|
title: [{ required: true, message: 'Please enter title', trigger: 'blur' }],
|
|
type: [{ required: true, message: 'Please select type', trigger: 'change' }],
|
|
status: [{ required: true, message: 'Please select status', trigger: 'change' }],
|
|
author: [{ required: true, message: 'Please enter author', trigger: 'blur' }]
|
|
}
|
|
|
|
// 方法
|
|
const getStatusTagType = (status) => {
|
|
const types = {
|
|
published: 'success',
|
|
pending: 'warning',
|
|
draft: 'info',
|
|
rejected: 'danger'
|
|
}
|
|
return types[status] || 'info'
|
|
}
|
|
|
|
const getTypeTagType = (type) => {
|
|
const types = {
|
|
article: 'primary',
|
|
image: 'success',
|
|
video: 'warning'
|
|
}
|
|
return types[type] || 'info'
|
|
}
|
|
|
|
const search = () => {
|
|
// 模拟搜索
|
|
console.log('Search with filters:', filters)
|
|
}
|
|
|
|
const resetFilters = () => {
|
|
filters.keyword = ''
|
|
filters.status = ''
|
|
filters.type = ''
|
|
filters.dateRange = []
|
|
}
|
|
|
|
const refresh = () => {
|
|
// 模拟刷新
|
|
console.log('Refresh data')
|
|
ElMessage.success('Data refreshed successfully')
|
|
}
|
|
|
|
const handleAddContent = () => {
|
|
isEditing.value = false
|
|
Object.assign(form, {
|
|
title: '',
|
|
type: '',
|
|
status: '',
|
|
author: ''
|
|
})
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
const handleView = (row) => {
|
|
ElMessage.info(`View content: ${row.title}`)
|
|
}
|
|
|
|
const handleEdit = (row) => {
|
|
isEditing.value = true
|
|
Object.assign(form, row)
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
const handleDelete = (row) => {
|
|
ElMessageBox.confirm(
|
|
`Are you sure you want to delete "${row.title}"?`,
|
|
'Confirm Deletion',
|
|
{
|
|
confirmButtonText: 'Yes',
|
|
cancelButtonText: 'No',
|
|
type: 'warning'
|
|
}
|
|
).then(() => {
|
|
ElMessage.success('Content deleted successfully')
|
|
})
|
|
}
|
|
|
|
const handleSubmit = () => {
|
|
formRef.value.validate((valid) => {
|
|
if (valid) {
|
|
ElMessage.success('Content saved successfully')
|
|
dialogVisible.value = false
|
|
}
|
|
})
|
|
}
|
|
|
|
const handleSizeChange = (size) => {
|
|
pagination.pageSize = size
|
|
console.log('Page size changed to:', size)
|
|
}
|
|
|
|
const handleCurrentChange = (page) => {
|
|
pagination.currentPage = page
|
|
console.log('Current page changed to:', page)
|
|
}
|
|
|
|
onMounted(() => {
|
|
// 初始化数据
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.admin-content {
|
|
padding: 20px;
|
|
background-color: #f5f5f5;
|
|
min-height: calc(100vh - 60px);
|
|
}
|
|
|
|
.dashboard-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: white;
|
|
padding: 24px;
|
|
border-radius: 8px;
|
|
margin-bottom: 16px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.header-left {
|
|
flex: 1;
|
|
}
|
|
|
|
.header-left .title {
|
|
margin: 0;
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.header-left .subtitle {
|
|
margin: 8px 0 0 0;
|
|
font-size: 14px;
|
|
color: #6b7280;
|
|
}
|
|
|
|
.header-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
.filter-card {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.filters {
|
|
padding: 0;
|
|
}
|
|
|
|
.table-card {
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.pagination-wrapper {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 20px 0;
|
|
}
|
|
|
|
.dialog-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 12px;
|
|
}
|
|
|
|
/* 响应式设计 */
|
|
@media (max-width: 768px) {
|
|
.admin-content {
|
|
padding: 16px;
|
|
}
|
|
|
|
.dashboard-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 16px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.header-actions {
|
|
width: 100%;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.filters .el-col {
|
|
margin-bottom: 12px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.admin-content {
|
|
padding: 12px;
|
|
}
|
|
|
|
.dashboard-header {
|
|
padding: 16px;
|
|
}
|
|
|
|
.header-actions {
|
|
flex-direction: column;
|
|
width: 100%;
|
|
}
|
|
|
|
.header-actions .el-button {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style> |