新增web端职代会管理(职代会届次取值teacher_congress_session表数据);

This commit is contained in:
2026-07-29 14:57:41 +08:00
parent 704bc278fa
commit 8bff6f8ce9
34 changed files with 3841 additions and 0 deletions
@@ -0,0 +1,295 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="会议名称">
<el-input v-model.trim="pageForm.name" clearable placeholder="请输入会议名称"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="投票管理">
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd">新增</el-button>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="name" label="会议名称" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column prop="typeName" label="投票类型" min-width="130"></el-table-column>
<el-table-column prop="materialsName" label="投票资料" min-width="180" show-overflow-tooltip>
<template slot-scope="{row}">
<el-link v-if="row.fileUrl" type="primary" :href="row.fileUrl" target="_blank">
{{row.materialsName}}
</el-link>
<span v-else>{{row.materialsName || "-"}}</span>
</template>
</el-table-column>
<el-table-column label="投票时间" min-width="300">
<template slot-scope="{row}">
{{formatDate(row.votingStartTime)}} 至 {{formatDate(row.votingEndTime)}}
</template>
</el-table-column>
<el-table-column prop="voteCount" label="已投人数" width="90" align="center"></el-table-column>
<el-table-column label="操作" width="160" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" :disabled="row.voteCount > 0" @click="openEdit(row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" class="text-danger" :disabled="row.voteCount > 0"
@click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑投票' : '新增投票'" :visible.sync="dialogFormVisible"
width="850px" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="届次" prop="sessionId">
<el-select v-model="formData.sessionId" filterable style="width:100%" @change="sessionChanged">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="投票类型" prop="typeId">
<el-select v-model="formData.typeId" filterable style="width:100%" @change="typeChanged">
<el-option v-for="item in voteTypeOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="会议名称" prop="name">
<el-input v-model.trim="formData.name" maxlength="255" show-word-limit></el-input>
</el-form-item>
<el-form-item label="资料" prop="materialsId">
<el-select v-model="formData.materialsId" filterable style="width:100%"
:disabled="!formData.sessionId || !formData.typeId">
<el-option v-for="item in materialOptions" :key="item.id"
:label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="投票开始" prop="votingStartTime">
<el-date-picker v-model="formData.votingStartTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="投票结束" prop="votingEndTime">
<el-date-picker v-model="formData.votingEndTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="事项表头">
<el-input v-model.trim="formData.votingContentItemHeader" placeholder="表决事项"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="选项表头">
<el-input v-model.trim="formData.votingContentOptionHeader" placeholder="表决内容"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="投票内容" required>
<el-table :data="voteItems" border size="small">
<el-table-column label="表决事项" min-width="230">
<template slot-scope="{row}">
<el-input v-model.trim="row.itemName" maxlength="255" placeholder="请输入表决事项"></el-input>
</template>
</el-table-column>
<el-table-column label="表决选项(逗号分隔)" min-width="330">
<template slot-scope="{row}">
<el-input v-model.trim="row.optionsText" placeholder="赞成,不赞成,弃权"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template slot-scope="scope">
<el-button type="text" class="text-danger" @click="removeVoteItem(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-button class="mt10" size="small" icon="el-icon-plus" @click="addVoteItem">添加表决事项</el-button>
</el-form-item>
<el-form-item label="投票说明">
<el-input v-model.trim="formData.description" type="textarea" :rows="3"
maxlength="1000" show-word-limit></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="dialogFormVisible=false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="doSave">确定</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const validateEnd = (rule, value, callback) => {
if (!value) return callback(new Error("请选择投票结束时间"))
if (this.formData.votingStartTime && value <= this.formData.votingStartTime) {
return callback(new Error("投票结束时间必须晚于开始时间"))
}
callback()
}
return {
pageDataUrl: "/platform/congress/materials/issue/pageData",
sessionOptions: [],
typeOptions: [],
materialOptions: [],
voteItems: [],
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0, sessionId: "", name: ""},
formData: {},
formRules: {
sessionId: [{required: true, message: "请选择届次", trigger: "change"}],
typeId: [{required: true, message: "请选择投票类型", trigger: "change"}],
name: [{required: true, message: "请输入会议名称", trigger: "blur"}],
materialsId: [{required: true, message: "请选择资料", trigger: "change"}],
votingStartTime: [{required: true, message: "请选择投票开始时间", trigger: "change"}],
votingEndTime: [{required: true, validator: validateEnd, trigger: "change"}]
}
}
},
computed: {
voteTypeOptions() {
return this.typeOptions.filter(v => !!v.isVote)
}
},
methods: {
formatDate(value) {
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : "-"
},
async loadOptions() {
const [sessionsRes, typesRes] = await Promise.all([
this.$axios.post("/platform/congress/materials/issue/sessions"),
this.$axios.post("/platform/congress/materials/issue/types")
])
if (sessionsRes.code === 0) this.sessionOptions = sessionsRes.data || []
if (typesRes.code === 0) this.typeOptions = typesRes.data || []
},
async loadMaterials() {
this.materialOptions = []
if (!this.formData.sessionId || !this.formData.typeId) return
const res = await this.$axios.post("/platform/congress/materials/issue/materials", {
sessionId: this.formData.sessionId,
typeId: this.formData.typeId
})
if (res.code === 0) this.materialOptions = res.data || []
},
async sessionChanged() {
this.formData.materialsId = ""
await this.loadMaterials()
},
async typeChanged() {
this.formData.materialsId = ""
await this.loadMaterials()
},
addVoteItem() {
this.voteItems.push({itemName: "", optionsText: "赞成,不赞成,弃权"})
},
removeVoteItem(index) {
this.voteItems.splice(index, 1)
},
openAdd() {
this.formData = {
name: "", sessionId: "", typeId: "", materialsId: "",
votingStartTime: "", votingEndTime: "", description: "",
votingContentItemHeader: "表决事项", votingContentOptionHeader: "表决内容"
}
this.voteItems = [{itemName: "", optionsText: "赞成,不赞成,弃权"}]
this.materialOptions = []
this.dialogFormVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
async openEdit(id) {
const res = await this.$axios.post("/platform/congress/materials/issue/get", {id})
if (res.code !== 0) return
const data = res.data || {}
data.votingStartTime = this.formatDate(data.votingStartTime)
data.votingEndTime = this.formatDate(data.votingEndTime)
this.formData = data
try {
this.voteItems = JSON.parse(data.votingContent || "[]").map(item => ({
itemName: item.itemName || item.item || "",
optionsText: (item.options || []).join(",")
}))
} catch (e) {
this.voteItems = []
}
await this.loadMaterials()
this.dialogFormVisible = true
},
buildVotingContent() {
const items = this.voteItems.map(item => ({
itemName: (item.itemName || "").trim(),
options: (item.optionsText || "").split(/[,,]/)
.map(v => v.trim()).filter(Boolean)
}))
if (!items.length || items.some(item => !item.itemName || !item.options.length)) {
this.$message.warning("请完整填写每个表决事项及表决选项")
return null
}
return JSON.stringify(items)
},
doSave() {
this.$refs.formRef.validate(async valid => {
if (!valid) return
const votingContent = this.buildVotingContent()
if (!votingContent) return
this.submitLoading = true
try {
const data = Object.assign({}, this.formData, {votingContent})
const res = await this.$axios.post("/platform/congress/materials/issue/save", data)
if (res.code === 0) {
this.dialogFormVisible = false
this.$message.success(res.msg)
this.pageData()
} else {
this.$message.error(res.msg)
}
} finally {
this.submitLoading = false
}
})
},
doDelete(id) {
this.$confirm("确认删除该投票议题吗?", "提示", {type: "warning"}).then(async () => {
const res = await this.$axios.post("/platform/congress/materials/issue/delete", {id})
if (res.code === 0) {
this.$message.success(res.msg)
this.doSearch()
} else {
this.$message.error(res.msg)
}
})
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,231 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="资料类型">
<el-select v-model="pageForm.typeId" clearable filterable placeholder="请选择资料类型">
<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-input v-model.trim="pageForm.name" clearable placeholder="请输入资料名称"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="资料管理">
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd">新增</el-button>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="name" label="资料名称" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="typeName" label="资料类型" min-width="140"></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column prop="sortNo" label="排序号" width="90" align="center"></el-table-column>
<el-table-column prop="fileName" label="文件" min-width="180" show-overflow-tooltip>
<template slot-scope="{row}">
<el-link v-if="row.fileUrl" type="primary" :href="row.fileUrl" target="_blank">
{{row.fileName || row.name}}
</el-link>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="openEdit(row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" class="text-danger" @click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑资料' : '新增资料'" :visible.sync="dialogFormVisible"
width="700px" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="届次" prop="sessionId">
<el-select v-model="formData.sessionId" filterable style="width:100%">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="资料类型" prop="typeId">
<el-select v-model="formData.typeId" filterable 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="name">
<el-input v-model.trim="formData.name" maxlength="255" show-word-limit
placeholder="不需要填写,默认是上传文件名称"></el-input>
</el-form-item>
<el-form-item label="排序号" prop="sortNo">
<el-input-number v-model="formData.sortNo" :min="0" :max="9999" style="width:100%"></el-input-number>
</el-form-item>
<el-form-item label="上传文件" prop="files">
<file-upload :value.sync="formData.files" :upload_number="1"
upload_result_type="url" upload_result_category="array"
complete_result accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"></file-upload>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="dialogFormVisible=false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="doSave">确定</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
pageDataUrl: "/platform/congress/materials/manage/pageData",
sessionOptions: [],
typeOptions: [],
pageForm: {
pageNumber: 1, pageSize: 10, totalCount: 0,
sessionId: "", typeId: "", name: ""
},
formData: {},
formRules: {
sessionId: [{required: true, message: "请选择届次", trigger: "change"}],
typeId: [{required: true, message: "请选择资料类型", trigger: "change"}],
name: [{required: true, message: "请输入资料名称", trigger: "blur"}],
files: [{required: true, message: "请上传文件", trigger: "change"}]
}
}
},
methods: {
async loadOptions() {
const [sessionsRes, typesRes] = await Promise.all([
this.$axios.post("/platform/congress/materials/manage/sessions"),
this.$axios.post("/platform/congress/materials/manage/types")
])
if (sessionsRes.code === 0) this.sessionOptions = sessionsRes.data || []
if (typesRes.code === 0) this.typeOptions = typesRes.data || []
},
openAdd() {
this.formData = {
name: "", sessionId: "", typeId: "",
sortNo: 0, fileName: "", fileUrl: "", fileType: "", fileSize: "", files: []
}
this.dialogFormVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
async openEdit(id) {
const res = await this.$axios.post("/platform/congress/materials/manage/get", {id})
if (res.code === 0) {
const data = res.data || {}
this.formData = Object.assign({}, data, {
files: this.buildUploadFiles(data)
})
this.dialogFormVisible = true
}
},
buildUploadFiles(data) {
if (!data || !data.fileUrl) return []
return [{
name: data.fileName || this.fileNameFromUrl(data.fileUrl) || data.name || "附件",
url: data.fileUrl,
status: "success",
response: {
code: 0,
data: data.fileUrl
}
}]
},
fileNameFromUrl(url) {
if (!url) return ""
const path = String(url).split("?")[0]
const name = path.substring(path.lastIndexOf("/") + 1)
try {
return decodeURIComponent(name)
} catch (e) {
return name
}
},
fileNameWithoutExt(name) {
return name ? String(name).replace(/\.[^/.]+$/, "") : ""
},
getUploadFileUrl(file) {
return file && file.response && file.response.data ? file.response.data : file && (file.url || file.data) || ""
},
syncUploadFiles(files) {
const file = Array.isArray(files) && files.length > 0 ? files[files.length - 1] : null
if (!file) {
this.$set(this.formData, "fileName", "")
this.$set(this.formData, "fileUrl", "")
this.$set(this.formData, "fileType", "")
this.$set(this.formData, "fileSize", "")
return
}
const fileName = file.name || this.fileNameFromUrl(this.getUploadFileUrl(file))
this.$set(this.formData, "fileName", fileName)
this.$set(this.formData, "fileUrl", this.getUploadFileUrl(file))
this.$set(this.formData, "fileType", file.raw && file.raw.type ? file.raw.type : file.type || "")
this.$set(this.formData, "fileSize", file.raw && file.raw.size ? file.raw.size : file.size || "")
if (!this.formData.name && fileName) {
this.$set(this.formData, "name", this.fileNameWithoutExt(fileName))
}
},
doSave() {
this.syncUploadFiles(this.formData.files)
this.$refs.formRef.validate(async valid => {
if (!valid) return
this.submitLoading = true
try {
const res = await this.$axios.post("/platform/congress/materials/manage/save", this.formData)
if (res.code === 0) {
this.dialogFormVisible = false
this.$message.success(res.msg)
this.pageData()
} else {
this.$message.error(res.msg)
}
} finally {
this.submitLoading = false
}
})
},
doDelete(id) {
this.$confirm("确认删除该会议资料吗?", "提示", {type: "warning"}).then(async () => {
const res = await this.$axios.post("/platform/congress/materials/manage/delete", {id})
if (res.code === 0) {
this.$message.success(res.msg)
this.doSearch()
} else {
this.$message.error(res.msg)
}
})
}
},
watch: {
"formData.files": {
handler(files) {
this.syncUploadFiles(files)
},
deep: true
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,170 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="会议名称">
<el-input v-model.trim="pageForm.name" clearable placeholder="请输入会议名称"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="投票统计"></table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="name" label="会议名称" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="typeName" label="投票类型" min-width="130"></el-table-column>
<el-table-column prop="materialsName" label="资料" min-width="180" show-overflow-tooltip>
<template slot-scope="{row}">
<el-link v-if="row.fileUrl" type="primary" :href="row.fileUrl" target="_blank">
{{row.materialsName}}
</el-link>
<span v-else>{{row.materialsName || "-"}}</span>
</template>
</el-table-column>
<el-table-column prop="voteCount" label="投票人数" width="100" align="center"></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="openDetail(row)">查看统计</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-drawer title="投票明细" :visible.sync="detailVisible" size="72%" :with-header="true">
<div style="padding:0 24px 24px">
<el-descriptions v-if="currentIssue.id" :column="2" border>
<el-descriptions-item label="会议名称">{{currentIssue.name}}</el-descriptions-item>
<el-descriptions-item label="届次">{{currentIssue.sessionName}}</el-descriptions-item>
<el-descriptions-item label="投票类型">{{currentIssue.typeName}}</el-descriptions-item>
<el-descriptions-item label="投票人数">{{currentIssue.voteCount}}</el-descriptions-item>
</el-descriptions>
<el-tabs v-model="detailTab" class="mt10">
<el-tab-pane label="投票统计" name="summary">
<el-table :data="summaryData" border v-loading="detailLoading">
<el-table-column type="index" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="itemName" label="投票项目/人员" min-width="200"></el-table-column>
<el-table-column prop="option" label="投票选项" min-width="160"></el-table-column>
<el-table-column prop="count" label="票数" width="100" align="center"></el-table-column>
<el-table-column label="占比" width="120" align="center">
<template slot-scope="{row}">{{percent(row.count, row.itemTotal)}}</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="投票记录" name="records">
<el-table :data="recordData" border v-loading="detailLoading">
<el-table-column type="index" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="emplid" label="工号" min-width="110"></el-table-column>
<el-table-column prop="name" label="代表" min-width="100"></el-table-column>
<el-table-column prop="unionName" label="工会" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="delegationName" label="代表团" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column label="投票结果" min-width="260" show-overflow-tooltip>
<template slot-scope="{row}">{{voteResultText(row.voteResult)}}</template>
</el-table-column>
<el-table-column label="投票时间" min-width="170">
<template slot-scope="{row}">{{formatDate(row.createTime)}}</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</el-drawer>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
pageDataUrl: "/platform/congress/materials/statistical/pageData",
sessionOptions: [],
detailVisible: false,
detailLoading: false,
detailTab: "summary",
currentIssue: {},
recordData: [],
summaryData: [],
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0, sessionId: "", name: ""}
}
},
methods: {
formatDate(value) {
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : "-"
},
percent(count, total) {
return total ? (count * 100 / total).toFixed(2) + "%" : "0.00%"
},
parseVoteResult(value) {
if (!value) return []
try {
const data = typeof value === "string" ? JSON.parse(value) : value
return Array.isArray(data) ? data : []
} catch (e) {
return []
}
},
voteResultText(value) {
return this.parseVoteResult(value).map(item => {
return (item.item || item.itemName || "投票项") + ":" +
(item.selected || item.option || item.value || "-")
}).join(";")
},
buildSummary(records) {
const counter = {}
records.forEach(record => {
this.parseVoteResult(record.voteResult).forEach(item => {
const itemName = item.item || item.itemName || "投票项"
const option = item.selected || item.option || item.value || "-"
if (!counter[itemName]) counter[itemName] = {}
counter[itemName][option] = (counter[itemName][option] || 0) + 1
})
})
const rows = []
Object.keys(counter).forEach(itemName => {
const itemTotal = Object.values(counter[itemName]).reduce((sum, count) => sum + count, 0)
Object.keys(counter[itemName]).forEach(option => {
rows.push({itemName, option, count: counter[itemName][option], itemTotal})
})
})
return rows
},
async loadOptions() {
const res = await this.$axios.post("/platform/congress/materials/statistical/sessions")
if (res.code === 0) this.sessionOptions = res.data || []
},
async openDetail(row) {
this.currentIssue = row
this.detailVisible = true
this.detailLoading = true
this.detailTab = "summary"
try {
const res = await this.$axios.post("/platform/congress/materials/statistical/records", {issueId: row.id})
if (res.code === 0) {
this.recordData = res.data || []
this.summaryData = this.buildSummary(this.recordData)
}
} finally {
this.detailLoading = false
}
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,130 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="资料类型">
<el-input v-model.trim="pageForm.name" clearable placeholder="请输入资料类型"></el-input>
</search-item>
<search-item label="是否投票">
<el-select v-model="pageForm.isVote" clearable placeholder="请选择">
<el-option label="是" value="1"></el-option>
<el-option label="否" value="0"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="资料类型">
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd">新增</el-button>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="name" label="资料类型" min-width="220"></el-table-column>
<el-table-column label="是否投票" width="120" align="center">
<template slot-scope="{row}">
<el-tag :type="row.isVote ? 'success' : 'info'" size="small">{{row.isVote ? "是" : "否"}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="sortNo" label="排序号" width="120" align="center"></el-table-column>
<el-table-column label="操作" width="160" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="openEdit(row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" class="text-danger" @click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑资料类型' : '新增资料类型'"
:visible.sync="dialogFormVisible" width="520px" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="资料类型" prop="name">
<el-input v-model.trim="formData.name" maxlength="100" show-word-limit></el-input>
</el-form-item>
<el-form-item label="是否投票" prop="isVote">
<el-radio-group v-model="formData.isVote">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序号" prop="sortNo">
<el-input-number v-model="formData.sortNo" :min="0" :max="9999" style="width:100%"></el-input-number>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="dialogFormVisible=false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="doSave">确定</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
pageDataUrl: "/platform/congress/materials/type/pageData",
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0, name: "", isVote: ""},
formData: {},
formRules: {
name: [{required: true, message: "请输入资料类型", trigger: "blur"}],
sortNo: [{required: true, message: "请输入排序号", trigger: "change"}]
}
}
},
methods: {
openAdd() {
this.formData = {name: "", isVote: false, sortNo: 0}
this.dialogFormVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
async openEdit(id) {
const res = await this.$axios.post("/platform/congress/materials/type/get", {id})
if (res.code === 0) {
this.formData = res.data || {}
this.formData.isVote = !!this.formData.isVote
this.dialogFormVisible = true
}
},
doSave() {
this.$refs.formRef.validate(async valid => {
if (!valid) return
this.submitLoading = true
try {
const res = await this.$axios.post("/platform/congress/materials/type/save", this.formData)
if (res.code === 0) {
this.dialogFormVisible = false
this.$message.success(res.msg)
this.pageData()
} else {
this.$message.error(res.msg)
}
} finally {
this.submitLoading = false
}
})
},
doDelete(id) {
this.$confirm("确认删除该资料类型吗?", "提示", {type: "warning"}).then(async () => {
const res = await this.$axios.post("/platform/congress/materials/type/delete", {id})
if (res.code === 0) {
this.$message.success(res.msg)
this.doSearch()
} else {
this.$message.error(res.msg)
}
})
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,84 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次" @change="loadMeetings">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="会议名称">
<el-select v-model="pageForm.meetingId" clearable filterable :disabled="!pageForm.sessionId">
<el-option v-for="item in meetingOptions" :key="item.id"
:label="item.meetingName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="关键字">
<el-input v-model.trim="pageForm.keyword" clearable placeholder="请输入工号或姓名"></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="签到记录"></table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="emplid" label="工号" min-width="120"></el-table-column>
<el-table-column prop="name" label="姓名" min-width="100"></el-table-column>
<el-table-column prop="meetingName" label="会议名称" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column prop="delegationName" label="代表团" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column label="签到状态" width="100" align="center">
<template><el-tag type="success" size="small">已签到</el-tag></template>
</el-table-column>
<el-table-column label="签到时间" min-width="170">
<template slot-scope="{row}">{{formatDate(row.signInTime)}}</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
pageDataUrl: "/platform/congress/record/pageData",
sessionOptions: [],
meetingOptions: [],
pageForm: {
pageNumber: 1, pageSize: 10, totalCount: 0,
sessionId: "", meetingId: "", keyword: ""
}
}
},
methods: {
formatDate(value) {
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : "-"
},
async loadOptions() {
const res = await this.$axios.post("/platform/congress/record/sessions")
if (res.code === 0) this.sessionOptions = res.data || []
},
async loadMeetings() {
this.pageForm.meetingId = ""
this.meetingOptions = []
if (!this.pageForm.sessionId) return
const res = await this.$axios.post("/platform/congress/record/meetings", {sessionId: this.pageForm.sessionId})
if (res.code === 0) this.meetingOptions = res.data || []
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,162 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="届次">
<el-select v-model="pageForm.sessionId" clearable filterable placeholder="请选择届次">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="会议管理">
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd">新增</el-button>
</table-tool>
<el-table :data="tableData" v-loading="tableLoading" class="vi-table">
<el-table-column type="index" :index="indexMethod" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="meetingName" label="会议名称" min-width="220" show-overflow-tooltip></el-table-column>
<el-table-column label="届次" min-width="220">
<template slot-scope="{row}">{{row.sessionName}}</template>
</el-table-column>
<el-table-column label="签到时间" min-width="300">
<template slot-scope="{row}">
{{formatDate(row.signInStartTime)}} 至 {{formatDate(row.signInEndTime)}}
</template>
</el-table-column>
<el-table-column prop="signInCount" label="签到人数" width="100" align="center"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="160" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="openEdit(row.id)">编辑</el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" class="text-danger" @click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-dialog :title="formData.id ? '编辑会议' : '新增会议'" :visible.sync="dialogFormVisible"
width="620px" :close-on-click-modal="false">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="届次" prop="sessionId">
<el-select v-model="formData.sessionId" filterable style="width:100%">
<el-option v-for="item in sessionOptions" :key="item.id"
:label="item.fullName" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="会议名称" prop="meetingName">
<el-input v-model.trim="formData.meetingName" maxlength="255" show-word-limit></el-input>
</el-form-item>
<el-form-item label="签到开始" prop="signInStartTime">
<el-date-picker v-model="formData.signInStartTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"></el-date-picker>
</el-form-item>
<el-form-item label="签到结束" prop="signInEndTime">
<el-date-picker v-model="formData.signInEndTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" style="width:100%"></el-date-picker>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model.trim="formData.remark" type="textarea" :rows="3"
maxlength="1000" show-word-limit></el-input>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="dialogFormVisible=false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="doSave">确定</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const validateEnd = (rule, value, callback) => {
if (!value) return callback(new Error("请选择签到结束时间"))
if (this.formData.signInStartTime && value <= this.formData.signInStartTime) {
return callback(new Error("签到结束时间必须晚于开始时间"))
}
callback()
}
return {
pageDataUrl: "/platform/congress/signIn/pageData",
sessionOptions: [],
pageForm: {pageNumber: 1, pageSize: 10, totalCount: 0, sessionId: ""},
formData: {},
formRules: {
sessionId: [{required: true, message: "请选择届次", trigger: "change"}],
meetingName: [{required: true, message: "请输入会议名称", trigger: "blur"}],
signInStartTime: [{required: true, message: "请选择签到开始时间", trigger: "change"}],
signInEndTime: [{required: true, validator: validateEnd, trigger: "change"}]
}
}
},
methods: {
formatDate(value) {
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : "-"
},
async loadOptions() {
const res = await this.$axios.post("/platform/congress/signIn/sessions")
if (res.code === 0) this.sessionOptions = res.data || []
},
openAdd() {
this.formData = {meetingName: "", sessionId: "", remark: ""}
this.dialogFormVisible = true
this.$nextTick(() => this.$refs.formRef && this.$refs.formRef.clearValidate())
},
async openEdit(id) {
const res = await this.$axios.post("/platform/congress/signIn/get", {id})
if (res.code === 0) {
const data = res.data || {}
data.signInStartTime = this.formatDate(data.signInStartTime)
data.signInEndTime = this.formatDate(data.signInEndTime)
this.formData = data
this.dialogFormVisible = true
}
},
doSave() {
this.$refs.formRef.validate(async valid => {
if (!valid) return
this.submitLoading = true
try {
const res = await this.$axios.post("/platform/congress/signIn/save", this.formData)
if (res.code === 0) {
this.dialogFormVisible = false
this.$message.success(res.msg)
this.pageData()
} else {
this.$message.error(res.msg)
}
} finally {
this.submitLoading = false
}
})
},
doDelete(id) {
this.$confirm("确认删除该会议签到配置吗?", "提示", {type: "warning"}).then(async () => {
const res = await this.$axios.post("/platform/congress/signIn/delete", {id})
if (res.code === 0) {
this.$message.success(res.msg)
this.doSearch()
} else {
this.$message.error(res.msg)
}
})
}
},
async created() {
await this.loadOptions()
this.pageData()
}
})
</script>
<!--#
}
#-->