试一下

This commit is contained in:
dmz 2025-12-05 23:11:43 +08:00
parent 6e02d90999
commit b936c8e0ca
37 changed files with 8181 additions and 0 deletions

27
amms_front/index.html Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邺城博物馆管理系统</title>
<style>
*{
margin: 0;
padding: 0;
}
html, body {
height: 100%;
width: 100%;
}
#app {
height: 100%;
width: 100%
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

BIN
amms_front/public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,41 @@
import axios from '@/utils/request'
// 查询藏品分类列表
export function listItemCategory(query) {
return axios.get('/itemCategory/list',{
params: {
...query
}
})
}
// 查询全部藏品分类列表
export function listAllItemCategory(query) {
return axios.get('/itemCategory/listAll', {
params: {
...query
}
})
}
// 查询藏品分类详细
export function getItemCategory(id) {
return axios.get('/itemCategory/info/' + id)
}
// 新增藏品分类
export function addItemCategory(data) {
return axios.post('/itemCategory/add', {
...data
})
}
// 修改藏品分类
export function updateItemCategory(data) {
return axios.put('/itemCategory', data)
}
// 删除藏品分类
export function delItemCategory(id) {
return axios.delete('/itemCategory/' + id)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,174 @@
import { createRouter, createWebHistory } from 'vue-router'
import { ElMessage } from 'element-plus'
import Layout from '@/components/Layout.vue'
import HLayout from '@/components/HLayout.vue'
import Cookies from 'js-cookie'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
redirect: '/tourist/home'
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login.vue')
},
{
path: '/register',
name: 'register',
component: () => import('@/views/register.vue')
},
// 管理端路径配置
{
path: '/admin',
name: 'admin',
component: Layout,
redirect: '/admin/statisticalAnalysis',
children: [
// 数据统计
{
path: 'statisticalAnalysis',
name: 'statisticalAnalysis',
component: () => import('@/views/admin/statisticalAnalysis/index.vue')
},
// 藏品管理
{
path: 'relic',
name: 'relic',
component: () => import('@/views/admin/relic/index.vue')
},
// 藏品分类管理
{
path: 'itemCategory',
name: 'itemCategory',
component: () => import('@/views/admin/itemCategory/index.vue')
},
// 预约时段管理
{
path: 'reservationTimeSlot',
name: 'reservationTimeSlot',
component: () => import('@/views/admin/reservationTimeSlot/index.vue')
},
// 预约明细管理
{
path: 'reservation',
name: 'reservation',
component: () => import('@/views/admin/reservation/index.vue')
},
// 公告管理
{
path: 'announcement',
name: 'announcement',
component: () => import('@/views/admin/announcement/index.vue')
},
// 轮播图管理
{
path: 'carousel',
name: 'carousel',
component: () => import('@/views/admin/carousel/index.vue')
},
// 用户管理
{
path: 'user',
name: 'user',
component: () => import('@/views/admin/user/user.vue')
},
// 博物馆信息管理
{
path: 'museumIntro',
name: 'museumIntro',
component: () => import('@/views/admin/museumIntro/index.vue')
},
// 用户详情
{
path: 'userInfo',
name: 'userInfo',
component: () => import('@/views/admin/user/userInfo.vue')
}
]
},
// 游客端路径配置
{
path: '/tourist',
name: 'tourist',
component: HLayout,
redirect: '/tourist/home',
children: [
// 首页
{
path: 'home',
name: 'tHome',
component: () => import('@/views/tourist/home/index.vue')
},
// 藏品
{
path: 'relic',
name: 'tRelic',
component: () => import('@/views/tourist/relic/index.vue')
},
// 藏品详情
{
path: 'relic/detail/:relicId',
name: 'tRelicDetail',
component: () => import('@/views/tourist/relic/detail.vue')
},
// 预约
{
path: 'reservation',
name: 'tReservation',
component: () => import('@/views/tourist/reservation/reserve.vue')
},
// 我的预约
{
path: 'myReservation',
name: 'myReservation',
component: () => import('@/views/tourist/reservation/index.vue')
},
// 公告
{
path: 'announcement',
name: 'tAnnouncement',
component: () => import('@/views/tourist/announcement/index.vue')
},
// 我的收藏
{
path: 'myRelicCollection',
name: 'tRelicCollection',
component: () => import('@/views/tourist/itemCollection/index.vue')
},
// 用户详情
{
path: 'userInfo',
name: 'tUserInfo',
component: () => import('@/views/tourist/user/userInfo.vue')
}
]
}
],
})
router.beforeEach((to, from, next) => {
const type = Cookies.get('role')
const publicPaths = ['/login', '/register', '/tourist/home', '/tourist/relic', '/tourist/announcement']
if (publicPaths.includes(to.path) || to.path.startsWith('/tourist/relic/detail')) {
next()
return
}
if (!type && to.path === '/tourist/reservation') {
ElMessage && ElMessage.warning('请先登录')
next(false)
return
}
if (!type) {
next('/login')
return
}
next()
})
export default router

View File

@ -0,0 +1,332 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="标题" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入标题"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="announcementList">
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="置顶标识" align="center" prop="isTop">
<template #default="scope">
<el-tag v-if="scope.row.isTop === 1" type="danger">置顶</el-tag>
<el-tag v-else type="info">普通</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">显示</el-tag>
<el-tag v-else type="warning">隐藏</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
link
type="primary"
v-if="scope.row.isTop === 0"
@click="toggleTop(scope.row, 1)"
icon="Top"
>设为置顶</el-button>
<el-button
link
type="primary"
v-if="scope.row.isTop === 1"
@click="toggleTop(scope.row, 0)"
icon="Bottom"
>取消置顶</el-button>
<el-button
link
type="primary"
v-if="scope.row.status === 1"
@click="toggleStatus(scope.row, 0)"
icon="Hide"
>隐藏</el-button>
<el-button
link
type="primary"
v-if="scope.row.status === 0"
@click="toggleStatus(scope.row, 1)"
icon="View"
>显示</el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改公告对话框 -->
<el-dialog :title="title" v-model="open" width="650px" append-to-body :close-on-click-modal="false">
<el-form ref="announcementRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="内容" prop="content">
<Editor
:value="form.content || ''"
@updateValue="updateContent"
filePath="announcementContentImg" />
</el-form-item>
<!-- <el-form-item label="置顶标识" prop="isTop">
<el-input v-model="form.isTop" placeholder="请输入置顶标识" />
</el-form-item> -->
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Announcement">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listAnnouncement, getAnnouncement, delAnnouncement, addAnnouncement, updateAnnouncement, setTopAnnouncement } from "@/api/announcement";
import Editor from "@/components/Editor.vue";
const { proxy } = getCurrentInstance();
const announcementList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
title: null,
content: null,
isTop: null,
status: null,
creator: null,
updater: null
},
rules: {
title: [
{ required: true, message: "标题不能为空", trigger: "blur" }
],
content: [
{ required: true, message: "内容不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询公告列表 */
function getList() {
loading.value = true;
listAnnouncement(queryParams.value).then(response => {
announcementList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
title: null,
content: null,
isTop: null,
status: null,
createTime: null,
creator: null,
updateTime: null,
updater: null
};
const announcementRef = proxy.$refs["announcementRef"]
if (announcementRef) {
announcementRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加公告";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getAnnouncement(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改公告";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["announcementRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addAnnouncement(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateAnnouncement(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除公告编号为"' + row.id + '"的数据项?').then(function() {
return delAnnouncement(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
function toggleTop(row, nextVal) {
const text = nextVal === 1 ? '是否要将该公告置顶?' : '是否要取消该公告的置顶?';
proxy.$confirm(text).then(function() {
if (nextVal === 1) {
return setTopAnnouncement(row.id)
} else {
return updateAnnouncement({ id: row.id, isTop: nextVal })
}
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
function toggleStatus(row, nextVal) {
const text = nextVal === 1 ? '是否要显示该公告?' : '是否要隐藏该公告?';
proxy.$confirm(text).then(function() {
return updateAnnouncement({ id: row.id, status: nextVal });
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/announcement/export', {
// ...queryParams.value
// }, `announcement_${new Date().getTime()}.xlsx`)
// }
function updateContent(value) {
form.value.content = value;
}
getList();
</script>

View File

@ -0,0 +1,351 @@
<template>
<div class="app-container">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="carouselList">
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="图片" align="center" prop="imageUrl">
<template #default="scope">
<el-image
v-if="scope.row.imageUrl"
:src="proxy.getFilePrefix + scope.row.imageUrl"
:preview-src-list="[proxy.getFilePrefix + scope.row.imageUrl]"
preview-teleported
fit="cover"
style="width: 80px; height: 80px; cursor: zoom-in"
/>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="跳转链接" align="center" prop="link" />
<el-table-column label="排序序号" align="center" prop="sort" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">显示</el-tag>
<el-tag v-else type="warning">隐藏</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
link
type="primary"
v-if="scope.row.status === 1"
@click="toggleStatus(scope.row, 0)"
icon="Hide"
>隐藏</el-button>
<el-button
link
type="primary"
v-if="scope.row.status === 0"
@click="toggleStatus(scope.row, 1)"
icon="View"
>显示</el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改轮播图对话框 -->
<el-dialog :title="title" v-model="open" width="650px" append-to-body :close-on-click-modal="false">
<el-form ref="carouselRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="图片" prop="imageUrl">
<el-upload
action="/api/files/upload"
:headers="headers"
name="file"
:data="{filePath: 'carousel'}"
list-type="picture-card"
:file-list="imageFileList"
:limit="1"
:on-success="handleImageSuccess"
:on-remove="handleImageRemove"
:on-exceed="handleImageExceed"
:before-upload="beforeImageUpload"
accept="image/*"
>
<el-icon><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="跳转链接" prop="link">
<el-input v-model="form.link" placeholder="请输入跳转链接" />
</el-form-item>
<el-form-item label="排序序号" prop="sort">
<el-input v-model="form.sort" placeholder="请输入排序序号" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Carousel">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listCarousel, getCarousel, delCarousel, addCarousel, updateCarousel } from "@/api/carousel";
const { proxy } = getCurrentInstance();
const carouselList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
title: null,
imageUrl: null,
link: null,
sort: null,
status: null,
},
rules: {
title: [
{ required: true, message: "标题不能为空", trigger: "blur" }
],
imageUrl: [
{ required: true, message: "图片地址不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
const imageFileList = ref([]);
const headers = {};
/** 查询轮播图列表 */
function getList() {
loading.value = true;
listCarousel(queryParams.value).then(response => {
carouselList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
title: null,
imageUrl: null,
link: null,
sort: null,
status: null,
createTime: null,
updateTime: null
};
const carouselRef = proxy.$refs["carouselRef"]
if (carouselRef) {
carouselRef.resetFields();
}
imageFileList.value = [];
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加轮播图";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getCarousel(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改轮播图";
isAdd.value = false;
imageFileList.value = form.value.imageUrl ? [{ name: '图片', url: proxy.getFilePrefix + form.value.imageUrl }] : [];
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["carouselRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addCarousel(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateCarousel(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
function handleImageSuccess(res, file, fileList) {
form.value.imageUrl = res;
imageFileList.value = [{ name: file.name, url: proxy.getFilePrefix + form.value.imageUrl }];
const carouselRef = proxy.$refs["carouselRef"];
if (carouselRef) {
carouselRef.validateField('imageUrl');
}
}
function handleImageRemove() {
form.value.imageUrl = null;
imageFileList.value = [];
}
function handleImageExceed(files, fileList) {
proxy.$message({
message: '只允许上传一张图片',
type: 'warning'
});
}
function beforeImageUpload(file) {
const ok = file.size / 1024 / 1024 <= 10;
if (!ok) {
proxy.$message({
message: '图片大小不能超过10MB',
type: 'error'
});
}
return ok;
}
function toggleStatus(row, nextVal) {
const text = nextVal === 1 ? '是否要显示该轮播图?' : '是否要隐藏该轮播图?';
proxy.$confirm(text).then(function() {
return updateCarousel({ id: row.id, status: nextVal });
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除轮播图编号为"' + row.id + '"的数据项?').then(function() {
return delCarousel(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/carousel/export', {
// ...queryParams.value
// }, `carousel_${new Date().getTime()}.xlsx`)
// }
getList();
</script>
<style scoped>
.el-upload--picture-card {
width: 120px;
height: 120px;
}
.el-upload-list__item {
width: 120px !important;
height: 120px !important;
}
</style>

View File

@ -0,0 +1,246 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="分类名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入分类名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="itemCategoryList">
<!-- <el-table-column label="分类ID" align="center" prop="id" /> -->
<el-table-column label="分类名称" align="center" prop="name" />
<!-- <el-table-column label="创建者" align="center" prop="creator" />
<el-table-column label="更新者" align="center" prop="updater" /> -->
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改藏品分类对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="itemCategoryRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="分类名称" prop="name">
<el-input v-model="form.name" placeholder="请输入分类名称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ItemCategory">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listItemCategory, getItemCategory, delItemCategory, addItemCategory, updateItemCategory } from "@/api/itemCategory";
const { proxy } = getCurrentInstance();
const itemCategoryList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
creator: null,
updater: null,
},
rules: {
name: [
{ required: true, message: "分类名称不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询藏品分类列表 */
function getList() {
loading.value = true;
listItemCategory(queryParams.value).then(response => {
itemCategoryList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
name: null,
creator: null,
createTime: null,
updater: null,
updateTime: null,
remark: null
};
const itemCategoryRef = proxy.$refs["itemCategoryRef"]
if (itemCategoryRef) {
itemCategoryRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加藏品分类";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getItemCategory(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改藏品分类";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["itemCategoryRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addItemCategory(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateItemCategory(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除藏品分类编号为"' + row.id + '"的数据项?').then(function() {
return delItemCategory(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/itemCategory/export', {
// ...queryParams.value
// }, `itemCategory_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,251 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="用户id" prop="userId">
<el-input
v-model="queryParams.userId"
placeholder="请输入用户id"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="藏品id" prop="itemId">
<el-input
v-model="queryParams.itemId"
placeholder="请输入藏品id"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="itemCollectionList">
<el-table-column label="收藏id" align="center" prop="id" />
<el-table-column label="用户id" align="center" prop="userId" />
<el-table-column label="藏品id" align="center" prop="itemId" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改收藏对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="itemCollectionRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="用户id" prop="userId">
<el-input v-model="form.userId" placeholder="请输入用户id" />
</el-form-item>
<el-form-item label="藏品id" prop="itemId">
<el-input v-model="form.itemId" placeholder="请输入藏品id" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ItemCollection">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listItemCollection, getItemCollection, delItemCollection, addItemCollection, updateItemCollection } from "@/api/itemCollection";
const { proxy } = getCurrentInstance();
const itemCollectionList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
userId: null,
itemId: null,
},
rules: {
userId: [
{ required: true, message: "用户id不能为空", trigger: "blur" }
],
itemId: [
{ required: true, message: "藏品id不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询收藏列表 */
function getList() {
loading.value = true;
listItemCollection(queryParams.value).then(response => {
itemCollectionList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
userId: null,
itemId: null,
createTime: null
};
const itemCollectionRef = proxy.$refs["itemCollectionRef"]
if (itemCollectionRef) {
itemCollectionRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加收藏";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getItemCollection(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改收藏";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["itemCollectionRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addItemCollection(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateItemCollection(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除收藏编号为"' + row.id + '"的数据项?').then(function() {
return delItemCollection(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/itemCollection/export', {
// ...queryParams.value
// }, `itemCollection_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,253 @@
<template>
<div class="app-container">
<el-table v-loading="loading" :data="museumIntroList">
<el-table-column label="博物馆名称" align="center" prop="museumName" />
<el-table-column label="内容" align="center" prop="content" :show-overflow-tooltip="true" />
<!-- <el-table-column label="Logo地址" align="center" prop="logoUrl" /> -->
<el-table-column label="地址" align="center" prop="address" />
<el-table-column label="联系电话" align="center" prop="phone" />
<el-table-column label="营业开始时间" align="center" prop="openTime" width="180">
<template #default="scope">
<span>{{ scope.row.openTime }}</span>
</template>
</el-table-column>
<el-table-column label="营业结束时间" align="center" prop="closeTime" width="180">
<template #default="scope">
<span>{{ scope.row.closeTime}}</span>
</template>
</el-table-column>
<!-- <el-table-column label="更新者id" align="center" prop="updater" /> -->
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button> -->
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改博物馆简介对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="museumIntroRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="内容">
<editor v-model="form.content" :min-height="192"/>
</el-form-item>
<el-form-item label="Logo地址" prop="logoUrl">
<el-input v-model="form.logoUrl" placeholder="请输入Logo地址" />
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="form.address" placeholder="请输入地址" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="营业开始时间" prop="openTime">
<el-date-picker clearable
v-model="form.openTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择营业开始时间">
</el-date-picker>
</el-form-item>
<el-form-item label="营业结束时间" prop="closeTime">
<el-date-picker clearable
v-model="form.closeTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择营业结束时间">
</el-date-picker>
</el-form-item>
<el-form-item label="更新者id" prop="updater">
<el-input v-model="form.updater" placeholder="请输入更新者id" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="MuseumIntro">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listMuseumIntro, getMuseumIntro, delMuseumIntro, addMuseumIntro, updateMuseumIntro } from "@/api/museumIntro";
const { proxy } = getCurrentInstance();
const museumIntroList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
content: null,
logoUrl: null,
address: null,
phone: null,
openTime: null,
closeTime: null,
updater: null
},
rules: {
content: [
{ required: true, message: "内容不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询博物馆简介列表 */
function getList() {
loading.value = true;
listMuseumIntro(queryParams.value).then(response => {
museumIntroList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
museumName: null,
content: null,
logoUrl: null,
address: null,
phone: null,
openTime: null,
closeTime: null,
updateTime: null,
updater: null
};
const museumIntroRef = proxy.$refs["museumIntroRef"]
if (museumIntroRef) {
museumIntroRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加博物馆简介";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _museumName = row.museumName || ids.value
getMuseumIntro(_museumName).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改博物馆简介";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["museumIntroRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addMuseumIntro(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateMuseumIntro(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除博物馆简介编号为"' + row.museumName + '"的数据项?').then(function() {
return delMuseumIntro(row.museumName);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/museumIntro/export', {
// ...queryParams.value
// }, `museumIntro_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,524 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="分类" prop="categoryId">
<el-select v-model="queryParams.categoryId" placeholder="请选择分类" clearable style="width: 170px">
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="年代" prop="age">
<el-input
v-model="queryParams.age"
placeholder="请输入年代"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="热门标识" prop="isHot">
<el-input
v-model="queryParams.isHot"
placeholder="请输入热门标识"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="relicList">
<!-- <el-table-column label="藏品id" align="center" prop="id" /> -->
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="分类" align="center">
<template #default="scope">
<span>{{ scope.row.categoryInfo && scope.row.categoryInfo.name ? scope.row.categoryInfo.name : '—' }}</span>
</template>
</el-table-column>
<el-table-column label="封面" align="center" prop="coverImageUrl">
<template #default="scope">
<el-image
v-if="scope.row.coverImageUrl"
:src="getFilePrefix + scope.row.coverImageUrl"
:preview-src-list="[getFilePrefix + scope.row.coverImageUrl]"
preview-teleported
fit="cover"
style="width: 80px; height: 80px; cursor: zoom-in"
/>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="年代" align="center" prop="age" />
<el-table-column label="材质" align="center" prop="material" />
<!-- <el-table-column label="出土信息" align="center" prop="excavationInfo" /> -->
<!-- <el-table-column label="文物故事" align="center" prop="story" /> -->
<!-- <el-table-column label="3D/360°图地址" align="center" prop="modelUrl" /> -->
<el-table-column label="热门标识" align="center" prop="isHot">
<template #default="scope">
<el-tag v-if="scope.row.isHot === 1" type="danger">热门</el-tag>
<el-tag v-else type="info">普通</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">显示</el-tag>
<el-tag v-else type="warning">隐藏</el-tag>
</template>
</el-table-column>
<!-- <el-table-column label="创建者id" align="center" prop="creator" />
<el-table-column label="更新者id" align="center" prop="updater" /> -->
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="350">
<template #default="scope">
<el-button
link
type="primary"
v-if="scope.row.isHot === 0"
@click="toggleHot(scope.row, 1)"
icon="Sunny"
>设为热门</el-button>
<el-button
link
type="primary"
v-if="scope.row.isHot === 1"
@click="toggleHot(scope.row, 0)"
icon="CloseBold"
>取消热门</el-button>
<el-button
link
type="primary"
v-if="scope.row.status === 1"
@click="toggleStatus(scope.row, 0)"
icon="Hide"
>隐藏</el-button>
<el-button
link
type="primary"
v-if="scope.row.status === 0"
@click="toggleStatus(scope.row, 1)"
icon="View"
>显示</el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改藏品对话框 -->
<el-dialog :title="title" v-model="open" width="800px" append-to-body :close-on-click-modal="false">
<el-form ref="relicRef" :model="form" :rules="rules" label-width="80px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" placeholder="请输入名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="分类" prop="categoryId">
<el-select v-model="form.categoryId" placeholder="请选择分类" style="width: 100%">
<el-option v-for="item in categoryOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="封面" prop="coverImageUrl">
<el-upload
action="/api/files/upload"
:headers="headers"
name="file"
:data="{filePath: 'relicCover'}"
list-type="picture-card"
:file-list="coverFileList"
:limit="1"
:on-success="handleCoverSuccess"
:on-remove="handleCoverRemove"
:on-exceed="handleCoverExceed"
:before-upload="beforeCoverUpload"
accept="image/*"
>
<el-icon><Plus /></el-icon>
</el-upload>
<div class="upload-tips">仅支持一张图片大小10MB</div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年代" prop="age">
<el-input v-model="form.age" placeholder="请输入年代" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="材质" prop="material">
<el-input v-model="form.material" placeholder="请输入材质" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="出土信息" prop="excavationInfo">
<el-input v-model="form.excavationInfo" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="3D/360°图地址" prop="modelUrl">
<el-input v-model="form.modelUrl" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="文物故事" prop="story">
<Editor
:value="form.story || ''"
@updateValue="updateStory"
filePath="relicStoryImg"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Relic">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listRelic, getRelic, delRelic, addRelic, updateRelic } from "@/api/relic";
import { listAllItemCategory } from "@/api/itemCategory";
import Editor from "@/components/Editor.vue";
const { proxy } = getCurrentInstance();
const relicList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
categoryId: null,
coverImageUrl: null,
age: null,
material: null,
excavationInfo: null,
story: null,
modelUrl: null,
isHot: null,
status: null,
creator: null,
updater: null
},
rules: {
name: [
{ required: true, message: "名称不能为空", trigger: "blur" }
],
categoryId: [
{ required: true, message: "分类ID不能为空", trigger: "blur" }
],
coverImageUrl: [
{ required: true, message: "封面不能为空", trigger: "change" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
const coverFileList = ref([]);
const categoryOptions = ref([]);
const headers = {};
//
function handleUploadSuccess(response, uploadFile, uploadFiles) {
form.value.coverImageUrl = response
proxy.$message.success('上传成功')
}
/*** 富文本回调 */
function updateStory(value) {
form.value.story = value;
}
/** 查询藏品列表 */
function getList() {
loading.value = true;
listRelic(queryParams.value).then(response => {
relicList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
function loadCategories() {
listAllItemCategory({}).then(res => {
const arr = res || [];
categoryOptions.value = Array.isArray(arr) ? arr : [];
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
name: null,
categoryId: null,
coverImageUrl: null,
age: null,
material: null,
excavationInfo: null,
story: null,
modelUrl: null,
isHot: null,
status: null,
createTime: null,
creator: null,
updateTime: null,
updater: null
};
const relicRef = proxy.$refs["relicRef"]
if (relicRef) {
relicRef.resetFields();
}
coverFileList.value = [];
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加藏品";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getRelic(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改藏品";
isAdd.value = false;
coverFileList.value = form.value.coverImageUrl ? [{ name: '封面', url: proxy.getFilePrefix + form.value.coverImageUrl }] : [];
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["relicRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addRelic(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateRelic(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除藏品编号为"' + row.id + '"的数据项?').then(function() {
return delRelic(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/relic/export', {
// ...queryParams.value
// }, `relic_${new Date().getTime()}.xlsx`)
// }
function handleCoverSuccess(res, file, fileList) {
form.value.coverImageUrl = res;
coverFileList.value = [{ name: file.name, url: proxy.getFilePrefix + form.value.coverImageUrl }];
const relicRef = proxy.$refs["relicRef"];
if (relicRef) {
relicRef.validateField('coverImageUrl');
}
}
function handleCoverRemove() {
form.value.coverImageUrl = null;
coverFileList.value = [];
}
function handleCoverExceed(files, fileList) {
proxy.$message({
message: '只允许上传一张图片',
type: 'warning'
});
}
function beforeCoverUpload(file) {
const ok = file.size / 1024 / 1024 <= 10;
if (!ok) {
proxy.$message({
message: '图片大小不能超过10MB',
type: 'error'
});
}
return ok;
}
function toggleHot(row, nextVal) {
const text = nextVal === 1 ? '是否要把该藏品设置为热门?' : '是否要取消该藏品的热门?';
proxy.$confirm(text).then(function() {
return updateRelic({ id: row.id, isHot: nextVal });
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
function toggleStatus(row, nextVal) {
const text = nextVal === 1 ? '是否要显示该藏品?' : '是否要隐藏该藏品?';
proxy.$confirm(text).then(function() {
return updateRelic({ id: row.id, status: nextVal });
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
getList();
loadCategories();
</script>
<style scoped>
.upload-tips {
color: #909399;
font-size: 12px;
margin-top: 8px;
}
.el-upload--picture-card {
width: 120px;
height: 120px;
}
.el-upload-list__item {
width: 120px !important;
height: 120px !important;
}
</style>

View File

@ -0,0 +1,474 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="用户名" prop="username">
<el-input
v-model="queryParams.username"
placeholder="请输入预约者用户名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input
v-model="queryParams.nickname"
placeholder="请输入预约者昵称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="预约日期" prop="dateRange">
<el-date-picker
v-model="queryParams.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
unlink-panels
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<br>
<el-table v-loading="loading" :data="reservationList">
<!-- <el-table-column label="预约id" align="center" prop="id" /> -->
<el-table-column label="用户名" align="center" prop="username" />
<el-table-column label="昵称" align="center" prop="nickname" />
<!-- <el-table-column label="预约发起者id" align="center" prop="userId" /> -->
<!-- <el-table-column label="预约时段id" align="center" prop="timeSlotId" /> -->
<el-table-column label="预约总人数" align="center" prop="totalVisitors" />
<el-table-column label="预约日期" align="center">
<template #default="scope">
{{ formatDateYMD(scope.row.reservationTimeSlot && scope.row.reservationTimeSlot.date) }}
</template>
</el-table-column>
<el-table-column label="预约时间" align="center" prop="reserveTime" />
<!-- <el-table-column label="预约凭证二维码" align="center" prop="qrCode" /> -->
<el-table-column label="整单状态" align="center">
<template #default="scope">
<el-tooltip v-if="Number(scope.row.status) === 3 && scope.row.remark" :content="`拒绝原因:${scope.row.remark}`" placement="top" effect="dark">
<el-tag :type="statusType(scope.row.status)" style="cursor: help;">{{ statusText(scope.row.status) }}</el-tag>
</el-tooltip>
<el-tag v-else :type="statusType(scope.row.status)">{{ statusText(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<!-- <el-table-column label="审核者id" align="center" prop="updater" />
<el-table-column label="审核备注" align="center" prop="remark" /> -->
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button v-if="Number(scope.row.status) === 0" link type="success" icon="Check" @click="openAudit(scope.row)">审核</el-button>
<el-button v-else link type="primary" icon="View" @click="openDetail(scope.row)">详情</el-button>
<!-- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button> -->
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改预约对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="reservationRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="预约发起者id" prop="userId">
<el-input v-model="form.userId" placeholder="请输入预约发起者id" />
</el-form-item>
<el-form-item label="预约时段id" prop="timeSlotId">
<el-input v-model="form.timeSlotId" placeholder="请输入预约时段id" />
</el-form-item>
<el-form-item label="预约总人数" prop="totalVisitors">
<el-input v-model="form.totalVisitors" placeholder="请输入预约总人数" />
</el-form-item>
<el-form-item label="预约时间" prop="reserveTime">
<el-input v-model="form.reserveTime" placeholder="请输入预约时间(HH:mm:ss)" />
</el-form-item>
<el-form-item label="预约凭证二维码" prop="qrCode">
<el-input v-model="form.qrCode" placeholder="请输入预约凭证二维码" />
</el-form-item>
<el-form-item label="审核者id" prop="updater">
<el-input v-model="form.updater" placeholder="请输入审核者id" />
</el-form-item>
<el-form-item label="审核备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog title="审核预约" v-model="auditOpen" width="700px" append-to-body>
<div class="audit-info">
<div class="audit-section-title">预约信息</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="用户名">{{ auditData.username }}</el-descriptions-item>
<el-descriptions-item label="昵称">{{ auditData.nickname }}</el-descriptions-item>
<el-descriptions-item label="预约日期">{{ formatDateYMD(auditData.reservationTimeSlot && auditData.reservationTimeSlot.date) }}</el-descriptions-item>
<el-descriptions-item label="预约时间">{{ auditData.reserveTime }}</el-descriptions-item>
<el-descriptions-item label="预约总人数">{{ auditData.totalVisitors }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left"><span class="audit-section-title">游客信息</span></el-divider>
<div class="audit-visitors" v-if="auditData.reservationVisitors && auditData.reservationVisitors.length">
<el-table :data="auditData.reservationVisitors" size="small" style="margin-top: 12px;">
<el-table-column label="姓名" prop="realName" align="center" />
<el-table-column label="身份证号" prop="idCard" align="center" />
<el-table-column label="手机号" prop="phone" align="center" />
</el-table>
</div>
<div class="audit-visitors-empty" v-else>
<el-empty description="暂无游客信息" image-size="80" />
</div>
</div>
<el-form :model="auditForm" :rules="auditRules" ref="auditRef" label-width="90px" style="margin-top: 12px;">
<el-form-item label="审核结果" prop="status">
<el-select v-model="auditForm.status" placeholder="请选择">
<el-option label="通过" :value="1" />
<el-option label="拒绝" :value="3" />
</el-select>
</el-form-item>
<el-form-item v-if="Number(auditForm.status) === 3" label="拒绝原因" prop="remark">
<el-input v-model="auditForm.remark" type="textarea" placeholder="请输入拒绝原因" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitAudit"> </el-button>
<el-button @click="auditOpen=false"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog title="预约详情" v-model="detailOpen" width="700px" append-to-body>
<div class="audit-info">
<div class="audit-section-title">预约信息</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="用户名">{{ detailData.username }}</el-descriptions-item>
<el-descriptions-item label="昵称">{{ detailData.nickname }}</el-descriptions-item>
<el-descriptions-item label="预约日期">{{ formatDateYMD(detailData.reservationTimeSlot && detailData.reservationTimeSlot.date) }}</el-descriptions-item>
<el-descriptions-item label="预约时间">{{ detailData.reserveTime }}</el-descriptions-item>
<el-descriptions-item label="预约总人数">{{ detailData.totalVisitors }}</el-descriptions-item>
<el-descriptions-item v-if="Number(detailData.status) === 1" label="整单核验二维码">
<el-image v-if="detailData.qrCode" :src="qrImg('/api' + detailData.qrCode)" :preview-src-list="[qrImg('/api' + detailData.qrCode)]" style="width: 160px; height: 160px;" />
</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left"><span class="audit-section-title">游客信息</span></el-divider>
<div class="audit-visitors" v-if="detailData.reservationVisitors && detailData.reservationVisitors.length">
<el-table :data="detailData.reservationVisitors" size="small" style="margin-top: 12px;">
<el-table-column label="姓名" prop="realName" align="center" />
<el-table-column label="身份证号" prop="idCard" align="center" />
<el-table-column label="手机号" prop="phone" align="center" />
<el-table-column label="核验二维码" align="center">
<template #default="scope">
<el-image v-if="scope.row.visitorQrCode" :src="qrImg('/api' + scope.row.visitorQrCode)" :preview-src-list="[qrImg('/api' + scope.row.visitorQrCode)]" style="width: 120px; height: 120px;" />
<span v-else></span>
</template>
</el-table-column>
</el-table>
</div>
<div class="audit-visitors-empty" v-else>
<el-empty description="暂无游客信息" image-size="80" />
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="detailOpen=false">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Reservation">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listReservation, getReservation, delReservation, addReservation, updateReservation, auditReservation } from "@/api/reservation";
const { proxy } = getCurrentInstance();
const reservationList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const auditOpen = ref(false)
const auditData = ref({})
const auditForm = ref({ id: null, status: null, remark: '' })
const auditRules = {
status: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
remark: [{ validator: (rule, value, callback) => {
const s = auditForm.value.status
if (Number(s) === 3 && (!value || !value.trim())) callback(new Error('拒绝原因不能为空'))
else callback()
}, trigger: 'blur' }]
}
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
username: null,
nickname: null,
dateRange: null,
startDate: null,
endDate: null,
userId: null,
timeSlotId: null,
totalVisitors: null,
reserveTime: null,
qrCode: null,
status: null,
updater: null,
},
rules: {
userId: [
{ required: true, message: "预约发起者id不能为空", trigger: "blur" }
],
timeSlotId: [
{ required: true, message: "预约时段id不能为空", trigger: "blur" }
],
totalVisitors: [
{ required: true, message: "预约总人数不能为空", trigger: "blur" }
],
reserveTime: [
{ required: true, message: "预约时间不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询预约列表 */
function getList() {
loading.value = true;
const qp = {
...queryParams.value,
startDate: queryParams.value.dateRange && queryParams.value.dateRange[0],
endDate: queryParams.value.dateRange && queryParams.value.dateRange[1]
}
listReservation(qp).then(response => {
reservationList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
userId: null,
timeSlotId: null,
totalVisitors: null,
reserveTime: null,
qrCode: null,
status: null,
createTime: null,
updateTime: null,
updater: null,
remark: null
};
const reservationRef = proxy.$refs["reservationRef"]
if (reservationRef) {
reservationRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加预约";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getReservation(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改预约";
isAdd.value = false;
});
}
function openAudit(row) {
getReservation(row.id).then(response => {
auditData.value = response.data
auditForm.value = { id: row.id, status: 1, remark: '' }
auditOpen.value = true
})
}
const detailOpen = ref(false)
const detailData = ref({})
function qrImg(url) {
if (!url) return ''
const u = encodeURIComponent(url)
return `https://api.qrserver.com/v1/create-qr-code/?size=160x160&data=${u}`
}
function openDetail(row) {
getReservation(row.id).then(response => {
detailData.value = response.data
detailOpen.value = true
})
}
function submitAudit() {
const auditRef = proxy.$refs["auditRef"]
if (auditRef) {
auditRef.validate(valid => {
if (!valid) return
const payload = { id: auditForm.value.id, status: auditForm.value.status, remark: auditForm.value.remark }
auditReservation(payload).then(resp => {
proxy.$message({ message: resp.msg, type: 'success' })
auditOpen.value = false
getList()
})
})
}
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["reservationRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addReservation(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateReservation(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除预约编号为"' + row.id + '"的数据项?').then(function() {
return delReservation(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/reservation/export', {
// ...queryParams.value
// }, `reservation_${new Date().getTime()}.xlsx`)
// }
getList();
</script>
<script>
export default {
methods: {
formatDateYMD(val) {
if (!val) return ''
if (typeof val === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(val)) return val
const d = new Date(val)
if (isNaN(d.getTime())) return ''
const y = d.getFullYear()
const M = String(d.getMonth() + 1).padStart(2, '0')
const D = String(d.getDate()).padStart(2, '0')
return `${y}-${M}-${D}`
},
statusText(s) {
const n = Number(s)
if (n === 1) return '已通过'
if (n === 2) return '已取消'
if (n === 3) return '已驳回'
return '待审核'
},
statusType(s) {
const n = Number(s)
if (n === 1) return 'success'
if (n === 2) return 'info'
if (n === 3) return 'danger'
return 'warning'
}
}
}
</script>

View File

@ -0,0 +1,733 @@
<template>
<div class="app-container">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增时段</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="List"
@click="toggleView"
>{{ viewMode === 'calendar' ? '列表视图' : '日历视图' }}</el-button>
</el-col>
</el-row>
<br>
<!-- 日历视图 -->
<div v-if="viewMode === 'calendar'" class="calendar-container">
<el-calendar v-model="calendarDate">
<template #date-cell="{ data }">
<div class="calendar-day">
<div class="day-number" :class="{ 'is-today': isToday(data.day) }">
{{ data.day.split('-').slice(-1)[0] }}
</div>
<div v-if="isDayBlocked(data.day)" class="blocked-day" @click="openEditForDay(data.day)">
<span class="blocked-tag">不可预约</span>
</div>
<div v-else-if="getTimeSlotsForDate(data.day).length > 0" class="time-slots">
<div
v-for="slot in getTimeSlotsForDate(data.day)"
:key="slot.id"
class="time-slot-card"
:class="getSlotStatusClass(slot)"
>
<div class="slot-time">
<el-icon><Clock /></el-icon>
{{ formatTime(slot.startTime) }}-{{ formatTime(slot.endTime) }}
</div>
<div class="slot-info">
<el-icon><User /></el-icon>
<span>{{ slot.currentPeople }}/{{ slot.maxPeople }}</span>
</div>
<div class="slot-actions">
<el-button
link
type="primary"
size="small"
icon="Edit"
@click.stop="handleUpdate(slot)"
></el-button>
<el-button
link
type="danger"
size="small"
icon="Delete"
@click.stop="handleDelete(slot)"
></el-button>
</div>
</div>
</div>
<div v-else class="no-slots">
<span class="no-slots-text">暂无时段</span>
</div>
</div>
</template>
</el-calendar>
</div>
<!-- 列表视图 -->
<div v-else>
<el-table v-loading="loading" :data="reservationTimeSlotList">
<el-table-column label="日期" align="center" prop="date" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.date, 'yyyy-MM-DD') }}</span>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime" width="180">
<template #default="scope">
<span>{{ formatTime(scope.row.startTime) }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="endTime" width="180">
<template #default="scope">
<span>{{ formatTime(scope.row.endTime) }}</span>
</template>
</el-table-column>
<el-table-column label="最大人数" align="center" prop="maxPeople" />
<el-table-column label="已预约人数" align="center" prop="currentPeople" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-tag :type="statusMap[scope.row.status] ? (statusMap[scope.row.status] === '可预约' ? 'success' : 'danger') : 'info'">
{{ statusMap[scope.row.status] || '未设置' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<el-dialog v-if="isAdd" :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="reservationTimeSlotRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="日期范围" prop="dateRange">
<el-date-picker
v-model="form.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
unlink-panels
clearable
/>
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-time-picker
v-model="form.startTime"
placeholder="请选择开始时间"
format="HH:mm:ss"
value-format="HH:mm:ss"
clearable
/>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-time-picker
v-model="form.endTime"
placeholder="请选择结束时间"
format="HH:mm:ss"
value-format="HH:mm:ss"
clearable
/>
</el-form-item>
<el-form-item label="最大人数" prop="maxPeople">
<el-input v-model="form.maxPeople" placeholder="请输入最大人数" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog v-else title="修改预约时段信息" v-model="open" width="600px" append-to-body>
<el-form ref="reservationTimeSlotRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="预约日期" prop="date">
<el-input :model-value="parseTime(form.date, 'yyyy-MM-DD')" disabled />
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-time-picker
v-model="form.startTime"
placeholder="请选择开始时间"
format="HH:mm:ss"
value-format="HH:mm:ss"
clearable
/>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-time-picker
v-model="form.endTime"
placeholder="请选择结束时间"
format="HH:mm:ss"
value-format="HH:mm:ss"
clearable
/>
</el-form-item>
<el-form-item label="最大人数" prop="maxPeople">
<el-input v-model="form.maxPeople" placeholder="请输入最大人数" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
<el-option label="不可约" :value="0" />
<el-option label="可约" :value="1" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ReservationTimeSlot">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listReservationTimeSlot, getReservationTimeSlot, delReservationTimeSlot, addReservationTimeSlot, updateReservationTimeSlot, batchAddReservationTimeSlot } from "@/api/reservationTimeSlot";
const { proxy } = getCurrentInstance();
const reservationTimeSlotList = ref([]);
const allTimeSlots = ref([]); //
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const viewMode = ref('calendar'); // 'calendar' 'list'
const calendarDate = ref(new Date());
const statusMap = { 1: '可预约', 0: '不可约' };
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
date: null,
startTime: null,
endTime: null,
maxPeople: null,
currentPeople: null,
status: null,
creator: null
},
rules: {
dateRange: [
{ required: true, message: "预约日期范围不能为空", trigger: "blur" }
],
startTime: [
{ required: true, message: "开始时间不能为空", trigger: "blur" }
],
endTime: [
{ required: true, message: "结束时间不能为空", trigger: "blur" }
],
maxPeople: [
{ required: true, message: "最大人数不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询预约时段列表 */
function getList() {
loading.value = true;
listReservationTimeSlot(queryParams.value).then(response => {
reservationTimeSlotList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
/** 获取所有时段数据(用于日历视图) */
function getAllTimeSlots() {
loading.value = true;
const params = {
pageNum: 1,
pageSize: 1000 //
};
listReservationTimeSlot(params).then(response => {
allTimeSlots.value = response.list;
loading.value = false;
});
}
/** 切换视图模式 */
function toggleView() {
viewMode.value = viewMode.value === 'calendar' ? 'list' : 'calendar';
if (viewMode.value === 'calendar') {
getAllTimeSlots();
} else {
getList();
}
}
/** 根据日期获取时段 */
function getTimeSlotsForDate(date) {
return allTimeSlots.value.filter(slot => {
const slotDate = parseTime(slot.date, 'yyyy-MM-DD');
return slotDate === date;
}).sort((a, b) => {
return formatTime(a.startTime).localeCompare(formatTime(b.startTime));
});
}
function isDayBlocked(date) {
const slots = allTimeSlots.value.filter(slot => {
const slotDate = parseTime(slot.date, 'yyyy-MM-DD');
return slotDate === date;
});
return slots.length > 0 && slots.every(s => Number(s.status) === 0);
}
function openEditForDay(date) {
const slots = getTimeSlotsForDate(date);
if (slots.length > 0) {
handleUpdate(slots[0]);
}
}
/** 格式化时间 */
function formatTime(time) {
if (!time) return '';
if (typeof time === 'string' && time.includes(' ')) {
return time.split(' ')[1].substring(0, 5);
}
if (typeof time === 'string' && time.includes(':')) {
return time.substring(0, 5);
}
return time;
}
/** 判断是否是今天 */
function isToday(date) {
const today = new Date();
const targetDate = new Date(date);
return today.getFullYear() === targetDate.getFullYear() &&
today.getMonth() === targetDate.getMonth() &&
today.getDate() === targetDate.getDate();
}
/** 获取时段状态样式 */
function getSlotStatusClass(slot) {
const ratio = slot.currentPeople / slot.maxPeople;
if (ratio >= 1) return 'slot-full';
if (ratio >= 0.8) return 'slot-almost-full';
return 'slot-available';
}
/** 时间格式化工具函数 */
function parseTime(time, format = 'yyyy-MM-DD HH:mm:ss') {
if (!time) return '';
const date = new Date(time);
const formatObj = {
yyyy: date.getFullYear(),
MM: String(date.getMonth() + 1).padStart(2, '0'),
DD: String(date.getDate()).padStart(2, '0'),
HH: String(date.getHours()).padStart(2, '0'),
mm: String(date.getMinutes()).padStart(2, '0'),
ss: String(date.getSeconds()).padStart(2, '0')
};
return format.replace(/(yyyy|MM|DD|HH|mm|ss)/g, (match) => formatObj[match]);
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
dateRange: null,
startTime: null,
endTime: null,
maxPeople: null,
currentPeople: null,
status: null,
createTime: null,
creator: null
};
const reservationTimeSlotRef = proxy.$refs["reservationTimeSlotRef"]
if (reservationTimeSlotRef) {
reservationTimeSlotRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加预约时段";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getReservationTimeSlot(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改预约时段信息";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["reservationTimeSlotRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
batchAddReservationTimeSlot({
startDate: form.value.dateRange && form.value.dateRange[0],
endDate: form.value.dateRange && form.value.dateRange[1],
startTime: form.value.startTime,
endTime: form.value.endTime,
maxPeople: form.value.maxPeople,
status: 1
}).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
refreshData();
});
} else { //
const payload = { ...form.value };
if (typeof payload.startTime === 'string') {
payload.startTime = payload.startTime.length > 8 ? payload.startTime.slice(-8) : payload.startTime;
}
if (typeof payload.endTime === 'string') {
payload.endTime = payload.endTime.length > 8 ? payload.endTime.slice(-8) : payload.endTime;
}
updateReservationTimeSlot(payload).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
refreshData();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除预约时段编号为"' + row.id + '"的数据项?').then(function() {
return delReservationTimeSlot(row.id);
}).then(response => {
refreshData();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/reservationTimeSlot/export', {
// ...queryParams.value
// }, `reservationTimeSlot_${new Date().getTime()}.xlsx`)
// }
//
if (viewMode.value === 'calendar') {
getAllTimeSlots();
} else {
getList();
}
function refreshData() {
if (viewMode.value === 'calendar') {
getAllTimeSlots();
} else {
getList();
}
}
</script>
<style scoped>
.calendar-container {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.calendar-day {
height: 100%;
min-height: 110px;
padding: 4px;
display: flex;
flex-direction: column;
}
.day-number {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 6px;
padding: 2px 6px;
border-radius: 4px;
text-align: center;
display: inline-block;
}
.day-number.is-today {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
color: #fff;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
}
.time-slots {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
overflow-y: auto;
max-height: 200px;
}
.time-slot-card {
background: #fff;
border-radius: 4px;
padding: 6px 8px;
font-size: 11px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
border-left: 2px solid transparent;
position: relative;
}
.time-slot-card:hover {
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.12);
}
.slot-available {
border-left-color: #67c23a;
background: linear-gradient(to right, #f0f9ff 0%, #fff 100%);
}
.slot-available:hover {
background: linear-gradient(to right, #e1f3ff 0%, #fff 100%);
}
.slot-almost-full {
border-left-color: #e6a23c;
background: linear-gradient(to right, #fef9f0 0%, #fff 100%);
}
.slot-almost-full:hover {
background: linear-gradient(to right, #fdf3e1 0%, #fff 100%);
}
.slot-full {
border-left-color: #f56c6c;
background: linear-gradient(to right, #fef0f0 0%, #fff 100%);
opacity: 0.8;
}
.slot-full:hover {
background: linear-gradient(to right, #fee5e5 0%, #fff 100%);
}
.slot-time {
display: flex;
align-items: center;
gap: 3px;
font-weight: 600;
color: #303133;
margin-bottom: 3px;
font-size: 11px;
}
.slot-time .el-icon {
font-size: 12px;
color: #409eff;
}
.slot-info {
display: flex;
align-items: center;
gap: 3px;
color: #606266;
margin-bottom: 2px;
font-size: 10px;
}
.slot-info .el-icon {
font-size: 11px;
color: #67c23a;
}
.slot-actions {
display: flex;
gap: 2px;
justify-content: flex-end;
margin-top: 2px;
opacity: 0;
transition: opacity 0.2s;
}
.time-slot-card:hover .slot-actions {
opacity: 1;
}
.slot-actions .el-button {
padding: 2px 4px;
}
.no-slots {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 0;
flex: 1;
}
.no-slots-text {
font-size: 11px;
color: #c0c4cc;
}
.blocked-day {
display: flex;
align-items: center;
justify-content: center;
padding: 16px 0;
flex: 1;
cursor: pointer;
}
.blocked-tag {
display: inline-block;
padding: 8px 12px;
font-size: 14px;
font-weight: 700;
color: #fff;
background: #f56c6c;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(245, 108, 108, 0.35);
letter-spacing: 2px;
}
:deep(.el-calendar-table .el-calendar-day) {
padding: 2px;
height: 140px;
}
:deep(.el-calendar-table td) {
border: 1px solid #ebeef5;
}
:deep(.el-calendar-table td.is-selected) {
background-color: #f0f9ff;
}
:deep(.el-calendar__header) {
padding: 12px 20px;
border-bottom: 2px solid #e4e7ed;
margin-bottom: 10px;
}
:deep(.el-calendar__body) {
padding: 0;
}
:deep(.el-calendar-table thead th) {
padding: 8px 0;
font-weight: 600;
color: #303133;
background-color: #f5f7fa;
}
.mb8 {
margin-bottom: 8px;
}
/* 滚动条样式 */
.time-slots::-webkit-scrollbar {
width: 4px;
}
.time-slots::-webkit-scrollbar-thumb {
background-color: #dcdfe6;
border-radius: 2px;
}
.time-slots::-webkit-scrollbar-thumb:hover {
background-color: #c0c4cc;
}
.time-slots::-webkit-scrollbar-track {
background-color: transparent;
}
</style>

View File

@ -0,0 +1,316 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="预约id" prop="reservationId">
<el-input
v-model="queryParams.reservationId"
placeholder="请输入预约id"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="游客真实姓名" prop="realName">
<el-input
v-model="queryParams.realName"
placeholder="请输入游客真实姓名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="游客身份证号" prop="idCard">
<el-input
v-model="queryParams.idCard"
placeholder="请输入游客身份证号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="游客手机号" prop="phone">
<el-input
v-model="queryParams.phone"
placeholder="请输入游客手机号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="入场验证时间" prop="verifyTime">
<el-date-picker clearable
v-model="queryParams.verifyTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择入场验证时间">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="reservationVisitorList">
<el-table-column label="明细ID" align="center" prop="id" />
<el-table-column label="预约id" align="center" prop="reservationId" />
<el-table-column label="游客真实姓名" align="center" prop="realName" />
<el-table-column label="游客身份证号" align="center" prop="idCard" />
<el-table-column label="游客手机号" align="center" prop="phone" />
<el-table-column label="游客个人入场二维码" align="center" prop="visitorQrCode" />
<el-table-column label="游客入场验证状态" align="center" prop="verifyStatus" />
<el-table-column label="入场验证时间" align="center" prop="verifyTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.verifyTime, 'yyyy-MM-DD') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改游客预约明细对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="reservationVisitorRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="预约id" prop="reservationId">
<el-input v-model="form.reservationId" placeholder="请输入预约id" />
</el-form-item>
<el-form-item label="游客真实姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入游客真实姓名" />
</el-form-item>
<el-form-item label="游客身份证号" prop="idCard">
<el-input v-model="form.idCard" placeholder="请输入游客身份证号" />
</el-form-item>
<el-form-item label="游客手机号" prop="phone">
<el-input v-model="form.phone" placeholder="请输入游客手机号" />
</el-form-item>
<el-form-item label="游客个人入场二维码" prop="visitorQrCode">
<el-input v-model="form.visitorQrCode" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="入场验证时间" prop="verifyTime">
<el-date-picker clearable
v-model="form.verifyTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择入场验证时间">
</el-date-picker>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ReservationVisitor">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listReservationVisitor, getReservationVisitor, delReservationVisitor, addReservationVisitor, updateReservationVisitor } from "@/api/reservationVisitor";
const { proxy } = getCurrentInstance();
const reservationVisitorList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
reservationId: null,
realName: null,
idCard: null,
phone: null,
visitorQrCode: null,
verifyStatus: null,
verifyTime: null
},
rules: {
reservationId: [
{ required: true, message: "预约id不能为空", trigger: "blur" }
],
realName: [
{ required: true, message: "游客真实姓名不能为空", trigger: "blur" }
],
idCard: [
{ required: true, message: "游客身份证号不能为空", trigger: "blur" }
],
phone: [
{ required: true, message: "游客手机号不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询游客预约明细列表 */
function getList() {
loading.value = true;
listReservationVisitor(queryParams.value).then(response => {
reservationVisitorList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
reservationId: null,
realName: null,
idCard: null,
phone: null,
visitorQrCode: null,
verifyStatus: null,
verifyTime: null
};
const reservationVisitorRef = proxy.$refs["reservationVisitorRef"]
if (reservationVisitorRef) {
reservationVisitorRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加游客预约明细";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getReservationVisitor(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改游客预约明细";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["reservationVisitorRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addReservationVisitor(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateReservationVisitor(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除游客预约明细编号为"' + row.id + '"的数据项?').then(function() {
return delReservationVisitor(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/reservationVisitor/export', {
// ...queryParams.value
// }, `reservationVisitor_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,241 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :span="4">
<el-card class="kpi-card"><div class="kpi-title">用户数</div><div class="kpi-value">{{ kpi.users }}</div></el-card>
</el-col>
<el-col :span="4">
<el-card class="kpi-card"><div class="kpi-title">藏品数</div><div class="kpi-value">{{ kpi.relics }}</div></el-card>
</el-col>
<el-col :span="4">
<el-card class="kpi-card"><div class="kpi-title">公告数</div><div class="kpi-value">{{ kpi.announcements }}</div></el-card>
</el-col>
<el-col :span="4">
<el-card class="kpi-card"><div class="kpi-title">预约数</div><div class="kpi-value">{{ kpi.reservations }}</div></el-card>
</el-col>
<el-col :span="4">
<el-card class="kpi-card"><div class="kpi-title">游客数</div><div class="kpi-value">{{ kpi.visitors }}</div></el-card>
</el-col>
<el-col :span="4">
<el-card class="kpi-card"><div class="kpi-title">时段数</div><div class="kpi-value">{{ kpi.timeSlots }}</div></el-card>
</el-col>
</el-row>
<br>
<el-row :gutter="20">
<el-col :span="12"><el-card><div ref="userRoleChart" class="chart"></div></el-card></el-col>
<el-col :span="12"><el-card><div ref="reservationStatusChart" class="chart"></div></el-card></el-col>
</el-row>
<br>
<el-row :gutter="20">
<el-col :span="12"><el-card><div ref="relicHotStatusChart" class="chart"></div></el-card></el-col>
<el-col :span="12"><el-card><div ref="visitorVerifyChart" class="chart"></div></el-card></el-col>
</el-row>
<br>
<el-row :gutter="20">
<el-col :span="12"><el-card><div ref="timeSlotDayChart" class="chart"></div></el-card></el-col>
<el-col :span="12"><el-card><div ref="reservationDailyChart" class="chart"></div></el-card></el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import * as echarts from 'echarts'
import { listAllSysUser } from '@/api/sysUser'
import { listAllRelic } from '@/api/relic'
import { listAllAnnouncement } from '@/api/announcement'
import { listAllReservation } from '@/api/reservation'
import { listAllReservationVisitor } from '@/api/reservationVisitor'
import { listAllReservationTimeSlot } from '@/api/reservationTimeSlot'
const { proxy } = getCurrentInstance()
const kpi = reactive({ users: 0, relics: 0, announcements: 0, reservations: 0, visitors: 0, timeSlots: 0 })
const userRoleChart = ref(null)
const reservationStatusChart = ref(null)
const relicHotStatusChart = ref(null)
const visitorVerifyChart = ref(null)
const timeSlotDayChart = ref(null)
const reservationDailyChart = ref(null)
let charts = []
function toArray(res) {
return Array.isArray(res) ? res : (res.list || res.data || [])
}
function formatDate(val) {
if (!val) return ''
const d = new Date(val)
const yyyy = d.getFullYear()
const MM = String(d.getMonth() + 1).padStart(2, '0')
const DD = String(d.getDate()).padStart(2, '0')
return `${yyyy}-${MM}-${DD}`
}
function getLastNDates(n) {
const arr = []
const d = new Date()
for (let i = n - 1; i >= 0; i--) {
const t = new Date(d)
t.setDate(d.getDate() - i)
arr.push(formatDate(t))
}
return arr
}
function renderUserRoleChart(users) {
const roleCount = { '1': 0, '2': 0 }
users.forEach(u => { if (u && u.role) roleCount[u.role] = (roleCount[u.role] || 0) + 1 })
const chart = echarts.init(userRoleChart.value)
charts.push(chart)
chart.setOption({
title: { text: '用户角色分布', left: 'center' },
tooltip: { trigger: 'item' },
legend: { bottom: 0 },
series: [{ type: 'pie', radius: '60%', data: [
{ name: '超级管理员', value: roleCount['1'] || 0 },
{ name: '普通用户', value: roleCount['2'] || 0 }
] }]
})
}
function renderReservationStatusChart(reservations) {
const map = { 0: '待审核', 1: '已通过', 2: '已取消', 3: '已驳回' }
const cnt = { 0: 0, 1: 0, 2: 0, 3: 0 }
reservations.forEach(r => { const s = Number(r.status); if (s in cnt) cnt[s]++ })
const chart = echarts.init(reservationStatusChart.value)
charts.push(chart)
chart.setOption({
title: { text: '预约状态分布', left: 'center' },
tooltip: { trigger: 'item' },
legend: { bottom: 0 },
series: [{ type: 'pie', radius: '60%', data: Object.keys(cnt).map(k => ({ name: map[k], value: cnt[k] })) }]
})
}
function renderRelicHotStatusChart(relics) {
let hot = 0, normal = 0, show = 0, hide = 0
relics.forEach(r => {
if (Number(r.isHot) === 1) hot++; else normal++
if (Number(r.status) === 1) show++; else hide++
})
const chart = echarts.init(relicHotStatusChart.value)
charts.push(chart)
chart.setOption({
title: { text: '藏品热门与状态', left: 'center' },
tooltip: { trigger: 'axis' },
legend: { bottom: 0 },
xAxis: { type: 'category', data: ['热门', '普通', '显示', '隐藏'] },
yAxis: { type: 'value' },
series: [{ name: '数量', type: 'bar', data: [hot, normal, show, hide] }]
})
}
function renderVisitorVerifyChart(visitors) {
let verified = 0, unverified = 0
visitors.forEach(v => { Number(v.verifyStatus) === 1 ? verified++ : unverified++ })
const chart = echarts.init(visitorVerifyChart.value)
charts.push(chart)
chart.setOption({
title: { text: '游客验证状态', left: 'center' },
tooltip: { trigger: 'item' },
legend: { bottom: 0 },
series: [{ type: 'pie', radius: '60%', data: [
{ name: '已验证', value: verified },
{ name: '未验证', value: unverified }
] }]
})
}
function renderTimeSlotDayChart(slots) {
const byDay = {}
slots.forEach(s => {
const d = formatDate(s.date)
const key = d
if (!byDay[key]) byDay[key] = { bookable: 0, blocked: 0 }
Number(s.status) === 1 ? byDay[key].bookable++ : byDay[key].blocked++
})
const days = getLastNDates(7)
const bookable = days.map(d => (byDay[d]?.bookable || 0))
const blocked = days.map(d => (byDay[d]?.blocked || 0))
const chart = echarts.init(timeSlotDayChart.value)
charts.push(chart)
chart.setOption({
title: { text: '近7天时段可约/不可约', left: 'center' },
tooltip: { trigger: 'axis' },
legend: { bottom: 0 },
xAxis: { type: 'category', data: days },
yAxis: { type: 'value' },
series: [
{ name: '可约', type: 'bar', stack: 'total', data: bookable },
{ name: '不可约', type: 'bar', stack: 'total', data: blocked }
]
})
}
function renderReservationDailyChart(reservations) {
const days = getLastNDates(14)
const cnt = days.reduce((acc, d) => (acc[d] = 0, acc), {})
reservations.forEach(r => { const d = formatDate(r.createTime || r.create_time); if (d in cnt) cnt[d]++ })
const chart = echarts.init(reservationDailyChart.value)
charts.push(chart)
chart.setOption({
title: { text: '近14天预约趋势', left: 'center' },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: days },
yAxis: { type: 'value' },
series: [{ name: '预约数', type: 'line', smooth: true, data: days.map(d => cnt[d]) }]
})
}
function init() {
Promise.all([
listAllSysUser({}),
listAllRelic({}),
listAllAnnouncement({}),
listAllReservation({}),
listAllReservationVisitor({}),
listAllReservationTimeSlot({})
]).then(([u, r, a, rs, rv, ts]) => {
const users = toArray(u)
const relics = toArray(r)
const announcements = toArray(a)
const reservations = toArray(rs)
const visitors = toArray(rv)
const timeSlots = toArray(ts)
kpi.users = users.length
kpi.relics = relics.length
kpi.announcements = announcements.length
kpi.reservations = reservations.length
kpi.visitors = visitors.length
kpi.timeSlots = timeSlots.length
renderUserRoleChart(users)
renderReservationStatusChart(reservations)
renderRelicHotStatusChart(relics)
renderVisitorVerifyChart(visitors)
renderTimeSlotDayChart(timeSlots)
renderReservationDailyChart(reservations)
window.addEventListener('resize', () => { charts.forEach(c => c.resize()) })
})
}
onMounted(() => { init() })
</script>
<style scoped>
.kpi-card { text-align: center; }
.kpi-title { font-size: 14px; color: #606266; }
.kpi-value { font-size: 24px; font-weight: 700; color: #303133; }
.chart { width: 100%; height: 360px; }
</style>

View File

@ -0,0 +1,273 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="真实姓名" prop="realName">
<el-input
v-model="queryParams.realName"
placeholder="请输入真实姓名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input
v-model="queryParams.idCard"
placeholder="请输入身份证号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input
v-model="queryParams.phone"
placeholder="请输入手机号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input
v-model="queryParams.email"
placeholder="请输入邮箱"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="touristList">
<el-table-column label="用户id" align="center" prop="id" />
<el-table-column label="真实姓名" align="center" prop="realName" />
<el-table-column label="身份证号" align="center" prop="idCard" />
<el-table-column label="手机号" align="center" prop="phone" />
<el-table-column label="邮箱" align="center" prop="email" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改普通用户子对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="touristRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="真实姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入真实姓名" />
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="form.idCard" placeholder="请输入身份证号" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Tourist">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listTourist, getTourist, delTourist, addTourist, updateTourist } from "@/api/tourist";
const { proxy } = getCurrentInstance();
const touristList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
realName: null,
idCard: null,
phone: null,
email: null,
},
rules: {
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询普通用户子列表 */
function getList() {
loading.value = true;
listTourist(queryParams.value).then(response => {
touristList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
realName: null,
idCard: null,
phone: null,
email: null,
updateTime: null
};
const touristRef = proxy.$refs["touristRef"]
if (touristRef) {
touristRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加普通用户子";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getTourist(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改普通用户子";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["touristRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addTourist(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateTourist(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除普通用户子编号为"' + row.id + '"的数据项?').then(function() {
return delTourist(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/tourist/export', {
// ...queryParams.value
// }, `tourist_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,308 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="标题" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入标题"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<br>
<div v-loading="loading">
<div v-if="topList && topList.length" class="ann-list">
<div class="ann-list-title">置顶公告</div>
<div class="ann-item" v-for="a in topList" :key="'top-'+a.id">
<div class="ann-left">
<span class="ann-icon"></span>
<span class="ann-title" @click="openDetail(a)">{{ a.title }}</span>
</div>
<div class="ann-date">{{ formatDate(a.createTime) }}</div>
</div>
</div>
<div v-if="announcementList && announcementList.length" class="ann-list" style="margin-top: 12px;">
<div class="ann-list-title">公告列表</div>
<div class="ann-item" v-for="a in announcementList" :key="a.id">
<div class="ann-left">
<span class="ann-icon"></span>
<span class="ann-title" @click="openDetail(a)">{{ a.title }}</span>
</div>
<div class="ann-date">{{ formatDate( a.createTime) }}</div>
</div>
</div>
<el-empty v-else description="暂无公告" />
</div>
<el-dialog v-model="detailOpen" title="公告详情" width="700px" append-to-body>
<div class="ann-detail">
<div class="ann-detail-title">{{ detailData.title }}</div>
<div class="ann-detail-meta">发布时间{{ formatDate(detailData.updateTime || detailData.createTime) }}</div>
<div class="ann-detail-content" v-html="detailData.content"></div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="detailOpen=false">关闭</el-button>
</div>
</template>
</el-dialog>
<br>
<div class="pagination-wrapper">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout=", prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</div>
</template>
<script setup name="Announcement">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listTouristAnnouncements, getAnnouncement, delAnnouncement, addAnnouncement, updateAnnouncement } from "@/api/announcement";
const { proxy } = getCurrentInstance();
const announcementList = ref([]);
const topList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
title: null,
content: null,
isTop: null,
status: null,
creator: null,
updater: null
},
rules: {
title: [
{ required: true, message: "标题不能为空", trigger: "blur" }
],
content: [
{ required: true, message: "内容不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询公告列表 */
function getList() {
loading.value = true;
const { pageNum, pageSize, title } = queryParams.value
listTouristAnnouncements({ pageNum, pageSize, title }).then(response => {
const data = response.data || response
topList.value = data.topList || []
announcementList.value = data.list || []
total.value = Number(data.total || 0)
}).finally(() => { loading.value = false })
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
title: null,
content: null,
isTop: null,
status: null,
createTime: null,
creator: null,
updateTime: null,
updater: null
};
const announcementRef = proxy.$refs["announcementRef"]
if (announcementRef) {
announcementRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加公告";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getAnnouncement(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改公告";
isAdd.value = false;
});
}
function formatDate(val) {
if (!val) return ''
const d = new Date(val)
if (isNaN(d.getTime())) return ''
const y = d.getFullYear()
const M = String(d.getMonth() + 1).padStart(2, '0')
const D = String(d.getDate()).padStart(2, '0')
return `${y}.${M}.${D}`
}
const detailOpen = ref(false)
const detailData = ref({})
function openDetail(a) {
const id = a && a.id
if (!id) return
getAnnouncement(id).then(response => {
detailData.value = response.data || {}
detailOpen.value = true
})
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["announcementRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addAnnouncement(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateAnnouncement(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除公告编号为"' + row.id + '"的数据项?').then(function() {
return delAnnouncement(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/announcement/export', {
// ...queryParams.value
// }, `announcement_${new Date().getTime()}.xlsx`)
// }
getList();
</script>
<style scoped>
.app-container {
width: 80%;
margin: 24px auto 40px;
}
.mb8 { margin-bottom: 8px; }
:deep(.el-form) {
display: flex;
justify-content: center;
}
.pagination-wrapper { display: flex; justify-content: center; }
:deep(.el-row.mb8) { justify-content: center; }
.ann-list { background: #fff; border: 1px solid #e7dfcf; border-radius: 12px; padding: 8px 16px; }
.ann-list-title { font-weight: 700; color: #2c1f1f; margin: 8px 0; }
.ann-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 6px; border-bottom: 1px dashed #e7dfcf; }
.ann-item:last-child { border-bottom: none; }
.ann-left { display: flex; align-items: center; gap: 8px; }
.ann-icon { width: 10px; height: 10px; border: 1px solid #c9a36a; border-radius: 2px; background: #f9f6f1; display: inline-block; }
.ann-title { color: #2c1f1f; }
.ann-title:hover { color: #8a2b2b; }
.ann-title { cursor: pointer; }
.ann-date { color: #8a2b2b; font-size: 12px; }
.ann-detail-title { font-size: 20px; font-weight: 700; color: #2c1f1f; }
.ann-detail-meta { color: #909399; font-size: 12px; margin: 6px 0 12px; }
.ann-detail-content :deep(p) { margin: 8px 0; line-height: 1.8; }
.ann-detail-content { color: #606266; }
.ann-detail-content>>>img {
width: 100%;
}
</style>

View File

@ -0,0 +1,283 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="标题" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入标题"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="图片地址" prop="imageUrl">
<el-input
v-model="queryParams.imageUrl"
placeholder="请输入图片地址"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="跳转链接" prop="link">
<el-input
v-model="queryParams.link"
placeholder="请输入跳转链接"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="排序序号" prop="sort">
<el-input
v-model="queryParams.sort"
placeholder="请输入排序序号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="carouselList">
<el-table-column label="轮播图id" align="center" prop="id" />
<el-table-column label="标题" align="center" prop="title" />
<el-table-column label="图片地址" align="center" prop="imageUrl" />
<el-table-column label="跳转链接" align="center" prop="link" />
<el-table-column label="排序序号" align="center" prop="sort" />
<el-table-column label="状态" align="center" prop="status" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改轮播图对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="carouselRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="图片地址" prop="imageUrl">
<el-input v-model="form.imageUrl" placeholder="请输入图片地址" />
</el-form-item>
<el-form-item label="跳转链接" prop="link">
<el-input v-model="form.link" placeholder="请输入跳转链接" />
</el-form-item>
<el-form-item label="排序序号" prop="sort">
<el-input v-model="form.sort" placeholder="请输入排序序号" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Carousel">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listCarousel, getCarousel, delCarousel, addCarousel, updateCarousel } from "@/api/carousel";
const { proxy } = getCurrentInstance();
const carouselList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
title: null,
imageUrl: null,
link: null,
sort: null,
status: null,
},
rules: {
title: [
{ required: true, message: "标题不能为空", trigger: "blur" }
],
imageUrl: [
{ required: true, message: "图片地址不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询轮播图列表 */
function getList() {
loading.value = true;
listCarousel(queryParams.value).then(response => {
carouselList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
title: null,
imageUrl: null,
link: null,
sort: null,
status: null,
createTime: null,
updateTime: null
};
const carouselRef = proxy.$refs["carouselRef"]
if (carouselRef) {
carouselRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加轮播图";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getCarousel(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改轮播图";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["carouselRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addCarousel(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateCarousel(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除轮播图编号为"' + row.id + '"的数据项?').then(function() {
return delCarousel(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/carousel/export', {
// ...queryParams.value
// }, `carousel_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,512 @@
<template>
<div class="home-container">
<el-carousel height="520px" indicator-position="outside" arrow="hover" :autoplay="true">
<el-carousel-item v-for="item in carouselList" :key="item.id">
<div class="hero" :style="{ backgroundImage: 'url(' + (getFilePrefix + item.imageUrl) + ')' }">
<div class="hero-overlay">
<div class="hero-title">{{ item.title }}</div>
<div class="hero-actions">
<el-button v-if="item.link" type="warning" size="large" @click="openLink(item.link)">查看详情</el-button>
</div>
</div>
</div>
</el-carousel-item>
</el-carousel>
<div class="info-bar combined">
<div class="time-row">
<div class="time-group">
<div class="time-label">开馆时间</div>
<div class="time-big">{{ formatTime(museumIntro?.openTime) }}</div>
</div>
<div class="time-dash"></div>
<div class="time-group">
<div class="time-label">闭馆时间</div>
<div class="time-big">{{ formatTime(museumIntro?.closeTime) }}</div>
</div>
</div>
<div class="circle-area">
<div class="circle-btn" @click="goReserve">参观预约</div>
</div>
</div>
<div class="hot-section">
<div class="hot-title">热门藏品</div>
<div class="hot-grid">
<div class="hot-card" v-for="item in hotRelics" :key="item.id">
<div class="hot-cover">
<el-image
v-if="item.coverImageUrl"
:src="getFilePrefix + item.coverImageUrl"
:preview-src-list="[getFilePrefix + item.coverImageUrl]"
preview-teleported
fit="cover"
/>
<div v-else class="hot-cover-empty"></div>
</div>
<div class="hot-info">
<div class="hot-name">{{ item.name }}</div>
<div class="hot-cat">{{ item.categoryInfo && item.categoryInfo.name ? item.categoryInfo.name : '—' }}</div>
</div>
</div>
</div>
<div class="hot-more">
<div class="hot-more-btn" @click="goMoreHot">查看更多藏品</div>
</div>
</div>
<div class="announce-section">
<div class="announce-title">公告</div>
<div class="announce-list">
<div class="announce-item" v-for="a in announcements" :key="a.id">
<div class="title"><span v-if="Number(a.isTop) === 1" class="top-badge">置顶</span><span class="ann-link" @click="openAnnDetail(a)">{{ a.title }}</span></div>
<div class="date">{{ formatDateYMD(a.updateTime || a.createTime) }}</div>
</div>
</div>
<div class="announce-more">
<div class="announce-more-btn" @click="goMoreAnnouncement">查看更多公告</div>
</div>
</div>
</div>
<!-- 公告详情弹出框 -->
<el-dialog v-model="annDetailOpen" title="公告详情" width="700px" append-to-body>
<div class="ann-detail">
<div class="ann-detail-title">{{ annDetailData.title }}</div>
<div class="ann-detail-meta">发布时间{{ formatDateYMD(annDetailData.updateTime || annDetailData.createTime) }}</div>
<div class="ann-detail-content" v-html="annDetailData.content"></div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="annDetailOpen=false">关闭</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, onMounted, getCurrentInstance } from 'vue'
import { listAllCarousel } from '@/api/carousel'
import { listAllMuseumIntro } from '@/api/museumIntro'
import { listAllRelic } from '@/api/relic'
import { listAllAnnouncement, getAnnouncement } from '@/api/announcement'
import Cookies from 'js-cookie'
const { proxy } = getCurrentInstance()
const getFilePrefix = proxy.getFilePrefix
const carouselList = ref([])
const museumIntro = ref(null)
const hotRelics = ref([])
const announcements = ref([])
const annDetailOpen = ref(false)
const annDetailData = ref({})
const home1 = new URL('@/assets/home1.png', import.meta.url).href
const home2 = new URL('@/assets/home2.png', import.meta.url).href
function openLink(link) {
window.open(link, '_blank')
}
function goReserve() {
const role = Cookies.get('role')
if (!role) {
proxy.$message.warning('请先登录')
return
}
proxy.$router.push('/tourist/reservation')
}
function formatTime(val) {
if (!val) return '—'
if (typeof val === 'string' && val.includes(':')) {
return val.slice(0, 5)
}
const d = new Date(val)
if (isNaN(d.getTime())) return '—'
const HH = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return `${HH}:${mm}`
}
function formatDateYMD(val) {
if (!val) return ''
const d = new Date(val)
if (isNaN(d.getTime())) return ''
const yyyy = d.getFullYear()
const MM = String(d.getMonth() + 1).padStart(2, '0')
const DD = String(d.getDate()).padStart(2, '0')
return `${yyyy}-${MM}-${DD}`
}
onMounted(() => {
Promise.all([
listAllCarousel({}),
listAllMuseumIntro({}),
listAllRelic({}),
listAllAnnouncement({})
]).then(([resC, resM, resR, resA]) => {
const arrC = Array.isArray(resC) ? resC : (resC.list || resC.data || [])
const listC = Array.isArray(arrC) ? arrC : []
carouselList.value = listC.filter(i => Number(i.status) === 1).sort((a, b) => (Number(a.sort || 0) - Number(b.sort || 0)))
const arrM = Array.isArray(resM) ? resM : (resM.list || resM.data || [])
const listM = Array.isArray(arrM) ? arrM : []
museumIntro.value = listM.length > 0 ? listM[0] : null
const arrR = Array.isArray(resR) ? resR : (resR.list || resR.data || [])
const listR = Array.isArray(arrR) ? arrR : []
const filteredHot = listR.filter(r => Number(r.isHot) === 1 && Number(r.status) === 1)
hotRelics.value = filteredHot.slice(0, 8)
const arrA = Array.isArray(resA) ? resA : (resA.list || resA.data || [])
const listA = Array.isArray(arrA) ? arrA : []
const active = listA.filter(a => Number(a.status) === 1)
const sortByTimeDesc = (x, y) => {
const tx = new Date(x.updateTime || x.createTime).getTime() || 0
const ty = new Date(y.updateTime || y.createTime).getTime() || 0
return ty - tx
}
const topList = active.filter(a => Number(a.isTop) === 1).sort(sortByTimeDesc)
const top = topList.length ? topList[0] : null
const nonTop = active.filter(a => Number(a.isTop) !== 1).sort(sortByTimeDesc)
const result = []
if (top) result.push(top)
result.push(...nonTop.slice(0, top ? 4 : 5))
announcements.value = result
})
})
function goMoreHot() {
proxy.$router.push('/tourist/relic')
}
function goMoreAnnouncement() {
proxy.$router.push('/tourist/announcement')
}
function openAnnDetail(a) {
const id = a && a.id
if (!id) return
getAnnouncement(id).then(res => {
annDetailData.value = res.data || {}
annDetailOpen.value = true
})
}
</script>
<style scoped>
.home-container {
width: 100%;
margin: 0;
}
.hero {
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
position: relative;
}
.hero-overlay {
position: absolute;
inset: 0;
background: linear-gradient(0deg, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.15) 50%, rgba(0,0,0,0.3) 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.hero-title {
color: #fff;
font-size: 40px;
font-weight: 800;
letter-spacing: 4px;
text-shadow: 0 2px 12px rgba(0,0,0,0.4);
margin-bottom: 18px;
}
.hero-actions {
display: flex;
gap: 12px;
}
:deep(.content-card .el-card__body) {
padding: 0 !important;
}
:deep(.el-carousel) {
width: 100%;
margin: 0;
}
:deep(.el-carousel__container) {
border-radius: 0;
}
.info-bar {
width: 55%;
margin: 16px auto;
display: flex;
align-items: center;
justify-content: center;
gap: 50px;
}
.info-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 12px;
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
border: 1px solid #ebeef5;
}
.info-icon img {
width: 110px;
height: auto;
display: block;
filter: hue-rotate(190deg) saturate(120%);
}
.info-content { flex: 1; width: 100%; }
.info-title {
font-size: 14px;
font-weight: 700;
color: #1f1f1f;
margin-bottom: 6px;
text-align: center;
}
.info-line { display: flex; align-items: center; justify-content: flex-start; gap: 8px; color: #606266; padding: 2px 0; }
.info-line .label { color: #909399; }
.info-line .val {
font-size: 13px;
font-weight: 600;
color: #303133;
}
.info-line .gap { margin: 0 6px; color: #c0c4cc; }
.info-text { color: #606266; line-height: 1.6; margin-bottom: 8px; }
.time-val { font-weight: 700; }
.reserve-block { display: flex; align-items: center; justify-content: center; gap: 16px; padding-top: 6px; }
.reserve-btn {
background: #1f4fff;
border-color: #1f4fff;
font-weight: 700;
padding: 11px 22px;
font-size: 13px;
border-radius: 8px;
box-shadow: 0 6px 16px rgba(31,79,255,0.22);
}
.reserve-btn:hover { filter: brightness(1.05); }
/* 开放时间与预约样式仿照参考图 */
.open-time .info-icon { display: none; }
.time-row {
display: flex;
align-items: flex-end;
justify-content: center;
gap: 8px;
}
.time-group { display: flex; flex-direction: column; align-items: flex-start; }
.time-label { font-size: 12px; color: #909399; }
.time-big { font-size: 22px; font-weight: 800; color: #333; letter-spacing: 2px; }
.time-dash { font-size: 22px; color: #999; padding: 0 2px; }
.reserve .info-icon { display: none; }
.circle-btn {
width: 90px;
height: 90px;
border-radius: 50%;
border: 2px solid #8a2b2b;
color: #8a2b2b;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
background: #fff;
cursor: pointer;
}
.circle-btn:hover { background: #fef4f4; }
.circle-area {
display: flex;
align-items: center;
justify-content: center;
}
.hot-section {
width: 80%;
margin: 24px auto 40px;
}
.hot-title {
font-size: 24px;
font-weight: 800;
color: #1f1f1f;
letter-spacing: 2px;
text-align: center;
margin-bottom: 18px;
}
.hot-title::after {
content: '';
display: block;
width: 64px;
height: 0;
border-bottom: 2px solid #8a2b2b;
margin: 8px auto 0;
}
.hot-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.hot-card {
background: #f9f6f1;
border: 1px solid #e7dfcf;
border-radius: 12px;
box-shadow: 0 6px 14px rgba(138, 43, 43, 0.08);
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.hot-cover {
width: 100%;
height: 160px;
position: relative;
overflow: hidden;
}
.hot-cover :deep(.el-image) {
width: 100%;
height: 100%;
transition: transform 0.25s ease;
}
.hot-cover:hover :deep(.el-image) { transform: scale(1.03); }
.hot-cover::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(0deg, rgba(0,0,0,0.06) 0%, rgba(0,0,0,0.02) 60%, rgba(0,0,0,0.05) 100%);
pointer-events: none;
}
.hot-cover-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
}
.hot-info {
padding: 14px;
background: linear-gradient(180deg, #faf7f2 0%, #f7f3ea 100%);
}
.hot-name {
font-size: 16px;
font-weight: 800;
color: #2c1f1f;
letter-spacing: 1px;
}
.hot-name::after {
content: '';
display: block;
width: 36px;
border-bottom: 2px solid #8a2b2b;
margin: 6px 0 0;
}
.hot-cat {
display: inline-block;
font-size: 12px;
color: #8a2b2b;
background: #f5e9e2;
border: 1px solid #e8d9cf;
border-radius: 999px;
padding: 2px 8px;
margin-top: 8px;
}
.hot-more {
display: flex;
justify-content: center;
margin-top: 12px;
}
.hot-more-btn {
display: inline-block;
padding: 8px 16px;
border: 1px solid #8a2b2b;
color: #8a2b2b;
background: #f5e9e2;
border-radius: 999px;
font-weight: 700;
cursor: pointer;
}
.hot-more-btn:hover { background: #fef4f4; }
.hot-card:hover { transform: translateY(-2px); box-shadow: 0 10px 18px rgba(138, 43, 43, 0.12); }
.announce-section {
width: 80%;
margin: 0 auto 40px;
}
.announce-title {
font-size: 24px;
font-weight: 800;
color: #1f1f1f;
letter-spacing: 2px;
text-align: center;
margin-bottom: 18px;
}
.announce-title::after {
content: '';
display: block;
width: 64px;
border-bottom: 2px solid #8a2b2b;
margin: 8px auto 0;
}
.announce-list {
background: #f9f6f1;
border: 1px solid #e7dfcf;
border-radius: 12px;
overflow: hidden;
}
.announce-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid #eadfce;
}
.announce-item:last-child { border-bottom: none; }
.announce-item .title {
color: #2c1f1f;
font-weight: 700;
}
.top-badge {
display: inline-block;
margin-right: 8px;
padding: 2px 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 800;
letter-spacing: 1px;
color: #fff;
background: #d93025;
box-shadow: 0 0 0 2px rgba(217,48,37,0.12) inset, 0 6px 12px rgba(217,48,37,0.18);
}
.announce-item .date {
color: #8a2b2b;
font-size: 12px;
}
.announce-more {
display: flex;
justify-content: center;
margin-top: 12px;
}
.announce-more-btn {
display: inline-block;
padding: 8px 16px;
border: 1px solid #8a2b2b;
color: #8a2b2b;
background: #f5e9e2;
border-radius: 999px;
font-weight: 700;
cursor: pointer;
}
.announce-more-btn:hover { background: #fef4f4; }
.ann-link { cursor: pointer; }
.ann-link:hover { color: #8a2b2b; }
:deep(.ann-detail-content img) { width: 100%; }
.ann-detail-title { font-size: 26px; font-weight: 800; color: #2c1f1f; letter-spacing: 1px; margin-bottom: 10px; }
</style>

View File

@ -0,0 +1,268 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="分类名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入分类名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="创建者" prop="creator">
<el-input
v-model="queryParams.creator"
placeholder="请输入创建者"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="更新者" prop="updater">
<el-input
v-model="queryParams.updater"
placeholder="请输入更新者"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="itemCategoryList">
<el-table-column label="分类ID" align="center" prop="id" />
<el-table-column label="分类名称" align="center" prop="name" />
<el-table-column label="创建者" align="center" prop="creator" />
<el-table-column label="更新者" align="center" prop="updater" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改藏品分类对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="itemCategoryRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="分类名称" prop="name">
<el-input v-model="form.name" placeholder="请输入分类名称" />
</el-form-item>
<el-form-item label="创建者" prop="creator">
<el-input v-model="form.creator" placeholder="请输入创建者" />
</el-form-item>
<el-form-item label="更新者" prop="updater">
<el-input v-model="form.updater" placeholder="请输入更新者" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ItemCategory">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listItemCategory, getItemCategory, delItemCategory, addItemCategory, updateItemCategory } from "@/api/itemCategory";
const { proxy } = getCurrentInstance();
const itemCategoryList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
name: null,
creator: null,
updater: null,
},
rules: {
name: [
{ required: true, message: "分类名称不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询藏品分类列表 */
function getList() {
loading.value = true;
listItemCategory(queryParams.value).then(response => {
itemCategoryList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
name: null,
creator: null,
createTime: null,
updater: null,
updateTime: null,
remark: null
};
const itemCategoryRef = proxy.$refs["itemCategoryRef"]
if (itemCategoryRef) {
itemCategoryRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加藏品分类";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getItemCategory(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改藏品分类";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["itemCategoryRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addItemCategory(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateItemCategory(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除藏品分类编号为"' + row.id + '"的数据项?').then(function() {
return delItemCategory(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/itemCategory/export', {
// ...queryParams.value
// }, `itemCategory_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,291 @@
<template>
<div class="collection-container">
<el-form class="search-form" :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div v-loading="loading">
<div class="collection-grid" v-if="collectionList && collectionList.length">
<div class="collection-card" v-for="ic in collectionList" :key="ic.id" @click="goDetail(ic.relic)">
<div class="collection-cover">
<el-image
v-if="ic.relic && ic.relic.coverImageUrl"
:src="getFilePrefix + ic.relic.coverImageUrl"
fit="cover"
/>
<div v-else class="collection-cover-empty"></div>
</div>
<div class="collection-info">
<div class="collection-info-top">
<div class="collection-name">{{ ic.relic && ic.relic.name ? ic.relic.name : ('#' + ic.itemId) }}</div>
<el-button class="collection-unfav" type="danger" size="small" plain @click.stop="handleDelete(ic)">取消收藏</el-button>
</div>
<div class="collection-meta">
<span class="collection-cat">{{ ic.relic && ic.relic.categoryInfo && ic.relic.categoryInfo.name ? ic.relic.categoryInfo.name : '—' }}</span>
<span v-if="ic.relic && ic.relic.material" class="collection-tag">{{ ic.relic.material }}</span>
<span v-if="ic.relic && ic.relic.age" class="collection-tag">{{ ic.relic.age }}</span>
</div>
</div>
</div>
</div>
<div class="empty" v-else>
<el-empty description="暂无收藏的藏品" />
</div>
</div>
<div class="grid-pagination">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total"
/>
</div>
</div>
</template>
<script setup name="ItemCollection">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listItemCollection, getItemCollection, delItemCollection, addItemCollection, updateItemCollection } from "@/api/itemCollection";
import { getRelic } from "@/api/relic";
import { getCurrentSysUser } from "@/api/sysUser";
const { proxy } = getCurrentInstance();
const itemCollectionList = ref([]);
const collectionList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const currentUserId = ref(null)
const getFilePrefix = proxy.getFilePrefix
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
userId: null,
name: null,
},
rules: {
userId: [
{ required: true, message: "用户id不能为空", trigger: "blur" }
],
itemId: [
{ required: true, message: "藏品id不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询收藏列表 */
async function getList() {
loading.value = true;
try {
const response = await listItemCollection(queryParams.value)
const list = Array.isArray(response.list) ? response.list : (response.data && response.data.list) || []
const tot = response.total ?? (response.data && response.data.total) ?? 0
itemCollectionList.value = list
total.value = tot
const ids = list.map(it => it.itemId).filter(Boolean)
const uniqueIds = Array.from(new Set(ids))
const detailMap = {}
await Promise.all(uniqueIds.map(id => getRelic(id).then(r => {
const d = r?.data || r
detailMap[id] = d
}).catch(() => {})))
let result = list.map(ic => ({ ...ic, relic: detailMap[ic.itemId] || {} }))
const kw = (queryParams.value.name || '').trim().toLowerCase()
if (kw) {
result = result.filter(ic => {
const n = (ic.relic && ic.relic.name) ? String(ic.relic.name) : ''
return n.toLowerCase().includes(kw)
})
}
collectionList.value = result
} finally {
loading.value = false
}
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
userId: null,
itemId: null,
createTime: null
};
const itemCollectionRef = proxy.$refs["itemCollectionRef"]
if (itemCollectionRef) {
itemCollectionRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
//
queryParams.value.userId = currentUserId.value
queryParams.value.name = null
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加收藏";
isAdd.value = true;
// ID
form.value.userId = currentUserId.value
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getItemCollection(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改收藏";
isAdd.value = false;
//
form.value.userId = currentUserId.value
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["itemCollectionRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addItemCollection(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateItemCollection(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('确定要取消收藏该藏品吗?').then(function() {
return delItemCollection(row.id);
}).then(response => {
getList();
// proxy.$message({
// message: response.msg,
// type: 'success'
// })
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/itemCollection/export', {
// ...queryParams.value
// }, `itemCollection_${new Date().getTime()}.xlsx`)
// }
//
getCurrentSysUser().then(res => {
const data = res?.data || {}
const uid = data.id || data.userId || null
currentUserId.value = uid
queryParams.value.userId = uid
getList();
}).catch(() => {
//
loading.value = false;
})
function goDetail(item) {
if (!item || !item.id) return
proxy.$router.push(`/tourist/relic/detail/${item.id}`)
}
</script>
<style scoped>
.collection-container { width: 80%; margin: 24px auto 40px; padding-top: 16px; }
.search-form { display: flex; justify-content: center; align-items: center; gap: 12px; }
.collection-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.collection-card { background: #f9f6f1; border: 1px solid #e7dfcf; border-radius: 12px; box-shadow: 0 6px 14px rgba(138,43,43,0.08); overflow: hidden; transition: transform 0.2s ease, box-shadow 0.2s ease; cursor: pointer; }
.collection-cover { width: 100%; height: 180px; position: relative; overflow: hidden; }
.collection-cover :deep(.el-image) { width: 100%; height: 100%; transition: transform 0.25s ease; }
.collection-cover:hover :deep(.el-image) { transform: scale(1.03); }
.collection-cover::after { content: ''; position: absolute; inset: 0; background: linear-gradient(0deg, rgba(0,0,0,0.06) 0%, rgba(0,0,0,0.02) 60%, rgba(0,0,0,0.05) 100%); pointer-events: none; }
.collection-cover-empty { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #909399; }
.collection-info { padding: 14px; background: linear-gradient(180deg, #faf7f2 0%, #f7f3ea 100%); }
.collection-info-top { display: flex; justify-content: space-between; align-items: center; }
.collection-name { font-size: 16px; font-weight: 800; color: #2c1f1f; letter-spacing: 1px; }
.collection-name::after { content: ''; display: block; width: 36px; border-bottom: 2px solid #8a2b2b; margin: 6px 0 0; }
.collection-meta { margin-top: 8px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.collection-cat { font-size: 12px; color: #8a2b2b; background: #f5e9e2; border: 1px solid #e8d9cf; border-radius: 999px; padding: 2px 8px; }
.collection-tag { font-size: 12px; color: #2c1f1f; background: #efe9df; border: 1px solid #e0d6c7; border-radius: 999px; padding: 2px 8px; }
.grid-pagination { display: flex; justify-content: center; margin-top: 16px; }
.empty { display: flex; justify-content: center; padding: 32px 0; }
</style>

View File

@ -0,0 +1,328 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="Logo地址" prop="logoUrl">
<el-input
v-model="queryParams.logoUrl"
placeholder="请输入Logo地址"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input
v-model="queryParams.address"
placeholder="请输入地址"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input
v-model="queryParams.phone"
placeholder="请输入联系电话"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="营业开始时间" prop="openTime">
<el-date-picker clearable
v-model="queryParams.openTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择营业开始时间">
</el-date-picker>
</el-form-item>
<el-form-item label="营业结束时间" prop="closeTime">
<el-date-picker clearable
v-model="queryParams.closeTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择营业结束时间">
</el-date-picker>
</el-form-item>
<el-form-item label="更新者id" prop="updater">
<el-input
v-model="queryParams.updater"
placeholder="请输入更新者id"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="museumIntroList">
<el-table-column label="博物馆名称" align="center" prop="museumName" />
<el-table-column label="内容" align="center" prop="content" />
<el-table-column label="Logo地址" align="center" prop="logoUrl" />
<el-table-column label="地址" align="center" prop="address" />
<el-table-column label="联系电话" align="center" prop="phone" />
<el-table-column label="营业开始时间" align="center" prop="openTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.openTime, 'yyyy-MM-DD') }}</span>
</template>
</el-table-column>
<el-table-column label="营业结束时间" align="center" prop="closeTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.closeTime, 'yyyy-MM-DD') }}</span>
</template>
</el-table-column>
<el-table-column label="更新者id" align="center" prop="updater" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改博物馆简介对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="museumIntroRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="内容">
<editor v-model="form.content" :min-height="192"/>
</el-form-item>
<el-form-item label="Logo地址" prop="logoUrl">
<el-input v-model="form.logoUrl" placeholder="请输入Logo地址" />
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="form.address" placeholder="请输入地址" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="营业开始时间" prop="openTime">
<el-date-picker clearable
v-model="form.openTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择营业开始时间">
</el-date-picker>
</el-form-item>
<el-form-item label="营业结束时间" prop="closeTime">
<el-date-picker clearable
v-model="form.closeTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择营业结束时间">
</el-date-picker>
</el-form-item>
<el-form-item label="更新者id" prop="updater">
<el-input v-model="form.updater" placeholder="请输入更新者id" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="MuseumIntro">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listMuseumIntro, getMuseumIntro, delMuseumIntro, addMuseumIntro, updateMuseumIntro } from "@/api/museumIntro";
const { proxy } = getCurrentInstance();
const museumIntroList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
content: null,
logoUrl: null,
address: null,
phone: null,
openTime: null,
closeTime: null,
updater: null
},
rules: {
content: [
{ required: true, message: "内容不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询博物馆简介列表 */
function getList() {
loading.value = true;
listMuseumIntro(queryParams.value).then(response => {
museumIntroList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
museumName: null,
content: null,
logoUrl: null,
address: null,
phone: null,
openTime: null,
closeTime: null,
updateTime: null,
updater: null
};
const museumIntroRef = proxy.$refs["museumIntroRef"]
if (museumIntroRef) {
museumIntroRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加博物馆简介";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _museumName = row.museumName || ids.value
getMuseumIntro(_museumName).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改博物馆简介";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["museumIntroRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addMuseumIntro(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateMuseumIntro(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除博物馆简介编号为"' + row.museumName + '"的数据项?').then(function() {
return delMuseumIntro(row.museumName);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/museumIntro/export', {
// ...queryParams.value
// }, `museumIntro_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,190 @@
<template>
<div class="relic-container">
<el-form class="search-form" :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="分类" prop="categoryId">
<el-select class="cat-select" v-model="queryParams.categoryId" placeholder="选择分类" clearable>
<el-option v-for="c in categoryOptions" :key="c.id" :label="c.name" :value="c.id" />
</el-select>
</el-form-item>
<el-form-item label="热门" prop="isHot">
<el-select class="hot-select" v-model="queryParams.isHot" placeholder="请选择" clearable>
<el-option label="热门" :value="1" />
<el-option label="非热门" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="relic-grid" v-loading="loading">
<div class="relic-card" v-for="item in relicList" :key="item.id" @click="goDetail(item)">
<div class="relic-cover">
<el-image
v-if="item.coverImageUrl"
:src="getFilePrefix + item.coverImageUrl"
:preview-src-list="[getFilePrefix + item.coverImageUrl]"
preview-teleported
fit="cover"
/>
<div v-else class="relic-cover-empty"></div>
</div>
<div class="relic-info">
<div class="relic-name">{{ item.name }}</div>
<div class="relic-meta">
<span class="relic-cat">{{ item.categoryInfo && item.categoryInfo.name ? item.categoryInfo.name : (item.categoryId || '—') }}</span>
<span v-if="item.material" class="relic-tag">{{ item.material }}</span>
<span v-if="item.age" class="relic-tag">{{ item.age }}</span>
</div>
</div>
</div>
</div>
<div class="grid-pagination">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[12, 24, 36, 48]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total"
/>
</div>
</div>
</template>
<script setup name="Relic">
import { ref, reactive, toRefs, getCurrentInstance, onMounted } from 'vue';
import { listRelic } from "@/api/relic";
import { listAllItemCategory } from "@/api/itemCategory";
const { proxy } = getCurrentInstance();
const relicList = ref([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const getFilePrefix = proxy.getFilePrefix
const categoryOptions = ref([])
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 12,
name: null,
categoryId: null,
isHot: null
},
});
const { queryParams } = toRefs(data);
/** 查询藏品列表 */
function getList() {
loading.value = true;
listRelic(queryParams.value).then(response => {
const list = Array.isArray(response.list) ? response.list : (response.data && response.data.list) || []
const tot = response.total ?? (response.data && response.data.total) ?? 0
relicList.value = list;
total.value = tot;
loading.value = false;
});
}
function fetchCategoryOptions() {
listAllItemCategory({}).then(res => {
const arr = Array.isArray(res) ? res : (res.list || res.data || [])
categoryOptions.value = Array.isArray(arr) ? arr : []
})
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
queryParams.value.name = null
queryParams.value.categoryId = null
queryParams.value.isHot = null
handleQuery()
}
/** 新增按钮操作 */
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/relic/export', {
// ...queryParams.value
// }, `relic_${new Date().getTime()}.xlsx`)
// }
getList();
onMounted(() => {
fetchCategoryOptions()
})
function goDetail(item) {
if (!item || !item.id) return
proxy.$router.push(`/tourist/relic/detail/${item.id}`)
}
</script>
<style scoped>
.relic-container { width: 80%; margin: 24px auto 40px; padding-top: 16px; }
.filter-bar { display: flex; gap: 12px; align-items: center; justify-content: center; margin-bottom: 18px; }
.relic-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
.relic-card { background: #f9f6f1; border: 1px solid #e7dfcf; border-radius: 12px; box-shadow: 0 6px 14px rgba(138,43,43,0.08); overflow: hidden; transition: transform 0.2s ease, box-shadow 0.2s ease; }
.relic-card { cursor: pointer; }
.relic-cover { width: 100%; height: 180px; position: relative; overflow: hidden; }
.relic-cover :deep(.el-image) { width: 100%; height: 100%; transition: transform 0.25s ease; }
.relic-cover:hover :deep(.el-image) { transform: scale(1.03); }
.relic-cover::after { content: ''; position: absolute; inset: 0; background: linear-gradient(0deg, rgba(0,0,0,0.06) 0%, rgba(0,0,0,0.02) 60%, rgba(0,0,0,0.05) 100%); pointer-events: none; }
.relic-cover-empty { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: #909399; }
.relic-info { padding: 14px; background: linear-gradient(180deg, #faf7f2 0%, #f7f3ea 100%); }
.relic-name { font-size: 16px; font-weight: 800; color: #2c1f1f; letter-spacing: 1px; }
.relic-name::after { content: ''; display: block; width: 36px; border-bottom: 2px solid #8a2b2b; margin: 6px 0 0; }
.relic-meta { margin-top: 8px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.relic-cat { font-size: 12px; color: #8a2b2b; background: #f5e9e2; border: 1px solid #e8d9cf; border-radius: 999px; padding: 2px 8px; }
.relic-tag { font-size: 12px; color: #2c1f1f; background: #efe9df; border: 1px solid #e0d6c7; border-radius: 999px; padding: 2px 8px; }
.search-form { display: flex; justify-content: center; align-items: center; gap: 12px; }
.hot-select { width: 120px; }
.cat-select { width: 180px; }
.grid-pagination { display: flex; justify-content: center; margin-top: 16px; }
</style>

View File

@ -0,0 +1,416 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="预约日期" prop="dateRange">
<el-date-picker
v-model="queryParams.dateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
unlink-panels
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<br>
<el-table v-loading="loading" :data="reservationList">
<el-table-column label="预约日期" align="center">
<template #default="scope">
{{ formatDateYMD(scope.row.reservationTimeSlot && scope.row.reservationTimeSlot.date) }}
</template>
</el-table-column>
<el-table-column label="预约时间" align="center" prop="reserveTime" />
<el-table-column label="预约总人数" align="center" prop="totalVisitors" />
<el-table-column label="整单状态" align="center">
<template #default="scope">
<el-tooltip v-if="Number(scope.row.status) === 3 && scope.row.remark" :content="`拒绝原因:${scope.row.remark}`" placement="top" effect="dark">
<el-tag :type="statusType(scope.row.status)" style="cursor: help;">{{ statusText(scope.row.status) }}</el-tag>
</el-tooltip>
<el-tag v-else :type="statusType(scope.row.status)">{{ statusText(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="View" @click="openDetail(scope.row)">详情</el-button>
<el-button
v-if="Number(scope.row.status) === 0 || Number(scope.row.status) === 1"
link
type="warning"
icon="CloseBold"
@click="handleCancel(scope.row)"
>取消预约</el-button>
</template>
</el-table-column>
</el-table>
<br>
<div class="pagination-wrapper">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<!-- 预约详情对话框 -->
<el-dialog title="预约详情" v-model="detailOpen" width="700px" append-to-body>
<div class="audit-info">
<div class="audit-section-title">预约信息</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="预约日期">{{ formatDateYMD(detailData.reservationTimeSlot && detailData.reservationTimeSlot.date) }}</el-descriptions-item>
<el-descriptions-item label="预约时间">{{ detailData.reserveTime }}</el-descriptions-item>
<el-descriptions-item label="预约总人数">{{ detailData.totalVisitors }}</el-descriptions-item>
<el-descriptions-item v-if="Number(detailData.status) === 1" label="整单核验二维码">
<el-image v-if="detailData.qrCode" :src="qrImg('/api' + detailData.qrCode)" :preview-src-list="[qrImg('/api' + detailData.qrCode)]" style="width: 160px; height: 160px;" />
</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left"><span class="audit-section-title">游客信息</span></el-divider>
<div class="audit-visitors" v-if="detailData.reservationVisitors && detailData.reservationVisitors.length">
<el-table :data="detailData.reservationVisitors" size="small" style="margin-top: 12px;">
<el-table-column label="姓名" prop="realName" align="center" />
<el-table-column label="身份证号" prop="idCard" align="center" />
<el-table-column label="手机号" prop="phone" align="center" />
<el-table-column label="核验状态" align="center">
<template #default="scope">
<el-tag :type="Number(scope.row.verifyStatus) === 1 ? 'success' : 'warning'">
{{ Number(scope.row.verifyStatus) === 1 ? '已验证' : '未验证' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="核验二维码" align="center">
<template #default="scope">
<el-image v-if="scope.row.visitorQrCode" :src="qrImg('/api' + scope.row.visitorQrCode)" :preview-src-list="[qrImg('/api' + scope.row.visitorQrCode)]" style="width: 120px; height: 120px;" />
<span v-else></span>
</template>
</el-table-column>
</el-table>
</div>
<div class="audit-visitors-empty" v-else>
<el-empty description="暂无游客信息" image-size="80" />
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="detailOpen=false">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Reservation">
import { ref, reactive, toRefs, getCurrentInstance, onMounted } from 'vue';
import { listReservation, getReservation, delReservation, addReservation, updateReservation } from "@/api/reservation";
import { getCurrentSysUser } from "@/api/sysUser";
const { proxy } = getCurrentInstance();
const reservationList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const detailOpen = ref(false)
const detailData = ref({})
function qrImg(url) {
if (!url) return ''
const u = encodeURIComponent(url)
return `https://api.qrserver.com/v1/create-qr-code/?size=160x160&data=${u}`
}
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
userId: null,
dateRange: null,
startDate: null,
endDate: null,
status: null,
},
rules: {}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询预约列表 */
function getList() {
loading.value = true;
const qp = {
...queryParams.value,
startDate: queryParams.value.dateRange && queryParams.value.dateRange[0],
endDate: queryParams.value.dateRange && queryParams.value.dateRange[1]
}
listReservation(qp).then(response => {
reservationList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
userId: null,
timeSlotId: null,
totalVisitors: null,
qrCode: null,
status: null,
createTime: null,
updateTime: null,
updater: null,
remark: null
};
const reservationRef = proxy.$refs["reservationRef"]
if (reservationRef) {
reservationRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加预约";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getReservation(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改预约";
isAdd.value = false;
});
}
function openDetail(row) {
getReservation(row.id).then(response => {
detailData.value = response.data
detailOpen.value = true
})
}
const formatDateCN = (val) => {
if (!val) return ''
let y, M, D
if (typeof val === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(val)) {
const parts = val.split('-')
y = Number(parts[0])
M = Number(parts[1])
D = Number(parts[2])
} else {
const d = new Date(val)
if (isNaN(d.getTime())) return ''
y = d.getFullYear()
M = d.getMonth() + 1
D = d.getDate()
}
return `${y}${M}${D}`
}
const formatTimeHMS = (v) => {
if (!v) return ''
if (typeof v === 'string') {
const m = v.match(/\d{2}:\d{2}:\d{2}/)
if (m) return m[0]
const m2 = v.match(/\d{2}:\d{2}/)
return m2 ? `${m2[0]}:00` : ''
}
const d = new Date(v)
if (isNaN(d.getTime())) return ''
const HH = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
const ss = String(d.getSeconds()).padStart(2, '0')
return `${HH}:${mm}:${ss}`
}
function handleCancel(row) {
const dateStr = formatDateCN(row.reservationTimeSlot && row.reservationTimeSlot.date)
const timeStr = formatTimeHMS(row.reserveTime)
const ppl = row.totalVisitors
const msg = `是否要取消${dateStr} ${timeStr} 预约人数为${ppl}人的预约?`
proxy.$confirm(msg).then(function() {
return updateReservation({ id: row.id, status: 2 })
}).then(resp => {
proxy.$message({ message: resp.msg || '取消成功', type: 'success' })
handleQuery()
}).catch(() => {})
}
const formatDateYMD = (val) => {
if (!val) return ''
if (typeof val === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(val)) return val
const d = new Date(val)
if (isNaN(d.getTime())) return ''
const y = d.getFullYear()
const M = String(d.getMonth() + 1).padStart(2, '0')
const D = String(d.getDate()).padStart(2, '0')
return `${y}-${M}-${D}`
}
const statusText = (s) => {
const n = Number(s)
if (n === 1) return '已通过'
if (n === 2) return '已取消'
if (n === 3) return '已驳回'
return '待审核'
}
const statusType = (s) => {
const n = Number(s)
if (n === 1) return 'success'
if (n === 2) return 'info'
if (n === 3) return 'danger'
return 'warning'
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["reservationRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addReservation(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateReservation(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除预约编号为"' + row.id + '"的数据项?').then(function() {
return delReservation(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/reservation/export', {
// ...queryParams.value
// }, `reservation_${new Date().getTime()}.xlsx`)
// }
getList();
onMounted(() => {
getCurrentSysUser().then(u => {
const data = u && (u.data || u)
const uid = data && (data.id || data.userId)
queryParams.value.userId = uid || null
handleQuery()
})
})
</script>
<style scoped>
.app-container {
width: 80%;
margin: 24px auto 40px;
}
.mb8 {
margin-bottom: 8px;
}
:deep(.el-form) {
display: flex;
justify-content: center;
}
:deep(.el-pagination) {
display: flex;
justify-content: center;
}
.pagination-wrapper {
display: flex;
justify-content: center;
}
:deep(.el-row.mb8) {
justify-content: center;
}
</style>

View File

@ -0,0 +1,343 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="预约日期" prop="date">
<el-date-picker clearable
v-model="queryParams.date"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择预约日期">
</el-date-picker>
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker clearable
v-model="queryParams.startTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择开始时间">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker clearable
v-model="queryParams.endTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择结束时间">
</el-date-picker>
</el-form-item>
<el-form-item label="最大人数" prop="maxPeople">
<el-input
v-model="queryParams.maxPeople"
placeholder="请输入最大人数"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="已预约人数" prop="currentPeople">
<el-input
v-model="queryParams.currentPeople"
placeholder="请输入已预约人数"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="创建者id" prop="creator">
<el-input
v-model="queryParams.creator"
placeholder="请输入创建者id"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="reservationTimeSlotList">
<el-table-column label="时段id" align="center" prop="id" />
<el-table-column label="预约日期" align="center" prop="date" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.date, 'yyyy-MM-DD') }}</span>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.startTime, 'yyyy-MM-DD') }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="endTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.endTime, 'yyyy-MM-DD') }}</span>
</template>
</el-table-column>
<el-table-column label="最大人数" align="center" prop="maxPeople" />
<el-table-column label="已预约人数" align="center" prop="currentPeople" />
<el-table-column label="状态" align="center" prop="status" />
<el-table-column label="创建者id" align="center" prop="creator" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改预约时段对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="reservationTimeSlotRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="预约日期" prop="date">
<el-date-picker clearable
v-model="form.date"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择预约日期">
</el-date-picker>
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker clearable
v-model="form.startTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择开始时间">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker clearable
v-model="form.endTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择结束时间">
</el-date-picker>
</el-form-item>
<el-form-item label="最大人数" prop="maxPeople">
<el-input v-model="form.maxPeople" placeholder="请输入最大人数" />
</el-form-item>
<el-form-item label="已预约人数" prop="currentPeople">
<el-input v-model="form.currentPeople" placeholder="请输入已预约人数" />
</el-form-item>
<el-form-item label="创建者id" prop="creator">
<el-input v-model="form.creator" placeholder="请输入创建者id" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ReservationTimeSlot">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listReservationTimeSlot, getReservationTimeSlot, delReservationTimeSlot, addReservationTimeSlot, updateReservationTimeSlot } from "@/api/reservationTimeSlot";
const { proxy } = getCurrentInstance();
const reservationTimeSlotList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
date: null,
startTime: null,
endTime: null,
maxPeople: null,
currentPeople: null,
status: null,
creator: null
},
rules: {
date: [
{ required: true, message: "预约日期不能为空", trigger: "blur" }
],
startTime: [
{ required: true, message: "开始时间不能为空", trigger: "blur" }
],
endTime: [
{ required: true, message: "结束时间不能为空", trigger: "blur" }
],
maxPeople: [
{ required: true, message: "最大人数不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询预约时段列表 */
function getList() {
loading.value = true;
listReservationTimeSlot(queryParams.value).then(response => {
reservationTimeSlotList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
date: null,
startTime: null,
endTime: null,
maxPeople: null,
currentPeople: null,
status: null,
createTime: null,
creator: null
};
const reservationTimeSlotRef = proxy.$refs["reservationTimeSlotRef"]
if (reservationTimeSlotRef) {
reservationTimeSlotRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加预约时段";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getReservationTimeSlot(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改预约时段";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["reservationTimeSlotRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addReservationTimeSlot(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateReservationTimeSlot(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除预约时段编号为"' + row.id + '"的数据项?').then(function() {
return delReservationTimeSlot(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/reservationTimeSlot/export', {
// ...queryParams.value
// }, `reservationTimeSlot_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,316 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="预约id" prop="reservationId">
<el-input
v-model="queryParams.reservationId"
placeholder="请输入预约id"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="游客真实姓名" prop="realName">
<el-input
v-model="queryParams.realName"
placeholder="请输入游客真实姓名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="游客身份证号" prop="idCard">
<el-input
v-model="queryParams.idCard"
placeholder="请输入游客身份证号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="游客手机号" prop="phone">
<el-input
v-model="queryParams.phone"
placeholder="请输入游客手机号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="入场验证时间" prop="verifyTime">
<el-date-picker clearable
v-model="queryParams.verifyTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择入场验证时间">
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>新增</el-button>
</el-col>
<!--<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
>导出</el-button>
</el-col>-->
</el-row>
<br>
<el-table v-loading="loading" :data="reservationVisitorList">
<el-table-column label="明细ID" align="center" prop="id" />
<el-table-column label="预约id" align="center" prop="reservationId" />
<el-table-column label="游客真实姓名" align="center" prop="realName" />
<el-table-column label="游客身份证号" align="center" prop="idCard" />
<el-table-column label="游客手机号" align="center" prop="phone" />
<el-table-column label="游客个人入场二维码" align="center" prop="visitorQrCode" />
<el-table-column label="游客入场验证状态" align="center" prop="verifyStatus" />
<el-table-column label="入场验证时间" align="center" prop="verifyTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.verifyTime, 'yyyy-MM-DD') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<br>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="->, total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<!-- 添加或修改游客预约明细对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="reservationVisitorRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="预约id" prop="reservationId">
<el-input v-model="form.reservationId" placeholder="请输入预约id" />
</el-form-item>
<el-form-item label="游客真实姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入游客真实姓名" />
</el-form-item>
<el-form-item label="游客身份证号" prop="idCard">
<el-input v-model="form.idCard" placeholder="请输入游客身份证号" />
</el-form-item>
<el-form-item label="游客手机号" prop="phone">
<el-input v-model="form.phone" placeholder="请输入游客手机号" />
</el-form-item>
<el-form-item label="游客个人入场二维码" prop="visitorQrCode">
<el-input v-model="form.visitorQrCode" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="入场验证时间" prop="verifyTime">
<el-date-picker clearable
v-model="form.verifyTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择入场验证时间">
</el-date-picker>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ReservationVisitor">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
import { listReservationVisitor, getReservationVisitor, delReservationVisitor, addReservationVisitor, updateReservationVisitor } from "@/api/reservationVisitor";
const { proxy } = getCurrentInstance();
const reservationVisitorList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const total = ref(0);
const title = ref("");
const isAdd = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
reservationId: null,
realName: null,
idCard: null,
phone: null,
visitorQrCode: null,
verifyStatus: null,
verifyTime: null
},
rules: {
reservationId: [
{ required: true, message: "预约id不能为空", trigger: "blur" }
],
realName: [
{ required: true, message: "游客真实姓名不能为空", trigger: "blur" }
],
idCard: [
{ required: true, message: "游客身份证号不能为空", trigger: "blur" }
],
phone: [
{ required: true, message: "游客手机号不能为空", trigger: "blur" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询游客预约明细列表 */
function getList() {
loading.value = true;
listReservationVisitor(queryParams.value).then(response => {
reservationVisitorList.value = response.list;
total.value = response.total;
loading.value = false;
});
}
//
function handleSizeChange(val) {
queryParams.value.pageSize = val;
getList();
}
//
function handleCurrentChange(val) {
queryParams.value.pageNum = val;
getList();
}
//
function cancel() {
open.value = false;
reset();
}
//
function reset() {
form.value = {
id: null,
reservationId: null,
realName: null,
idCard: null,
phone: null,
visitorQrCode: null,
verifyStatus: null,
verifyTime: null
};
const reservationVisitorRef = proxy.$refs["reservationVisitorRef"]
if (reservationVisitorRef) {
reservationVisitorRef.resetFields();
}
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
function resetQuery() {
const queryRef = proxy.$refs["queryRef"]
if (queryRef) {
queryRef.resetFields();
}
handleQuery();
}
/** 新增按钮操作 */
function handleAdd() {
reset();
open.value = true;
title.value = "添加游客预约明细";
isAdd.value = true;
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _id = row.id || ids.value
getReservationVisitor(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "修改游客预约明细";
isAdd.value = false;
});
}
/** 表单提交按钮 */
function submitForm() {
proxy.$refs["reservationVisitorRef"].validate(valid => {
if (valid) {
if (isAdd.value) { //
addReservationVisitor(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
} else { //
updateReservationVisitor(form.value).then(response => {
proxy.$message({
message: response.msg,
type: 'success'
})
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$confirm('是否确认删除游客预约明细编号为"' + row.id + '"的数据项?').then(function() {
return delReservationVisitor(row.id);
}).then(response => {
getList();
proxy.$message({
message: response.msg,
type: 'success'
})
}).catch(() => {});
}
/** 导出按钮操作 */
// function handleExport() {
// proxy.download('system/reservationVisitor/export', {
// ...queryParams.value
// }, `reservationVisitor_${new Date().getTime()}.xlsx`)
// }
getList();
</script>

View File

@ -0,0 +1,72 @@
package com.amms.controller;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.amms.domain.vo.Result;
import com.amms.domain.ItemCategory;
import com.amms.service.IItemCategoryService;
import java.util.List;
/**
* 藏品分类Controller
*/
@RestController
@RequestMapping("/itemCategory")
public class ItemCategoryController {
@Autowired
private IItemCategoryService itemCategoryService;
/**
* 查询藏品分类列表
*/
@GetMapping("/list")
public PageInfo list(ItemCategory itemCategory, @RequestParam("pageNum") Integer pageNum, @RequestParam("pageSize") Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<ItemCategory> itemCategorys = itemCategoryService.selectItemCategoryList(itemCategory);
return new PageInfo(itemCategorys);
}
/**
* 查询全部藏品分类列表
*/
@GetMapping("/listAll")
public List<ItemCategory> listAll(ItemCategory itemCategory) {
return itemCategoryService.selectItemCategoryList(itemCategory);
}
/**
* 获取藏品分类详细信息
*/
@GetMapping(value = "/info/{id}")
public Result getInfo(@PathVariable("id") Long id) {
return Result.success(itemCategoryService.selectItemCategoryById(id));
}
/**
* 新增藏品分类
*/
@PostMapping("/add")
public Result add(@RequestBody ItemCategory itemCategory) {
return itemCategoryService.insertItemCategory(itemCategory) > 0 ? Result.success("新增成功") : Result.error("新增失败");
}
/**
* 修改藏品分类
*/
@PutMapping
public Result update(@RequestBody ItemCategory itemCategory) {
return itemCategoryService.updateItemCategory(itemCategory) > 0 ? Result.success("修改成功") : Result.error("修改失败");
}
/**
* 删除藏品分类
*/
@DeleteMapping("/{id}")
public Result delete(@PathVariable Long id) {
return itemCategoryService.deleteItemCategoryById(id) > 0 ? Result.success("删除成功") : Result.error("删除失败");
}
}

View File

@ -0,0 +1,108 @@
package com.amms.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* 藏品分类对象 item_category
*/
public class ItemCategory {
/** 分类ID */
private Long id;
/** 分类名称 */
private String name;
/** 创建者 */
private Long creator;
/** 更新者 */
private Long updater;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/** 备注*/
private String remark;
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setCreator(Long creator) {
this.creator = creator;
}
public Long getCreator() {
return creator;
}
public void setUpdater(Long updater) {
this.updater = updater;
}
public Long getUpdater() {
return updater;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public String toString() {
return "ItemCategory{" +
"id=" + id +
", name=" + name +
", creator=" + creator +
", createTime=" + createTime +
", updater=" + updater +
", updateTime=" + updateTime +
", remark=" + remark +
'}';
}
}

View File

@ -0,0 +1,52 @@
package com.amms.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.amms.domain.ItemCategory;
/**
* 藏品分类Mapper接口
*/
@Mapper
public interface ItemCategoryMapper {
/**
* 查询藏品分类
*
* @param id 藏品分类主键
* @return 藏品分类
*/
public ItemCategory selectItemCategoryById(Long id);
/**
* 查询藏品分类列表
*
* @param itemCategory 藏品分类
* @return 藏品分类集合
*/
public List<ItemCategory> selectItemCategoryList(ItemCategory itemCategory);
/**
* 新增藏品分类
*
* @param itemCategory 藏品分类
* @return 结果
*/
public int insertItemCategory(ItemCategory itemCategory);
/**
* 修改藏品分类
*
* @param itemCategory 藏品分类
* @return 结果
*/
public int updateItemCategory(ItemCategory itemCategory);
/**
* 删除藏品分类
*
* @param id 藏品分类主键
* @return 结果
*/
public int deleteItemCategoryById(Long id);
}

View File

@ -0,0 +1,68 @@
package com.amms.service;
import java.util.List;
import com.amms.domain.Announcement;
/**
* 公告Service接口
*/
public interface IAnnouncementService {
/**
* 查询公告列表
*
* @param announcement 公告
* @return 公告集合
*/
public List<Announcement> selectAnnouncementList(Announcement announcement);
/**
* 查询公告
*
* @param id 公告主键
* @return 公告
*/
public Announcement selectAnnouncementById(Long id);
/**
* 新增公告
*
* @param announcement 公告
* @return 结果
*/
public int insertAnnouncement(Announcement announcement);
/**
* 修改公告
*
* @param announcement 公告
* @return 结果
*/
public int updateAnnouncement(Announcement announcement);
/**
* 删除公告信息
*
* @param id 公告主键
* @return 结果
*/
public int deleteAnnouncementById(Long id);
/**
* 设为置顶保证全局仅一个置顶
* @param id 公告ID
* @return 结果
*/
public int setTop(Long id);
/**
* 查询置顶公告显示状态
* @return 置顶公告列表
*/
public List<Announcement> selectTopAnnouncements();
/**
* 查询普通公告非置顶且显示时间倒序
* @return 普通公告列表
*/
public List<Announcement> selectNormalAnnouncements(Announcement filter);
}

View File

@ -0,0 +1,49 @@
package com.amms.service;
import java.util.List;
import com.amms.domain.Carousel;
/**
* 轮播图Service接口
*/
public interface ICarouselService {
/**
* 查询轮播图列表
*
* @param carousel 轮播图
* @return 轮播图集合
*/
public List<Carousel> selectCarouselList(Carousel carousel);
/**
* 查询轮播图
*
* @param id 轮播图主键
* @return 轮播图
*/
public Carousel selectCarouselById(Long id);
/**
* 新增轮播图
*
* @param carousel 轮播图
* @return 结果
*/
public int insertCarousel(Carousel carousel);
/**
* 修改轮播图
*
* @param carousel 轮播图
* @return 结果
*/
public int updateCarousel(Carousel carousel);
/**
* 删除轮播图信息
*
* @param id 轮播图主键
* @return 结果
*/
public int deleteCarouselById(Long id);
}

View File

@ -0,0 +1,49 @@
package com.amms.service;
import java.util.List;
import com.amms.domain.ItemCategory;
/**
* 藏品分类Service接口
*/
public interface IItemCategoryService {
/**
* 查询藏品分类列表
*
* @param itemCategory 藏品分类
* @return 藏品分类集合
*/
public List<ItemCategory> selectItemCategoryList(ItemCategory itemCategory);
/**
* 查询藏品分类
*
* @param id 藏品分类主键
* @return 藏品分类
*/
public ItemCategory selectItemCategoryById(Long id);
/**
* 新增藏品分类
*
* @param itemCategory 藏品分类
* @return 结果
*/
public int insertItemCategory(ItemCategory itemCategory);
/**
* 修改藏品分类
*
* @param itemCategory 藏品分类
* @return 结果
*/
public int updateItemCategory(ItemCategory itemCategory);
/**
* 删除藏品分类信息
*
* @param id 藏品分类主键
* @return 结果
*/
public int deleteItemCategoryById(Long id);
}

View File

@ -0,0 +1,49 @@
package com.amms.service;
import java.util.List;
import com.amms.domain.ItemCollection;
/**
* 收藏Service接口
*/
public interface IItemCollectionService {
/**
* 查询收藏列表
*
* @param itemCollection 收藏
* @return 收藏集合
*/
public List<ItemCollection> selectItemCollectionList(ItemCollection itemCollection);
/**
* 查询收藏
*
* @param id 收藏主键
* @return 收藏
*/
public ItemCollection selectItemCollectionById(Long id);
/**
* 新增收藏
*
* @param itemCollection 收藏
* @return 结果
*/
public int insertItemCollection(ItemCollection itemCollection);
/**
* 修改收藏
*
* @param itemCollection 收藏
* @return 结果
*/
public int updateItemCollection(ItemCollection itemCollection);
/**
* 删除收藏信息
*
* @param id 收藏主键
* @return 结果
*/
public int deleteItemCollectionById(Long id);
}

View File

@ -0,0 +1,49 @@
package com.amms.service;
import java.util.List;
import com.amms.domain.MuseumIntro;
/**
* 博物馆简介Service接口
*/
public interface IMuseumIntroService {
/**
* 查询博物馆简介列表
*
* @param museumIntro 博物馆简介
* @return 博物馆简介集合
*/
public List<MuseumIntro> selectMuseumIntroList(MuseumIntro museumIntro);
/**
* 查询博物馆简介
*
* @param museumName 博物馆简介主键
* @return 博物馆简介
*/
public MuseumIntro selectMuseumIntroByMuseumName(String museumName);
/**
* 新增博物馆简介
*
* @param museumIntro 博物馆简介
* @return 结果
*/
public int insertMuseumIntro(MuseumIntro museumIntro);
/**
* 修改博物馆简介
*
* @param museumIntro 博物馆简介
* @return 结果
*/
public int updateMuseumIntro(MuseumIntro museumIntro);
/**
* 删除博物馆简介信息
*
* @param museumName 博物馆简介主键
* @return 结果
*/
public int deleteMuseumIntroByMuseumName(String museumName);
}

View File

@ -0,0 +1,57 @@
package com.amms.service;
import java.util.List;
import com.amms.domain.ReservationVisitor;
/**
* 游客预约明细Service接口
*/
public interface IReservationVisitorService {
/**
* 查询游客预约明细列表
*
* @param reservationVisitor 游客预约明细
* @return 游客预约明细集合
*/
public List<ReservationVisitor> selectReservationVisitorList(ReservationVisitor reservationVisitor);
/**
* 查询游客预约明细
*
* @param id 游客预约明细主键
* @return 游客预约明细
*/
public ReservationVisitor selectReservationVisitorById(Long id);
/**
* 新增游客预约明细
*
* @param reservationVisitor 游客预约明细
* @return 结果
*/
public int insertReservationVisitor(ReservationVisitor reservationVisitor);
/**
* 修改游客预约明细
*
* @param reservationVisitor 游客预约明细
* @return 结果
*/
public int updateReservationVisitor(ReservationVisitor reservationVisitor);
/**
* 删除游客预约明细信息
*
* @param id 游客预约明细主键
* @return 结果
*/
public int deleteReservationVisitorById(Long id);
/**
* 根据预约ID批量删除游客预约明细信息
*
* @param reservationId 预约主表ID
* @return 结果
*/
public int deleteReservationVisitorsByReservationId(Long reservationId);
}

View File

@ -0,0 +1,65 @@
package com.amms.service;
import com.amms.domain.SysUser;
import com.amms.domain.dto.LoginParam;
import com.amms.domain.vo.Result;
import java.util.List;
public interface ISysUserService {
/**
* 登录方法
* @param loginParam 登录参数
* @return
*/
Result login(LoginParam loginParam);
/**
* 查询用户列表
* @param sysUser 用户
* @return 用户
*/
List<SysUser> selectSysUserList(SysUser sysUser);
/**
* 查询用户
* @param username 用户
* @return 用户
*/
SysUser selectByUsername(String username);
/**
* 查询用户详情
* @param id 用户
* @return 用户
*/
SysUser selectById(Long id);
/**
* 新增用户
*
* @param sysUser 用户
* @return 结果
*/
int insertSysUser(SysUser sysUser);
/**
* 修改用户
*
* @param sysUser 用户
* @return 结果
*/
int updateSysUser(SysUser sysUser);
/**
* 删除用户
*
* @param id 用户主键
* @return 结果
*/
int deleteSysUserById(Long id);
int registerTourist(SysUser sysUser);
}

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.amms.mapper.ItemCategoryMapper">
<resultMap type="com.amms.domain.ItemCategory" id="ItemCategoryResult">
<result property="id" column="id" />
<result property="name" column="name" />
<result property="creator" column="creator" />
<result property="createTime" column="create_time" />
<result property="updater" column="updater" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectItemCategoryVo">
select id, name, creator, create_time, updater, update_time, remark from item_category
</sql>
<select id="selectItemCategoryList" parameterType="com.amms.domain.ItemCategory" resultMap="ItemCategoryResult">
<include refid="selectItemCategoryVo"/>
<where>
<if test="name != null and name != ''"> and name like concat('%', #{name}, '%')</if>
<if test="creator != null "> and creator = #{creator}</if>
<if test="updater != null "> and updater = #{updater}</if>
</where>
order by create_time desc
</select>
<select id="selectItemCategoryById" parameterType="Long" resultMap="ItemCategoryResult">
<include refid="selectItemCategoryVo"/>
where id = #{id}
</select>
<insert id="insertItemCategory" parameterType="com.amms.domain.ItemCategory" useGeneratedKeys="true" keyProperty="id">
insert into item_category
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">name,</if>
<if test="creator != null">creator,</if>
<if test="createTime != null">create_time,</if>
<if test="updater != null">updater,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="name != null and name != ''">#{name},</if>
<if test="creator != null">#{creator},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updater != null">#{updater},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateItemCategory" parameterType="com.amms.domain.ItemCategory">
update item_category
<trim prefix="SET" suffixOverrides=",">
<if test="name != null and name != ''">name = #{name},</if>
<if test="creator != null">creator = #{creator},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updater != null">updater = #{updater},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteItemCategoryById" parameterType="Long">
delete from item_category where id = #{id}
</delete>
</mapper>