9 changed files with 1972 additions and 0 deletions
@ -0,0 +1,69 @@ |
|||
import request from '@/utils/request'; |
|||
|
|||
export interface CarouselBannerQuery { |
|||
title?: string; |
|||
startTime?: string; |
|||
endTime?: string; |
|||
department?: string; |
|||
category?: string; |
|||
publishTo?: string; |
|||
pageNum?: number; |
|||
pageSize?: number; |
|||
} |
|||
|
|||
export interface CarouselBannerItem { |
|||
id: number; |
|||
title: string; |
|||
publishTime: string; |
|||
category: string; |
|||
department: string; |
|||
publishTo: string; |
|||
viewCount: string; |
|||
} |
|||
|
|||
export interface CarouselBannerResponse { |
|||
code: number; |
|||
msg: string; |
|||
data: { |
|||
total: number; |
|||
list: CarouselBannerItem[]; |
|||
}; |
|||
} |
|||
|
|||
export function listCarouselBanner(query: CarouselBannerQuery) { |
|||
return request<CarouselBannerResponse>({ |
|||
url: '/web/enforcementcode/educationServices/list', |
|||
method: 'get', |
|||
params: query |
|||
}); |
|||
} |
|||
|
|||
export function getCarouselBanner(id: number) { |
|||
return request({ |
|||
url: `/web/supervision/carouselBanner/${id}`, |
|||
method: 'get' |
|||
}); |
|||
} |
|||
|
|||
export function addCarouselBanner(data: any) { |
|||
return request({ |
|||
url: '/web/supervision/carouselBanner', |
|||
method: 'post', |
|||
data |
|||
}); |
|||
} |
|||
|
|||
export function updateCarouselBanner(data: any) { |
|||
return request({ |
|||
url: '/web/supervision/carouselBanner', |
|||
method: 'put', |
|||
data |
|||
}); |
|||
} |
|||
|
|||
export function deleteCarouselBanner(id: number) { |
|||
return request({ |
|||
url: `/web/supervision/carouselBanner/${id}`, |
|||
method: 'delete' |
|||
}); |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
import request from '@/utils/request'; |
|||
|
|||
export interface NoticeInfoQuery { |
|||
content?: string; |
|||
startTime?: string; |
|||
endTime?: string; |
|||
publishTo?: string; |
|||
pageNum?: number; |
|||
pageSize?: number; |
|||
} |
|||
|
|||
export interface NoticeInfoItem { |
|||
id: number; |
|||
content: string; |
|||
publishTime: string; |
|||
displayDays: string; |
|||
publishTo: string; |
|||
status: string; |
|||
} |
|||
|
|||
export interface NoticeInfoResponse { |
|||
code: number; |
|||
msg: string; |
|||
data: { |
|||
total: number; |
|||
list: NoticeInfoItem[]; |
|||
}; |
|||
} |
|||
|
|||
export function listNoticeInfo(query: NoticeInfoQuery) { |
|||
return request<NoticeInfoResponse>({ |
|||
url: '/web/supervision/noticeInfo/list', |
|||
method: 'get', |
|||
params: query |
|||
}); |
|||
} |
|||
|
|||
export function getNoticeInfo(id: number) { |
|||
return request({ |
|||
url: `/web/supervision/noticeInfo/${id}`, |
|||
method: 'get' |
|||
}); |
|||
} |
|||
|
|||
export function addNoticeInfo(data: any) { |
|||
return request({ |
|||
url: '/web/supervision/noticeInfo', |
|||
method: 'post', |
|||
data |
|||
}); |
|||
} |
|||
|
|||
export function updateNoticeInfo(data: any) { |
|||
return request({ |
|||
url: '/web/supervision/noticeInfo', |
|||
method: 'put', |
|||
data |
|||
}); |
|||
} |
|||
|
|||
export function deleteNoticeInfo(id: number) { |
|||
return request({ |
|||
url: `/web/supervision/noticeInfo/${id}`, |
|||
method: 'delete' |
|||
}); |
|||
} |
|||
@ -0,0 +1,262 @@ |
|||
<template> |
|||
<div class="carousel-add-container"> |
|||
<div class="page-header"> |
|||
<h2>新增轮播图</h2> |
|||
</div> |
|||
|
|||
<div class="form-wrapper"> |
|||
<el-form :model="form" ref="formRef" :rules="rules" label-width="100px" class="form-container"> |
|||
<el-form-item label="标题" prop="title"> |
|||
<el-input v-model="form.title" placeholder="请输入标题" class="form-input" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="内容发布到" prop="publishTo"> |
|||
<el-select v-model="form.publishTo" placeholder="请选择" class="form-select"> |
|||
<el-option label="企业端" value="enterprise" /> |
|||
<el-option label="监管端" value="supervision" /> |
|||
<el-option label="企业端+监管端" value="both" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="橱窗选择" prop="windowSelect"> |
|||
<el-select v-model="form.windowSelect" placeholder="请选择" class="form-select"> |
|||
<el-option label="橱窗1" value="window1" /> |
|||
<el-option label="橱窗2" value="window2" /> |
|||
<el-option label="橱窗3" value="window3" /> |
|||
<el-option label="橱窗4" value="window4" /> |
|||
<el-option label="橱窗5" value="window5" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="显示天数" prop="displayDays"> |
|||
<el-input v-model.number="form.displayDays" placeholder="请输入天数" class="form-input" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="链接" prop="link"> |
|||
<el-input v-model="form.link" placeholder="请输入链接" class="form-input" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="橱窗图" prop="windowImage"> |
|||
<div class="upload-area"> |
|||
<div class="upload-box" @click="handleUpload"> |
|||
<div class="upload-icon"> |
|||
<span>+</span> |
|||
</div> |
|||
<span class="upload-text">上传</span> |
|||
</div> |
|||
<p class="upload-hint">建议比例 16:9,小于5M的JPG、PNG格式图片</p> |
|||
<div v-if="form.windowImage" class="preview-image"> |
|||
<img :src="form.windowImage" alt="橱窗图预览" /> |
|||
<span class="remove-btn" @click="removeImage">×</span> |
|||
</div> |
|||
</div> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<div class="form-footer"> |
|||
<el-button @click="handleCancel">取消</el-button> |
|||
<el-button @click="handleSaveDraft">保存草稿</el-button> |
|||
<el-button type="primary" @click="handleSubmit">提交</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { reactive, ref } from 'vue'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { ElMessage } from 'element-plus'; |
|||
|
|||
const router = useRouter(); |
|||
const formRef = ref(); |
|||
|
|||
const form = reactive({ |
|||
title: '', |
|||
publishTo: '', |
|||
windowSelect: '', |
|||
displayDays: '', |
|||
link: '', |
|||
windowImage: '' |
|||
}); |
|||
|
|||
const rules = { |
|||
title: [ |
|||
{ required: true, message: '请输入标题', trigger: 'blur' } |
|||
], |
|||
publishTo: [ |
|||
{ required: true, message: '请选择内容发布到', trigger: 'change' } |
|||
], |
|||
windowSelect: [ |
|||
{ required: true, message: '请选择橱窗', trigger: 'change' } |
|||
], |
|||
displayDays: [ |
|||
{ required: true, message: '请输入显示天数', trigger: 'blur' }, |
|||
{ type: 'number' as const, min: 1, message: '显示天数必须大于0', trigger: 'blur' } |
|||
], |
|||
windowImage: [ |
|||
{ required: true, message: '请上传橱窗图', trigger: 'blur' } |
|||
] |
|||
}; |
|||
|
|||
const handleUpload = () => { |
|||
ElMessage.info('图片上传功能开发中'); |
|||
form.windowImage = 'https://via.placeholder.com/900x100?text=橱窗图预览'; |
|||
}; |
|||
|
|||
const removeImage = () => { |
|||
form.windowImage = ''; |
|||
}; |
|||
|
|||
const handleCancel = () => { |
|||
router.push('/supervision/carouselBanner'); |
|||
}; |
|||
|
|||
const handleSaveDraft = () => { |
|||
ElMessage.success('草稿已保存'); |
|||
}; |
|||
|
|||
const handleSubmit = async () => { |
|||
if (!formRef.value) return; |
|||
|
|||
try { |
|||
const isValid = await formRef.value.validate(); |
|||
if (!isValid) { |
|||
return; |
|||
} |
|||
ElMessage.success('提交成功'); |
|||
router.push('/supervision/carouselBanner'); |
|||
} catch (error) { |
|||
console.error('表单验证失败:', error); |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.carousel-add-container { |
|||
padding: 20px; |
|||
background: #f5f7fa; |
|||
min-height: calc(100vh - 100px); |
|||
} |
|||
|
|||
.page-header { |
|||
background: #fff; |
|||
padding: 16px 20px; |
|||
border-radius: 8px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.page-header h2 { |
|||
margin: 0; |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
color: #303133; |
|||
} |
|||
|
|||
.form-wrapper { |
|||
background: #fff; |
|||
padding: 24px; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.form-container { |
|||
max-width: 600px; |
|||
} |
|||
|
|||
:deep(.el-form-item__label) { |
|||
font-weight: bold; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
:deep(.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label)::before { |
|||
content: '*'; |
|||
color: #f56c6c; |
|||
margin-right: 4px; |
|||
} |
|||
|
|||
.form-input, |
|||
.form-select { |
|||
width: 100%; |
|||
max-width: 400px; |
|||
} |
|||
|
|||
.upload-area { |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.upload-box { |
|||
width: 100%; |
|||
max-width: 400px; |
|||
border: 2px dashed #d9d9d9; |
|||
border-radius: 8px; |
|||
padding: 40px; |
|||
text-align: center; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
} |
|||
|
|||
.upload-box:hover { |
|||
border-color: #409eff; |
|||
background: #f0f5ff; |
|||
} |
|||
|
|||
.upload-icon { |
|||
width: 48px; |
|||
height: 48px; |
|||
margin: 0 auto 12px; |
|||
border: 2px dashed #d9d9d9; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: 24px; |
|||
color: #909399; |
|||
} |
|||
|
|||
.upload-text { |
|||
color: #909399; |
|||
} |
|||
|
|||
.upload-hint { |
|||
margin: 12px 0 0; |
|||
font-size: 12px; |
|||
color: #909399; |
|||
} |
|||
|
|||
.preview-image { |
|||
margin-top: 12px; |
|||
position: relative; |
|||
max-width: 400px; |
|||
} |
|||
|
|||
.preview-image img { |
|||
width: 100%; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.remove-btn { |
|||
position: absolute; |
|||
top: 8px; |
|||
right: 8px; |
|||
width: 24px; |
|||
height: 24px; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
color: #fff; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
cursor: pointer; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.form-footer { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
gap: 12px; |
|||
margin-top: 32px; |
|||
padding-top: 20px; |
|||
border-top: 1px solid #ebeef5; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,394 @@ |
|||
<template> |
|||
<div class="carousel-container"> |
|||
<!-- 搜索区域 --> |
|||
<div class="search-panel"> |
|||
<el-form :model="searchForm" inline> |
|||
<el-form-item label="标题:"> |
|||
<el-input v-model="searchForm.title" placeholder="请输入关键字" class="search-input" clearable |
|||
@keyup.enter="handleSearch" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="发布时间:"> |
|||
<el-date-picker v-model="searchForm.timeRange" type="datetimerange" range-separator="至" |
|||
start-placeholder="开始时间" end-placeholder="结束时间" class="date-picker" format="YYYY-MM-DD HH:mm:ss" |
|||
value-format="YYYY-MM-DD HH:mm:ss" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="内容发布到:"> |
|||
<el-select v-model="searchForm.publishTo" placeholder="请选择" class="search-select" clearable> |
|||
<el-option label="企业端" value="enterprise" /> |
|||
<el-option label="执法端" value="law_enforcement" /> |
|||
<el-option label="监管端" value="supervision" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="状态:"> |
|||
<el-select v-model="searchForm.status" placeholder="请选择" class="search-select" clearable> |
|||
<el-option label="在线" value="online" /> |
|||
<el-option label="下架" value="offline" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleSearch" :loading="loading">搜索</el-button> |
|||
</el-form-item> |
|||
|
|||
<el-form-item> |
|||
<el-button @click="handleReset">重置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- 操作栏 --> |
|||
<div class="action-bar"> |
|||
<div class="action-left"> |
|||
<el-button type="primary" @click="handleAdd">新增</el-button> |
|||
<el-button @click="handleBatchDelete" :disabled="selectedRows.length === 0">删除</el-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 数据表格 --> |
|||
<div class="table-container"> |
|||
<el-table v-loading="loading" :data="tableData" border |
|||
:header-cell-style="{ backgroundColor: '#f5f7fa', fontWeight: 'bold' }" |
|||
@selection-change="handleSelectionChange"> |
|||
<el-table-column type="selection" width="55" /> |
|||
|
|||
<el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip /> |
|||
|
|||
<el-table-column prop="publishTime" label="发布时间" width="180" /> |
|||
|
|||
<el-table-column prop="displayDays" label="显示时长" width="120" /> |
|||
|
|||
<el-table-column prop="publishTo" label="内容发布到" width="150" /> |
|||
|
|||
<el-table-column prop="windowName" label="橱窗" width="120" /> |
|||
|
|||
<el-table-column prop="status" label="状态" width="100"> |
|||
<template #default="scope"> |
|||
<div class="status-badge"> |
|||
<span :class="scope.row.status === 'online' ? 'status-dot online' : 'status-dot offline'"></span> |
|||
{{ scope.row.status === 'online' ? '在线' : '下架' }} |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="操作" fixed="right"> |
|||
<template #default="scope"> |
|||
<el-button size="small" type="text" @click="handleView(scope.row)">查看</el-button> |
|||
<el-button size="small" type="text" @click="handleEdit(scope.row)">编辑</el-button> |
|||
<el-button size="small" type="text" :class="scope.row.status === 'online' ? 'offline-btn' : 'online-btn'" |
|||
@click="handleStatusChange(scope.row)"> |
|||
{{ scope.row.status === 'online' ? '下架' : '上架' }} |
|||
</el-button> |
|||
<el-button size="small" type="text" class="delete-btn" @click="handleDelete(scope.row)">删除</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
|
|||
<!-- 分页 --> |
|||
<div class="pagination-container"> |
|||
<span class="total-text">共 {{ total }} 条数据</span> |
|||
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :total="total" |
|||
:page-sizes="[10, 20, 50, 100]" layout="prev, pager, next, jumper, ->, total, sizes" |
|||
@size-change="handleSizeChange" @current-change="handleCurrentChange" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, onMounted } from 'vue'; |
|||
import { ElMessage, ElMessageBox } from 'element-plus'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { listCarouselBanner, deleteCarouselBanner } from '@/api/supervision/carouselBanner'; |
|||
|
|||
const router = useRouter(); |
|||
|
|||
const loading = ref(false); |
|||
const tableData = ref<any[]>([]); |
|||
const total = ref(0); |
|||
const currentPage = ref(1); |
|||
const pageSize = ref(10); |
|||
const selectedRows = ref<any[]>([]); |
|||
|
|||
const searchForm = reactive({ |
|||
title: '', |
|||
timeRange: [] as string[], |
|||
publishTo: '', |
|||
status: '' |
|||
}); |
|||
|
|||
const getList = async () => { |
|||
loading.value = true; |
|||
try { |
|||
const queryParams = { |
|||
...searchForm, |
|||
startTime: searchForm.timeRange[0] || '', |
|||
endTime: searchForm.timeRange[1] || '', |
|||
pageNum: currentPage.value, |
|||
pageSize: pageSize.value |
|||
}; |
|||
delete queryParams.timeRange; |
|||
|
|||
const res: any = await listCarouselBanner(queryParams); |
|||
|
|||
if (res.code === 200) { |
|||
tableData.value = res.rows || []; |
|||
total.value = res.total || 0; |
|||
} else { |
|||
ElMessage.error(res.msg || '获取数据失败'); |
|||
} |
|||
} catch (error) { |
|||
console.error('获取列表数据失败:', error); |
|||
ElMessage.error('获取数据失败,请检查网络连接'); |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
|
|||
const handleSearch = () => { |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
const handleReset = () => { |
|||
searchForm.title = ''; |
|||
searchForm.timeRange = []; |
|||
searchForm.publishTo = ''; |
|||
searchForm.status = ''; |
|||
currentPage.value = 1; |
|||
handleSearch(); |
|||
ElMessage.info('已重置搜索条件'); |
|||
}; |
|||
|
|||
const handleAdd = () => { |
|||
router.push('/supervision/carouselBanner/add'); |
|||
}; |
|||
|
|||
const handleBatchDelete = () => { |
|||
if (selectedRows.value.length === 0) { |
|||
ElMessage.warning('请选择要删除的数据'); |
|||
return; |
|||
} |
|||
ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 条数据吗?`, '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}).then(() => { |
|||
ElMessage.success('批量删除成功'); |
|||
getList(); |
|||
selectedRows.value = []; |
|||
}).catch(() => { |
|||
ElMessage.info('已取消删除'); |
|||
}); |
|||
}; |
|||
|
|||
const handleSelectionChange = (selection: any[]) => { |
|||
selectedRows.value = selection; |
|||
}; |
|||
|
|||
const handleTop = (row: any) => { |
|||
ElMessage.success(`"${row.title}" 已置顶`); |
|||
}; |
|||
|
|||
const handleStatusChange = (row: any) => { |
|||
const newStatus = row.status === 'online' ? 'offline' : 'online'; |
|||
const actionText = newStatus === 'online' ? '上架' : '下架'; |
|||
|
|||
ElMessageBox.confirm(`确定要${actionText}轮播图"${row.title}"吗?`, '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}).then(() => { |
|||
row.status = newStatus; |
|||
ElMessage.success(`${actionText}成功`); |
|||
}).catch(() => { |
|||
ElMessage.info('已取消操作'); |
|||
}); |
|||
}; |
|||
|
|||
const handleEdit = (row: any) => { |
|||
console.log('编辑:', row); |
|||
ElMessage.info(`编辑标题: ${row.title}`); |
|||
}; |
|||
|
|||
const handleView = (row: any) => { |
|||
console.log('查看:', row); |
|||
ElMessage.info(`查看标题: ${row.title}`); |
|||
}; |
|||
|
|||
const handleDelete = async (row: any) => { |
|||
try { |
|||
await ElMessageBox.confirm('确定要删除这条数据吗?', '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}); |
|||
|
|||
const res: any = await deleteCarouselBanner(row.id); |
|||
if (res.code === 200) { |
|||
ElMessage.success('删除成功'); |
|||
getList(); |
|||
} else { |
|||
ElMessage.error(res.msg || '删除失败'); |
|||
} |
|||
} catch (error: any) { |
|||
if (error !== 'cancel') { |
|||
console.error('删除失败:', error); |
|||
ElMessage.error('删除失败,请重试'); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const handleSizeChange = (val: number) => { |
|||
pageSize.value = val; |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
const handleCurrentChange = (val: number) => { |
|||
currentPage.value = val; |
|||
getList(); |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
getList(); |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.carousel-container { |
|||
padding: 20px; |
|||
background: #f5f7fa; |
|||
min-height: calc(100vh - 100px); |
|||
} |
|||
|
|||
.search-panel { |
|||
background: #fff; |
|||
padding: 16px 20px; |
|||
border-radius: 8px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
:deep(.el-form-item__label) { |
|||
white-space: nowrap; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.search-input, |
|||
.date-picker, |
|||
.search-select { |
|||
width: 150px; |
|||
} |
|||
|
|||
.action-bar { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: flex-start; |
|||
background: #fff; |
|||
padding: 12px 20px; |
|||
border-radius: 8px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.action-left { |
|||
display: flex; |
|||
gap: 8px; |
|||
} |
|||
|
|||
.action-right { |
|||
max-width: 600px; |
|||
} |
|||
|
|||
.tips-label { |
|||
font-weight: bold; |
|||
color: #303133; |
|||
display: block; |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
.tips-list { |
|||
margin: 0; |
|||
padding-left: 20px; |
|||
font-size: 12px; |
|||
color: #606266; |
|||
line-height: 1.8; |
|||
} |
|||
|
|||
.tips-list li { |
|||
margin-bottom: 4px; |
|||
} |
|||
|
|||
.table-container { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
padding: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.status-badge { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
} |
|||
|
|||
.status-dot { |
|||
width: 8px; |
|||
height: 8px; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.status-dot.online { |
|||
background: #67c23a; |
|||
} |
|||
|
|||
.status-dot.offline { |
|||
background: #909399; |
|||
} |
|||
|
|||
.pagination-container { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
background: #fff; |
|||
padding: 12px 20px; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.total-text { |
|||
color: #909399; |
|||
} |
|||
|
|||
:deep(.el-button--text) { |
|||
padding: 4px 8px; |
|||
} |
|||
|
|||
:deep(.online-btn) { |
|||
color: #67c23a; |
|||
} |
|||
|
|||
:deep(.online-btn:hover) { |
|||
color: #67c23a; |
|||
background: rgba(103, 194, 58, 0.1); |
|||
} |
|||
|
|||
:deep(.offline-btn) { |
|||
color: #e6a23c; |
|||
} |
|||
|
|||
:deep(.offline-btn:hover) { |
|||
color: #e6a23c; |
|||
background: rgba(230, 162, 60, 0.1); |
|||
} |
|||
|
|||
:deep(.delete-btn) { |
|||
color: #f56c6c; |
|||
} |
|||
|
|||
:deep(.delete-btn:hover) { |
|||
color: #f56c6c; |
|||
background: rgba(245, 108, 108, 0.1); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,287 @@ |
|||
<template> |
|||
<div class="add-carousel-container"> |
|||
<div class="page-header"> |
|||
<h1>新增普法服务</h1> |
|||
</div> |
|||
|
|||
<el-form :model="form" ref="formRef" :rules="rules" label-width="80px" class="form-container"> |
|||
<el-form-item label="标题:" prop="title"> |
|||
<el-input v-model="form.title" placeholder="请输入标题" class="form-input" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="发布部门:" prop="department"> |
|||
<el-input v-model="form.department" placeholder="请输入发布部门" class="form-input" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="发布部门:" prop="department2"> |
|||
<el-input v-model="form.department2" placeholder="请选择发布部门" class="form-input" /> |
|||
<span class="form-tip">注: 多部门用,分隔</span> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="内容发布在:" prop="publishTo"> |
|||
<el-select v-model="form.publishTo" placeholder="请选择" class="form-select"> |
|||
<el-option label="企业端" value="enterprise" /> |
|||
<el-option label="执法端" value="law_enforcement" /> |
|||
<el-option label="公众端" value="public" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="内容分类:" prop="category"> |
|||
<el-select v-model="form.category" placeholder="请选择" class="form-select"> |
|||
<el-option label="法律法规解读" value="law_解读" /> |
|||
<el-option label="典型案例警示" value="case_warning" /> |
|||
<el-option label="政策文件解读" value="policy_interpretation" /> |
|||
<el-option label="普法宣传" value="law_publicity" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="内容分类:" prop="category2"> |
|||
<el-select v-model="form.category2" placeholder="请选择" class="form-select"> |
|||
<el-option label="法律法规解读" value="law_解读" /> |
|||
<el-option label="典型案例警示" value="case_warning" /> |
|||
<el-option label="政策文件解读" value="policy_interpretation" /> |
|||
<el-option label="普法宣传" value="law_publicity" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="服务信息:" prop="content"> |
|||
<div class="editor-wrapper"> |
|||
<QuillEditor ref="quillEditor" v-model:content="form.content" content-type="html" :toolbar="toolbarOptions" |
|||
theme="snow" class="quill-editor" placeholder="请输入服务信息" /> |
|||
</div> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="附件:"> |
|||
<div class="upload-area"> |
|||
<div class="upload-box" @click="handleUpload"> |
|||
<div class="upload-icon">📁</div> |
|||
<p>点击或将文件拖拽到这里上传</p> |
|||
<p class="upload-tip">支持扩展名称:.pdf .word;文件大小:10MB以内</p> |
|||
</div> |
|||
</div> |
|||
</el-form-item> |
|||
|
|||
<el-form-item class="form-actions"> |
|||
<el-button type="primary" @click="handlePreview">预览</el-button> |
|||
<el-button @click="handleCancel">取消</el-button> |
|||
<el-button type="info" @click="handleSaveDraft">保存草稿箱</el-button> |
|||
<el-button type="success" @click="handleSubmit">发布</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { reactive, ref } from 'vue'; |
|||
import { ElMessage, ElForm, FormRules } from 'element-plus'; |
|||
import { QuillEditor } from '@vueup/vue-quill'; |
|||
import '@vueup/vue-quill/dist/vue-quill.snow.css'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { addCarouselBanner } from '@/api/supervision/carouselBanner'; |
|||
|
|||
const router = useRouter(); |
|||
const formRef = ref<InstanceType<typeof ElForm>>(); |
|||
const quillEditor = ref(); |
|||
|
|||
const rules: FormRules = { |
|||
title: [ |
|||
{ required: true, message: '请输入标题', trigger: 'blur' } |
|||
], |
|||
department: [ |
|||
{ required: true, message: '请输入发布部门', trigger: 'blur' } |
|||
], |
|||
department2: [ |
|||
{ required: true, message: '请选择发布部门', trigger: 'blur' } |
|||
], |
|||
publishTo: [ |
|||
{ required: true, message: '请选择内容发布到', trigger: 'change' } |
|||
], |
|||
category: [ |
|||
{ required: true, message: '请选择内容分类', trigger: 'change' } |
|||
], |
|||
content: [ |
|||
{ required: true, message: '请输入服务信息', trigger: 'blur' } |
|||
] |
|||
}; |
|||
|
|||
const toolbarOptions = [ |
|||
['bold', 'italic', 'underline', 'strike'], |
|||
['blockquote', 'code-block'], |
|||
[{ 'header': 1 }, { 'header': 2 }], |
|||
[{ 'list': 'ordered' }, { 'list': 'bullet' }], |
|||
[{ 'script': 'sub' }, { 'script': 'super' }], |
|||
[{ 'indent': '-1' }, { 'indent': '+1' }], |
|||
[{ 'direction': 'rtl' }], |
|||
[{ 'size': ['small', false, 'large', 'huge'] }], |
|||
[{ 'header': [1, 2, 3, 4, 5, 6, false] }], |
|||
[{ 'color': [] }, { 'background': [] }], |
|||
[{ 'font': [] }], |
|||
[{ 'align': [] }], |
|||
['link', 'image', 'video'], |
|||
['clean'] |
|||
]; |
|||
|
|||
const form = reactive({ |
|||
title: '', |
|||
department: '', |
|||
department2: '', |
|||
publishTo: '', |
|||
category: '', |
|||
category2: '', |
|||
content: '', |
|||
attachment: '' |
|||
}); |
|||
|
|||
const handlePreview = () => { |
|||
ElMessage.info('预览功能开发中'); |
|||
}; |
|||
|
|||
const handleCancel = () => { |
|||
router.push('/supervision/carouselBanner'); |
|||
}; |
|||
|
|||
const handleSaveDraft = () => { |
|||
ElMessage.success('已保存到草稿箱'); |
|||
}; |
|||
|
|||
const handleSubmit = async () => { |
|||
if (!formRef.value) return; |
|||
|
|||
try { |
|||
const isValid = await formRef.value.validate(); |
|||
if (!isValid) { |
|||
return; |
|||
} |
|||
|
|||
const res: any = await addCarouselBanner(form); |
|||
if (res.code === 200) { |
|||
ElMessage.success('发布成功'); |
|||
router.push('/supervision/carouselBanner'); |
|||
} else { |
|||
ElMessage.error(res.msg || '发布失败'); |
|||
} |
|||
} catch (error) { |
|||
console.error('发布失败:', error); |
|||
ElMessage.error('发布失败,请重试'); |
|||
} |
|||
}; |
|||
|
|||
const handleUpload = () => { |
|||
ElMessage.info('文件上传功能开发中'); |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.add-carousel-container { |
|||
padding: 20px; |
|||
background: #f5f7fa; |
|||
min-height: calc(100vh - 100px); |
|||
} |
|||
|
|||
.page-header { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.page-header h1 { |
|||
font-size: 24px; |
|||
font-weight: bold; |
|||
color: #303133; |
|||
} |
|||
|
|||
.form-container { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
padding: 24px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.form-input { |
|||
width: 100%; |
|||
max-width: 500px; |
|||
} |
|||
|
|||
.form-select { |
|||
width: 100%; |
|||
max-width: 500px; |
|||
} |
|||
|
|||
.form-tip { |
|||
display: block; |
|||
color: #909399; |
|||
font-size: 12px; |
|||
margin-top: 4px; |
|||
} |
|||
|
|||
.editor-wrapper { |
|||
width: 100%; |
|||
border: 1px solid #dcdfe6; |
|||
border-radius: 4px; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.quill-editor { |
|||
height: 500px; |
|||
} |
|||
|
|||
:deep(.ql-container) { |
|||
min-height: 500px; |
|||
height: 100%; |
|||
} |
|||
|
|||
:deep(.ql-editor) { |
|||
min-height: 450px; |
|||
} |
|||
|
|||
.upload-area { |
|||
width: 100%; |
|||
} |
|||
|
|||
.upload-box { |
|||
border: 2px dashed #d9d9d9; |
|||
border-radius: 8px; |
|||
padding: 40px; |
|||
text-align: center; |
|||
cursor: pointer; |
|||
transition: all 0.3s; |
|||
} |
|||
|
|||
.upload-box:hover { |
|||
border-color: #409eff; |
|||
background: #f0f5ff; |
|||
} |
|||
|
|||
.upload-icon { |
|||
font-size: 48px; |
|||
color: #409eff; |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.upload-box p { |
|||
color: #606266; |
|||
margin: 8px 0; |
|||
} |
|||
|
|||
.upload-tip { |
|||
font-size: 12px; |
|||
color: #909399 !important; |
|||
} |
|||
|
|||
.form-actions { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
gap: 12px; |
|||
padding-top: 20px; |
|||
border-top: 1px solid #ebeef5; |
|||
} |
|||
|
|||
:deep(.el-form-item__label) { |
|||
font-weight: bold; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
:deep(.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label)::before { |
|||
content: '*'; |
|||
color: #f56c6c; |
|||
margin-right: 4px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,347 @@ |
|||
<template> |
|||
<div class="law-service-container"> |
|||
<!-- 搜索区域 --> |
|||
<div class="search-panel"> |
|||
<el-form :model="searchForm" inline> |
|||
<el-form-item label="标题:"> |
|||
<el-input v-model="searchForm.title" placeholder="请输入关键字" class="search-input" clearable |
|||
@keyup.enter="handleSearch" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="发布时间:"> |
|||
<el-date-picker v-model="searchForm.timeRange" type="datetimerange" range-separator="至" |
|||
start-placeholder="开始时间" end-placeholder="结束时间" class="date-picker publish-time-picker" format="YYYY-MM-DD HH:mm:ss" |
|||
value-format="YYYY-MM-DD HH:mm:ss" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="发布部门:"> |
|||
<el-input v-model="searchForm.department" placeholder="请输入关键字" class="search-input" clearable |
|||
@keyup.enter="handleSearch" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="内容分类:"> |
|||
<el-select v-model="searchForm.category" placeholder="请选择" class="search-select" clearable> |
|||
<el-option label="法律法规解读" value="law_解读" /> |
|||
<el-option label="典型案例警示" value="case_warning" /> |
|||
<el-option label="政策文件解读" value="policy_interpretation" /> |
|||
<el-option label="普法宣传" value="law_publicity" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="内容发布到:"> |
|||
<el-select v-model="searchForm.publishTo" placeholder="请选择" class="search-select" clearable> |
|||
<el-option label="企业端" value="enterprise" /> |
|||
<el-option label="执法端" value="law_enforcement" /> |
|||
<!-- <el-option label="公众端" value="public" /> --> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item> |
|||
<el-button type="primary" :icon="Search" @click="handleSearch" :loading="loading"> |
|||
搜索 |
|||
</el-button> |
|||
</el-form-item> |
|||
|
|||
<el-form-item> |
|||
<el-button :icon="Refresh" @click="handleReset"> |
|||
重置 |
|||
</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- 统计信息 --> |
|||
<div class="stats-bar"> |
|||
<div class="stats-left"> |
|||
<span class="total-count">总浏览次数:{{ totalViewCount }}次</span> |
|||
</div> |
|||
<div class="stats-right"> |
|||
<el-button size="small" type="primary" :icon="Plus" @click="handleAdd">新增</el-button> |
|||
<el-button size="small" type="success" :icon="Upload" @click="handleImport">导入</el-button> |
|||
<el-button size="small" type="warning" :icon="Download" @click="handleExport">导出</el-button> |
|||
<el-button size="small" type="danger" :icon="Delete" @click="handleBatchDelete" :disabled="selectedRows.length === 0">删除</el-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 数据表格 --> |
|||
<div class="table-container"> |
|||
<el-table v-loading="loading" :data="tableData" border |
|||
:header-cell-style="{ backgroundColor: '#f5f7fa', fontWeight: 'bold' }" |
|||
@selection-change="handleSelectionChange"> |
|||
<el-table-column type="selection" width="55" /> |
|||
|
|||
<el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip /> |
|||
|
|||
<el-table-column prop="publishTime" label="发布&转载时间" width="180" /> |
|||
|
|||
<el-table-column prop="category" label="内容分类" width="120" /> |
|||
|
|||
<el-table-column prop="department" label="发布部门" width="150" /> |
|||
|
|||
<el-table-column prop="publishTo" label="内容发布到" width="120" /> |
|||
|
|||
<el-table-column prop="viewCount" label="浏览次数" width="100" /> |
|||
|
|||
<el-table-column label="操作" width="240" fixed="right"> |
|||
<template #default="scope"> |
|||
<el-button size="small" type="text" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button> |
|||
<el-button size="small" type="text" icon="el-icon-view" @click="handleView(scope.row)">查看</el-button> |
|||
<el-button size="small" type="text" icon="el-icon-delete" class="delete-btn" |
|||
@click="handleDelete(scope.row)">删除</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
|
|||
<!-- 分页 --> |
|||
<div class="pagination-container"> |
|||
<span class="total-text">共 {{ total }} 条数据</span> |
|||
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :total="total" |
|||
:page-sizes="[10, 20, 50, 100]" layout="prev, pager, next, jumper, ->, total, sizes" |
|||
@size-change="handleSizeChange" @current-change="handleCurrentChange" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, onMounted } from 'vue'; |
|||
import { ElMessage, ElMessageBox } from 'element-plus'; |
|||
import { Plus, Upload, Download, Delete, Search, Refresh } from '@element-plus/icons-vue'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { listCarouselBanner, deleteCarouselBanner } from '@/api/supervision/carouselBanner'; |
|||
|
|||
const router = useRouter(); |
|||
|
|||
const loading = ref(false); |
|||
const tableData = ref<any[]>([]); |
|||
const total = ref(0); |
|||
const totalViewCount = ref(0); |
|||
const currentPage = ref(1); |
|||
const pageSize = ref(10); |
|||
const selectedRows = ref<any[]>([]); |
|||
|
|||
const searchForm = reactive({ |
|||
title: '', |
|||
timeRange: [] as string[], |
|||
department: '', |
|||
category: '', |
|||
publishTo: '' |
|||
}); |
|||
|
|||
const getList = async () => { |
|||
loading.value = true; |
|||
try { |
|||
const queryParams = { |
|||
...searchForm, |
|||
startTime: searchForm.timeRange[0] || '', |
|||
endTime: searchForm.timeRange[1] || '', |
|||
pageNum: currentPage.value, |
|||
pageSize: pageSize.value |
|||
}; |
|||
delete queryParams.timeRange; |
|||
|
|||
const res: any = await listCarouselBanner(queryParams); |
|||
|
|||
if (res.code === 200) { |
|||
tableData.value = res.rows || []; |
|||
total.value = res.total || 0; |
|||
totalViewCount.value = res.totalViewCount || 0; |
|||
} else { |
|||
ElMessage.error(res.msg || '获取数据失败'); |
|||
} |
|||
} catch (error) { |
|||
console.error('获取列表数据失败:', error); |
|||
ElMessage.error('获取数据失败,请检查网络连接'); |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
|
|||
const handleSearch = () => { |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
const handleReset = () => { |
|||
searchForm.title = ''; |
|||
searchForm.timeRange = []; |
|||
searchForm.department = ''; |
|||
searchForm.category = ''; |
|||
searchForm.publishTo = ''; |
|||
currentPage.value = 1; |
|||
handleSearch(); |
|||
ElMessage.info('已重置搜索条件'); |
|||
}; |
|||
|
|||
const handleAdd = () => { |
|||
router.push('/supervision/carouselBanner/add'); |
|||
}; |
|||
|
|||
const handleImport = () => { |
|||
ElMessage.info('导入功能开发中'); |
|||
}; |
|||
|
|||
const handleExport = () => { |
|||
ElMessage.info('导出功能开发中'); |
|||
}; |
|||
|
|||
const handleBatchDelete = () => { |
|||
if (selectedRows.value.length === 0) { |
|||
ElMessage.warning('请选择要删除的数据'); |
|||
return; |
|||
} |
|||
ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 条数据吗?`, '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}).then(async () => { |
|||
ElMessage.success('批量删除成功'); |
|||
}).catch(() => { |
|||
ElMessage.info('已取消删除'); |
|||
}); |
|||
}; |
|||
|
|||
const handleSelectionChange = (selection: any[]) => { |
|||
selectedRows.value = selection; |
|||
}; |
|||
|
|||
const handleEdit = (row: any) => { |
|||
console.log('编辑:', row); |
|||
ElMessage.info(`编辑标题: ${row.title}`); |
|||
}; |
|||
|
|||
const handleView = (row: any) => { |
|||
console.log('查看:', row); |
|||
ElMessage.info(`查看标题: ${row.title}`); |
|||
}; |
|||
|
|||
const handleDelete = async (row: any) => { |
|||
try { |
|||
await ElMessageBox.confirm('确定要删除这条数据吗?', '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}); |
|||
|
|||
const res: any = await deleteCarouselBanner(row.id); |
|||
if (res.code === 200) { |
|||
ElMessage.success('删除成功'); |
|||
getList(); |
|||
} else { |
|||
ElMessage.error(res.msg || '删除失败'); |
|||
} |
|||
} catch (error: any) { |
|||
if (error !== 'cancel') { |
|||
console.error('删除失败:', error); |
|||
ElMessage.error('删除失败,请重试'); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const handleSizeChange = (val: number) => { |
|||
pageSize.value = val; |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
const handleCurrentChange = (val: number) => { |
|||
currentPage.value = val; |
|||
getList(); |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
getList(); |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.law-service-container { |
|||
padding: 20px; |
|||
background: #f5f7fa; |
|||
min-height: calc(100vh - 100px); |
|||
} |
|||
|
|||
.search-panel { |
|||
background: #fff; |
|||
padding: 16px 20px; |
|||
border-radius: 8px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
/* 让标签不换行 */ |
|||
:deep(.el-form-item__label) { |
|||
white-space: nowrap; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
/* 让所有输入框和选择框宽度统一 */ |
|||
.search-input, |
|||
.date-picker, |
|||
.search-select { |
|||
width: 126px; |
|||
} |
|||
|
|||
:deep(.publish-time-picker) { |
|||
width: 200px; |
|||
} |
|||
.stats-bar { |
|||
display: flex; |
|||
/* justify-content: space-between; */ |
|||
align-items: center; |
|||
background: #fff; |
|||
padding: 12px 20px; |
|||
border-radius: 8px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.stats-left { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.total-count { |
|||
font-weight: bold; |
|||
color: #303133; |
|||
} |
|||
|
|||
.stats-right { |
|||
display: flex; |
|||
gap: 8px; |
|||
margin-left: 30px; |
|||
} |
|||
|
|||
.table-container { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
padding: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.pagination-container { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
background: #fff; |
|||
padding: 12px 20px; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.total-text { |
|||
color: #909399; |
|||
} |
|||
|
|||
:deep(.el-button--text) { |
|||
padding: 4px 8px; |
|||
} |
|||
|
|||
:deep(.delete-btn) { |
|||
color: #f56c6c; |
|||
} |
|||
|
|||
:deep(.delete-btn:hover) { |
|||
color: #f56c6c; |
|||
background: rgba(245, 108, 108, 0.1); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,161 @@ |
|||
<template> |
|||
<div class="notice-add-container"> |
|||
<div class="page-header"> |
|||
<h2>新增公告</h2> |
|||
</div> |
|||
|
|||
<div class="form-wrapper"> |
|||
<el-form :model="form" ref="formRef" :rules="rules" label-width="100px" class="form-container"> |
|||
<el-form-item label="公告内容" prop="content"> |
|||
<el-input |
|||
v-model="form.content" |
|||
type="textarea" |
|||
:rows="2" |
|||
placeholder="请输入公告内容" |
|||
maxlength="100" |
|||
show-word-limit |
|||
class="form-textarea" |
|||
/> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="内容发布到" prop="publishTo"> |
|||
<el-select v-model="form.publishTo" placeholder="请选择" class="form-select"> |
|||
<el-option label="企业端" value="enterprise" /> |
|||
<el-option label="执法端" value="law_enforcement" /> |
|||
<el-option label="企业端+执法端" value="both" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="显示时长" prop="displayDays"> |
|||
<el-input v-model.number="form.displayDays" placeholder="请输入天数" class="form-input" /> |
|||
</el-form-item> |
|||
</el-form> |
|||
|
|||
<div class="form-footer"> |
|||
<el-button @click="handleCancel">取消</el-button> |
|||
<el-button @click="handleSaveDraft">保存草稿箱</el-button> |
|||
<el-button type="primary" @click="handleSubmit">发布</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { reactive, ref } from 'vue'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { ElMessage } from 'element-plus'; |
|||
import { addNoticeInfo } from '@/api/supervision/noticeInfo'; |
|||
|
|||
const router = useRouter(); |
|||
const formRef = ref(); |
|||
|
|||
const form = reactive({ |
|||
content: '', |
|||
publishTo: '', |
|||
displayDays: '' |
|||
}); |
|||
|
|||
const rules = { |
|||
content: [ |
|||
{ required: true, message: '请输入公告内容', trigger: 'blur' }, |
|||
{ max: 100, message: '公告内容最多100个字', trigger: 'blur' } |
|||
], |
|||
publishTo: [ |
|||
{ required: true, message: '请选择内容发布到', trigger: 'change' } |
|||
], |
|||
displayDays: [ |
|||
{ required: true, message: '请输入显示时长', trigger: 'blur' }, |
|||
{ type: 'number' as const, min: 1, message: '显示时长必须大于0', trigger: 'blur' } |
|||
] |
|||
}; |
|||
|
|||
const handleCancel = () => { |
|||
router.push('/general-manage/supervision/noticeInfo'); |
|||
} |
|||
|
|||
const handleSaveDraft = () => { |
|||
ElMessage.success('已保存到草稿箱'); |
|||
}; |
|||
|
|||
const handleSubmit = async () => { |
|||
if (!formRef.value) return; |
|||
|
|||
try { |
|||
const isValid = await formRef.value.validate(); |
|||
if (!isValid) { |
|||
return; |
|||
} |
|||
|
|||
const res: any = await addNoticeInfo(form); |
|||
if (res.code === 200) { |
|||
ElMessage.success('发布成功'); |
|||
router.push('/supervision/noticeInfo'); |
|||
} else { |
|||
ElMessage.error(res.msg || '发布失败'); |
|||
} |
|||
} catch (error) { |
|||
console.error('表单验证失败:', error); |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.notice-add-container { |
|||
padding: 20px; |
|||
background: #f5f7fa; |
|||
min-height: calc(100vh - 100px); |
|||
} |
|||
|
|||
.page-header { |
|||
background: #fff; |
|||
padding: 16px 20px; |
|||
border-radius: 8px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.page-header h2 { |
|||
margin: 0; |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
color: #303133; |
|||
} |
|||
|
|||
.form-wrapper { |
|||
background: #fff; |
|||
padding: 24px; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.form-container { |
|||
max-width: 800px; |
|||
} |
|||
|
|||
:deep(.el-form-item__label) { |
|||
font-weight: bold; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
:deep(.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label)::before { |
|||
content: '*'; |
|||
color: #f56c6c; |
|||
margin-right: 4px; |
|||
} |
|||
|
|||
.form-textarea, |
|||
.form-select, |
|||
.form-input { |
|||
width: 100%; |
|||
max-width: 600px; |
|||
} |
|||
|
|||
.form-footer { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
gap: 12px; |
|||
margin-top: 32px; |
|||
padding-top: 20px; |
|||
border-top: 1px solid #ebeef5; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,348 @@ |
|||
<template> |
|||
<div class="notice-info-container"> |
|||
<!-- 搜索区域 --> |
|||
<div class="search-panel"> |
|||
<el-form :model="searchForm" inline> |
|||
<el-form-item label="公告内容:"> |
|||
<el-input v-model="searchForm.content" placeholder="请输入关键字" class="search-input" clearable |
|||
@keyup.enter="handleSearch" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="发布时间:"> |
|||
<el-date-picker v-model="searchForm.timeRange" type="datetimerange" range-separator="至" |
|||
start-placeholder="开始时间" end-placeholder="结束时间" class="date-picker" format="YYYY-MM-DD HH:mm:ss" |
|||
value-format="YYYY-MM-DD HH:mm:ss" /> |
|||
</el-form-item> |
|||
|
|||
<el-form-item label="内容发布到:"> |
|||
<el-select v-model="searchForm.publishTo" placeholder="请选择" class="search-select" clearable> |
|||
<el-option label="企业端" value="enterprise" /> |
|||
<el-option label="执法端" value="law_enforcement" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
|
|||
<el-form-item> |
|||
<el-button type="primary" @click="handleSearch" :loading="loading">搜索</el-button> |
|||
</el-form-item> |
|||
|
|||
<el-form-item> |
|||
<el-button @click="handleReset">重置</el-button> |
|||
</el-form-item> |
|||
</el-form> |
|||
</div> |
|||
|
|||
<!-- 操作栏 --> |
|||
<div class="action-bar"> |
|||
<div class="action-left"> |
|||
<el-button type="primary" @click="handleAdd">新增</el-button> |
|||
<el-button @click="handleBatchDelete" :disabled="selectedRows.length === 0">删除</el-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 数据表格 --> |
|||
<div class="table-container"> |
|||
<el-table v-loading="loading" :data="tableData" border |
|||
:header-cell-style="{ backgroundColor: '#f5f7fa', fontWeight: 'bold' }" |
|||
@selection-change="handleSelectionChange"> |
|||
<el-table-column type="selection" width="55" /> |
|||
|
|||
<el-table-column prop="content" label="公告内容" min-width="250" show-overflow-tooltip /> |
|||
|
|||
<el-table-column prop="publishTime" label="发布时间" width="180" /> |
|||
|
|||
<el-table-column prop="displayDays" label="显示时长" width="100" /> |
|||
|
|||
<el-table-column prop="publishTo" label="内容发布到" width="120" /> |
|||
|
|||
<el-table-column prop="status" label="状态" width="100"> |
|||
<template #default="scope"> |
|||
<div class="status-badge"> |
|||
<span :class="scope.row.status === 'online' ? 'status-dot online' : 'status-dot offline'"></span> |
|||
{{ scope.row.status === 'online' ? '在线' : '下架' }} |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
|
|||
<el-table-column label="操作" width="180" fixed="right"> |
|||
<template #default="scope"> |
|||
<el-button size="small" type="text" :class="scope.row.status === 'online' ? 'offline-btn' : 'online-btn'" |
|||
@click="handleStatusChange(scope.row)"> |
|||
{{ scope.row.status === 'online' ? '下架' : '上架' }} |
|||
</el-button> |
|||
<el-button size="small" type="text" @click="handleEdit(scope.row)">编辑</el-button> |
|||
<el-button size="small" type="text" class="delete-btn" @click="handleDelete(scope.row)">删除</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</div> |
|||
|
|||
<!-- 分页 --> |
|||
<div class="pagination-container"> |
|||
<span class="total-text">共 {{ total }} 条数据</span> |
|||
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :total="total" |
|||
:page-sizes="[10, 20, 50, 100]" layout="prev, pager, next, jumper, ->, total, sizes" |
|||
@size-change="handleSizeChange" @current-change="handleCurrentChange" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, onMounted } from 'vue'; |
|||
import { ElMessage, ElMessageBox } from 'element-plus'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { listNoticeInfo, deleteNoticeInfo } from '@/api/supervision/noticeInfo'; |
|||
|
|||
const router = useRouter(); |
|||
|
|||
const loading = ref(false); |
|||
const tableData = ref<any[]>([]); |
|||
const total = ref(0); |
|||
const currentPage = ref(1); |
|||
const pageSize = ref(10); |
|||
const selectedRows = ref<any[]>([]); |
|||
|
|||
const searchForm = reactive({ |
|||
content: '', |
|||
timeRange: [] as string[], |
|||
publishTo: '' |
|||
}); |
|||
|
|||
const getList = async () => { |
|||
loading.value = true; |
|||
try { |
|||
const queryParams = { |
|||
...searchForm, |
|||
startTime: searchForm.timeRange[0] || '', |
|||
endTime: searchForm.timeRange[1] || '', |
|||
pageNum: currentPage.value, |
|||
pageSize: pageSize.value |
|||
}; |
|||
delete queryParams.timeRange; |
|||
|
|||
const res: any = await listNoticeInfo(queryParams); |
|||
|
|||
if (res.code === 200) { |
|||
tableData.value = res.rows || []; |
|||
total.value = res.total || 0; |
|||
} else { |
|||
ElMessage.error(res.msg || '获取数据失败'); |
|||
} |
|||
} catch (error) { |
|||
console.error('获取列表数据失败:', error); |
|||
ElMessage.error('获取数据失败,请检查网络连接'); |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
|
|||
const handleSearch = () => { |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
const handleReset = () => { |
|||
searchForm.content = ''; |
|||
searchForm.timeRange = []; |
|||
searchForm.publishTo = ''; |
|||
currentPage.value = 1; |
|||
handleSearch(); |
|||
ElMessage.info('已重置搜索条件'); |
|||
}; |
|||
|
|||
const handleAdd = () => { |
|||
router.push('/supervision/noticeInfo/add'); |
|||
}; |
|||
|
|||
const handleBatchDelete = () => { |
|||
if (selectedRows.value.length === 0) { |
|||
ElMessage.warning('请选择要删除的数据'); |
|||
return; |
|||
} |
|||
ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 条数据吗?`, '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}).then(() => { |
|||
ElMessage.success('批量删除成功'); |
|||
getList(); |
|||
selectedRows.value = []; |
|||
}).catch(() => { |
|||
ElMessage.info('已取消删除'); |
|||
}); |
|||
}; |
|||
|
|||
const handleSelectionChange = (selection: any[]) => { |
|||
selectedRows.value = selection; |
|||
}; |
|||
|
|||
const handleStatusChange = (row: any) => { |
|||
const newStatus = row.status === 'online' ? 'offline' : 'online'; |
|||
const actionText = newStatus === 'online' ? '上架' : '下架'; |
|||
|
|||
ElMessageBox.confirm(`确定要${actionText}公告"${row.content}"吗?`, '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}).then(() => { |
|||
row.status = newStatus; |
|||
ElMessage.success(`${actionText}成功`); |
|||
}).catch(() => { |
|||
ElMessage.info('已取消操作'); |
|||
}); |
|||
}; |
|||
|
|||
const handleEdit = (row: any) => { |
|||
ElMessage.info(`编辑公告: ${row.content}`); |
|||
}; |
|||
|
|||
const handleDelete = async (row: any) => { |
|||
try { |
|||
await ElMessageBox.confirm('确定要删除这条数据吗?', '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}); |
|||
|
|||
const res: any = await deleteNoticeInfo(row.id); |
|||
if (res.code === 200) { |
|||
ElMessage.success('删除成功'); |
|||
getList(); |
|||
} else { |
|||
ElMessage.error(res.msg || '删除失败'); |
|||
} |
|||
} catch (error: any) { |
|||
if (error !== 'cancel') { |
|||
console.error('删除失败:', error); |
|||
ElMessage.error('删除失败,请重试'); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const handleSizeChange = (val: number) => { |
|||
pageSize.value = val; |
|||
currentPage.value = 1; |
|||
getList(); |
|||
}; |
|||
|
|||
const handleCurrentChange = (val: number) => { |
|||
currentPage.value = val; |
|||
getList(); |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
getList(); |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.notice-info-container { |
|||
padding: 20px; |
|||
background: #f5f7fa; |
|||
min-height: calc(100vh - 100px); |
|||
} |
|||
|
|||
.search-panel { |
|||
background: #fff; |
|||
padding: 16px 20px; |
|||
border-radius: 8px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
:deep(.el-form-item__label) { |
|||
white-space: nowrap; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.search-input, |
|||
.date-picker, |
|||
.search-select { |
|||
width: 150px; |
|||
} |
|||
|
|||
.action-bar { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
background: #fff; |
|||
padding: 12px 20px; |
|||
border-radius: 8px; |
|||
margin-bottom: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.action-left { |
|||
display: flex; |
|||
gap: 8px; |
|||
} |
|||
|
|||
.table-container { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
padding: 16px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.status-badge { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
} |
|||
|
|||
.status-dot { |
|||
width: 8px; |
|||
height: 8px; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.status-dot.online { |
|||
background: #67c23a; |
|||
} |
|||
|
|||
.status-dot.offline { |
|||
background: #909399; |
|||
} |
|||
|
|||
.pagination-container { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
background: #fff; |
|||
padding: 12px 20px; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.total-text { |
|||
color: #909399; |
|||
} |
|||
|
|||
:deep(.el-button--text) { |
|||
padding: 4px 8px; |
|||
} |
|||
|
|||
:deep(.online-btn) { |
|||
color: #67c23a; |
|||
} |
|||
|
|||
:deep(.online-btn:hover) { |
|||
color: #67c23a; |
|||
background: rgba(103, 194, 58, 0.1); |
|||
} |
|||
|
|||
:deep(.offline-btn) { |
|||
color: #e6a23c; |
|||
} |
|||
|
|||
:deep(.offline-btn:hover) { |
|||
color: #e6a23c; |
|||
background: rgba(230, 162, 60, 0.1); |
|||
} |
|||
|
|||
:deep(.delete-btn) { |
|||
color: #f56c6c; |
|||
} |
|||
|
|||
:deep(.delete-btn:hover) { |
|||
color: #f56c6c; |
|||
background: rgba(245, 108, 108, 0.1); |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue