feat:浦发项目模块迁移-重大事项初始化

This commit is contained in:
2026-07-28 10:54:28 +08:00
parent 785b9a4b80
commit 5df36acc22
14 changed files with 1520 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
CREATE TABLE IF NOT EXISTS `material_type` (
`id` varchar(64) NOT NULL COMMENT '主键ID',
`name` varchar(255) DEFAULT NULL COMMENT '类型名称',
`sort` int DEFAULT 0 COMMENT '排序',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_material_type_sort` (`sort`, `createdAt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='重大事项-事项类型';
CREATE TABLE IF NOT EXISTS `material_item` (
`id` varchar(64) NOT NULL COMMENT '主键ID',
`typeId` varchar(64) DEFAULT NULL COMMENT '重大事项类型ID',
`title` varchar(255) DEFAULT NULL COMMENT '标题',
`content` text COMMENT '内容',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
`visible` tinyint(1) DEFAULT 1 COMMENT '是否公开',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_material_item_type` (`typeId`),
KEY `idx_material_item_visible` (`visible`, `delFlag`, `updatedAt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='重大事项-事项';
-- 浦发 PostgreSQL 数据迁移到本项目 MySQL 时的字段映射:
-- material_type: id -> id, name -> name, sort -> sort,
-- create_time/update_time 转 13 位时间戳写入 createdAt/updatedAt,
-- deleted=0/1 转 delFlag=0/1。
-- material_item: type_id -> typeId, visible=0/1 可直接写入 visible,
-- create_time/update_time 转 13 位时间戳写入 createdAt/updatedAt,
-- deleted=0/1 转 delFlag=0/1。
@@ -0,0 +1,66 @@
-- 重大事项电脑端菜单。
-- 使用 permission 做幂等判断,避免同一环境重复执行时插入重复菜单。
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT
'f6d70b1a40b64d0f98684426a6200001',
'',
LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'),
'重大事项',
'Material',
'menu',
'',
'',
'ti-announcement',
1,
0,
'material',
NULL,
992,
1,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
0,
'PC',
NULL,
NULL,
'z',
0,
0
FROM sys_menu
WHERE (parentId = '' OR parentId IS NULL)
AND CHAR_LENGTH(path) = 4
HAVING NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'material') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'f6d70b1a40b64d0f98684426a6200002', p.id, CONCAT(p.path, '0001'), '类型管理', 'Material Type', 'menu', '/platform/material/type', 'data-pjax', '', 1, 0, 'material.type', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'material'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'material.type') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'f6d70b1a40b64d0f98684426a6200003', p.id, CONCAT(p.path, '0002'), '事项管理', 'Material Item', 'menu', '/platform/material/item', 'data-pjax', '', 1, 0, 'material.item', NULL, 2, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 's', 0, 0
FROM sys_menu p
WHERE p.permission = 'material'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'material.item') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'f6d70b1a40b64d0f98684426a6200004', p.id, CONCAT(p.path, '0003'), '事项展示', 'Material Display', 'menu', '/platform/material/item/displayIndex', 'data-pjax', '', 1, 0, 'material.display', NULL, 3, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 's', 0, 0
FROM sys_menu p
WHERE p.permission = 'material'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'material.display') t);
-- 给系统管理员默认授权,其他角色可在角色管理中按需分配。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id
FROM sys_role r
JOIN sys_menu m ON m.permission IN (
'material',
'material.type',
'material.item',
'material.display'
)
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
WHERE r.code = 'SYSADMIN'
AND rm.roleId IS NULL;
@@ -0,0 +1,635 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.material-display-page {
padding: 20px;
box-sizing: border-box;
}
.material-home {
height: 65vh;
}
.material-header {
display: flex;
align-items: center;
width: 100%;
margin-bottom: 20px;
}
.material-title {
margin: 0;
font-size: 24px;
color: #303133;
}
.material-subtitle {
margin: 8px 0 0;
color: #909399;
font-size: 14px;
}
.material-title-refresh {
margin-left: 8px;
vertical-align: middle;
}
.material-type-filter {
min-width: 20%;
}
.material-home-scroll {
height: 65vh;
overflow-y: auto;
}
.material-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
grid-auto-rows: 360px;
align-items: start;
gap: 50px;
padding: 14px 15px 10px;
}
.material-card-slot {
height: 360px;
min-width: 0;
}
.material-card {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
border-radius: 8px;
transition: all 0.3s ease;
}
.material-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.material-card-head {
display: flex;
align-items: center;
gap: 12px;
padding-bottom: 10px;
cursor: pointer;
}
.material-card-name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 18px;
font-weight: 600;
color: #303133;
}
.material-card .el-card__body {
flex: 1;
display: flex;
flex-direction: column;
}
.material-card-body {
height: 270px;
padding: 10px 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.material-list {
margin: 0;
padding: 0;
width: 100%;
list-style: none;
}
.material-list-item {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: color 0.2s;
}
.material-list-item:last-child {
border-bottom: none;
}
.material-list-item:only-child {
border-bottom: 1px solid #f0f0f0;
}
.material-list-item:hover {
color: #409eff;
}
.material-list-item:hover .material-dot {
background: #409eff;
}
.material-list-item:hover .material-list-title {
color: #409eff;
}
.material-list-item:hover .material-list-time {
color: #409eff;
background: #ecf5ff;
}
.material-dot {
width: 6px;
height: 6px;
margin-right: 12px;
border-radius: 50%;
background: #dcdfe6;
flex-shrink: 0;
}
.material-list-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #606266;
font-size: 14px;
}
.material-list-time {
margin-left: 12px;
padding: 2px 6px;
border-radius: 4px;
background: #f5f7fa;
color: #999;
font-size: 12px;
flex-shrink: 0;
}
.material-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20px 0;
color: #b4b4b4;
}
.material-home-empty {
height: 65vh;
display: flex;
align-items: center;
justify-content: center;
}
.material-detail-title {
margin: 0;
font-size: 24px;
font-weight: 700;
color: #303133;
}
.material-detail-meta {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-top: 10px;
color: #606266;
font-size: 14px;
}
.material-detail-panel {
margin: 0;
padding: 0;
background: #f0f2f5;
min-height: 65vh;
box-sizing: border-box;
}
.material-detail-card {
min-height: 65vh;
padding: 40px;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
.material-detail-head {
display: grid;
grid-template-columns: 90px 1fr 90px;
align-items: start;
gap: 12px;
}
.material-detail-back {
justify-self: start;
padding: 0;
}
.material-detail-heading {
min-width: 0;
text-align: center;
}
.material-detail-divider {
height: 1px;
margin: 22px 0;
background: #ebeef5;
}
.material-detail-body {
padding: 20px 0;
font-size: 16px;
line-height: 1.8;
color: #303133;
}
.material-detail-scroll {
max-height: 50vh;
overflow-y: auto;
padding: 0 20px;
box-sizing: border-box;
}
.material-detail-content {
line-height: 1.8;
color: #303133;
overflow: auto;
}
.material-detail-content img,
.material-detail-content video,
.material-detail-content iframe {
max-width: 100%;
}
.material-detail-remark {
margin-top: 16px;
color: #999;
font-size: 12px;
text-align: right;
word-break: break-all;
}
.material-list-panel {
margin: 0;
padding: 0;
background: #f0f2f5;
min-height: 65vh;
box-sizing: border-box;
}
.material-list-card {
min-height: 65vh;
padding: 40px;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
.material-list-head {
display: grid;
grid-template-columns: 90px 1fr 260px;
align-items: center;
gap: 16px;
}
.material-list-back {
justify-self: start;
padding: 0;
}
.material-list-heading {
min-width: 0;
text-align: center;
}
.material-list-title-large {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 20px;
font-weight: 700;
color: #303133;
}
.material-list-search {
justify-self: end;
width: 240px;
}
.material-list-divider {
height: 1px;
margin: 22px 0;
background: #ebeef5;
}
.material-full-list {
height: calc(65vh - 210px);
min-height: 260px;
display: flex;
align-items: stretch;
justify-content: center;
}
.material-full-list-scroll {
width: 100%;
overflow-y: auto;
padding-right: 30px;
box-sizing: border-box;
}
.material-full-list ul {
margin: 0;
padding: 0;
list-style: none;
}
.material-full-list-item {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: color 0.2s;
}
.material-full-list-item:hover {
color: #409eff;
}
.material-full-list-item:hover .material-bullet {
background: #409eff;
}
.material-bullet {
width: 6px;
height: 6px;
margin-right: 12px;
border-radius: 50%;
background: #dcdfe6;
flex-shrink: 0;
}
.material-full-list-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #606266;
font-size: 15px;
}
.material-full-list-item:hover .material-full-list-title {
color: #409eff;
}
.material-full-list-date {
margin-left: 12px;
padding: 2px 6px;
border-radius: 4px;
background: #f5f7fa;
color: #999;
font-size: 14px;
flex-shrink: 0;
}
.material-full-list-item:hover .material-full-list-date {
color: #409eff;
background: #ecf5ff;
}
.material-list-pagination {
display: flex;
justify-content: center;
margin-top: 20px;
}
</style>
<div id="app" class="material-display-page" v-cloak>
<div v-if="viewMode === 'home'" class="material-home">
<div class="material-header">
<div style="flex: 1">
<h2 class="material-title">
重大事项
<el-link class="material-title-refresh"
underline="never"
icon="el-icon-refresh"
@click="filterPage"></el-link>
</h2>
<p class="material-subtitle">根据相关规定,现将各类信息公开如下:</p>
</div>
<el-select class="material-type-filter"
v-model="query.typeIds"
multiple
collapse-tags
clearable
placeholder="筛选重大事项类型"
@change="filterPage">
<el-option v-for="item in typeOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</div>
<div class="material-home-scroll" v-loading="loading" @scroll="onHomeScroll">
<div class="material-grid" v-if="groups.length">
<div class="material-card-slot" v-for="item in groups" :key="item.id">
<el-card class="material-card" shadow="hover">
<div slot="header" class="material-card-head" @click="openList(item)">
<span class="material-card-name">{{ item.name }}</span>
</div>
<div class="material-card-body">
<ul class="material-list" v-if="item.details && item.details.length">
<li class="material-list-item" v-for="detail in item.details" :key="detail.id"
@click="openDetail(detail, item.name)">
<span class="material-dot"></span>
<span class="material-list-title">{{ detail.title }}</span>
<span class="material-list-time">{{ formatDate(detail.updatedAt || detail.createdAt) }}</span>
</li>
</ul>
<div class="material-empty" v-else>暂无数据</div>
</div>
</el-card>
</div>
</div>
<div class="material-home-empty" v-else>
<el-empty v-if="!loading"></el-empty>
</div>
</div>
</div>
<div v-else-if="viewMode === 'list'" class="material-list-panel">
<div class="material-list-card">
<div class="material-list-head">
<el-button type="text" class="material-list-back" icon="el-icon-back" @click="backHome">返回</el-button>
<div class="material-list-heading">
<span class="material-list-title-large">{{ currentType.name }}</span>
</div>
<el-input class="material-list-search"
v-model="listKeyword"
placeholder="搜索标题..."
prefix-icon="el-icon-search"
clearable
@keyup.enter.native="searchList"
@clear="searchList"
@blur="searchList"></el-input>
</div>
<div class="material-list-divider"></div>
<div class="material-full-list" v-loading="loading">
<div class="material-full-list-scroll" v-if="listData.length">
<ul>
<li class="material-full-list-item"
v-for="detail in listData"
:key="detail.id"
@click="openDetail(detail, currentType.name)">
<span class="material-bullet"></span>
<span class="material-full-list-title">{{ detail.title }}</span>
<span class="material-full-list-date">{{ formatDate(detail.updatedAt || detail.createdAt) }}</span>
</li>
</ul>
</div>
<el-empty v-else-if="!loading"></el-empty>
</div>
<div class="material-list-pagination" v-if="listPage.totalCount > 0">
<el-pagination
small
layout="total, sizes, prev, pager, next, jumper"
:current-page.sync="listPage.pageNumber"
:page-size="listPage.pageSize"
:page-sizes="[10, 20, 30, 40, 50, 100]"
:total="listPage.totalCount"
@size-change="onListSizeChange"
@current-change="loadList">
</el-pagination>
</div>
</div>
</div>
<div v-else class="material-detail-panel">
<div class="material-detail-card" v-loading="loading">
<div class="material-detail-head">
<el-button type="text" class="material-detail-back" icon="el-icon-back" @click="backFromDetail">返回</el-button>
<div class="material-detail-heading">
<h2 class="material-detail-title">{{ detailData.title }}</h2>
<div class="material-detail-meta">
<el-tag size="small" effect="plain">{{ detailData.typeName || currentType.name }}</el-tag>
<span>发布时间:{{ formatDateTime(detailData.updatedAt || detailData.createdAt) }}</span>
</div>
</div>
<span></span>
</div>
<div class="material-detail-divider"></div>
<article class="material-detail-body">
<div class="material-detail-scroll">
<div class="material-detail-content" v-html="detailData.content"></div>
</div>
<div class="material-detail-remark" v-if="detailData.remark">
备注:{{ detailData.remark }}
</div>
</article>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
viewMode: "home",
previousMode: "home",
loading: false,
groups: [],
listData: [],
detailData: {},
currentType: {},
typeOptions: [],
listKeyword: '',
query: {
typeIds: []
},
pageForm: {
pageNumber: 1,
pageSize: 8,
totalCount: 0
},
listPage: {
pageNumber: 1,
pageSize: 10,
totalCount: 0
}
}
},
methods: {
formatDate(value) {
if (!value) return ''
const date = new Date(Number(value))
if (isNaN(date.getTime())) return ''
const pad = (num) => String(num).padStart(2, '0')
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate())
},
formatDateTime(value) {
if (!value) return '-'
const date = new Date(Number(value))
if (isNaN(date.getTime())) return '-'
const pad = (num) => String(num).padStart(2, '0')
return date.getFullYear() + '-' +
pad(date.getMonth() + 1) + '-' +
pad(date.getDate()) + ' ' +
pad(date.getHours()) + ':' +
pad(date.getMinutes())
},
loadTypes() {
this.$axios.post("/platform/material/type/list").then((res) => {
if (res.code === 0) {
this.typeOptions = res.data || []
}
})
},
filterPage() {
this.pageForm.pageNumber = 1
this.groups = []
this.pageData()
},
onHomeScroll(event) {
const el = event.target
const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 20
if (!nearBottom || this.loading || this.groups.length >= this.pageForm.totalCount) {
return
}
this.pageForm.pageNumber += 1
this.pageData(true)
},
pageData(append) {
this.loading = true
this.$axios.post("/platform/material/item/display", {
pageNumber: this.pageForm.pageNumber,
pageSize: this.pageForm.pageSize,
typeIds: JSON.stringify(this.query.typeIds || [])
}).then((res) => {
if (res.code === 0) {
const list = res.data.list || []
this.groups = append ? this.groups.concat(list) : list
this.pageForm.totalCount = res.data.totalCount || 0
}
}).finally(() => {
this.loading = false
})
},
openList(type) {
this.currentType = type
this.listKeyword = ''
this.listPage.pageNumber = 1
this.viewMode = "list"
this.loadList()
},
searchList() {
this.listPage.pageNumber = 1
this.loadList()
},
onListSizeChange(size) {
this.listPage.pageSize = size
this.listPage.pageNumber = 1
this.loadList()
},
loadList() {
this.loading = true
this.$axios.post("/platform/material/item/detailList", {
pageNumber: this.listPage.pageNumber,
pageSize: this.listPage.pageSize,
typeId: this.currentType.id,
searchKeyword: this.listKeyword
}).then((res) => {
if (res.code === 0) {
this.listData = res.data.list || []
this.listPage.totalCount = res.data.totalCount || 0
}
}).finally(() => {
this.loading = false
})
},
openDetail(row, typeName) {
this.previousMode = this.viewMode
this.loading = true
this.$axios.post("/platform/material/item/info", {id: row.id}).then((res) => {
if (res.code === 0) {
this.detailData = res.data || {}
if (typeName && !this.detailData.typeName) {
this.detailData.typeName = typeName
}
this.viewMode = "detail"
}
}).finally(() => {
this.loading = false
})
},
backHome() {
this.viewMode = "home"
this.currentType = {}
},
backFromDetail() {
this.viewMode = this.previousMode || "home"
}
},
created() {
this.loadTypes()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,200 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="标题/备注">
<el-input placeholder="请输入标题或备注" clearable v-model="pageForm.searchKeyword"
@keyup.enter.native="doSearch"></el-input>
</search-item>
<search-item label="类型">
<el-select v-model="query.typeId" clearable placeholder="请选择类型" @change="doSearch">
<el-option v-for="item in typeOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="是否公开">
<el-select v-model="query.visible" clearable placeholder="请选择" @change="doSearch">
<el-option label="公开" :value="true"></el-option>
<el-option label="不公开" :value="false"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt20">
<table-tool label="重大事项">
<el-button type="primary" size="small" @click="onAdd">
<i class="ti-plus"></i>
新增事项
</el-button>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column label="类型" prop="typeName" align="center" width="160" show-overflow-tooltip></el-table-column>
<el-table-column label="标题" prop="title" align="center" min-width="220" show-overflow-tooltip></el-table-column>
<el-table-column label="备注" prop="remark" align="center" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column label="是否公开" prop="visible" align="center" width="110">
<template v-slot="{ row }">
<el-switch v-model="row.visible"
@change="onVisibleChange(row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createdAt" align="center" width="180">
<template v-slot="{ row }">{{ formatTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="更新时间" prop="updatedAt" align="center" width="180">
<template v-slot="{ row }">{{ formatTime(row.updatedAt || row.createdAt) }}</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="200">
<template v-slot="{ row }">
<el-button @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button @click="onDelete(row)" size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog :title="formData.id ? '编辑事项' : '新增事项'" :visible.sync="dialogVisible" width="72%" top="5vh">
<el-form :model="formData" ref="formRef" label-width="100px">
<el-form-item label="类型" prop="typeId" :rules="{ required: true, message: '请选择类型', trigger: 'change' }">
<el-select v-model="formData.typeId" filterable placeholder="请选择类型" style="width: 100%">
<el-option v-for="item in typeOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="标题" prop="title" :rules="{ required: true, message: '请输入标题', trigger: 'blur' }">
<el-input v-model="formData.title" maxlength="255" placeholder="请输入标题"></el-input>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" maxlength="255" placeholder="请输入备注"></el-input>
</el-form-item>
<el-form-item label="是否公开" prop="visible">
<el-switch v-model="formData.visible" active-text="公开" inactive-text="不公开"></el-switch>
</el-form-item>
<el-form-item label="内容" prop="content" :rules="{ required: true, message: '请输入内容', trigger: 'blur' }">
<text-editor v-model="formData.content"></text-editor>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
</template>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
data() {
return {
dialogVisible: false,
formData: {},
typeOptions: [],
query: {
typeId: '',
visible: ''
}
}
},
methods: {
formatTime(value) {
if (!value) return '-'
const date = new Date(Number(value))
if (isNaN(date.getTime())) return '-'
const pad = (num) => String(num).padStart(2, '0')
return date.getFullYear() + '-' +
pad(date.getMonth() + 1) + '-' +
pad(date.getDate()) + ' ' +
pad(date.getHours()) + ':' +
pad(date.getMinutes())
},
loadTypes() {
this.$axios.post("/platform/material/type/list").then((res) => {
if (res.code === 0) {
this.typeOptions = res.data || []
}
})
},
doSearch() {
this.pageForm.pageNumber = 1
this.pageData()
},
onAdd() {
this.formData = {visible: true}
this.dialogVisible = true
},
onEdit(row) {
this.$axios.post("/platform/material/item/info", {id: row.id}).then((res) => {
if (res.code === 0) {
this.formData = Object.assign({visible: true}, res.data)
this.dialogVisible = true
}
})
},
onDelete(row) {
this.$confirm("您确定要删除该事项吗?", "提示", {
type: "warning",
confirmButtonText: "确定",
cancelButtonText: "取消"
}).then(() => {
this.$axios.post("/platform/material/item/delete", {id: row.id}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
}).catch(() => {})
},
onVisibleChange(row) {
this.$axios.post("/platform/material/item/updateVisible", {
id: row.id,
visible: row.visible
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
} else {
this.pageData()
}
}).catch(() => {
this.pageData()
})
},
onSubmit() {
this.$refs.formRef.validate((valid) => {
if (!valid) return
this.$axios.post("/platform/material/item/submit", {data: JSON.stringify(this.formData)}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.dialogVisible = false
this.pageData()
}
})
})
},
pageData() {
const params = Object.assign({}, this.pageForm, this.query)
this.$axios.post("/platform/material/item/pageData", params).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
})
},
},
created() {
this.loadTypes()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,53 @@
const basicForm = {
template: /*language=HTML*/ `
<div>
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="110px" label-position="left">
<el-form-item label="类型名称" prop="name">
<el-input v-model="formData.name" maxlength="100" placeholder="请输入类型名称"></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort" :min="0" :step="1"></el-input-number>
</el-form-item>
</el-form>
<el-row class="mt10" justify="end" type="flex">
<el-button @click="$emit('refresh')">取消</el-button>
<el-button @click="onSubmit" type="primary">提交</el-button>
</el-row>
</div>
`,
data() {
return {
formData: {
sort: 0
},
formRules: {
name: [{required: true, message: '必填', trigger: ['blur', 'change']}],
sort: [{required: true, message: '必填', trigger: ['blur', 'change']}],
},
}
},
methods: {
onOpen(row) {
this.formData = row && row.id ? clone(row) : {sort: 0}
},
onSubmit() {
this.$refs.formRef.validate((valid) => {
if (valid) {
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
const resp = await this.$axios.post("/platform/material/type/submit", this.formData)
if (resp.code === 0) {
this.$message.success(resp.msg)
this.$emit('refresh')
} else {
this.$message.warning(resp.msg)
}
})
}
})
},
}
}
@@ -0,0 +1,98 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<template>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="类型名称">
<el-input placeholder="请输入类型名称" clearable v-model="pageForm.searchKeyword"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt20">
<table-tool label="重大事项类型">
<el-button type="primary" size="small" @click="onAdd">
<i class="ti-plus"></i>
新增类型
</el-button>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column label="类型名称" prop="name" align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="排序" prop="sort" align="center" sortable="custom" width="120"></el-table-column>
<el-table-column label="操作" width="180">
<template v-slot="{ row }">
<el-button @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button @click="onDelete(row)" size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</template>
<template #edit>
<basic-form ref="basicFormRef" @refresh="refresh"></basic-form>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include('basicForm.js'){}#-->
const vue = new Vue({
el: "#app",
mixins: [initTableMixins],
components: {
"basic-form": basicForm,
},
methods: {
refresh() {
this.doSearch()
this.$refs.guava.index()
},
onAdd() {
this.$refs.guava.edit(() => {
this.$refs.basicFormRef.onOpen()
})
},
onEdit(row) {
this.$refs.guava.edit(() => {
this.$refs.basicFormRef.onOpen(row)
})
},
onDelete(row) {
this.$confirm("您确定要删除吗, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/material/type/delete", {id: row.id}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
}).catch(() => {})
},
pageData() {
this.$axios.post(loc() + "/pageData", this.pageForm).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
})
},
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->