Merge remote-tracking branch 'refs/remotes/origin/feature_品牌工作室' into release_20260829

This commit is contained in:
2026-08-29 08:52:42 +08:00
85 changed files with 12236 additions and 0 deletions
@@ -0,0 +1,24 @@
import request from '@/utils/request';
/**
* 根据id查询流程列表
*/
export async function getBrandApplyFlowListApi(params) {
return await request.get('/act/application/flow/process', {
params: params
});
}
/**
* 审核
*/
export async function auditApplicationApi(data) {
return await request.put('/act/application/flow/audit', data);
}
/**
* 撤回
*/
export async function recallApplicationApi(data) {
return await request.put('/act/application/flow/recall', data);
}
@@ -0,0 +1,92 @@
import request from '@/utils/request';
/**
* 查询申请人工会和机构
*/
export async function getUserBelongUnionApi(params) {
return request.get('/act/brand/application/union', { params });
}
/**
* 查询领衔人和荣誉
*/
export async function getLeaderHonorApi(params) {
return request.get('/act/brand/application/leader', { params });
}
/**
* 分页查询品牌工作室列表
*/
export async function getBrandStudioApplicationPageApi(params) {
return request.get('/act/brand/application/page', { params });
}
/**
* 根据id查询品牌工作室申请信息
*/
export async function getBrandStudioApplicationApi(id) {
return await request.get('/act/brand/application/' + id);
}
/**
* 添加品牌工作室
*/
export async function addBrandStudioApplicationApi(data) {
return await request.post('/act/brand/application', data);
}
/**
* 修改品牌工作室信息
*/
export async function updateBrandStudioApplicationApi(data) {
return await request.put('/act/brand/application', data);
}
/**
* 修改品牌工作室基础信息
*/
export async function updateBrandStudioBaseInfoApi(data) {
return await request.put('/act/brand/application/base', data);
}
/**
* 修改品牌工作室成员和荣誉信息
*/
export async function updateBrandStudioMemberOrHonorApi(data) {
return await request.post('/act/brand/application/honor', data);
}
/**
* 批量删除品牌工作室
*/
export async function removeBrandStudioApplicationsApi(ids) {
return await request.post('/act/brand/application/delete', { data: ids });
}
/**
* 分页查询品牌工作室 待审核 列表
*/
export async function getBrandApplicationAuditPageApi(params) {
return request.get('/act/brand/application/auditPage', { params });
}
/**
* 分页查询品牌工作室+荣誉列表
*/
export async function getBrandStudioApplicationHonorPageApi(params) {
return request.get('/act/brand/application/summaryPage', { params });
}
/**
* 根据id查询品牌工作室荣誉信息
*/
export async function getBrandStudioHonorApi(id) {
return await request.get('/act/brand/application/honor/' + id);
}
/**
* 提交申请
*/
export async function submitApplicationApi(data) {
return await request.put('/act/brand/application/submitApply', data);
}
@@ -0,0 +1,37 @@
import request from '@/utils/request';
/**
* 查询工会审批统计表单
*/
export async function getUnionApplyStatsApi(params) {
return await request.get('/act/application/statistics/unionStats', {
params: params
});
}
/**
* 各申请表单进度情况
*/
export async function getApplyProgressStatsApi(params) {
return await request.get('/act/application/statistics/applyProgress', {
params: params
});
}
/**
* 工会申请预警
*/
export async function getApplyWarningStatsApi(params) {
return await request.get('/act/application/statistics/applyWarning', {
params: params
});
}
/**
* 统计用户申请的各字段分组
*/
export async function getApplyCountStatsApi(params) {
return await request.get('/act/application/statistics/applyCount', {
params: params
});
}
@@ -0,0 +1,235 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<el-form-item label="创立年度">
<el-date-picker v-model="pageForm.createYear" type="year" value-format="yyyy"></el-date-picker>
</el-form-item>
<el-form-item label="工作室名称">
<el-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="申请列表">
<el-button size="small" type="primary" @click="openApply()">申请品牌工作室</el-button>
</table-tool>
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="name" label="工作室名称" min-width="160"></el-table-column>
<el-table-column prop="createYear" label="创立年度" width="100"></el-table-column>
<el-table-column prop="leadPeople" label="领衔人" width="120"></el-table-column>
<el-table-column prop="deptName" label="所属机构" min-width="140"></el-table-column>
<el-table-column prop="type" label="工作室类型" width="120"></el-table-column>
<el-table-column prop="track" label="工作室赛道" width="120"></el-table-column>
<el-table-column prop="memberNum" label="成员人数" width="90"></el-table-column>
<el-table-column label="流程状态" width="100">
<template slot-scope="{row}">
<el-tag v-if="!row.instanceId" type="info">草稿</el-tag>
<enum-tag
v-else
:value="row.instanceState"
name="ProcessInstanceStateEnum"
label_key="message"
size="small">
</enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="260" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)" v-if="row.instanceId">查看</el-button>
<el-button size="mini" type="primary" @click="openApply(row.id)" v-if="row.taskKey === 'startTask' || !row.instanceId">编辑</el-button>
<el-button size="mini" type="danger" @click="recallApply(row)" v-if="row.canRevoke === true || row.canRevoke === 1">撤回</el-button>
<el-button size="mini" type="danger" @click="deleteApply(row.id)" v-if="row.taskKey === 'startTask' || !row.instanceId">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<template #view>
<div>
<el-tabs tab-position="top" v-model="activeName">
<el-tab-pane name="1" label="基础信息">
<div class="process-title">
品牌工作室申请
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="工作室名称">{{detail.name}}</el-descriptions-item>
<el-descriptions-item label="创立年度">{{detail.createYear}}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{detail.unionName}}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{detail.deptName}}</el-descriptions-item>
<el-descriptions-item label="领衔人">{{detail.leadPeople}}</el-descriptions-item>
<el-descriptions-item label="领衔人年龄">{{detail.leadAge}}</el-descriptions-item>
<el-descriptions-item label="领衔人身份">{{detail.leadIdentity}}</el-descriptions-item>
<el-descriptions-item label="领衔人荣誉">{{detail.leadHonor}}</el-descriptions-item>
<el-descriptions-item label="工作室类型">{{detail.type}}</el-descriptions-item>
<el-descriptions-item label="所属赛道">{{detail.track}}</el-descriptions-item>
<el-descriptions-item label="工作室地址" :span="2">{{detail.address}}</el-descriptions-item>
<el-descriptions-item label="工作室介绍" :span="2">{{detail.introduction}}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{detail.remark}}</el-descriptions-item>
</el-descriptions>
<snaker-chart ref="snakerChartRef"></snaker-chart>
</el-tab-pane>
<el-tab-pane name="2" label="成员信息">
<div class="process-title">成员信息</div>
<el-table :data="detail.brandStudioMembers || []" border size="small">
<el-table-column label="姓名" prop="name" width="120"></el-table-column>
<el-table-column label="出生年月" prop="birthdate" width="120"></el-table-column>
<el-table-column label="学位" prop="grade" show-overflow-tooltip></el-table-column>
<el-table-column label="职称" prop="professional" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="deptName" show-overflow-tooltip></el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane name="3" label="审核记录">
<template v-if="doneTasks.length > 0" v-for="task in doneTasks">
<div class="mt10">
<div class="process-title">{{ task.displayName }}</div>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" v-if="!task.ext.isFirstTaskNode">{{ task.taskFormData.opinion || task.taskFormData.remark || task.taskFormData.approvalComment }}</el-descriptions-item>
</el-descriptions>
</div>
</template>
<template v-else>
<el-empty description="暂无审核记录"></el-empty>
</template>
</el-tab-pane>
</el-tabs>
</div>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
pageForm: {
page: 1,
limit: 20,
createYear: String(new Date().getFullYear())
},
tableData: [],
detail: {},
currentRow: {},
activeName: "1",
doneTasks: []
}
},
methods: {
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
pageData() {
this.$axios.get('/platform/zhgh/brand/application/page', {params: this.pageForm}).then((res) => {
const data = this.getData(res) || {}
this.tableData = data.list || data.data || []
})
},
openApply(id, mode) {
let url = '/platform/zhgh/brand/apply/index'
const params = []
if (id) {
params.push('id=' + id)
}
if (mode) {
params.push('mode=' + mode)
}
if (params.length) {
url += '?' + params.join('&')
}
window.location.href = url
},
openView(row) {
this.$refs.guava.view(() => {
this.currentRow = row
this.activeName = "1"
this.loadDetail(row.id)
this.getDoneTasks(row)
})
},
loadDetail(id) {
this.$axios.get('/platform/zhgh/brand/application/' + id).then((res) => {
this.detail = this.getData(res) || {}
this.detail.brandStudioMembers = this.detail.brandStudioMembers || []
})
},
getDoneTasks(row) {
this.$axios.post('/flow/common/doneTasks', {
instanceId: row.instanceId,
bizId: row.id
}).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.currentRow.instanceId || !this.currentRow.instanceProcessDefineId) {
this.$message.warning('暂无流程图信息')
return
}
this.$refs.snakerChartRef.onOpenFull(this.currentRow.instanceProcessDefineId, this.currentRow.instanceId)
},
recallApply(row) {
this.$confirm('您确定要撤回吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$axios.post('/platform/zhgh/brand/flow/recall', {
applicationId: row.id,
processInstanceTaskId: row.startTaskId
}).then(() => {
this.$message.success('撤回成功')
this.pageData()
})
})
},
deleteApply(id) {
this.$confirm('您确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$axios.post('/platform/zhgh/brand/application/delete', {
data: [id]
}).then(() => {
this.$message.success('删除成功')
this.pageData()
})
})
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,416 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="{ fileName: '参数设置' }"
cache-key="systemConfigTable"
:tools="false"
>
<template #toolbar>
<el-button
type="primary"
class="ele-btn-icon"
:icon="PlusOutlined"
v-permission="'system:config:add'"
@click="openEdit()"
>
新建
</el-button>
<el-button
type="danger"
class="ele-btn-icon hidden-sm-and-down"
:icon="DeleteOutlined"
v-permission="'system:config:remove'"
@click="removeBatch()"
>
删除
</el-button>
</template>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #status="{ row }">
<el-tag style="cursor: pointer" v-if="row.status === 0" type="info">
草稿箱
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.status === 1"
type="warning"
>
审核中
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 2" type="danger">
驳回
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 4" type="info">
取消
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.status === 5"
type="success"
>
通过
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 6">
正在创建
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 7">
创建失败
</el-tag>
</template>
<template #action="{ row }">
<el-link
v-if="[0, 2, 4].includes(row.status)"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="openEdit(row)"
:icon="EditOutlined"
>
修改
</el-link>
<el-divider
v-if="[2, 4].includes(row.status)"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status !== 0"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click.stop="handleProcessDetail(row)"
:icon="View"
:disabled="row.status === 6 || row.status === 7"
>
查看
</el-link>
<el-divider
v-if="row.status === 0"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status === 0 || row.status === 7"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="submitApply(row)"
:icon="FileOutlined"
>
申请
</el-link>
<!-- <el-divider
v-if="row.status !== 0 && row.status !== 2"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status !== 0 && row.status !== 2"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="exportDocx(row.id)"
:icon="DownloadOutlined"
>
导出
</el-link>-->
</template>
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script setup>
import { computed, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import { EleMessage } from 'ele-admin-plus';
import {
DeleteOutlined,
EditOutlined,
PlusOutlined
} from '@/components/icons';
import ParamSearch from '../components/param-search.vue';
import dayjs from 'dayjs';
import {
getBrandStudioApplicationPageApi,
removeBrandStudioApplicationsApi
} from '@/api/office/brand/index.js';
import { useRouter } from 'vue-router';
import { BRAND_APPLY_PATH } from '@/config/setting.js';
const { push } = useRouter();
import { usePageTab } from '@/utils/use-page-tab';
import { View } from '@element-plus/icons-vue';
import $enums from '@/utils/enums.js';
import { FileOutlined, DownloadOutlined } from '@/components/icons/index.js';
import { submitApplicationApi } from '@/api/office/brand/index.js';
import request from '@/utils/request.js';
const { addPageTab } = usePageTab();
defineOptions({ name: 'Application' });
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
{
type: 'selection',
columnKey: 'selection',
width: 50,
align: 'center'
},
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '所属单位机构',
minWidth: 120,
align: 'center'
},
{
prop: 'type',
label: '工作室类型',
align: 'center',
minWidth: 110,
slot: 'type'
},
{
prop: 'track',
label: '工作室赛道',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
{
prop: 'createTime',
label: '申请时间',
minWidth: 100,
align: 'center',
slot: 'createTime'
},
{
prop: 'status',
label: '状态',
minWidth: 80,
align: 'center',
slot: 'status'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
/** 当前编辑数据 */
const current = ref(null);
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
where = {
...where,
createYear: new Date().getFullYear().toString()
};
const { data } = await getBrandStudioApplicationPageApi({
...where,
...filters,
...pages
});
return data;
};
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openEdit = (row) => {
current.value = row ?? null;
const path = row ? BRAND_APPLY_PATH + '/' + row.id : BRAND_APPLY_PATH;
addPageTab({
title: row ? `修改申请` : '创建申请',
key: path,
closable: true,
meta: { icon: 'LinkOutlined' }
});
push(path);
};
/** 批量删除 */
const removeBatch = (row) => {
const rows = row == null ? selections.value : [row];
if (!rows.length) {
EleMessage.error({ message: '请至少选择一条数据', plain: true });
return;
}
ElMessageBox.confirm(
`是否确认删除"${rows.map((d) => d.name).join()}"工作室?`,
'系统提示',
{ type: 'warning', draggable: true }
)
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
console.log(rows);
removeBrandStudioApplicationsApi(rows.map((d) => d.id))
.then(() => {
loading.close();
EleMessage.success({ message: '删除成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
// 提交申请 id
const submitApply = (row) => {
console.log('submitApply');
ElMessageBox.confirm(`确认申请「${row.name}」工作室?`, '系统提示', {
type: 'warning',
draggable: true
})
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
const params = {
applicationId: row.id,
status: 1
};
submitApplicationApi(params)
.then(() => {
loading.close();
EleMessage.success({ message: '申请成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
/** 审批进度 */
const handleProcessDetail = (row) => {
push({
name: 'WorkflowProcessInstanceDetail',
query: {
businessId: row.id,
businessType:
$enums.BUSINESS_PROCESS_KEY_NUMBER.BRAND_OFFICE_USER_APPLY,
processFormPath: encodeURI('/brand/components/flow-view.vue')
}
});
};
import { saveAs } from 'file-saver';
/** 导出申请表格文档 */
const exportDocx = async (id) => {
const token = localStorage.getItem('token');
const res = await request({
url: `/act/brand/application/export/docx/${id}`,
method: 'POST',
responseType: 'blob',
headers: { Authorization: token }
});
console.log('OK res=', res);
// 处理返回的Blob数据
const blob = new Blob([res.data], {
type: res.data.type || 'application/docx'
});
const fileName = `上海浦东发展银行个人品牌工作室推荐表.docx`;
saveAs(blob, fileName, null);
};
</script>
@@ -0,0 +1,411 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="{ fileName: '参数设置' }"
cache-key="systemConfigTable"
:tools="false"
>
<template #toolbar>
<el-button
type="primary"
class="ele-btn-icon"
:icon="PlusOutlined"
v-permission="'system:config:add'"
@click="openEdit()"
>
新建
</el-button>
<el-button
type="danger"
class="ele-btn-icon hidden-sm-and-down"
:icon="DeleteOutlined"
v-permission="'system:config:remove'"
@click="removeBatch()"
>
删除
</el-button>
</template>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #status="{ row }">
<el-tag style="cursor: pointer" v-if="row.status === 0" type="info">
草稿箱
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.status === 1"
type="warning"
>
审核中
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 2" type="danger">
驳回
</el-tag>
<el-tag style="cursor: pointer" v-if="row.status === 3" type="info">
撤销
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.status === 5"
type="success"
>
通过
</el-tag>
</template>
<template #action="{ row }">
<el-link
v-if="row.status !== 0"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="showFlow(row)"
:icon="FileOutlined"
>
查看
</el-link>
<el-divider
v-if="![0, 3, 5].includes(row.status)"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status === 0 || row.status === 2"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="openEdit(row)"
:icon="EditOutlined"
>
修改
</el-link>
<el-divider
v-if="row.status === 0"
v-permission="['system:config:edit', 'system:config:remove']"
direction="vertical"
/>
<el-link
v-if="row.status === 0"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="submitApply(row)"
:icon="FileOutlined"
>
申请
</el-link>
<el-link
v-if="row.status === 1"
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click.stop="handleReturn(row)"
:icon="ReloadOutlined"
>
撤回
</el-link>
</template>
</ele-pro-table>
</ele-card>
<flow-audit
v-model="showFlowCurrent"
:data="current"
:is-audit="false"
@done="reload"
/>
</ele-page>
</template>
<script setup>
import { computed, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import {EleMessage} from 'ele-admin-plus';
import {
DeleteOutlined,
EditOutlined,
PlusOutlined
} from '@/components/icons';
import ParamSearch from '../components/param-search.vue';
import dayjs from 'dayjs';
import {
getBrandStudioApplicationPageApi,
removeBrandStudioApplicationsApi
} from '@/api/office/brand/index.js';
import { FileOutlined, ReloadOutlined } from '@/components/icons/index.js';
import {
recallApplicationApi,
} from '@/api/office/application/index.js';
import FlowAudit from '@/views/brand/components/flow-audit.vue';
import { useRouter } from 'vue-router';
import { BRAND_APPLY_PATH } from '@/config/setting.js';
const { push } = useRouter();
import { usePageTab } from '@/utils/use-page-tab';
const { addPageTab } = usePageTab();
defineOptions({ name: 'Application' });
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
{
type: 'selection',
columnKey: 'selection',
width: 50,
align: 'center'
},
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '所属单位机构',
minWidth: 120,
align: 'center'
},
{
prop: 'type',
label: '工作室类型',
align: 'center',
minWidth: 110,
slot: 'type'
},
{
prop: 'track',
label: '工作室赛道',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
{
prop: 'createTime',
label: '申请时间',
minWidth: 100,
align: 'center',
slot: 'createTime'
},
{
prop: 'status',
label: '状态',
minWidth: 80,
align: 'center',
slot: 'status'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
/** 当前编辑数据 */
const current = ref(null);
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
const { data } = await getBrandStudioApplicationPageApi({
...where,
...filters,
...pages
});
return data;
};
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openEdit = (row) => {
current.value = row ?? null;
const path = row ? BRAND_APPLY_PATH + '/' + row.id : BRAND_APPLY_PATH;
addPageTab({
title: row ? `修改申请` : '创建申请',
key: path,
closable: true,
meta: { icon: 'LinkOutlined' }
});
push(path);
};
/** 批量删除 */
const removeBatch = (row) => {
const rows = row == null ? selections.value : [row];
if (!rows.length) {
EleMessage.error({ message: '请至少选择一条数据', plain: true });
return;
}
ElMessageBox.confirm(
`是否确认删除"${rows.map((d) => d.name).join()}"工作室?`,
'系统提示',
{ type: 'warning', draggable: true }
)
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
console.log(rows);
removeBrandStudioApplicationsApi(rows.map((d) => d.id))
.then(() => {
loading.close();
EleMessage.success({ message: '删除成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
const showFlowCurrent = ref(false);
//查看流程列表
const showFlow = (row) => {
console.log('showFlow');
current.value = row ?? null;
showFlowCurrent.value = true;
};
// 用户撤回申请
const handleReturn = (row) => {
console.log('handleReturn');
console.log(row);
ElMessageBox.confirm(`确认撤回"${row.name}"工作室申请吗?`, '系统提示', {
type: 'warning',
draggable: true
})
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
const params = {
applicationId: row.id
};
recallApplicationApi(params)
.then(() => {
loading.close();
EleMessage.success({ message: '撤回成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
// 提交申请 id
const submitApply = (row) => {
console.log('submitApply');
ElMessageBox.confirm(`确认申请"${row.name}"工作室?`, '系统提示', {
type: 'warning',
draggable: true
})
.then(() => {
const loading = EleMessage.loading({
message: '请求中..',
plain: true
});
const params = {
applicationId: row.id,
status: 1
};
submitApplicationApi(params)
.then(() => {
loading.close();
EleMessage.success({ message: '申请成功', plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
</script>
@@ -0,0 +1,126 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { BRAND_APPLICATION_PATH } from '@/config/setting';
import {
getBrandStudioApplicationApi,
getUserBelongUnionApi,
} from '@/api/office/brand/index.js';
import { useRoute, useRouter } from 'vue-router';
import TableInfo from "@/views/brand/components/table-info.vue";
const { push } = useRouter();
const route = useRoute();
const childRef = ref()
/** 是否是修改:针对整体 */
const isUpdate = ref(false);
/** 提交状态 */
const loading = ref(false);
const unionId = ref('');
const topUnionId = ref('');
const handleCancel = () => {
push(BRAND_APPLICATION_PATH);
};
const save = (saveType) => {
loading.value = true;
// 调用子组件进行数据提交
childRef.value.submit({
saveType: saveType,
isUpdate: isUpdate.value
})
};
// 编辑结束回调
const editFinish = () => {
push(BRAND_APPLICATION_PATH);
}
const brandForm = ref({})
const queryInfoById = (id) => {
loading.value = true
getBrandStudioApplicationApi(id)
.then(({ data }) => {
console.log(data)
brandForm.value = data
})
.catch((err) => {
console.log(err);
})
.finally(() => {
loading.value = false
});
};
const userUnionInfo = ref({})
// 获取成员当前所在工会
const loadUserUnion = async () => {
const { data } = await getUserBelongUnionApi();
unionId.value = data.unionId;
topUnionId.value = data.topUnionId;
userUnionInfo.value = data
}
onMounted(async () => {
console.log('mounted');
await loadUserUnion();
});
watch(
() => route.params.id,
() => {
const id = route.params.id;
if (id) {
queryInfoById(id);
isUpdate.value = true;
} else {
isUpdate.value = false;
}
},
{ immediate: true }
);
</script>
<template>
<ele-page>
<ele-card
:body-style="{ paddingTop: '30px' }"
>
<table-info
ref="childRef"
:edit-mode="true"
v-model:loading="loading"
:user-union-info="userUnionInfo"
:brand-form="brandForm"
@editFinish="editFinish"
/>
<template #footer>
<div class="foot-btn">
<el-button @click="handleCancel">取消</el-button>
<el-button
v-if="!isUpdate"
type="primary"
:loading="loading"
@click="save(0)"
>
保存草稿
</el-button>
<el-button type="primary" :loading="loading" @click="save(1)">
提交申请
</el-button>
</div>
</template>
</ele-card>
</ele-page>
</template>
<style scoped>
.foot-btn {
padding-right: 60px;
display: flex;
justify-content: flex-end;
}
</style>
@@ -0,0 +1,431 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<snaker-start slot="header" label="品牌工作室申请" define_key="BRAND_STUDIO"></snaker-start>
<el-form ref="form" :model="form" label-width="130px" :disabled="isView">
<el-row :gutter="15">
<el-col :span="12">
<el-form-item label="工作室名称">
<el-input v-model="form.name"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建年份">
<el-input v-model="form.createYear" disabled></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属工会">
<el-select v-model="form.unionId" filterable placeholder="请选择所属工会" style="width: 100%" @change="unionChange">
<el-option
v-for="item in unionOptions"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所在单位">
<el-input v-model="form.deptName" disabled></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人">
<el-select v-model="leadPeopleEmplid" filterable placeholder="请选择领衔人" style="width: 100%" @change="leaderChange">
<el-option
v-for="item in leaderHonors"
:key="item.emplid"
:label="item.label"
:value="item.emplid">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人年龄">
<el-input-number v-model="form.leadAge" :min="0" :max="150" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人身份">
<el-select v-model="form.leadIdentity" multiple filterable placeholder="请选择领衔人身份" style="width: 100%">
<el-option
v-for="item in leadPeopleDict"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人荣誉">
<el-input v-model="form.leadHonor"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工作室类型">
<el-select v-model="form.type" filterable placeholder="请选择工作室类型" style="width: 100%">
<el-option
v-for="item in brandOfficeTypeDict"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属赛道">
<el-select v-model="form.track" filterable placeholder="请选择所属赛道" style="width: 100%" @change="trackChange">
<el-option
v-for="item in brandOfficeTrackDict"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="工作室地址">
<el-input v-model="form.address"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="工作室介绍">
<el-input type="textarea" :rows="4" v-model="form.introduction"></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注">
<el-input type="textarea" :rows="2" v-model="form.remark"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">选择工作室成员</el-divider>
<div class="brand-member-toolbar" v-if="!isView">
<div class="brand-member-count">已选择 {{form.brandStudioMembers.length}} 人</div>
<el-button size="small" type="primary" icon="el-icon-plus" @click="openMemberDialog">选择工作室成员</el-button>
</div>
<div class="brand-member-toolbar" v-else>
<div class="brand-member-count">已选择 {{form.brandStudioMembers.length}} 人</div>
</div>
<div class="brand-member-table">
<el-table :data="form.brandStudioMembers" border size="small">
<el-table-column label="姓名" min-width="120">
<template slot-scope="{row}">
<el-input v-model="row.name" disabled></el-input>
</template>
</el-table-column>
<el-table-column label="出生年月" min-width="120">
<template slot-scope="{row}">
<el-date-picker
v-model="row.birthdate"
type="month"
value-format="yyyy-MM"
placeholder="请选择出生年月"
style="width: 100%">
</el-date-picker>
</template>
</el-table-column>
<el-table-column label="学位" min-width="120">
<template slot-scope="{row}">
<el-select v-model="row.grade" filterable placeholder="请选择学位" style="width: 100%">
<el-option
v-for="item in memberDegreeDict"
:key="item.id"
:label="item.name"
:value="item.name">
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="职称" min-width="120">
<template slot-scope="{row}">
<el-input v-model="row.professional"></el-input>
</template>
</el-table-column>
<el-table-column label="所在单位" min-width="160">
<template slot-scope="{row}">
<el-input v-model="row.deptName" disabled></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="90" v-if="!isView">
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="removeMember(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-form-item class="brand-form-actions" v-if="!isView">
<el-button type="primary" :loading="loading" @click="save(0)">保存草稿</el-button>
<el-button type="primary" :loading="loading" @click="save(6)">提交申请</el-button>
</el-form-item>
</el-form>
</el-card>
<el-dialog title="选择工作室成员" :visible.sync="memberDialogVisible" width="760px" class="brand-member-dialog">
<div class="brand-member-search">
<el-input v-model="memberKeyword" placeholder="请输入姓名或工号" clearable @keyup.enter.native="loadMemberCandidates"></el-input>
<el-button type="primary" icon="el-icon-search" @click="loadMemberCandidates">查询</el-button>
</div>
<el-table :data="memberCandidates" border size="small" height="360" @selection-change="memberSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="loginname" label="工号" width="120"></el-table-column>
<el-table-column prop="username" label="姓名" width="120"></el-table-column>
<el-table-column prop="unitName" label="所属机构" show-overflow-tooltip></el-table-column>
</el-table>
<div slot="footer">
<el-button @click="memberDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmSelectMembers">确定</el-button>
</div>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
loading: false,
isView: false,
form: {
archive: 0,
status: 0,
createYear: String(new Date().getFullYear()),
leadIdentity: [],
brandStudioMembers: []
},
unionOptions: [],
leaderHonors: [],
leadPeopleDict: [],
brandOfficeTypeDict: [],
brandOfficeTrackDict: [],
memberDegreeDict: [],
leadPeopleEmplid: '',
memberDialogVisible: false,
memberKeyword: '',
memberCandidates: [],
selectedMemberCandidates: []
}
},
methods: {
getParam(name) {
return new URLSearchParams(window.location.search).get(name)
},
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
loadUserInfo() {
const user = this.$store.state.user || {}
const unit = user.unit || {}
const union = user.union || {}
this.form.unionId = union.id || unit.unionId
this.form.unionName = union.name
this.form.deptid = user.unitId || unit.id
this.form.deptName = unit.name
this.form.createYear = String(new Date().getFullYear())
},
async loadUnionOptions() {
const user = this.$store.state.user || {}
const union = user.union || {}
const roles = user.roles || []
const isAdmin = roles.some(item => item.code === 'SYSADMIN' || item.code === 'SCHOOL_UNION_ADMIN')
this.unionOptions = await this.$businessTool.listUnion(isAdmin ? null : union.id)
if (!this.form.unionId && this.unionOptions.length > 0) {
this.form.unionId = this.unionOptions[0].id
this.form.unionName = this.unionOptions[0].name
}
},
unionChange(unionId) {
const union = this.unionOptions.find(item => item.id === unionId)
this.form.unionName = union ? union.name : ''
},
loadDictOptions(code) {
return this.$axios.get('/open/common/dictOptions', {params: {code: code}}).then((res) => {
return this.getData(res) || []
})
},
loadDicts() {
Promise.all([
this.loadDictOptions('brand_office_lead_people'),
this.loadDictOptions('brand_office_type'),
this.loadDictOptions('brand_office_track'),
this.loadDictOptions('USER_ACADEMIC_DEGREE')
]).then(([leadPeopleDict, brandOfficeTypeDict, brandOfficeTrackDict, memberDegreeDict]) => {
this.leadPeopleDict = leadPeopleDict
this.brandOfficeTypeDict = brandOfficeTypeDict
this.brandOfficeTrackDict = brandOfficeTrackDict
this.memberDegreeDict = memberDegreeDict
})
},
loadLeaderHonors() {
this.$axios.get('/platform/zhgh/brand/application/leader').then((res) => {
const data = this.getData(res) || []
this.leaderHonors = data.map(item => {
item.label = item.userName + '(' + item.emplid + ')'
return item
})
if (this.form.leadPeople) {
const leader = this.leaderHonors.find(item => item.userName === this.form.leadPeople)
this.leadPeopleEmplid = leader ? leader.emplid : ''
}
})
},
leaderChange(emplid) {
const leader = this.leaderHonors.find(item => item.emplid === emplid)
if (leader) {
this.form.leadPeople = leader.userName
this.form.leadAge = leader.age
this.form.leadHonor = (leader.honorNames || []).join(',')
} else {
this.form.leadPeople = ''
this.form.leadAge = ''
this.form.leadHonor = ''
}
},
trackChange(track) {
this.form.domain = track
},
openMemberDialog() {
this.memberDialogVisible = true
this.loadMemberCandidates()
},
loadMemberCandidates() {
this.$axios.get('/platform/zhgh/brand/application/members', {
params: {
unionId: this.form.unionId,
keyword: this.memberKeyword
}
}).then((res) => {
this.memberCandidates = this.getData(res) || []
})
},
memberSelectionChange(selection) {
this.selectedMemberCandidates = selection
},
confirmSelectMembers() {
if (!this.selectedMemberCandidates.length) {
this.$message.warning('请至少选择一位工作室成员')
return
}
const existingIds = new Set((this.form.brandStudioMembers || []).map(item => item.userId || item.id))
const members = this.selectedMemberCandidates
.filter(item => !existingIds.has(item.id))
.map(item => ({
userId: item.id,
emplid: item.emplid || item.loginname,
name: item.name || item.username,
role: '',
birthdate: item.birthdate,
grade: item.grade,
professional: item.professional,
deptName: item.deptName || item.unitName
}))
this.form.brandStudioMembers = [...(this.form.brandStudioMembers || []), ...members]
this.form.memberNum = this.form.brandStudioMembers.length
this.memberDialogVisible = false
},
loadDetail(id) {
this.$axios.get('/platform/zhgh/brand/application/' + id).then((res) => {
const data = this.getData(res) || {}
data.brandStudioMembers = data.brandStudioMembers || []
data.memberNum = data.brandStudioMembers.length
data.leadIdentity = data.leadIdentity ? data.leadIdentity.split(',') : []
this.form = data
this.loadUnionOptions()
this.loadLeaderHonors()
})
},
addMember() {
this.form.brandStudioMembers.push({})
this.form.memberNum = this.form.brandStudioMembers.length
},
removeMember(index) {
this.form.brandStudioMembers.splice(index, 1)
this.form.memberNum = this.form.brandStudioMembers.length
},
save(status) {
this.loading = true
this.form.status = status
const params = Object.assign({}, this.form)
params.leadIdentity = (params.leadIdentity || []).join(',')
params.domain = params.track
params.memberNum = (params.brandStudioMembers || []).length
params.brandStudioMembersJson = JSON.stringify(params.brandStudioMembers || [])
delete params.brandStudioMembers
const request = this.form.id
? this.$axios.post('/platform/zhgh/brand/application/update', params)
: this.$axios.post('/platform/zhgh/brand/application', params)
request.then(() => {
this.$message.success('保存成功')
this.goBack()
}).finally(() => {
this.loading = false
})
},
goBack() {
window.location.href = '/platform/zhgh/brand/application/index'
}
},
created() {
this.loadDicts()
this.loadLeaderHonors()
const id = this.getParam('id')
this.isView = this.getParam('mode') === 'view'
if (id) {
this.loadDetail(id)
} else {
this.loadUserInfo()
this.loadUnionOptions()
}
}
})
</script>
<style>
.brand-member-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 0 12px;
padding: 0 2px 0 16px;
}
.brand-member-count {
color: #606266;
font-size: 13px;
}
.brand-member-table {
padding-left: 16px;
}
.brand-form-actions {
margin-top: 20px;
text-align: right;
}
.brand-member-search {
display: flex;
gap: 10px;
margin-bottom: 12px;
}
.brand-member-search .el-input {
flex: 1;
}
.brand-member-dialog .el-dialog__body {
padding-top: 12px;
padding-bottom: 12px;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,149 @@
<script setup>
import { onMounted, ref, watch } from 'vue';
import { BRAND_APPLICATION_PATH } from '@/config/setting';
import {
getBrandStudioApplicationApi,
getUserBelongUnionApi
} from '@/api/office/brand/index.js';
import { useRoute, useRouter } from 'vue-router';
import $enums from '@/utils/enums.js';
import BusinessInstanceTimeLine from '@/components/BusinessInstanceTimeLine/index.vue';
import ApplyInfo from '@/views/brand/components/apply-info.vue';
const { push } = useRouter();
const route = useRoute();
const childRef = ref();
/** 是否是修改:针对整体 */
const isUpdate = ref(false);
/** 提交状态 */
const loading = ref(false);
const handleCancel = () => {
push(BRAND_APPLICATION_PATH);
};
const save = (saveType) => {
loading.value = true;
childRef.value.submit({
saveType: saveType,
isUpdate: isUpdate.value
});
};
// 编辑结束回调
const editFinish = () => {
push(BRAND_APPLICATION_PATH);
};
const brandForm = ref({});
const queryInfoById = (id) => {
loading.value = true;
getBrandStudioApplicationApi(id)
.then(({ data }) => {
brandForm.value = data;
})
.catch((err) => {
console.log(err);
})
.finally(() => {
loading.value = false;
});
};
const unionId = ref('');
const topUnionId = ref('');
const userUnionInfo = ref({});
// 获取成员当前所在工会
const loadUserUnion = async () => {
const { data } = await getUserBelongUnionApi();
unionId.value = data.unionId;
topUnionId.value = data.topUnionId;
userUnionInfo.value = data;
};
onMounted(async () => {
console.log('mounted');
await loadUserUnion();
});
watch(
() => route.params.id,
() => {
const id = route.params.id;
if (id) {
queryInfoById(id);
isUpdate.value = true;
} else {
isUpdate.value = false;
}
},
{ immediate: true }
);
</script>
<template>
<ele-page>
<!-- :header="isUpdate ? '修改申请' : '创建申请'"-->
<ele-card :body-style="{ paddingTop: '8px' }" v-loading="loading">
<div class="container-fluid">
<el-scrollbar style="height: 730px; flex: 1;min-width: 800px">
<div style="padding: 0 40px 0 30px">
<h3 style="color: black">{{
isUpdate ? '修改申请' : '创建申请'
}}</h3>
<apply-info
ref="childRef"
:edit-mode="true"
v-model:loading="loading"
:user-union-info="userUnionInfo"
:brand-form="brandForm"
@editFinish="editFinish"
/>
<div class="foot-btn">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" :loading="loading" @click="save(0)">
保存草稿
</el-button>
<el-button type="primary" :loading="loading" @click="save(6)">
提交申请
</el-button>
</div>
</div>
</el-scrollbar>
<div class="border-left ml-10px mr-20px"></div>
<div class="container-right">
<h3 style="color: black">审批流程</h3>
<BusinessInstanceTimeLine
ref="businessInstanceTimeLine"
v-model:business-data="userUnionInfo"
:oa-key="$enums.BUSINESS_PROCESS_KEY.BRAND_OFFICE_USER_APPLY"
/>
</div>
</div>
</ele-card>
</ele-page>
</template>
<style scoped>
@import url('@/styles/workflow.scss');
.container-fluid {
display: flex;
justify-content: space-between;
}
:deep(.el-scrollbar__bar.is-vertical) {
display: none;
}
.foot-btn {
display: flex;
justify-content: flex-end;
margin: 5px 0 30px 0;
}
.container-right {
min-width: 350px;
}
</style>
@@ -0,0 +1,699 @@
<script setup>
import {StrUtil} from "@/utils/toolkit.js";
import {DeleteOutlined} from "@/components/icons/index.js";
import SurveyTreeSelect from "@/views/survey/components/survey-tree-select.vue";
import {QuestionFilled} from "@element-plus/icons-vue";
import {computed, onMounted, reactive, ref, watch} from "vue";
import {useDictData} from "@/utils/use-dict-data.js";
import {useFormData} from "@/utils/use-form-data.js";
import {ElMessageBox} from "element-plus";
import {
addBrandStudioApplicationApi,
getLeaderHonorApi,
updateBrandStudioApplicationApi
} from "@/api/office/brand/index.js";
import UnionUserSelect from "@/components/UnionUserSelect/index.vue";
import {EleMessage} from "ele-admin-plus";
import {withRequiredHeader} from "@/views/brand/js/common-brand.js";
import {useUserStore} from "@/store/modules/user.js";
const props = defineProps({
editMode: Boolean,
loading: Boolean,
userUnionInfo: Object, // 用户工会相关信息
topUnionId: String, // 最上层工会ID
brandForm: Object // 表格信息,修改时使用
})
const emit = defineEmits(['update:loading', 'editFinish'])
/** 领衔人荣誉 */
const leaderHonors = ref([]);
const userStore = useUserStore()
/** 表单实例 */
const formRef = ref(null);
/** 字典数据 */
const [
leadPeopleDict,
brandOfficeTypeDict,
userEducationDict,
brandOfficeDomainDict
] = useDictData([
'lead_people',
'brand_office_type',
'user_education',
'brand_office_domain'
]);
/** 表单数据 */
const [form, resetFields, assignFields] = useFormData({
id: '',
unionName: '', // 所属单位工会
deptName: '',
name: '', // 工作室名称
createYear: '', // 创建年度
leadPeople: '', // 工作室领衔人
leadAge: 0, // 领衔人年龄
leadIdentity: [], // 领衔人身份
leadHonor: '', // 领衔人荣誉称号
memberNum: 0, // 成员人数
type: '', // 工作室类型
track: '', // 工作室赛道
address: '', // 工作室具体位置
introduction: '', // 工作室基本情况介绍
brandStudioMembers: [] // 工作室成员 { name, birthday, grade, professional, unionName }
});
/** 表单验证规则 */
const rules = reactive({
name: [
{
required: true,
message: '请输入工作室名称',
type: 'string',
trigger: 'blur'
}
],
unionName: [
{
required: true,
message: '请输入所属单位工会',
type: 'string',
trigger: 'blur'
}
],
deptName: [
{
required: true,
message: '请输入所属单位机构',
type: 'string',
trigger: 'blur'
}
],
createYear: [
{
required: true,
message: '请选择创建年度',
type: 'string',
trigger: 'change'
}
],
leadPeople: [
{
required: true,
message: '请选择工作室领衔人',
type: 'string',
trigger: 'blur'
},
{ type: 'string', max: 20, message: '领衔人最多 20 个字符', trigger: ['blur', 'change'] }
],
leadAge: [
{
required: true,
message: '请输入领衔人年龄',
type: 'number',
trigger: 'blur'
},
{
type: 'number',
min: 0,
max: 150,
message: '年龄必须在0-150岁之间',
trigger: 'blur'
}
],
leadIdentity: [
{
required: true,
message: '请选择领衔人身份',
type: 'array',
trigger: 'change'
}
],
leadHonor: [
{
required: true,
message: '请输入领衔人荣誉称号',
type: 'string',
trigger: 'change'
}
],
memberNum: [
{
required: true,
message: '请输入成员人数',
type: 'number',
trigger: 'blur'
}
],
type: [
{
required: true,
message: '请选择工作室类型',
type: 'string',
trigger: 'change'
}
],
track: [
{
required: true,
message: '请输入工作室赛道',
type: 'string',
trigger: 'blur'
}
],
address: [
{
required: true,
message: '请输入工作室具体位置',
type: 'string',
trigger: 'blur'
}
],
introduction: [
{
required: true,
message: '请填写工作室基本情况介绍',
type: 'string',
trigger: 'blur'
}
],
brandStudioMembers: [
{
required: true,
validator: (rule, value, callback) => {
// 1. 必须是数组且长度 >= 3
if (!Array.isArray(value) || value.length < 1) {
ElMessageBox.alert(
`请至少选择1位工作室成员!`,
`系统提示`, {
confirmButtonText: '确定',
type: 'warning',
});
return callback(new Error('请至少选择1位工作室成员'));
}
// 2. 检查每个成员的必填字段
const requiredFields = columns.value
.filter((col) => col.notRequired !== true)
.map((col) => col.prop); // ['name', 'birthdate', ...]
for (let i = 0; i < value.length; i++) {
const item = value[i];
for (const field of requiredFields) {
const val = item?.[field];
if (StrUtil.isEmpty(val)) {
const label = fieldLabelMap[field] || field; // fallback to field name
ElMessageBox.alert(
`【${item.name}】成员的【${label}】不能为空!`,
`系统提示`, {
confirmButtonText: '确定',
type: 'warning',
});
return callback(
new Error(`${item.name} 的“${label}”不能为空`)
);
}
}
}
callback(); // 校验通过
},
trigger: 'change'
}
]
});
/** 二级弹窗是否打开:选择用户 */
const selectUserVisible = ref(false);
/** beforeConfirm 为确定按钮点击钩子, 可以 return false 阻止确定 */
const beforeConfirm = (data) => {
if (!data?.length) {
EleMessage.error('请至少选择一个用户');
return false;
}
};
/** 表格列配置 */
const columns = computed(() => [
withRequiredHeader({ prop: 'name', label: '姓名', align: 'center', minWidth: 80, slot: 'name' }),
withRequiredHeader({ prop: 'birthdate', label: '出生年月', align: 'center', minWidth: 120, slot: 'birthdate', required: true }),
withRequiredHeader({ prop: 'grade', label: '学历', align: 'center', minWidth: 80, slot: 'grade', required: true }),
withRequiredHeader({ prop: 'professional', label: '职称', align: 'center', minWidth: 100, slot: 'professional', required: true }),
withRequiredHeader({ prop: 'deptName', label: '所在部门', align: 'center', minWidth: 100, slot: 'deptName' }),
withRequiredHeader({ columnKey: 'action', label: '操作', width: 80, align: 'center', slot: 'action', notRequired: true })
]);
// 自动生成映射:{ name: '姓名', birthdate: '出生年月', ... }
const fieldLabelMap = {};
columns.value.forEach((col) => {
fieldLabelMap[col.prop] = col.label;
});
const leaderChange = (emplid) => {
console.log('emplid: ' + emplid);
const matchedItem = leaderHonors.value.find(
(item) => item.emplid === emplid
);
if (matchedItem) {
form.leadHonor = matchedItem.honorNames?.join(',') || '';
form.leadAge = matchedItem.age || 0;
form.leadPeople = matchedItem.userName || '';
} else {
form.leadHonor = '';
form.leadAge = 0;
}
};
const unionAssign = (item) => {
if (!item) {
form.unionName = '';
return;
}
form.unionName = item.name;
}
const showSelectUser = () => {
selectUserVisible.value = true;
};
// 可编辑的副本(用于 v-model)
const editableData = ref([]);
const removeMember = (row) => {
const index = editableData.value.findIndex(
(item) => item.userId === row.userId
);
if (index !== -1) {
userIds.value.splice(index, 1);
editableData.value.splice(index, 1);
form.memberNum--;
}
};
/** 表格实例 */
const tableRef = ref(null);
// 可选择的成员
const userIds = ref([]);
/** 选择成员 */
// user-select 组件回调
const handleSelect = (data) => {
// orgEditableData 原来数据
const orgEditableData = editableData.value;
// 查找已经存在的用户
const existingIds = new Set(
orgEditableData.map((item) => item.userId || item.id)
);
// 过滤掉已经选择的用户,之前有的就不做处理
const selectData = data
.filter((item) => !existingIds.has(item.id))
.map((item) => ({
...item,
userId: item.id,
oldName: item.name,
oldBirthdate: item.birthdate
}));
editableData.value = [...orgEditableData, ...selectData];
form.memberNum = editableData.value.length;
// 选完情况组件中的用户
userIds.value = [];
};
// 获取成员当前所在工会和所有工会
const assignUserUnion = (userUnionInfo) => {
form.unionId = userUnionInfo.unionId;
form.unionName = userUnionInfo.unionName;
form.deptid = userUnionInfo.deptId;
form.deptName = userUnionInfo.deptName;
form.createYear = new Date().getFullYear().toString();
}
watch(() => props.userUnionInfo,
(newVal, oldVal) => {
if (StrUtil.isBlank(newVal?.unionId)) {
return;
}
assignUserUnion(props.userUnionInfo);
}, {immediate: true}
)
watch(
() => props.brandForm,
(newVal, oldVal) => {
if (StrUtil.isBlank(newVal?.id)) {
return
}
const data = {
...newVal,
leadIdentity: JSON.parse(newVal?.leadIdentity)
}
assignFields(data)
editableData.value =
data.brandStudioMembers.map((item) => {
return {
...item,
oldName: item.name,
oldBirthdate: item.birthdate
};
}) || [];
}, {immediate: true}
)
const userUnionId = ref('')
onMounted(async () => {
console.log('mounted');
// 调用接口获取所有工会列表并转为tree
userUnionId.value = userStore.userUnionIdByRole || userStore.userUnionId || '0';
console.log("=======", userUnionId.value)
const res = await getLeaderHonorApi();
leaderHonors.value = res.data.map((item, index) => {
return {
id: index,
emplid: item.emplid,
userName: item.userName,
age: item.age,
honorNames: item.honorNames
};
});
});
// 提交数据/保存草稿
const submit = (item) => {
form.status = item.saveType;
form.brandStudioMembers = editableData.value || [];
const params = { ...form };
params.leadIdentity = JSON.stringify(form.leadIdentity) || {};
params.domain = params.track;
formRef.value?.validate?.((valid) => {
if (!valid) {
emit('update:loading', false)
return;
}
const saveOrUpdate = item.isUpdate
? updateBrandStudioApplicationApi
: addBrandStudioApplicationApi;
saveOrUpdate(params)
.then(() => {
EleMessage.success({ message: '保存成功', plain: true });
emit('editFinish')
})
.catch((err) => {
EleMessage.error({ message: '服务错误', plain: true });
console.warn(err);
})
.finally(() => {
emit('update:loading', false)
});
})
}
defineExpose({
submit
})
</script>
<template>
<div v-loading="loading">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="110px"
@submit.prevent=""
:disabled="!editMode"
>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="所属单位工会" prop="unionName" required>
<survey-tree-select class="ele-fluid" v-model="form.unionId" :union-id="userUnionId" placeholder="请选择所属工会" @unionAssign="unionAssign" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属单位机构" prop="deptName" required>
<el-input
v-model="form.deptName"
placeholder="请输入所属单位机构"
:disabled="true"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室名称" prop="name" required>
<el-input
v-model="form.name"
placeholder="请输入工作室名称"
maxlength="30"
show-word-limit
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建年度" required prop="createYear">
<el-date-picker
clearable
v-model="form.createYear"
type="year"
value-format="YYYY"
placeholder="请选择年度"
:disabled="true"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室领衔人" prop="leadPeople" required>
<el-select
v-model="form.leadPeople"
filterable
allow-create
default-first-option
placeholder="请选择工作室领衔人"
value-key="id"
@change="leaderChange"
clearable
>
<el-option
v-for="item in leaderHonors"
:key="item.emplid"
:value="item.emplid"
:label="`${item.emplid} - ${item.userName}`"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人年龄" required prop="leadAge">
<el-input-number
placeholder="请输入领衔人年龄"
v-model="form.leadAge"
:min="0"
:max="150"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="领衔人身份" prop="leadIdentity">
<el-select
placeholder="请选择领衔人身份"
v-model="form.leadIdentity"
multiple
value-key="id"
clearable
>
<el-option
v-for="item in leadPeopleDict"
:key="item.id"
:value="{
id: item.id,
dictLabel: item.dictLabel,
dictTypeId: item.dictTypeId
}"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="荣誉称号" required prop="leadHonor">
<el-input
placeholder="请输入领衔人荣誉称号"
v-model="form.leadHonor"
maxlength="40"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室类型" prop="type">
<el-select
placeholder="请选择工作室类型"
v-model="form.type"
value-key="id"
clearable
>
<el-option
v-for="item in brandOfficeTypeDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属赛道" prop="track" required>
<el-select
placeholder="请选择工作室赛道"
v-model="form.track"
value-key="id"
clearable
>
<el-option
v-for="item in brandOfficeDomainDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="具体位置" prop="address" required>
<el-input
v-model="form.address"
placeholder="请输入工作室具体位置"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="基本情况介绍" required prop="introduction">
<el-input
:rows="8"
type="textarea"
v-model="form.introduction"
placeholder="请输入基本情况介绍"
clearable
maxlength="1500"
show-word-limit
/>
</el-form-item>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item
label="工作室成员"
required
prop="brandStudioMembers"
>
<span>{{ form.memberNum }}人</span>
<el-tooltip
content="团队成员数量,根据选择成员人数自动变化"
placement="top"
>
<el-icon
style="cursor: help; margin-left: 4px; color: #909399"
>
<QuestionFilled />
</el-icon>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12" style="text-align: right">
<el-button type="primary" @click="showSelectUser">
选择工作室成员
</el-button>
</el-col>
</el-row>
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="editableData"
:show-overflow-tooltip="true"
highlight-current-row
:export-config="{ fileName: '用户数据' }"
:style="{ paddingBottom: '16px' }"
cache-key="systemUserTable"
:tools="false"
>
<template #name="{ row }">
<span v-if="StrUtil.isEmpty(row.oldName)">
<el-input v-model="row.name" />
</span>
<span v-else>{{ row.name }}</span>
</template>
<template #birthdate="{ row }">
<el-date-picker
style="width: 160px; padding: 0 20px"
v-model="row.birthdate"
type="month"
placeholder="选择年月"
format="YYYY-MM"
value-format="YYYY-MM"
size="large"
/>
</template>
<template #grade="{ row }">
<span>
<el-select
placeholder="请选择学历类型"
v-model="row.grade"
value-key="id"
clearable
>
<el-option
v-for="item in userEducationDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</span>
</template>
<template #professional="{ row }">
<span class="ipt-text">
<el-input v-model="row.professional" maxlength="30" show-word-limit />
</span>
</template>
<template #deptName="{ row }">
<span v-if="StrUtil.isEmpty(row.deptName)">
<el-input v-model="row.deptName" />
</span>
<span v-else>{{ row.deptName }}</span>
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:remove'"
type="danger"
underline="never"
@click="removeMember(row)"
:icon="DeleteOutlined"
>
删除
</el-link>
</template>
</ele-pro-table>
</el-form>
<union-user-select
clearable
multiple
view-type="picker"
v-model:visible="selectUserVisible"
v-model="userIds"
placeholder="请选择用户"
queryType="union"
:union-id="topUnionId"
@select="handleSelect"
:before-confirm="beforeConfirm"
/>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,528 @@
<!-- 审批弹窗 -->
<template>
<ele-modal
:form="true"
:destroy-on-close="true"
:width="1200"
v-model="visible"
title="申请详情"
>
<el-row :gutter="24" style="padding-bottom: 16px">
<el-col :span="24">
<ele-timeline
:data="flowItems"
style="margin-top: 28px; font-size: 14px !important; width: 100%"
placement="left"
>
<template #itemDescription="{ item }">
<div class="flow-text">{{ item.auditor }}</div>
<div v-if="item.time" class="flow-text">
{{ item.key === 1 ? '申请时间' : '审批时间' }}:{{ item.time }}
</div>
<el-tooltip class="item" effect="dark" :content="item.remark" placement="top">
<div
v-if="item.remark && getStatusDesc(item.status) === 'PASSED'"
class="flow-text"
>审批意见:{{ item.remark }}
</div>
<div
v-if="item.remark && getStatusDesc(item.status) === 'REJECT'"
class="flow-text"
>驳回原因:{{ item.remark }}
</div>
<div
v-if="item.remark && getStatusDesc(item.status) === 'REPEAL'"
class="flow-text"
>
撤销原因:{{ item.remark }}
</div>
</el-tooltip>
</template>
</ele-timeline>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<info-audit :data="current" disabled/>
<!-- <el-card header="申请信息" shadow="never">-->
<!-- <info-audit :data="current" disabled></info-audit>-->
<!-- </el-card>-->
</el-col>
</el-row>
<template #footer>
<div
v-if="
getStatusNameByCode(props.data.status) === 'IN_REVIEW' &&
props.isAudit
"
>
<el-button :loading="loading" @click="handleRepeal"> 撤销 </el-button>
<el-button :loading="loading" @click="handleRefuse">驳回</el-button>
<el-button type="primary" :loading="loading" @click="handlePass">
通过
</el-button>
</div>
</template>
</ele-modal>
<el-dialog
v-model="passDialogVisible"
title="审批意见"
width="400px"
:before-close="handlePassDialogClose"
>
<el-form :model="passForm" :rules="passRules" ref="passFormRef">
<el-form-item label="审核意见" prop="reason">
<el-input
v-model="passForm.reason"
type="textarea"
:rows="4"
placeholder="请输入审核意见(必填)"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="passDialogVisible = false">取消</el-button>
<el-button type="primary" @click="onAudit" :loading="loading"
>确认</el-button
>
</template>
</el-dialog>
<!-- 新增:拒绝原因弹窗 -->
<el-dialog
v-model="refuseDialogVisible"
title="驳回原因"
width="400px"
:before-close="handleRefuseDialogClose"
>
<el-form :model="refuseForm" :rules="refuseRules" ref="refuseFormRef">
<el-form-item label="驳回原因" prop="reason">
<el-input
v-model="refuseForm.reason"
type="textarea"
:rows="4"
placeholder="请输入驳回原因(必填)"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="refuseDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmRefuse" :loading="loading"
>确认</el-button
>
</template>
</el-dialog>
<!-- 新增:撤销原因弹窗 -->
<el-dialog
v-model="repealDialogVisible"
title="撤销原因"
width="400px"
:before-close="handleRepealDialogClose"
>
<el-form :model="repealForm" :rules="repealRules" ref="repealFormRef">
<el-form-item label="撤销原因" prop="reason">
<el-input
v-model="repealForm.reason"
type="textarea"
:rows="4"
placeholder="请输入撤销原因(必填)"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="repealDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmRepeal" :loading="loading"
>确认</el-button
>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch } from 'vue';
import { EleMessage } from 'ele-admin-plus';
import { getBrandStudioApplicationApi } from '@/api/office/brand/index.js';
import {
auditApplicationApi,
getBrandApplyFlowListApi
} from '@/api/office/application/index.js';
import InfoAudit from '@/views/brand/components/info-audit.vue';
import { getStatusDesc, getStatusNameByCode } from '@/utils/toolkit.js';
const props = defineProps({
/** 修改回显的数据 */
data: Object,
/** 查看/审批 */
isAudit: Boolean
});
const emit = defineEmits(['done']);
/** 弹窗是否打开 */
const visible = defineModel({ type: Boolean });
/** 提交状态 */
const loading = ref(false);
/** 工作流数组 */
const flowItems = ref([]);
// ========== 新增:拒绝功能相关代码 ==========
// 拒绝弹窗显示状态
const refuseDialogVisible = ref(false);
// 拒绝表单数据
const refuseForm = ref({
reason: '' // 拒绝原因
});
// 拒绝表单验证规则
const refuseRules = reactive({
reason: [
{
required: true,
message: '请输入拒绝原因',
trigger: 'blur'
},
{
min: 2,
max: 500,
message: '拒绝原因长度在 2 到 500 个字符',
trigger: 'blur'
}
]
});
// 拒绝表单引用
const refuseFormRef = ref();
// 打开驳回
const handleRefuse = () => {
// 清空之前的驳回原因
refuseForm.value.reason = '';
// 打开弹窗
refuseDialogVisible.value = true;
};
// 关闭驳回弹窗时的处理
const handleRefuseDialogClose = () => {
refuseDialogVisible.value = false;
};
// 确认驳回
const confirmRefuse = () => {
// 验证表单
refuseFormRef.value.validate((valid) => {
if (!valid) {
return;
}
loading.value = true;
// 调用驳回接口
const payload = {};
payload.applicationId = props.data.id;
payload.status = 2;
payload.flowId = props.data.flowId;
payload.remark = refuseForm.value.reason;
auditApplicationApi(payload)
.then(() => {
EleMessage.success({ message: '驳回成功', plain: true });
refuseDialogVisible.value = false;
handleCancel();
emit('done');
})
.catch(() => {
EleMessage.error({ message: '拒绝失败', plain: true });
})
.finally(() => {
loading.value = false;
});
});
};
const current = ref({});
/** 关闭弹窗 */
const handleCancel = () => {
visible.value = false;
};
//获取基本信息详细
const getBrandApplicationDetail = () => {
getBrandStudioApplicationApi(props.data.id)
.then(({ data }) => {
loading.value = false;
current.value = data;
})
.finally(() => {
loading.value = false;
});
};
// ========== 新增:通过功能相关代码 ==========
const passFormRef = ref()
const passDialogVisible = ref(false);
// 撤销表单数据
const passForm = ref({
reason: '' // 通过原因
});
// 关闭通过弹窗时的处理
const handlePassDialogClose = () => {
passDialogVisible.value = false;
};
//审核通过
const onAudit = () => {
passFormRef.value.validate((valid) => {
if (!valid) {
return;
}
const payload = {};
payload.flowId = props.data.flowId;
payload.applicationId = props.data.id;
payload.status = 5;
payload.remark = passForm.value.reason;
const loadingInstance = EleMessage.loading({
message: '请求中...',
plain: true,
duration: 0 // 手动控制关闭,不自动消失
});
auditApplicationApi(payload)
.then(() => {
EleMessage.success({
message: '审核通过',
plain: true
});
passDialogVisible.value = false;
handleCancel();
emit('done');
})
.finally(() => {
loadingInstance.close();
});
})
// ElMessageBox.confirm(`是否通过审核?`, '系统提示', {
// type: 'warning',
// draggable: true
// })
// .then(() => {
// const payload = {};
// payload.flowId = props.data.flowId;
// payload.applicationId = props.data.id;
// payload.status = 5;
// const loadingInstance = EleMessage.loading({
// message: '请求中...',
// plain: true,
// duration: 0 // 手动控制关闭,不自动消失
// });
// auditApplicationApi(payload)
// .then(({ message, data }) => {
// EleMessage.success({
// message: '审核通过',
// plain: true
// });
// handleCancel();
// emit('done');
// })
// .finally(() => {
// loadingInstance.close();
// });
// })
// .catch(() => {});
};
// 打开撤销
const handlePass = () => {
// 清空之前的驳回原因
passForm.value.reason = '';
// 打开弹窗
passDialogVisible.value = true;
};
// 撤销表单验证规则
const passRules = reactive({
reason: [
{
required: true,
message: '请输入通过原因',
trigger: 'blur'
},
{
min: 2,
max: 500,
message: '通过原因长度在 2 到 500 个字符',
trigger: 'blur'
}
]
});
// ========== 新增:撤销功能相关代码 ==========
// 撤销弹窗显示状态
const repealDialogVisible = ref(false);
// 撤销表单数据
const repealForm = ref({
reason: '' // 撤销原因
});
// 撤销表单验证规则
const repealRules = reactive({
reason: [
{
required: true,
message: '请输入撤销原因',
trigger: 'blur'
},
{
min: 2,
max: 500,
message: '撤销原因长度在 2 到 500 个字符',
trigger: 'blur'
}
]
});
// 撤销表单引用
const repealFormRef = ref(null);
// 打开撤销
const handleRepeal = () => {
// 清空之前的驳回原因
repealForm.value.reason = '';
// 打开弹窗
repealDialogVisible.value = true;
};
// 关闭撤销弹窗时的处理
const handleRepealDialogClose = () => {
repealDialogVisible.value = false;
};
// 确认撤销
const confirmRepeal = () => {
// 验证表单
repealFormRef.value.validate((valid) => {
if (!valid) {
return;
}
loading.value = true;
// 调用接口
const payload = {};
payload.status = 3;
payload.flowId = props.data.flowId;
payload.applicationId = props.data.id;
payload.remark = repealForm.value.reason;
auditApplicationApi(payload)
.then(() => {
EleMessage.success({ message: '撤销成功', plain: true });
repealDialogVisible.value = false;
handleCancel();
emit('done');
})
.catch(() => {})
.finally(() => {
loading.value = false;
});
});
};
//流程实例
const getApplyFlowList = async () => {
const requestParams = {
applicationId: props.data.id // 关键Query参数:申请单ID
};
const { data } = await getBrandApplyFlowListApi(requestParams);
if (data && data.length > 0) {
const flowList = data.map((item, index) => {
let type = '';
// 定义不同状态的节点类型
if (getStatusDesc(item.status) === 'PASSED') {
type = 'primary';
} else if (getStatusDesc(item.status) === 'REJECT') {
type = 'danger';
}
return {
key: index + 1,
type: type,
title: item.unionName,
time:
getStatusDesc(item.status) !== 'WAIT_AUDIT' &&
getStatusDesc(item.status) !== 'TO_BE_AUDIT'
? item.updateTime
: '',
remark: item.remark,
auditor: item.auditor,
sortNo: item.sortNo,
status: item.status
};
});
flowItems.value = flowList.sort((a, b) => a.sortNo - b.sortNo);
}
};
/** 监听弹窗打开 */
watch(visible, () => {
if (visible.value && props.data) {
getBrandApplicationDetail();
getApplyFlowList();
}
});
</script>
<style scoped>
.el-steps--vertical {
height: 20% !important;
}
.auditor-time-row {
display: flex;
width: 100%;
justify-content: space-between;
padding-top: 8px;
}
.main-title {
font-weight: bold;
}
.auditor-date-time {
color: #9c9c9c;
}
.ele-modal-body.is-form {
padding-bottom: 20px !important;
}
:deep(.ele-time-line-list) {
margin-top: 0 !important;
}
:deep(.ele-time-line-item-title) {
font-size: 14px !important;
}
:deep(.ele-time-line-item-body) {
padding-bottom: 20px !important;
}
:deep(.el-timeline-item .is-placeholder) {
display: none !important; /* 直接隐藏占位 DOM */
height: 0 !important; /* 兜底:清空高度 */
margin: 0 !important; /* 清空边距 */
}
:deep(.is-placeholder) {
display: none;
}
</style>
<style>
.flow-text {
display: -webkit-box; /* For flexbox-like behavior */
-webkit-box-orient: vertical; /* Allow vertical box layout */
-webkit-line-clamp: 3; /* Limit text to 3 lines */
overflow: hidden; /* Hide overflow */
text-overflow: ellipsis; /* Show ellipsis for overflow */
white-space: normal; /* Allow text to wrap */
font-size: 11px;
width: 100%;
justify-content: center;
padding-top: 3px;
}
</style>
@@ -0,0 +1,50 @@
<!-- 审批弹窗 -->
<template>
<div>
<el-row :gutter="20">
<el-col :span="24">
<info-audio :data="current" :edit-mode="true"/>
<!-- <table-info-->
<!-- v-model:loading="loading"-->
<!-- :edit-mode="false"-->
<!-- :brand-form="current"-->
<!-- />-->
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import InfoAudio from '@/views/brand/components/info-audit.vue';
// import TableInfo from "@/views/brand/components/table-info.vue";
import { useRoute } from 'vue-router';
import {getBrandStudioApplicationApi} from "@/api/office/brand/index.js";
const loading = ref(false)
const route = useRoute();
const current = ref({});
//获取基本信息
const getBrandApplicationDetail = async () => {
// console.log("getBrandApplicationDetail", route.query)
loading.value = true
getBrandStudioApplicationApi(route.query.businessId)
.then(({ data }) => {
current.value = data;
})
.catch(err => console.log(err))
.finally(() => {
loading.value = false
});
};
onMounted(() => {
getBrandApplicationDetail();
});
</script>
<style scoped>
</style>
@@ -0,0 +1,242 @@
<!-- 弹窗先进工作者 -->
<template>
<el-form ref="formRef" :model="form" label-width="120" @submit.prevent="" :disabled="editMode">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属单位工会" prop="unionName">
<el-input
v-model="form.unionName"
placeholder="请输入所属单位工会"
:disabled="true"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属单位机构" prop="deptName">
<el-input
v-model="form.deptName"
placeholder="请输入所属单位机构"
:disabled="true"
/>
</el-form-item>
</el-col>
</el-row>
<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="createYear">
<el-input v-model="form.createYear" placeholder="请选择年度"/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="工作室领衔人" prop="leadPeople">
<el-input
v-model="form.leadPeople"
placeholder="请输入工作室领衔人"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人年龄" prop="leadAge">
<el-input-number
placeholder="请输入成员人数"
v-model="form.leadAge"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="领衔人身份" prop="leadIdentity">
<el-input
placeholder="请选择领衔人身份"
v-model="form.leadIdentity"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="荣誉称号" prop="leadHonor">
<el-input
placeholder="请输入领衔人荣誉称号"
v-model="form.leadHonor"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="工作室类型" prop="type">
<el-select
placeholder="请选择工作室类型"
v-model="form.type"
value-key="id"
>
<el-option
v-for="(item, index) in form.type"
:key="index"
:value="item"
:label="item"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属赛道" prop="track">
<el-input v-model="form.track" placeholder="请输入所属赛道" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="具体位置" prop="address">
<el-input v-model="form.address" placeholder="请输入工作室具体位置" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成员人数" prop="memberNum">
<el-input v-model="form.memberNum" placeholder="请输入工作室成员人数" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="基本情况介绍" prop="introduction">
<span style="color: #999999; font-size: 12px"
>(说明:事迹材料客观翔实、重点突出,不超过1500字。)</span
>
<el-input
:rows="4"
type="textarea"
disabled
v-model="form.introduction"
/>
</el-form-item>
<!-- 表格 -->
<ele-pro-table
row-key="id"
:columns="columns"
:datasource="tableData"
:show-overflow-tooltip="true"
highlight-current-row
:export-config="{ fileName: '用户数据' }"
:style="{ paddingBottom: '16px' }"
cache-key="systemUserTable"
:tools="false"
/>
</el-form>
</template>
<script setup>
import { ref, watch, computed } from 'vue';
import { useFormData } from '@/utils/use-form-data';
const props = defineProps({
/** 修改回显的数据 */
data: Object,
editMode: Boolean,
});
defineEmits(['done', 'back']);
/** 弹窗是否打开 */
defineModel({ type: Boolean });
/** 表单实例 */
const formRef = ref();
/** 表单数据 */
const [form, resetFields, assignFields] = useFormData({
id: '',
unionName: '', // 所属单位工会
deptName: '', // 所属单位工会
name: '', // 工作室名称
createYear: '', // 创建年份
leadPeople: '', // 工作室领衔人
leadAge: 25, // 领衔人年龄
leadIdentity: '', // 领衔人身份
leadHonor: '',
memberNum: 0, // 成员人数
type: '', // 工作室类型
track: '', // 工作室赛道
address: '', // 工作室具体位置
introduction: '', // 工作室基本情况介绍
unionUsers: [], // 工作室成员 { name, birthday, grade, professional, unionName }
remark: ''
});
/** 表格列配置 */
const columns = computed(() => {
return [
{
prop: 'name',
label: '姓名',
align: 'center',
minWidth: 80,
slot: 'name'
},
{
prop: 'birthdate',
label: '出生年月',
align: 'center',
minWidth: 120,
slot: 'birthdate'
},
{
prop: 'grade',
label: '学历',
align: 'center',
minWidth: 80,
slot: 'grade'
},
{
prop: 'professional',
label: '职称',
align: 'center',
minWidth: 100,
slot: 'professional'
},
{
prop: 'deptName',
label: '所在部门',
align: 'center',
minWidth: 100,
slot: 'deptName'
}
];
});
const tableData = ref([]);
/** 监听弹窗打开 */
watch(
() => props.data,
(newData) => {
if (newData && Object.keys(newData).length) {
// 深拷贝 newData,避免直接操作 props 原数据
const dataCopy = JSON.parse(JSON.stringify(newData));
const leadIdentity = JSON.parse(dataCopy['leadIdentity']);
tableData.value = newData.brandStudioMembers || [];
// 处理需要转换的字段
const formData = {
...dataCopy,
id: '', // 强制置空 id
leadIdentity: leadIdentity.map((item) => item.dictLabel).join(',')
};
// 用 assignFields 将处理后的数据赋值给 form(而非修改 props)
assignFields(formData);
} else {
// 数据为空时重置表单
resetFields();
}
},
{ immediate: true, deep: true }
);
</script>
<style>
</style>
@@ -0,0 +1,68 @@
<!-- 搜索表单 -->
<template>
<ele-card :body-style="{ paddingBottom: '2px' }">
<el-form label-width="72px" @keyup.enter="search" @submit.prevent="">
<el-row :gutter="8">
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="工作室名称" label-width="100px">
<el-input
clearable
v-model.trim="form.name"
placeholder="请输入工作室名称"
/>
</el-form-item>
</el-col>
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="创立年度">
<el-date-picker
clearable
v-model="form.createYear"
type="year"
value-format="YYYY"
placeholder="请选择工作室创立年度"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="工作室领衔人" label-width="100px">
<el-input
clearable
v-model.trim="form.leadPeople"
placeholder="请输入工作室领衔人"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xs="24">
<el-form-item label-width="20px">
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</ele-card>
</template>
<script setup>
import { useFormData } from '@/utils/use-form-data.js';
const emit = defineEmits(['search']);
/** 表单数据 */
const [form, resetFields] = useFormData({
name: '',
leadPeople: '',
createYear: new Date().getFullYear().toString()
});
/** 搜索 */
const search = () => {
emit('search', { ...form, params: {} });
};
/** 重置 */
const reset = () => {
resetFields();
search();
};
</script>
@@ -0,0 +1,103 @@
<script setup>
import UnionUserSelect from "@/components/UnionUserSelect/index.vue";
import {EleMessage} from "ele-admin-plus";
import {ref, watch} from "vue";
import {StrUtil} from "@/utils/toolkit.js";
const props = defineProps({
unionMembers: {
type: Array,
default: () => []
},
topUnionId: {
type: String
}
})
const emit = defineEmits(['update:unionMembers'])
const selectUserVisible = ref(false)
// 可编辑的副本(用于 v-model)
const editableData = ref([]);
watch(
() => props.unionMembers,
(val) => {
editableData.value = val ? [...val] : []
},
{ immediate: true, deep: true }
)
/** 打开弹窗 */
const showSelectUser = () => {
selectUserVisible.value = true;
}
// 可选择的成员
const userIds = ref([]);
/** 选择成员 */
// user-select 组件回调
const handleSelect = (data) => {
// orgEditableData 原来数据
const orgEditableData = editableData.value.filter(item => !StrUtil.isBlank(item.name));
// 查找已经存在的用户
const existingIds = new Set(
orgEditableData.map((item) => item.userId || item.id)
);
// 过滤掉已经选择的用户,之前有的就不做处理
const selectData = data
.filter((item) => !existingIds.has(item.id))
.map((item) => ({
...item,
userId: item.id,
oldName: item.name,
oldBirthdate: item.birthdate
}));
editableData.value = [...orgEditableData, ...selectData];
// 选完情况组件中的用户
userIds.value = [];
emit('update:unionMembers', editableData.value)
};
/** beforeConfirm 为确定按钮点击钩子, 可以 return false 阻止确定 */
const beforeConfirm = (data) => {
if (!data?.length) {
EleMessage.error('请至少选择一个用户');
return false;
}
return true
};
</script>
<template>
<div>
<el-button
type="primary"
size="default"
:plain="true"
:text="true"
:bg="true"
@click="showSelectUser"
>
选择工作室成员
</el-button>
<union-user-select
clearable
multiple
view-type="picker"
v-model:visible="selectUserVisible"
v-model="userIds"
placeholder="请选择用户"
:query-type="'union'"
:union-id="topUnionId"
@select="handleSelect"
:before-confirm="beforeConfirm"
/>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,823 @@
<template>
<div style="padding: 0 100px">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="right"
:label-width="80"
:inline-message="true"
@submit.prevent=""
:disabled="!editMode"
v-loading="loading"
>
<ele-text
size="xl"
:strong="true"
:style="{ textAlign: 'center', marginBottom: '12px' }"
>
上海浦东发展银行个人品牌工作室推荐表
</ele-text>
<div style="padding: 10px 0">
<el-form-item label="所属单位工会" prop="unionId" label-position="left" label-width="110px">
<survey-tree-select class="ele-fluid" v-model="form.unionId" :union-id="userUnionId" placeholder="请选择所属工会" @unionAssign="unionAssign" />
</el-form-item>
</div>
<ele-table
:border="true"
:has-header="false"
:style="{
'--ele-table-border-color': 'var(--el-text-color-secondary)',
tableLayout: 'fixed',
marginBottom: '28px'
}"
>
<tr>
<td :style="{ width: '140px' }" class="label-center">
<ele-text tag="span" class="label-required">
工作室名称
</ele-text>
</td>
<td :style="{ minWidth: '140px' }" :colspan="3">
<el-form-item
label=""
prop="name"
label-width="0px"
label-position="left"
:style="{ marginBottom: '0px' }"
class="pro-form-error-popper"
>
<el-input
:clearable="true"
type="text"
placeholder=""
:maxlength="30"
:show-word-limit="true"
ref="nameRef"
v-model="form.name"
/>
</el-form-item>
</td>
<td :style="{ width: '120px' }" class="label-center">
<ele-text tag="span" class="label-required">
创建年度
</ele-text>
</td>
<td :colspan="2">
<el-form-item label="" prop="createYear" label-width="0px">
<el-date-picker
class="ele-fluid"
value-format="YYYY"
placeholder="请选择创建年度"
:clearable="false"
:editable="true"
type="year"
format="YYYY"
v-model="form.createYear"
/>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 工作室领衔人 </ele-text>
</td>
<td :colspan="3">
<el-form-item prop="leadPeople" label-width="0px" label-position="left">
<el-select
v-model="form.leadPeople"
filterable
allow-create
default-first-option
placeholder="请选择工作室领衔人"
value-key="id"
class="ele-fluid"
@change="leaderChange"
clearable
>
<el-option
v-for="item in leaderHonors"
:key="item.emplid"
:value="item.emplid"
:label="`${item.emplid} - ${item.userName}`"
/>
</el-select>
</el-form-item>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 年龄 </ele-text>
</td>
<td :colspan="2">
<el-form-item label="" prop="leadAge" label-width="0px" label-position="left">
<el-input-number
class="ele-fluid"
controls-position="right"
placeholder="请输入年龄"
:min="0"
:max="150"
:controls="false"
v-model="form.leadAge"
/>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 领衔人身份 </ele-text>
</td>
<td :colspan="3">
<el-form-item label="" prop="leadIdentity" label-width="0px" label-position="left">
<el-checkbox-group
:style="{ gap: '10px', display: 'flex', 'flex-wrap': 'wrap' }"
class=""
@change="(value) => {}"
:disabled="false"
:max="2"
v-model="form.leadIdentity"
>
<el-checkbox
v-for="item in leadPeopleDict"
:key="item.id"
:label="item.dictLabel"
:value="{
id: item.id,
dictLabel: item.dictLabel,
dictTypeId: item.dictTypeId
}"
/>
</el-checkbox-group>
</el-form-item>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 成员人数 </ele-text>
</td>
<td :colspan="2">
<el-form-item prop="memberNum" label-width="0px" label-position="left">
<el-input-number
class="ele-fluid"
controls-position="right"
placeholder="请输入成员人数"
:min="0"
:max="99999"
:controls="false"
v-model="form.memberNum"
/>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 工作室类型 </ele-text>
</td>
<td :colspan="3">
<el-form-item prop="type" label-width="0px" label-position="left">
<el-radio-group
class="radio-as-checkbox"
:style="{
display: 'flex',
gap: '10px',
'flex-wrap': 'wrap'
}"
v-model="form.type"
>
<el-radio
v-for="item in brandOfficeTypeDict"
:key="item.id"
:value="item.dictLabel"
>
<span>{{ item.dictLabel }}</span>
<el-input
v-if="item.dictLabel === '其他' && form.type === '其他'"
v-model="form.type"
placeholder="请输入工作室类型"
style="width:150px;margin-left:20px"
@click.stop
/>
</el-radio>
</el-radio-group>
</el-form-item>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 所属赛道 </ele-text>
</td>
<td :colspan="2">
<el-form-item label="" prop="track" label-width="0px" label-position="left">
<el-select
class="ele-fluid"
:clearable="false"
placeholder="请选择所属赛道"
v-model="form.track"
>
<el-option
v-for="item in brandOfficeDomainDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 工作室具体位置 </ele-text>
</td>
<td :colspan="6">
<el-form-item
label=""
prop="address"
label-width="0px"
label-position="left"
:style="{ marginBottom: '0px' }"
class="pro-form-error-popper"
>
<el-input
:clearable="true"
type="text"
placeholder=""
:maxlength="80"
:show-word-limit="true"
:show-password="false"
v-model="form.address"
/>
</el-form-item>
</td>
</tr>
<tr>
<td class="label-center">
<ele-text class="label-required" tag="span"> 工作室基本情况介绍 </ele-text>
</td>
<td :colspan="6">
<el-form-item prop="introduction" label-width="0px" label-position="left">
<el-input
:rows="20"
placeholder="请输入工作室基本情况介绍"
:show-word-limit="true"
:maxlength="1500"
type="textarea"
v-model="form.introduction"
/>
</el-form-item>
</td>
</tr>
<tr>
<td :colspan="7" :style="{ 'text-align': 'center' }">
<div
:style="{
position: 'relative',
display: 'flex',
'align-items': 'center',
'justify-content': 'center'
}"
>
<ele-text tag="span" type="default">
工作室成员基本情况
</ele-text>
<div v-if="editMode" :style="{ right: '0', position: 'absolute' }">
<select-member-btn
v-model:unionMembers="form.brandStudioMembers"
:top-union-id="topUnionId"
/>
</div>
</div>
</td>
</tr>
<colgroup v-if="editMode">
<col style="width: 120px" />
<col style="width: 140px" />
<col style="width: 150px" />
<col style="width: 120px" />
<col style="width: 120px" />
<col style="width: 240px" />
<col style="width: 80px" />
</colgroup>
<colgroup v-if="!editMode">
<col style="width: 180px" />
<col style="width: 210px" />
<col style="width: 230px" />
<col style="width: 170px" />
<col style="max-width: 180px" />
</colgroup>
<el-form-item prop="brandStudioMembers" style="display: none;" />
<tr>
<td class="label-center">
<ele-text tag="span"> 姓名 </ele-text>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 出生年月 </ele-text>
</td>
<td class="label-center">
<ele-text class="label-required" tag="span"> 学历 </ele-text>
</td>
<td :colspan="2" :style="{ minWidth: '160px' }" class="label-center">
<ele-text class="label-required" tag="span"> 职称 </ele-text>
</td>
<td :colspan="editMode ? 1 : 2" :style="{ width: '220px' }" class="label-center">
<ele-text tag="span"> 所在部门 </ele-text>
</td>
<td v-if="editMode" :style="{ width: '70px' }" class="label-center">
<ele-text tag="span"> 操作 </ele-text>
</td>
</tr>
<tr
v-for="(row, idx) in form.brandStudioMembers"
:key="idx"
>
<td class="union-member">
<ele-text tag="span"> {{ row.name }} </ele-text>
</td>
<td class="union-member">
<el-form-item
v-if="StrUtil.isNotBlank(row.name)"
:prop="`brandStudioMembers.${idx}.birthdate`"
label-width="0px"
:rules="rulesMember.birthdate"
>
<el-date-picker
value-format="YYYY-MM"
placeholder="选择年月"
format="YYYY-MM"
v-model="row.birthdate"
/>
</el-form-item>
<ele-text v-else tag="span"> {{ row.birthdate }} </ele-text>
</td>
<td class="union-member">
<el-form-item
v-if="StrUtil.isNotBlank(row.name)"
:prop="`brandStudioMembers.${idx}.grade`"
label-width="0px"
:rules="rulesMember.grade"
>
<el-select
class="ele-fluid"
:clearable="true"
placeholder="请选择学历类型"
v-model="row.grade"
>
<el-option
v-for="item in userEducationDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
<ele-text v-else tag="span"> {{ row.grade }} </ele-text>
</td>
<td class="union-member" :colspan="2">
<el-form-item
v-if="StrUtil.isNotBlank(row.name)"
:prop="`brandStudioMembers.${idx}.professional`"
label-width="0px"
label-position="right"
:rules="rulesMember.professional"
>
<el-input
:clearable="true"
type="text"
placeholder="请输入成员职称"
:maxlength="30"
:show-word-limit="true"
v-model="row.professional"
/>
</el-form-item>
<ele-text v-else tag="span"> {{ row.professional }} </ele-text>
</td>
<td class="union-member" :colspan="editMode ? 1 : 2" :style="{ minWidth: '220px' }">
<ele-text tag="span"> {{ row.deptName }} </ele-text>
</td>
<td v-if="editMode" :style="{ minWidth: '70px', textAlign: 'center' }">
<el-button
type="danger"
size="default"
:plain="true"
:text="false"
:bg="false"
@click="handleRemoveMember(idx)"
>
删除
</el-button>
</td>
</tr>
</ele-table>
</el-form>
</div>
</template>
<script setup>
import {ref, reactive, nextTick, watch, onMounted, computed} from 'vue';
import { useFormData } from '@/utils/use-form-data';
import {get2union} from "@/api/club/apply/index.js";
import {useDictData} from "@/utils/use-dict-data.js";
import SurveyTreeSelect from "@/views/survey/components/survey-tree-select.vue";
import {
addBrandStudioApplicationApi,
getLeaderHonorApi,
updateBrandStudioApplicationApi
} from "@/api/office/brand/index.js";
import {EleMessage, EleTable} from "ele-admin-plus";
import SelectMemberBtn from "@/views/brand/components/select-member-btn.vue";
import {StrUtil} from "@/utils/toolkit.js";
import dayjs from "dayjs";
import {ElMessageBox} from "element-plus";
const props = defineProps({
editMode: Boolean,
loading: Boolean,
userUnionInfo: Object, // 用户工会相关信息
topUnionId: String, // 最上层工会ID
brandForm: Object // 表格信息,修改时使用
})
const emit = defineEmits(['update:loading', 'editFinish'])
/** 字典数据 */
const [
leadPeopleDict,
brandOfficeTypeDict,
userEducationDict,
brandOfficeDomainDict
] = useDictData([
'lead_people',
'brand_office_type',
'user_education',
'brand_office_domain'
]);
/** 表单组件 */
const formRef = ref(null);
/** 表单数据(只改字段命名) */
const [form, resetFields, assignFields] = useFormData({
// 基本信息
id: '',
unionId: void 0, // 所属单位工会
unionName: void 0, // 所属单位工会
deptId: void 0,
deptName: void 0,
name: void 0, // 工作室名称
createYear: void 0, // 创建年度
leadPeople: void 0, // 工作室领衔人
leadAge: 0, // 领衔人年龄
leadIdentity: void 0, // 领衔人身份
memberNum: 3, // 成员人数
type: void 0, // 工作室类型
track: void 0, // 工作室赛道
address: void 0, // 工作室具体位置
introduction: void 0, // 工作室基本情况介绍
brandStudioMembers: [
{ name: void 0, birthdate: void 0, grade: void 0, professional: void 0, deptName: void 0 },
{ name: void 0, birthdate: void 0, grade: void 0, professional: void 0, deptName: void 0 },
{ name: void 0, birthdate: void 0, grade: void 0, professional: void 0, deptName: void 0 }
]
});
// 成员信息校验
const rulesMember = reactive({
birthdate: [
{ required: true, message: '请选择出生年月', trigger: ['change', 'blur'] },
{
validator: (_, v, cb) => {
if (StrUtil.isBlank(v)) return cb(new Error('请选择出生年月'));
const s = String(v).trim();
if (!/^\d{4}-(0[1-9]|1[0-2])$/.test(s)) {
return cb(new Error('出生年月格式应为 YYYY-MM'));
}
// 不能大于当前月份(可按业务改)
const d = dayjs(s, 'YYYY-MM', true);
if (!d.isValid()) return cb(new Error('出生年月不合法'));
if (d.isAfter(dayjs(), 'month')) return cb(new Error('出生年月不能晚于当前月份'));
cb();
},
trigger: ['change', 'blur']
}
],
grade: [
{ required: true, message: '请选择学历', trigger: ['change', 'blur'] }
],
professional: [
{ required: true, message: '请输入职称', trigger: ['blur', 'change'] },
{
validator: (_, v, cb) => {
if (StrUtil.isBlank(v)) return cb(new Error('请输入职称'));
const s = String(v).trim();
if (s.length > 30) return cb(new Error('职称长度不能超过 30'));
// 常见职称字符集(中英数/空格/.-/括号)
const ok = /^[\u4e00-\u9fa5A-Za-z0-9\s.\-()()]+$/.test(s);
if (!ok) return cb(new Error('职称包含非法字符'));
cb();
},
trigger: ['blur', 'change']
}
],
});
/** 表格列配置 */
const columns = computed(() => [
{ prop: 'name', label: '姓名', align: 'center', minWidth: 80, slot: 'name' },
{ prop: 'birthdate', label: '出生年月', align: 'center', minWidth: 120, slot: 'birthdate', required: true },
{ prop: 'grade', label: '学历', align: 'center', minWidth: 80, slot: 'grade', required: true },
{ prop: 'professional', label: '职称', align: 'center', minWidth: 100, slot: 'professional', required: true },
{ prop: 'deptName', label: '所在部门', align: 'center', minWidth: 100, slot: 'deptName' },
{ columnKey: 'action', label: '操作', width: 80, align: 'center', slot: 'action', notRequired: true }
]);
const fieldLabelMap = {};
columns.value.forEach((col) => {
fieldLabelMap[col.prop] = col.label;
});
/** 表单验证规则(同步改 prop key) */
const rules = reactive({
unionId: [
{ required: true, message: '所属单位工会', trigger: 'change' }
],
name: [
{ required: true, message: '请输入工作室名称', trigger: 'blur' }
],
leadPeople: [
{ required: true, message: '请选择领衔人', trigger: 'change' }
],
leadAge: [
{ required: true, message: '请输入年龄', trigger: 'change' }
],
leadIdentity: [
{ required: true, message: '请选择领衔人身份', trigger: 'change', type: 'array' }
],
memberNum: [
{ required: true, message: '请输入成员人数', trigger: 'change' }
],
type: [
{ required: true, message: '请选择工作室类型', trigger: 'change' }
],
track: [
{ required: true, message: '请选择所属赛道', trigger: 'change' }
],
address: [
{ required: true, message: '请输入工作室具体位置', trigger: 'blur' }
],
introduction: [
{ required: true, message: '请输入工作室基本情况介绍', trigger: 'blur' }
],
brandStudioMembers: {
type: 'array',
required: true,
trigger: 'blur',
validator: (rule, value, callback) => {
const members = value.filter(item => StrUtil.isNotBlank(item.name))
console.log(value)
// 1. 必须是数组且长度 >= 1
if (!Array.isArray(members) || members.length < 1) {
ElMessageBox.alert(
`请至少选择1位工作室成员!`,
`系统提示`, {
confirmButtonText: '确定',
type: 'warning',
});
return callback(new Error('请至少选择1位工作室成员'));
}
// 2. 检查每个成员的必填字段
const requiredFields = columns.value
.filter((col) => col.notRequired !== true)
.map((col) => col.prop); // ['name', 'birthdate', ...]
for (let i = 0; i < value.length; i++) {
const item = value[i];
for (const field of requiredFields) {
const val = item?.[field];
if (StrUtil.isEmpty(val)) {
const label = fieldLabelMap[field] || field; // fallback to field name
ElMessageBox.alert(
`【${item.name}】成员的【${label}】不能为空!`,
`系统提示`, {
confirmButtonText: '确定',
type: 'warning',
});
return callback(
new Error(`第 ${i + 1} 位成员的“${label}”不能为空`)
);
}
}
}
callback();
}
}
});
/** 领衔人荣誉 */
const leaderHonors = ref([]);
const leaderChange = (emplid) => {
console.log('emplid: ' + emplid);
const matchedItem = leaderHonors.value.find(
(item) => item.emplid === emplid
);
console.log(matchedItem)
if (matchedItem) {
form.leadHonor = matchedItem.honorNames?.join(',') || '';
form.leadAge = matchedItem.age || 0;
form.leadPeople = matchedItem.userName || '';
} else {
form.leadHonor = '';
form.leadAge = 0;
}
};
const handleRemoveMember = (idx) => {
form.brandStudioMembers.splice(idx, 1);
form.memberNum = form.brandStudioMembers.length;
// 可选:删完后清一下校验(避免残留错误)
nextTick(() => formRef.value?.clearValidate?.());
};
const unionAssign = (item) => {
if (!item) {
form.unionName = '';
return;
}
form.unionName = item.name;
}
const userUnionId = ref('')
// 获取成员当前所在工会和所有工会
const assignUserUnion = (userUnionInfo) => {
form.unionId = userUnionInfo.unionId;
form.unionName = userUnionInfo.unionName;
form.deptId = userUnionInfo.deptId;
form.deptName = userUnionInfo.deptName;
form.createYear = new Date().getFullYear().toString();
}
watch(() => props.userUnionInfo,
(newVal, oldVal) => {
if (StrUtil.isBlank(newVal?.unionId)) {
return;
}
assignUserUnion(props.userUnionInfo);
}, {immediate: true})
watch(
() => form.brandStudioMembers.length,
(len) => {
form.memberNum = len
}, { immediate: true }
)
watch(
() => props.brandForm,
(newVal, oldVal) => {
console.log('newVal---', newVal)
if (StrUtil.isBlank(newVal?.id)) {
return
}
const data = {
...newVal,
leadIdentity: JSON.parse(newVal?.leadIdentity)
}
assignFields(data)
}, {immediate: true}
)
onMounted(async () => {
console.log('mounted');
// 调用接口获取所有工会列表并转为tree
get2union().then((res) => {
userUnionId.value = res.data
})
const res = await getLeaderHonorApi();
leaderHonors.value = res.data.map((item, index) => {
return {
id: index,
emplid: item.emplid,
userName: item.userName,
age: item.age,
honorNames: item.honorNames
};
});
});
// 提交数据/保存草稿
const submit = (item) => {
form.status = item.saveType;
const params = { ...form };
params.leadIdentity = JSON.stringify(form.leadIdentity) || {};
params.domain = params.track;
formRef.value?.validate?.((valid) => {
if (!valid) {
emit('update:loading', false)
return;
}
const saveOrUpdate = item.isUpdate
? updateBrandStudioApplicationApi
: addBrandStudioApplicationApi;
saveOrUpdate(params)
.then(() => {
EleMessage.success({ message: '保存成功', plain: true });
emit('editFinish')
})
.catch((err) => {
EleMessage.error({ message: '服务错误', plain: true });
console.warn(err);
})
.finally(() => {
emit('update:loading', false)
});
})
}
defineExpose({
submit
})
</script>
<style scoped>
.el-form-item {
margin-bottom: 0;
}
.label-center, .union-member {
text-align: center;
}
/* 基础尺寸:和 el-checkbox 完全一致 */
.radio-as-checkbox :deep(.el-radio__inner) {
box-sizing: border-box;
width: 16px;
height: 16px;
border-radius: 4px;
}
/* 干掉 radio 原点 */
.radio-as-checkbox :deep(.el-radio__inner::after) {
display: none;
}
/* hover 边框 */
.radio-as-checkbox :deep(.el-radio__input:not(.is-disabled):hover .el-radio__inner) {
border-color: var(--el-color-primary);
width: 16px;
height: 16px;
border-radius: 4px;
}
/* 选中态:背景 + 边框 */
.radio-as-checkbox :deep(.el-radio__input.is-checked .el-radio__inner) {
background-color: var(--el-color-primary);
border-color: var(--el-color-primary);
}
/* el-checkbox 同款对勾 */
.radio-as-checkbox :deep(.el-radio__input.is-checked .el-radio__inner::before) {
content: '';
position: absolute;
left: 4px;
bottom: 3px;
width: 4px;
height: 8px;
border: 2px solid #fff;
border-top: none;
border-left: none;
transform: rotate(45deg);
}
/* disabled 状态(和 el-checkbox 对齐) */
.radio-as-checkbox :deep(.el-radio__input.is-disabled .el-radio__inner) {
background-color: var(--el-fill-color-light);
border-color: var(--el-border-color-light);
}
.radio-as-checkbox :deep(.el-radio__input.is-disabled.is-checked .el-radio__inner) {
background-color: var(--el-color-primary-light-5);
border-color: var(--el-color-primary-light-5);
}
/* 只放开人员表格里的 td */
.union-member {
overflow: visible !important;
}
.union-member :deep(.el-select__selected-item),
.union-member :deep(.el-input__inner),
.union-member :deep(.el-input__wrapper .el-input__inner) {
text-align: center;
}
.label-required::before {
content: '*';
color: var(--el-color-danger);
}
</style>
@@ -0,0 +1,27 @@
import {h} from "vue";
export const withRequiredHeader = (col) => ({
...col,
renderHeader: () => {
if (!col.required) return col.label;
return h(
'span',
{ style: { display: 'inline-flex', alignItems: 'center' } },
[
h('span', null, col.label),
h(
'span',
{
style: {
color: '#f56c6c', // 红色
fontWeight: '700',
fontSize: '16px',
marginLeft: '4px' // 放右侧留间距
}
},
'*'
)
]
);
}
});
@@ -0,0 +1,345 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<el-form-item label="创立年度">
<el-date-picker v-model="pageForm.createYear" type="year" value-format="yyyy"></el-date-picker>
</el-form-item>
<el-form-item label="工作室名称">
<el-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="工作室列表"></table-tool>
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="name" label="工作室名称" min-width="160"></el-table-column>
<el-table-column prop="createYear" label="创立年度" width="100"></el-table-column>
<el-table-column prop="leadPeople" label="领衔人" width="120"></el-table-column>
<el-table-column prop="deptName" label="所在单位" min-width="140"></el-table-column>
<el-table-column prop="type" label="工作室类型" width="120"></el-table-column>
<el-table-column prop="track" label="工作室赛道" width="120"></el-table-column>
<el-table-column prop="memberNum" label="成员人数" width="90"></el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openDetail(row)">编辑品牌工作室</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<template #public>
<div>
<div class="process-title">编辑品牌工作室</div>
<el-tabs v-model="activeName">
<el-tab-pane name="base" label="基本信息">
<el-descriptions :column="2" border>
<el-descriptions-item label="工作室名称">{{form.name}}</el-descriptions-item>
<el-descriptions-item label="创立年度">{{form.createYear}}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{form.unionName}}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{form.deptName}}</el-descriptions-item>
<el-descriptions-item label="领衔人">{{form.leadPeople}}</el-descriptions-item>
<el-descriptions-item label="领衔人年龄">{{form.leadAge}}</el-descriptions-item>
<el-descriptions-item label="领衔人身份">{{Array.isArray(form.leadIdentity) ? form.leadIdentity.join(',') : form.leadIdentity}}</el-descriptions-item>
<el-descriptions-item label="领衔人荣誉">{{form.leadHonor}}</el-descriptions-item>
<el-descriptions-item label="工作室类型">{{form.type}}</el-descriptions-item>
<el-descriptions-item label="工作室赛道">{{form.track}}</el-descriptions-item>
<el-descriptions-item label="工作室地址" :span="2">{{form.address}}</el-descriptions-item>
<el-descriptions-item label="工作室介绍" :span="2">{{form.introduction}}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{form.remark}}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane name="members" label="成员信息">
<div class="brand-member-toolbar">
<span>成员人数:{{(form.brandStudioMembers || []).length}}</span>
<div>
<el-button type="primary" size="small" @click="openMemberDialog">选择成员</el-button>
<el-button type="primary" size="small" :loading="savingMembers" @click="saveMembers">保存人员</el-button>
</div>
</div>
<el-table :data="form.brandStudioMembers || []" border size="small">
<el-table-column label="姓名" prop="name" width="120"></el-table-column>
<el-table-column label="出生年月" prop="birthdate" width="180">
<template slot-scope="{row}">
<el-date-picker v-model="row.birthdate" type="month" value-format="yyyy-MM" style="width: 100%"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="学位" prop="grade" min-width="180">
<template slot-scope="{row}">
<el-select v-model="row.grade" style="width: 100%">
<el-option v-for="item in memberDegreeDict" :key="item.value || item.code" :label="item.label || item.name" :value="item.value || item.code"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="职称" prop="professional" min-width="180">
<template slot-scope="{row}">
<el-input v-model="row.professional"></el-input>
</template>
</el-table-column>
<el-table-column label="所在单位" prop="deptName" min-width="180"></el-table-column>
<el-table-column label="操作" width="90">
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="removeMember(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane name="honors" label="荣誉信息">
<div class="brand-member-toolbar">
<span>荣誉数量:{{(form.brandStudioHonors || []).length}}</span>
<div>
<el-button type="primary" size="small" @click="addHonor">添加荣誉</el-button>
<el-button type="primary" size="small" :loading="savingHonors" @click="saveHonors">保存荣誉</el-button>
</div>
</div>
<el-table :data="form.brandStudioHonors || []" border size="small">
<el-table-column label="荣誉称号" prop="honorName" min-width="160">
<template slot-scope="{row}">
<el-input v-model="row.honorName"></el-input>
</template>
</el-table-column>
<el-table-column label="获取年度" prop="requireYear" min-width="180">
<template slot-scope="{row}">
<el-date-picker v-model="row.requireYear" type="year" value-format="yyyy" style="width: 100%"></el-date-picker>
</template>
</el-table-column>
<el-table-column label="荣誉类别" prop="honorCategory" min-width="180">
<template slot-scope="{row}">
<el-select v-model="row.honorCategory" style="width: 100%">
<el-option label="外部荣誉" :value="1"></el-option>
<el-option label="行内荣誉" :value="2"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="操作" min-width="160">
<template slot-scope="scope">
<el-button size="mini" type="danger" @click="removeHonor(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<el-row type="flex" justify="end" class="brand-detail-actions">
</el-row>
</div>
</template>
</guava>
<el-dialog title="选择工作室成员" :visible.sync="memberDialogVisible" width="760px" class="brand-member-dialog">
<div class="brand-member-search">
<el-input v-model="memberKeyword" placeholder="请输入姓名或工号" clearable @keyup.enter.native="loadMemberCandidates"></el-input>
<el-button type="primary" icon="el-icon-search" @click="loadMemberCandidates">查询</el-button>
</div>
<el-table :data="memberCandidates" border size="small" height="360" @selection-change="memberSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="loginname" label="工号" width="120"></el-table-column>
<el-table-column prop="username" label="姓名" width="120"></el-table-column>
<el-table-column prop="unitName" label="所在单位" show-overflow-tooltip></el-table-column>
</el-table>
<div slot="footer">
<el-button @click="memberDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmSelectMembers">确定</el-button>
</div>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
pageForm: {
page: 1,
limit: 20,
status: 5,
createYear: String(new Date().getFullYear())
},
tableData: [],
activeName: 'base',
savingMembers: false,
savingHonors: false,
form: {
brandStudioMembers: [],
brandStudioHonors: [],
leadIdentity: []
},
memberDegreeDict: [],
memberDialogVisible: false,
memberKeyword: '',
memberCandidates: [],
selectedMemberCandidates: []
}
},
methods: {
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
pageData() {
this.$axios.get('/platform/zhgh/brand/application/page', {params: this.pageForm}).then((res) => {
const data = this.getData(res) || {}
this.tableData = data.list || data.data || []
})
},
loadDictOptions(code) {
return this.$axios.get('/open/common/dictOptions', {params: {code: code}}).then((res) => {
return this.getData(res) || []
})
},
loadDicts() {
Promise.all([
this.loadDictOptions('USER_ACADEMIC_DEGREE')
]).then(([memberDegreeDict]) => {
this.memberDegreeDict = memberDegreeDict
})
},
openDetail(row) {
this.$refs.guava.public(() => {
this.activeName = 'base'
this.$axios.get('/platform/zhgh/brand/application/' + row.id).then((res) => {
const data = this.getData(res) || {}
data.brandStudioMembers = data.brandStudioMembers || []
data.brandStudioHonors = data.brandStudioHonors || []
data.memberNum = data.brandStudioMembers.length
data.leadIdentity = data.leadIdentity ? data.leadIdentity.split(',') : []
this.form = data
})
})
},
openMemberDialog() {
this.memberDialogVisible = true
this.selectedMemberCandidates = []
this.loadMemberCandidates()
},
loadMemberCandidates() {
this.$axios.get('/platform/zhgh/brand/application/members', {
params: {
unionId: this.form.unionId,
keyword: this.memberKeyword
}
}).then((res) => {
this.memberCandidates = this.getData(res) || []
})
},
memberSelectionChange(selection) {
this.selectedMemberCandidates = selection
},
confirmSelectMembers() {
if (!this.selectedMemberCandidates.length) {
this.$message.warning('请至少选择一位工作室成员')
return
}
const existingIds = new Set((this.form.brandStudioMembers || []).map(item => item.userId || item.id))
const members = this.selectedMemberCandidates
.filter(item => !existingIds.has(item.id))
.map(item => ({
userId: item.id,
emplid: item.emplid || item.loginname,
name: item.name || item.username,
role: '',
birthdate: item.birthdate,
grade: item.grade,
professional: item.professional,
deptName: item.deptName || item.unitName
}))
this.form.brandStudioMembers = [...(this.form.brandStudioMembers || []), ...members]
this.form.memberNum = this.form.brandStudioMembers.length
this.memberDialogVisible = false
},
removeMember(index) {
this.form.brandStudioMembers.splice(index, 1)
this.form.memberNum = this.form.brandStudioMembers.length
},
addHonor() {
this.form.brandStudioHonors = this.form.brandStudioHonors || []
this.form.brandStudioHonors.push({
applicationId: this.form.id,
honorName: '',
requireYear: String(new Date().getFullYear()),
honorCategory: 1
})
},
removeHonor(index) {
this.form.brandStudioHonors.splice(index, 1)
},
buildSaveParams() {
const params = Object.assign({}, this.form)
params.status = 5
params.leadIdentity = (params.leadIdentity || []).join(',')
params.domain = params.track
params.memberNum = (params.brandStudioMembers || []).length
params.brandStudioMembersJson = JSON.stringify(params.brandStudioMembers || [])
delete params.brandStudioMembers
return params
},
saveMembers() {
this.savingMembers = true
const params = this.buildSaveParams()
this.$axios.post('/platform/zhgh/brand/application/honor', params).then(() => {
this.$message.success('保存人员成功')
this.pageData()
}).finally(() => {
this.savingMembers = false
})
},
saveHonors() {
this.savingHonors = true
const params = this.buildSaveParams()
params.brandStudioHonors = (this.form.brandStudioHonors || []).map(item => ({
applicationId: this.form.id,
honorName: item.honorName,
honorGrade: item.honorGrade,
requireYear: item.requireYear,
honorCategory: item.honorCategory
}))
params.brandStudioHonorsJson = JSON.stringify(params.brandStudioHonors)
this.$axios.post('/platform/zhgh/brand/application/honor', params).then(() => {
this.$message.success('保存荣誉成功')
this.pageData()
}).finally(() => {
this.savingHonors = false
})
}
},
created() {
this.loadDicts()
this.pageData()
}
})
</script>
<style>
.brand-detail-actions {
margin-top: 16px;
}
.brand-member-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.brand-member-search {
display: flex;
gap: 10px;
margin-bottom: 12px;
}
.brand-member-search .el-input {
flex: 1;
}
.brand-member-dialog .el-dialog__body {
padding-top: 12px;
padding-bottom: 12px;
}
</style>
<!--#
}
#-->
@@ -0,0 +1,205 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="{ fileName: '参数设置' }"
cache-key="systemConfigTable"
:tools="false"
>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="openEdit(row)"
:icon="EditOutlined"
>
编辑工作室
</el-link>
</template>
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import ParamSearch from '../components/param-search.vue';
import dayjs from 'dayjs';
import {
getBrandStudioApplicationPageApi,
getUserBelongUnionApi
} from '@/api/office/brand/index.js';
import { EditOutlined } from '@/components/icons/index.js';
import { BRAND_EDIT_PATH } from '@/config/setting.js';
import { useRouter } from 'vue-router';
const { push } = useRouter();
import { usePageTab } from '@/utils/use-page-tab';
const { addPageTab } = usePageTab();
defineOptions({ name: 'Application' });
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
// {
// type: 'selection',
// columnKey: 'selection',
// width: 50,
// align: 'center'
// },
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '所属单位机构',
minWidth: 140,
align: 'center'
},
{
prop: 'type',
label: '工作室类型',
align: 'center',
minWidth: 110,
slot: 'type'
},
{
prop: 'track',
label: '工作室赛道',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
{
prop: 'createTime',
label: '申请时间',
minWidth: 100,
align: 'center',
slot: 'createTime'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
where = {
...where,
status: 5,
createYear: new Date().getFullYear().toString(),
};
const { data } = await getBrandStudioApplicationPageApi({
...where,
...filters,
...pages
});
return data;
};
const unionName = ref('');
onMounted(async () => {
console.log('mounted');
const { data } = await getUserBelongUnionApi();
unionName.value = data.name;
});
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openEdit = (row) => {
const path = BRAND_EDIT_PATH + '/' + row.id + '/edit';
addPageTab({
title: '工作室管理',
key: path,
closable: true,
meta: { icon: 'LinkOutlined' }
});
push(path);
};
</script>
@@ -0,0 +1,145 @@
<!-- 项目进度 -->
<template>
<ele-card :header="title" :body-style="{ padding: '10px', height: '370px' }">
<ele-pro-table
:height="352"
row-key="id"
:columns="columns"
:datasource="applyWarning"
:show-overflow-tooltip="true"
highlight-current-row
:pagination="false"
:toolbar="false"
:bottom-line="false"
size="large"
class="project-table"
>
<template #pendingDays="{ row }">
<span v-if="Math.floor(row.pendingDays) === 0">
{{ Math.round(row.pendingDays * 24) + '小时' }}
</span>
<span v-else>
{{ Math.round(row.pendingDays * 100) / 100 + '天' }}
</span>
</template>
<template #warnStatus="{ row }">
<ele-text v-if="row.warnStatus === 1" type="default">待处理</ele-text>
<ele-text v-else-if="row.warnStatus === 2" type="primary"
>关注</ele-text
>
<ele-text v-else-if="row.warnStatus === 3" type="warning"
>预警</ele-text
>
<ele-text v-else-if="row.warnStatus === 4" type="info">严重</ele-text>
<ele-text v-else type="danger">紧急</ele-text>
</template>
<template #action="{ row }">
<el-link
v-if="row.status !== 0"
v-permission="'system:config:edit'"
type="primary"
underline="never"
:icon="FileOutlined"
>
<span @click="handleCommand(row)"> 审批 </span>
</el-link>
</template>
</ele-pro-table>
</ele-card>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { getApplyWarningStatsApi } from '@/api/office/statistics/index.js';
import { FileOutlined } from '@/components/icons/index.js';
defineProps({
title: String
});
/** 表格列配置 */
const columns = ref([
{
prop: 'name',
label: '品牌工作室名称',
minWidth: 110
},
{
prop: 'unionName',
label: '直属工会',
align: 'center',
minWidth: 110
},
{
prop: 'leadPeople',
label: '领衔人',
align: 'center',
minWidth: 110
},
{
prop: 'receiveTime',
label: '接收时间',
align: 'center',
minWidth: 110
},
{
prop: 'pendingDays',
label: '已提交时间',
width: 110,
align: 'center',
slot: 'pendingDays'
},
{
prop: 'warnStatus',
label: '审核预警',
width: 110,
align: 'center',
slot: 'warnStatus'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
]);
/** 申请预警数据表 */
const applyWarning = ref([]);
const emit = defineEmits(['command']);
const handleCommand = (command) => {
emit('command', {
...command,
id: command.applicationId,
op: 'applyWarning'
});
};
/** 查询申请进度数据 */
const queryApplyWarningList = () => {
getApplyWarningStatsApi()
.then((res) => {
// console.log(res);
applyWarning.value = res.data;
})
.catch((err) => {
console.log(err);
});
};
onMounted(() => {
queryApplyWarningList();
});
</script>
<style lang="scss" scoped>
.project-table :deep(.el-progress__text) {
font-size: 12px !important;
}
</style>
@@ -0,0 +1,144 @@
<!-- 项目进度 -->
<template>
<ele-card :header="title" :body-style="{ padding: '10px', height: '370px' }">
<ele-pro-table
:height="352"
row-key="id"
:columns="columns"
:datasource="applyProgress"
:show-overflow-tooltip="true"
highlight-current-row
:pagination="false"
:toolbar="false"
:bottom-line="false"
size="large"
class="project-table"
>
<template #projectName="{ row }">
<el-link type="primary" underline="never">
{{ row.projectName }}
</el-link>
</template>
<template #status="{ row }">
<ele-text v-if="row.status === 0" type="danger">待申请</ele-text>
<ele-text v-else-if="row.status === 1" type="primary">
审核中
</ele-text>
<ele-text v-else-if="[2, 4].includes(row.status)" type="warning">
待修改
</ele-text>
<ele-text v-else type="success">已完成</ele-text>
</template>
<template #progressPct="{ row }">
<el-progress :percentage="row.progressPct" />
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="handleCommand(row)"
:icon="FileOutlined"
>
查看
</el-link>
</template>
</ele-pro-table>
</ele-card>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { getApplyProgressStatsApi } from '@/api/office/statistics/index.js';
import { FileOutlined } from '@/components/icons/index.js';
defineProps({
title: String
});
/** 表格列配置 */
const columns = ref([
{
prop: 'applicationName',
label: '品牌工作室名称',
minWidth: 110
},
{
prop: 'unionName',
label: '直属工会',
align: 'center',
minWidth: 110
},
{
prop: 'leadPeople',
label: '领衔人',
align: 'center',
minWidth: 110
},
{
prop: 'createTime',
label: '创建时间',
align: 'center',
minWidth: 110
},
{
prop: 'status',
label: '状态',
slot: 'status',
align: 'center',
width: 90
},
{
prop: 'progressPct',
label: '进度',
width: 180,
align: 'center',
slot: 'progressPct',
showOverflowTooltip: false
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
]);
/** 申请进度数据表 */
const applyProgress = ref([]);
const emit = defineEmits(['command']);
const handleCommand = (command) => {
emit('command', {
...command,
id: command.applicationId,
op: 'applyProgress'
});
};
/** 查询申请进度数据 */
const queryApplyProgressList = () => {
getApplyProgressStatsApi()
.then((res) => {
applyProgress.value = res.data;
})
.catch((err) => {
console.log(err);
});
};
onMounted(() => {
queryApplyProgressList();
});
</script>
<style lang="scss" scoped>
.project-table :deep(.el-progress__text) {
font-size: 12px !important;
}
</style>
@@ -0,0 +1,81 @@
<template>
<ele-card :header="title" :body-style="{ height: '370px' }">
<div class="workplace-goal">
<v-chart
ref="saleChartRef"
:style="{ height: height }"
:option="saleChartOption"
/>
</div>
</ele-card>
</template>
<script setup>
import { ref, provide, computed } from 'vue';
import VChart, { THEME_KEY } from 'vue-echarts';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { BarChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { useEcharts } from '@/utils/use-echarts';
import { useBodyResize } from '@/utils/use-body-resize.js';
import { ChartTheme } from 'ele-admin-plus';
use([CanvasRenderer, BarChart, GridComponent, TooltipComponent]);
const props = defineProps({
title: String,
data: Object,
height: {
type: String,
default: () => '320px'
},
grid: {
type: Object,
default: () => {
return {left: 50, right: 20, top: 20, bottom: 70}
}
},
xAxisLabel: {
type: Object,
default: () => {
return {
interval: 0,
rotate: 25,
formatter: (val) => {
const text = String(val ?? '');
return text.length > 10 ? `${text.slice(0, 10)}…` : text;
}
}
}
}
});
// 图表ref
const saleChartRef = ref(null);
// 传递图表的ref, 数组形式, 可传多个
useEcharts([saleChartRef]);
// EleAdminPlus提供的主题, 对颜色间距等都做了默认设置
provide(THEME_KEY, ChartTheme);
// 在侧栏折叠展开以及浏览器窗口大小改变后重置尺寸
useBodyResize(() => {
saleChartRef.value?.resize?.();
});
// 柱状图配置
const saleChartOption = computed(() => {
return {
tooltip: { trigger: 'axis' },
grid: props.grid,
xAxis: [
{
type: 'category',
data: props.data.x || [],
axisLabel: props.xAxisLabel
}
],
yAxis: [{ type: 'value' }],
series: [{ type: 'bar', data: props.data.y || [] }]
};
});
</script>
@@ -0,0 +1,61 @@
<!-- 本月目标 -->
<template>
<ele-card :header="title" :body-style="{ height: '370px' }">
<div class="workplace-goal">
<el-progress
:width="180"
:percentage="80"
color="var(--el-color-primary)"
type="dashboard"
:format="() => ''"
/>
<div class="workplace-goal-body">
<el-tag
size="large"
:disable-transitions="true"
style="width: 36px; height: 36px; border-radius: 50%; line-height: 0"
>
<el-icon style="cursor: default; border-radius: 0">
<TrophyBase />
</el-icon>
</el-tag>
<div style="font-size: 40px">285</div>
</div>
<div>恭喜, 本月目标已达标!</div>
</div>
</ele-card>
</template>
<script setup>
import { TrophyBase } from '@element-plus/icons-vue';
defineProps({
title: String
});
const emit = defineEmits(['command']);
const handleCommand = (command) => {
emit('command', command);
};
</script>
<style lang="scss" scoped>
.workplace-goal {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
.workplace-goal-body {
position: absolute;
top: 50%;
left: 50%;
width: 180px;
margin: -48px 0 0 -90px;
text-align: center;
}
}
</style>
@@ -0,0 +1,62 @@
<template>
<ele-card :header="title" :body-style="{ height: '370px' }">
<div class="workplace-goal">
<v-chart
ref="saleChartRef"
style="height: 320px"
:option="saleChartOption"
/>
</div>
</ele-card>
</template>
<script setup>
import { ref, provide, computed } from 'vue';
import VChart, { THEME_KEY } from 'vue-echarts';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { LineChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { useEcharts } from '@/utils/use-echarts';
import { useBodyResize } from '@/utils/use-body-resize.js';
import { ChartTheme } from 'ele-admin-plus';
use([CanvasRenderer, LineChart, GridComponent, TooltipComponent]);
const props = defineProps({
title: String,
data: Object
});
// 图表ref
const saleChartRef = ref(null);
// 传递图表的ref, 数组形式, 可传多个
useEcharts([saleChartRef]);
// EleAdminPlus提供的主题, 对颜色间距等都做了默认设置
provide(THEME_KEY, ChartTheme);
// 在侧栏折叠展开以及浏览器窗口大小改变后重置尺寸
useBodyResize(() => {
saleChartRef.value?.resize?.();
});
// 柱状图配置
const saleChartOption = computed(() => {
return {
tooltip: { trigger: 'axis' },
xAxis: [
{
type: 'category',
data: props.data.x
}
],
yAxis: [{ type: 'value' }],
series: [
{
type: 'line',
smooth: true,
data: props.data.y
}
]
}
});
</script>
@@ -0,0 +1,76 @@
<template>
<ele-card :header="title" :body-style="{ height: '370px' }">
<div class="workplace-goal">
<v-chart
ref="saleChartRef"
:style="{ height: height }"
:option="saleChartOption"
/>
</div>
</ele-card>
</template>
<script setup>
import { ref, computed, provide, onMounted } from 'vue';
import VChart, { THEME_KEY } from 'vue-echarts';
import { use } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { PieChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { useEcharts } from '@/utils/use-echarts';
import { useBodyResize } from '@/utils/use-body-resize.js';
import { ChartTheme } from 'ele-admin-plus';
use([CanvasRenderer, PieChart, GridComponent, TooltipComponent]);
const props = defineProps({
title: {
type: String,
default: () => ''
},
data: {
type: Array,
default: () => []
},
height: {
type: String,
default: () => '320px'
},
});
// 图表ref
const saleChartRef = ref(null);
// 传递图表的ref, 数组形式, 可传多个
useEcharts([saleChartRef]);
// EleAdminPlus提供的主题, 对颜色间距等都做了默认设置
provide(THEME_KEY, ChartTheme);
// 在侧栏折叠展开以及浏览器窗口大小改变后重置尺寸
useBodyResize(() => {
saleChartRef.value?.resize?.();
});
// 柱状图配置
const saleChartOption = computed(() => ({
tooltip: {
trigger: 'item'
},
series: [
{
name: props.title,
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}: {d}%'
},
data: props.data || []
}
]
}));
</script>
@@ -0,0 +1,114 @@
<!-- 项目进度 -->
<template>
<ele-card
:header="title"
:body-style="{ padding: '10px', minHeight: '150px' }"
>
<ele-pro-table
row-key="id"
:columns="columns"
:datasource="unionApplies"
:show-overflow-tooltip="true"
highlight-current-row
:pagination="false"
:toolbar="false"
:bottom-line="false"
size="large"
class="project-table"
>
<template #passRate="{ row }">
<el-link type="primary" underline="never">
{{ row.passRate + '%' }}
</el-link>
</template>
<template #repealRate="{ row }">
<el-link type="danger" underline="never">
{{ row.repealRate + '%' }}
</el-link>
</template>
</ele-pro-table>
</ele-card>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { getUnionApplyStatsApi } from '@/api/office/statistics/index.js';
defineProps({
title: String
});
const emit = defineEmits(['command']);
/** 表格列配置 */
const columns = ref([
{
type: 'index',
columnKey: 'index',
width: 50,
align: 'center'
},
{
prop: 'unionName',
label: '工会名称',
slot: 'unionName',
minWidth: 110
},
{
prop: 'applyCnt',
label: '申请总数',
align: 'center'
},
{
prop: 'passedCnt',
label: '通过总数',
align: 'center'
},
{
prop: 'reviewCnt',
label: '待审批数',
align: 'center'
},
{
prop: 'rejectCnt',
label: '驳回数',
align: 'center'
},
{
prop: 'repealCnt',
label: '撤销数',
align: 'center'
},
{
prop: 'passRate',
label: '通过率',
align: 'center',
slot: 'passRate'
},
{
prop: 'repealRate',
label: '撤销率',
align: 'center',
slot: 'repealRate'
}
]);
/** 工会审核指标数据 */
const unionApplies = ref([]);
/** 查询工会审核指标数据 */
const unionApplyStats = () => {
getUnionApplyStatsApi()
.then((res) => {
const { data } = res;
unionApplies.value = data;
})
.catch((err) => {
console.error(err);
});
};
onMounted(() => {
unionApplyStats();
});
</script>
@@ -0,0 +1,236 @@
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import GoalCard from './components/goal-card.vue';
import PieChart from './components/pie-chart.vue';
import UnionTable from './components/union-table.vue';
import ApplyProgress from './components/apply-progress.vue';
import ApplyPending from './components/apply-pending.vue';
import BarChart from './components/bar-chart.vue';
import LineChart from './components/line-chart.vue';
import SortableJs from 'sortablejs';
import FlowAudit from '@/views/brand/components/flow-audit.vue';
import { getApplyCountStatsApi } from '@/api/office/statistics/index.js';
defineOptions({
name: 'Overview',
components: {
GoalCard,
PieChart,
UnionTable,
ApplyProgress,
ApplyPending,
LineChart,
BarChart
}
});
/** 卡片数据 */
const data = ref([
{
name: 'pie-chart',
title: '申请情况(个人)',
data: [
{ value: 1048, name: '通过' },
{ value: 735, name: '待审批' },
{ value: 580, name: '待提交' },
{ value: 484, name: '驳回' },
{ value: 300, name: '失败' }
],
md: 8,
sm: 24,
xs: 24
},
{
name: 'apply-progress',
title: '申请进度记录(个人)',
md: 16,
sm: 24,
xs: 24
},
{
name: 'pie-chart',
title: '年龄分布(组织)',
data: [
{ value: 1048, name: '<20' },
{ value: 735, name: '20-40' },
{ value: 580, name: '40-60' },
{ value: 256, name: '60+' }
],
md: 8,
sm: 24,
xs: 24
},
{
name: 'apply-pending',
title: '工会平均审核时长和预警(组织)',
md: 16,
sm: 24,
xs: 24
},
{
name: 'bar-chart',
title: '工作室类型统计(个人/组织)',
label: 'type',
data: [],
md: 8,
sm: 24,
xs: 24
},
{
name: 'line-chart',
title: '年度申请数统计(个人/组织)',
label: 'createYear',
data: [],
md: 8,
sm: 24,
xs: 24
},
{
name: 'union-table',
title: '工会审核情况统计(组织)',
md: 24,
sm: 24,
xs: 24
}
]);
/** 拖拽排序实例 */
let sortableIns = null;
/** 容器 */
const wrapRef = ref(null);
/** 当前审核流数据 */
const current = ref(null);
/** 显示审核流界面 */
const showFlowCurrent = ref(false);
/** 审核流界面是否可以编辑 */
const isAudit = ref(false);
/** 编辑卡片 */
const handleCommand = (command, index) => {
switch (command.op) {
case 'applyProgress': // 刷新
current.value = command;
isAudit.value = false;
showFlowCurrent.value = true;
break;
case 'applyWarning':
current.value = command;
isAudit.value = true;
showFlowCurrent.value = true;
break;
}
};
const applyCountObject = ref({});
/** 统计用户申请的各字段分组 */
const queryApplyCountStats = () => {
getApplyCountStatsApi().then((res) => {
console.log(res.data);
applyCountObject.value = res.data;
for (const element of data.value) {
if (element.label) {
const label = element.label;
const item = res.data[label];
element.data = {
x: Object.keys(item), // ['公益活动', '社会实践', '社会实践报告', '科学科研', '绿色环保']
y: Object.values(item) // [1, 1, 1, 1, 1]
};
}
}
console.log(data.value);
});
};
onMounted(() => {
queryApplyCountStats();
// 卡片支持拖动排序
sortableIns = new SortableJs(wrapRef.value?.$el, {
handle: '.ele-card-header',
filter: '.demo-more-icon',
animation: 300,
delay: 150,
delayOnTouchOnly: true,
onUpdate: ({ oldIndex, newIndex }) => {
if (typeof oldIndex === 'number' && typeof newIndex === 'number') {
const temp = [...data.value];
temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]);
data.value = temp;
}
},
setData: () => {},
forceFallback: true
});
});
onBeforeUnmount(() => {
sortableIns && sortableIns.destroy();
sortableIns = null;
});
</script>
<template>
<ele-page>
<el-row :gutter="16" ref="wrapRef">
<el-col
v-for="(item, index) in data"
:key="item.name"
:md="item.md"
:sm="item.sm"
:xs="item.xs"
>
<component
:is="item.name"
:title="item.title"
:data="item.data"
@command="(command) => handleCommand(command, index)"
/>
</el-col>
</el-row>
<flow-audit v-model="showFlowCurrent" :data="current" :is-audit="isAudit" />
</ele-page>
</template>
<style lang="scss" scoped>
.workplace-page {
:deep(.ele-card-header) {
user-select: none;
cursor: move;
}
:deep(.el-col) {
&.sortable-chosen > .ele-card {
box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.2);
}
&.sortable-ghost {
opacity: 0;
}
&.sortable-fallback {
opacity: 1 !important;
}
}
}
/* 底部按钮 */
.workplace-bottom {
display: flex;
align-items: center;
.workplace-button {
flex: 1;
padding: 10px 0;
transition: background-color 0.2s;
&:hover {
background: hsla(0, 0%, 60%, 0.05);
}
:deep(.el-icon) {
font-size: 15px;
margin: -1px 6px 0 0;
}
}
}
</style>
@@ -0,0 +1,274 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="创立年度:">
<el-date-picker
placeholder="选择创立年度"
type="year"
style="width: 100%"
v-model="pageForm.createYear"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="工作室名称:">
<el-input
@keyup.enter.native="doSearch"
clearable
placeholder="请输入工作室名称"
style="width: 100%"
v-model="pageForm.name"
></el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="申请列表">
<el-radio-group @change="doSearch" size="small" v-model="pageForm.audit">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
</el-radio-group>
</table-tool>
<el-table :data="tableData" :size="tableSize">
<el-table-column :index="indexMethod" label="序号" type="index" width="80px"></el-table-column>
<el-table-column label="工作室名称" prop="name" sortable show-overflow-tooltip min-width="180"></el-table-column>
<el-table-column label="创立年度" prop="createYear" sortable show-overflow-tooltip width="120"></el-table-column>
<el-table-column label="领衔人" prop="leadPeople" sortable show-overflow-tooltip width="120"></el-table-column>
<el-table-column label="所在单位" prop="deptName" sortable show-overflow-tooltip min-width="180"></el-table-column>
<el-table-column label="工作室类型" prop="type" sortable show-overflow-tooltip min-width="140"></el-table-column>
<el-table-column label="工作室赛道" prop="track" sortable show-overflow-tooltip min-width="140"></el-table-column>
<el-table-column label="成员人数" prop="memberNum" sortable show-overflow-tooltip width="110"></el-table-column>
<el-table-column label="工作室地址" prop="address" show-overflow-tooltip min-width="220"></el-table-column>
<el-table-column label="当前节点" prop="curTaskName" show-overflow-tooltip min-width="180"></el-table-column>
<el-table-column label="审核时间" prop="finishTime" show-overflow-tooltip min-width="180">
<template v-slot="{ row }">
<span>{{ row.finishTime ? $moment(row.finishTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="流程状态" prop="instanceState" show-overflow-tooltip min-width="180">
<template v-slot="{ row }">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template v-slot="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskState === 10" @click="onAudit(row)" size="mini" type="primary">审核</el-button>
<el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger">撤回</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #public>
<div>
<el-tabs tab-position="top" v-model="activeName">
<el-tab-pane name="1" label="基础信息">
<div class="process-title">
品牌工作室申请
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="工作室名称">{{detail.name}}</el-descriptions-item>
<el-descriptions-item label="创立年度">{{detail.createYear}}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{detail.unionName}}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{detail.deptName}}</el-descriptions-item>
<el-descriptions-item label="领衔人">{{detail.leadPeople}}</el-descriptions-item>
<el-descriptions-item label="领衔人年龄">{{detail.leadAge}}</el-descriptions-item>
<el-descriptions-item label="领衔人身份">{{detail.leadIdentity}}</el-descriptions-item>
<el-descriptions-item label="领衔人荣誉">{{detail.leadHonor}}</el-descriptions-item>
<el-descriptions-item label="工作室类型">{{detail.type}}</el-descriptions-item>
<el-descriptions-item label="所属赛道">{{detail.track}}</el-descriptions-item>
<el-descriptions-item label="工作室地址" :span="2">{{detail.address}}</el-descriptions-item>
<el-descriptions-item label="工作室介绍" :span="2">{{detail.introduction}}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{detail.remark}}</el-descriptions-item>
</el-descriptions>
<snaker-chart ref="snakerChartRef"></snaker-chart>
<div v-if="showApprovalForm">
<div class="process-title">
{{formData.taskName}}
</div>
<el-form :model="formData" ref="formRef" label-width="0" label-suffix=":" class="flow-task-form">
<el-form-item label="审批意见" prop="remark"
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<user-opinion-textarea v-model="formData.remark"></user-opinion-textarea>
</el-form-item>
</el-form>
<el-row type="flex" justify="end">
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
</el-row>
</div>
</el-tab-pane>
<el-tab-pane name="2" label="成员信息">
<div class="process-title">成员信息</div>
<el-table :data="detail.brandStudioMembers || []" border size="small">
<el-table-column label="姓名" prop="name" width="120"></el-table-column>
<el-table-column label="出生年月" prop="birthdate" width="120"></el-table-column>
<el-table-column label="学位" prop="grade" show-overflow-tooltip></el-table-column>
<el-table-column label="职称" prop="professional" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="deptName" show-overflow-tooltip></el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane name="3" label="审核记录">
<template v-if="doneTasks.length > 0" v-for="task in doneTasks">
<div class="mt10">
<div class="process-title">{{ task.displayName }}</div>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName }}({{task.ext.initiatorAccount}})</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName }}({{task.taskFormData.loginName}})</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" v-if="!task.ext.isFirstTaskNode">{{ task.taskFormData.opinion || task.taskFormData.remark || task.taskFormData.approvalComment }}</el-descriptions-item>
</el-descriptions>
</div>
</template>
<template v-else>
<el-empty description="暂无审核记录"></el-empty>
</template>
</el-tab-pane>
</el-tabs>
</div>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
pageForm: {
audit: false,
createYear: String(new Date().getFullYear())
},
detail: {},
formData: {},
currentRow: {},
showApprovalForm: false,
activeName: "1",
doneTasks: []
}
},
methods: {
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
onView(row) {
this.$refs.guava.public(() => {
this.currentRow = row
this.activeName = "1"
this.showApprovalForm = false
this.loadDetail(row.id)
this.getDoneTasks(row)
})
},
onAudit(row) {
this.$refs.guava.public(() => {
this.currentRow = row
this.activeName = "1"
this.formData = {
applicationId: row.id,
processInstanceId: row.processInstanceId,
processInstanceTaskId: row.processInstanceTaskId || row.taskId,
taskName: row.curTaskName,
remark: ''
}
this.showApprovalForm = true
this.loadDetail(row.id)
this.getDoneTasks(row)
})
},
loadDetail(id) {
this.$axios.get('/platform/zhgh/brand/application/' + id).then((res) => {
this.detail = this.getData(res) || {}
this.detail.brandStudioMembers = this.detail.brandStudioMembers || []
})
},
getDoneTasks(row) {
this.$axios.post('/flow/common/doneTasks', {
instanceId: row.processInstanceId || row.instanceId,
bizId: row.id
}).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.currentRow.processInstanceId || !this.currentRow.instanceProcessDefineId) {
this.$message.warning('暂无流程图信息')
return
}
this.$refs.snakerChartRef.onOpenFull(this.currentRow.instanceProcessDefineId, this.currentRow.processInstanceId)
},
handleTaskAction(val) {
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post('/platform/zhgh/brand/flow/audit', {
...this.formData,
submitType: val
}).then((res) => {
if (res.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
onRevoke(row) {
this.$confirm("您确定要撤回吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
async pageData() {
const resp = await this.$axios.post('/platform/zhgh/brand/application/auditPage', this.pageForm)
if (resp.code === 0) {
this.tableData = resp.data.list
this.pageForm.totalCount = resp.data.totalCount
} else {
this.$message.warning(resp.msg)
}
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,270 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<div class="radio-right">
<el-radio-group
v-model="auditStatus"
size="default"
@change="handleChange"
>
<el-radio-button
v-for="item in auditStatusOptions"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</el-radio-button>
</el-radio-group>
</div>
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="{ fileName: '参数设置' }"
cache-key="systemConfigTable"
:tools="false"
>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #status="{ row }">
<el-tag
style="cursor: pointer"
v-if="row.processUserTaskStatus === 1"
type="warning"
>
待审核
</el-tag>
<el-tag style="cursor: pointer" v-if="row.processUserTaskStatus === 3" type="danger">
拒绝
</el-tag>
<el-tag style="cursor: pointer" v-if="row.processUserTaskStatus === 4" type="info">
取消
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.processUserTaskStatus === 2"
type="success"
>
通过
</el-tag>
<el-tag
style="cursor: pointer"
v-if="row.processUserTaskStatus === 5"
type="danger"
>
已退回
</el-tag>
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click.stop="handleProcessDetail(row)"
:icon="row.processUserTaskStatus === 1 ? FileOutlined : View"
>
{{ row.processUserTaskStatus === 1 ? '审批' : '查看' }}
</el-link>
</template>
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script setup>
import { computed, ref } from 'vue';
import ParamSearch from '../components/param-search.vue';
import dayjs from 'dayjs';
import { getBrandApplicationAuditPageApi } from '@/api/office/brand/index.js';
import { FileOutlined } from '@/components/icons/index.js';
import {View} from "@element-plus/icons-vue";
import $enums from "@/utils/enums.js";
import {useRouter} from "vue-router";
defineOptions({ name: 'Review' });
const { push } = useRouter();
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '所属单位机构',
minWidth: 140,
align: 'center'
},
{
prop: 'type',
label: '工作室类型',
align: 'center',
minWidth: 110,
slot: 'type'
},
{
prop: 'track',
label: '工作室赛道',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
{
prop: 'createTime',
label: '申请时间',
minWidth: 100,
align: 'center',
slot: 'createTime'
},
{
prop: 'status',
label: '状态',
minWidth: 90,
align: 'center',
slot: 'status'
},
{
prop: 'processTaskNode',
label: '审核节点',
minWidth: 150,
align: 'center'
},
{
columnKey: 'action',
label: '操作',
width: 150,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
const auditStatus = ref(1);
const auditStatusOptions = [
{ label: '全部', value: 0 },
{ label: '未审核', value: 1 },
{ label: '已审核', value: 2 },
];
const handleChange = (val) => {
console.log('当前选中值:', val);
auditStatus.value = val;
if (val === 0) {
reload({});
} else {
reload({ processStatus: val });
}
};
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
const finalWhere = { ...where, processStatus: auditStatus.value, createYear: new Date().getFullYear().toString() };
const { data } = await getBrandApplicationAuditPageApi({
...finalWhere,
...filters,
...pages
});
return data;
};
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 审批进度 */
const handleProcessDetail = (row) => {
push({
name: 'WorkflowProcessInstanceDetail',
query: {
businessId: row.id,
businessType: $enums.BUSINESS_PROCESS_KEY_NUMBER.BRAND_OFFICE_USER_APPLY,
processInstanceId: row.processInstanceId,
processFormPath: encodeURI(
'/brand/components/flow-view.vue'
)
}
});
};
</script>
<style scoped>
.radio-right {
display: flex;
justify-content: flex-end; /* 关键:右对齐 */
}
</style>
@@ -0,0 +1,68 @@
<!-- 搜索表单 -->
<template>
<ele-card :body-style="{ paddingBottom: '2px' }">
<el-form label-width="72px" @keyup.enter="search" @submit.prevent="">
<el-row :gutter="8">
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="工作室名称" label-width="100px">
<el-input
clearable
v-model.trim="form.name"
placeholder="请输入工作室名称"
/>
</el-form-item>
</el-col>
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="创立年度">
<el-date-picker
clearable
v-model="form.createYear"
type="year"
value-format="YYYY"
placeholder="请选择工作室创立年度"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :lg="4" :md="12" :sm="12" :xs="24">
<el-form-item label="工作室领衔人" label-width="100px">
<el-input
clearable
v-model.trim="form.leadPeople"
placeholder="请输入工作室领衔人"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xs="24">
<el-form-item label-width="20px">
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</ele-card>
</template>
<script setup>
import { useFormData } from '@/utils/use-form-data.js';
const emit = defineEmits(['search']);
/** 表单数据 */
const [form, resetFields] = useFormData({
name: '',
leadPeople: '',
createYear: new Date().getFullYear().toString()
});
/** 搜索 */
const search = () => {
emit('search', { ...form, params: {} });
};
/** 重置 */
const reset = () => {
resetFields();
search();
};
</script>
@@ -0,0 +1,138 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<el-form :inline="true" :model="pageForm">
<el-form-item label="创立年度">
<el-date-picker v-model="pageForm.createYear" type="year" value-format="yyyy"></el-date-picker>
</el-form-item>
<el-form-item label="工作室名称">
<el-input v-model="pageForm.name" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="pageData">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="工作室台账"></table-tool>
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" label="序号" width="60"></el-table-column>
<el-table-column prop="name" label="工作室名称" min-width="160"></el-table-column>
<el-table-column prop="createYear" label="创立年度" width="100"></el-table-column>
<el-table-column prop="leadPeople" label="领衔人" width="120"></el-table-column>
<el-table-column prop="deptName" label="所在单位" min-width="140"></el-table-column>
<el-table-column prop="honorName" label="荣誉称号" min-width="150"></el-table-column>
<el-table-column prop="track" label="所属领域" width="120"></el-table-column>
<el-table-column prop="leadHonor" label="领衔人荣誉称号" min-width="160"></el-table-column>
<el-table-column prop="memberNum" label="成员人数" width="90"></el-table-column>
<el-table-column prop="address" label="工作室地址" min-width="180"></el-table-column>
<el-table-column label="操作" width="90" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<template #public>
<div>
<div class="process-title">查看品牌工作室</div>
<el-tabs v-model="activeName">
<el-tab-pane name="base" label="基本信息">
<el-descriptions :column="2" border>
<el-descriptions-item label="工作室名称">{{form.name}}</el-descriptions-item>
<el-descriptions-item label="创立年度">{{form.createYear}}</el-descriptions-item>
<el-descriptions-item label="所属工会">{{form.unionName}}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{form.deptName}}</el-descriptions-item>
<el-descriptions-item label="领衔人">{{form.leadPeople}}</el-descriptions-item>
<el-descriptions-item label="领衔人年龄">{{form.leadAge}}</el-descriptions-item>
<el-descriptions-item label="领衔人身份">{{form.leadIdentity}}</el-descriptions-item>
<el-descriptions-item label="领衔人荣誉">{{form.leadHonor}}</el-descriptions-item>
<el-descriptions-item label="工作室类型">{{form.type}}</el-descriptions-item>
<el-descriptions-item label="所属领域">{{form.track}}</el-descriptions-item>
<el-descriptions-item label="工作室地址" :span="2">{{form.address}}</el-descriptions-item>
<el-descriptions-item label="工作室介绍" :span="2">{{form.introduction}}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{form.remark}}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane name="members" label="成员信息">
<el-table :data="form.brandStudioMembers || []" border size="small">
<el-table-column label="姓名" prop="name" width="120"></el-table-column>
<el-table-column label="出生年月" prop="birthdate" width="120"></el-table-column>
<el-table-column label="学位" prop="grade" show-overflow-tooltip></el-table-column>
<el-table-column label="职称" prop="professional" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="deptName" show-overflow-tooltip></el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane name="honors" label="荣誉信息">
<el-table :data="form.brandStudioHonors || []" border size="small">
<el-table-column label="荣誉称号" prop="honorName" min-width="160"></el-table-column>
<el-table-column label="获取年度" prop="requireYear" min-width="120"></el-table-column>
<el-table-column label="荣誉类别" prop="honorCategory" min-width="120">
<template slot-scope="{row}">
<span>{{row.honorCategory === 1 ? '外部荣誉' : (row.honorCategory === 2 ? '行内荣誉' : row.honorCategory)}}</span>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
pageForm: {
page: 1,
limit: 20,
status: 5,
createYear: String(new Date().getFullYear())
},
tableData: [],
activeName: 'base',
form: {
brandStudioMembers: [],
brandStudioHonors: []
}
}
},
methods: {
getData(res) {
return res.data && res.data.data !== undefined ? res.data.data : res.data
},
pageData() {
this.$axios.get('/platform/zhgh/brand/application/summaryPage', {params: this.pageForm}).then((res) => {
const data = this.getData(res) || {}
this.tableData = data.list || data.data || []
})
},
openView(row) {
this.$refs.guava.public(() => {
this.activeName = 'base'
this.$axios.get('/platform/zhgh/brand/application/' + row.id).then((res) => {
const data = this.getData(res) || {}
data.brandStudioMembers = data.brandStudioMembers || []
data.brandStudioHonors = data.brandStudioHonors || []
this.form = data
})
})
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,338 @@
<template>
<ele-page>
<!-- 搜索表单 -->
<param-search @search="reload" />
<ele-card :body-style="{ paddingTop: '8px' }">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:show-overflow-tooltip="true"
v-model:selections="selections"
highlight-current-row
:export-config="exportConfig"
cache-key="systemConfigTable"
:tools="['export', 'print', 'columns']"
>
<template #createTime="{ row }">
{{ dayjs(row.createTime).format('YYYY-MM-DD') }}
</template>
<template #leadIdentity="{ row }">
<div
style="display: flex"
v-for="(item, index) in JSON.parse(row.leadIdentity)"
:key="index"
>
<el-tag type="primary">
{{ item.dictLabel }}
</el-tag>
</div>
</template>
<template #memberNum="{ row }">
{{ row.memberNum + '人' }}
</template>
<template #createYear="{ row }">
{{ row.createYear }}
</template>
<template #honorName="{ row }">
{{ row.honorName || '暂无' }}
</template>
<!-- <template #honorGrade="{ row }">-->
<!-- {{ row.honorGrade || '暂无' }}-->
<!-- </template>-->
<template #action="{ row }">
<el-link
v-permission="'system:config:edit'"
type="primary"
underline="never"
@click="openLook(row)"
:icon="FileOutlined"
>
查看
</el-link>
</template>
</ele-pro-table>
</ele-card>
</ele-page>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from 'vue';
import {EleMessage} from 'ele-admin-plus';
import ParamSearch from './components/param-search.vue';
import dayjs from 'dayjs';
import {
getBrandStudioApplicationHonorPageApi,
getUserBelongUnionApi
} from '@/api/office/brand/index.js';
import { FileOutlined } from '@/components/icons/index.js';
import { BRAND_EDIT_PATH } from '@/config/setting.js';
import { useRouter } from 'vue-router';
const { push } = useRouter();
import { usePageTab } from '@/utils/use-page-tab';
import request from '@/utils/request.js';
import { download } from '@/utils/common.js';
const { addPageTab } = usePageTab();
defineOptions({ name: 'BrandSummary' });
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => {
return [
// {
// type: 'selection',
// columnKey: 'selection',
// width: 50,
// align: 'center'
// },
{
type: 'index',
columnKey: 'index',
width: 60,
align: 'center',
label: '序号'
},
{
prop: 'name',
label: '工作室名称',
align: 'center',
minWidth: 180
},
{
prop: 'createYear',
label: '创立年度',
align: 'center',
minWidth: 80,
slot: 'createYear'
},
{
prop: 'leadPeople',
label: '领衔人',
minWidth: 100,
align: 'center',
slot: 'leadPeople'
},
{
prop: 'deptName',
label: '一级机构',
minWidth: 120,
align: 'center'
},
{
prop: 'honorName',
label: '外部荣誉称号',
minWidth: 150,
align: 'center',
slot: 'honorName'
},
// {
// prop: 'honorGrade',
// label: '级别',
// minWidth: 80,
// align: 'center',
// slot: 'honorGrade'
// },
{
prop: 'track',
label: '所属领域',
align: 'center',
minWidth: 110,
slot: 'track'
},
{
prop: 'leadHonor',
label: '领衔人荣誉称号',
align: 'center',
minWidth: 110,
slot: 'leadHonor'
},
{
prop: 'memberNum',
label: '成员人数',
align: 'center',
minWidth: 80,
slot: 'memberNum'
},
{
prop: 'address',
label: '工作室地址',
minWidth: 140,
align: 'center',
slot: 'address'
},
// {
// prop: 'remark',
// label: '备注',
// minWidth: 140,
// align: 'center'
// }
{
columnKey: 'action',
label: '操作',
width: 100,
align: 'center',
slot: 'action',
hideInPrint: true,
hideInExport: true,
fixed: 'right'
}
];
});
/** 表格选中数据 */
const selections = ref([]);
/** 表格数据源 */
const datasource = async ({ pages, where, filters }) => {
where = {
...where,
status: 5
};
const { data } = await getBrandStudioApplicationHonorPageApi({
...where,
...filters,
...pages
});
return data;
};
const unionName = ref('');
onMounted(async () => {
console.log('mounted');
const { data } = await getUserBelongUnionApi();
unionName.value = data.name;
});
/** 搜索 */
const reload = (where) => {
tableRef.value?.reload?.({ page: 1, where });
};
/** 打开编辑弹窗 */
const openLook = (row) => {
const path = BRAND_EDIT_PATH + '/' + row.id + '/look';
addPageTab({
title: '工作室管理',
key: path,
closable: true,
meta: { icon: 'LinkOutlined' }
});
push(path);
};
const exportUsers = async (data) => {
const res = await request({
url: '/act/brand/application/export',
method: 'POST',
data,
responseType: 'blob'
});
console.log('OK res=', res);
download(
res.data,
'全行品牌工作室清单.xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
};
import {excelExport} from "@/utils/export-config.js";
const exportConfig = reactive({
beforeExport: (params) => {
console.log("beforeExport")
// 导出为excel格式
excelExport(params, 'Sheet1');
// 禁用掉原始默认导出
return false;
},
// 数据源方法加 async 然后 return null
datasource: async ({ where, filters, pages, orders }) => {
console.log(columns.value);
// 这里就可以调用后端接口下载文件, 还可以传递表格当前的搜索、排序参数等
const loading = EleMessage.loading({
message: '正在下载文件..',
plain: true
});
exportUsers({ ...where, ...orders })
.then(() => {
loading.close();
})
.catch((e) => {
loading.close();
EleMessage.error(e.message);
});
},
// 例如设置导出默认的文件名
fileName: '全行品牌工作室清单'
});
// 模板导出
// import { utils, writeFile } from 'xlsx';
/* 导出excel */
const onExport = async () => {
const { data } = await getBrandStudioApplicationHonorPageApi({
page: 1,
limit: 1000
});
console.log(data);
const array = [
[
'序号',
'一级机构',
'工作室名称',
'领衔人',
'外部荣誉称号',
'级别',
'创立/获评年度',
'所属领域',
'是否有总行先进领衔(领衔人荣誉称号)',
'是否有创新团队(工作室成员人数)',
'备注'
]
];
data.forEach((d, i) => {
array.push([
i + 1,
d.deptName,
d.name,
d.leadPeople,
d.honorName,
d.honorGrade,
d.createYear + '/' + d.requireYear,
d.track,
d.leadHonor,
d.memberNum,
d.remark
]);
});
console.log(data);
// const sheet = utils.aoa_to_sheet(array);
// // 如果需要设置列宽可以这样写
// sheet['!cols'] = [
// { wch: 10 },
// { wch: 60 },
// { wch: 20 },
// { wch: 20 },
// { wch: 20 },
// { wch: 20 },
// { wch: 20 }
// ];
// writeFile(
// {
// SheetNames: ['Sheet1'],
// Sheets: { Sheet1: sheet }
// },
// '用户数据.xlsx'
// );
};
</script>
@@ -0,0 +1,998 @@
<script setup>
import { EleMessage } from 'ele-admin-plus';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import {
ClusterOutlined,
DeleteOutlined,
UserOutlined,
AppstoreOutlined
} from '@/components/icons/index.js';
import { useFormData } from '@/utils/use-form-data.js';
import { useDictData } from '@/utils/use-dict-data.js';
import {
getBrandStudioApplicationApi,
getLeaderHonorApi,
getUserBelongUnionApi,
updateBrandStudioBaseInfoApi,
updateBrandStudioMemberOrHonorApi
} from '@/api/office/brand/index.js';
import { useRoute, useRouter } from 'vue-router';
import { QuestionFilled } from '@element-plus/icons-vue';
import UnionUserSelect from "@/components/UnionUserSelect/index.vue";
import {withRequiredHeader} from "@/views/brand/js/common-brand.js";
import {ElMessageBox} from "element-plus";
import {StrUtil} from "@/utils/toolkit.js";
const router = useRouter();
const route = useRoute();
const activeTab = ref('basic'); // 默认选中
const tabsOptions = ref([
{ name: 'basic', label: '基本信息', value: 'basic', index: 1 },
{ name: 'member', label: '成员信息', value: 'member', index: 2 },
{ name: 'honor', label: '荣誉信息', value: 'honor', index: 3 }
]);
/** 二级弹窗是否打开:选择用户 */
const selectUserVisible = ref(false);
/** 是否是修改:针对整体 */
const isUpdate = ref(false);
/** 是否可以编辑:针对申请表部分,主要是为了区分成员部分 */
const editMember = ref(true);
/** 提交状态 */
const loading = ref(false);
/** 领衔人荣誉 */
const leaderHonors = ref([]);
/** 表单实例 */
const formRef = ref();
/** 字典数据 */
const [
leadPeopleDict,
brandOfficeTypeDict,
userEducationDict,
brandOfficeDomainDict
] = useDictData([
'lead_people',
'brand_office_type',
'user_education',
'brand_office_domain'
]);
/** 表单数据 */
const [form, resetFields, assignFields] = useFormData({
id: '',
unionName: '', // 所属单位工会
deptName: '',
name: '', // 工作室名称
createYear: '', // 创建年度
leadPeople: '', // 工作室领衔人
leadAge: 25, // 领衔人年龄
leadIdentity: [], // 领衔人身份
memberNum: 0, // 成员人数
type: '', // 工作室类型
track: '', // 工作室赛道
address: '', // 工作室具体位置
introduction: '' // 工作室基本情况介绍
});
/** 表单验证规则 */
const rules = reactive({
name: [
{
required: true,
message: '请输入工作室名称',
type: 'string',
trigger: 'blur'
}
],
unionName: [
{
required: true,
message: '请输入所属单位工会',
type: 'string',
trigger: 'blur'
}
],
deptName: [
{
required: true,
message: '请输入所属单位机构',
type: 'string',
trigger: 'blur'
}
],
createYear: [
{
required: true,
message: '请选择创建年度',
type: 'string',
trigger: 'change'
}
],
leadPeople: [
{
required: true,
message: '请选择工作室领衔人',
type: 'string',
trigger: 'blur'
},
{ type: 'string', max: 20, message: '领衔人最多 20 个字符', trigger: ['blur', 'change'] }
],
leadAge: [
{
required: true,
message: '请输入领衔人年龄',
type: 'number',
trigger: 'blur'
},
{
type: 'number',
min: 0,
max: 150,
message: '年龄必须在0-150岁之间',
trigger: 'blur'
}
],
leadIdentity: [
{
required: true,
message: '请选择领衔人身份',
type: 'array',
trigger: 'change'
}
],
leadHonor: [
{
required: true,
message: '请输入领衔人荣誉称号',
type: 'string',
trigger: 'change'
}
],
memberNum: [
{
required: true,
message: '请输入成员人数',
type: 'number',
trigger: 'blur'
}
],
type: [
{
required: true,
message: '请选择工作室类型',
type: 'string',
trigger: 'change'
}
],
track: [
{
required: true,
message: '请输入工作室赛道',
type: 'string',
trigger: 'blur'
}
],
address: [
{
required: true,
message: '请输入工作室具体位置',
type: 'string',
trigger: 'blur'
}
],
introduction: [
{
required: true,
message: '请填写工作室基本情况介绍',
type: 'string',
trigger: 'blur'
}
]
});
const validateMembers = () => {
const members = editableData.value;
// 1. 至少 1 人
if (!Array.isArray(members) || members.length < 1) {
ElMessageBox.alert(
'请至少选择 1 位工作室成员',
'系统提示',
{ type: 'warning' }
);
return false;
}
// 2. 必填字段校验
const requiredFields = columns.value
.filter(col => col.notRequired !== true)
.map(col => col.prop);
for (let i = 0; i < members.length; i++) {
const member = members[i];
for (const field of requiredFields) {
if (StrUtil.isEmpty(member[field])) {
const label = fieldLabelMap[field] || field;
ElMessageBox.alert(
`【${member.name || '未命名'}】的【${label}】不能为空`,
'系统提示',
{ type: 'warning' }
);
return false;
}
}
}
return true;
};
const validateHonors = () => {
const honors = editableHonorData.value;
// 1. 至少一条
if (!Array.isArray(honors) || honors.length < 1) {
ElMessageBox.alert(
'请至少填写一条工作室荣誉',
'系统提示',
{ type: 'warning' }
);
return false;
}
// 2. 必填字段校验
const requiredFields = [
{ field: 'honorName', label: '荣誉称号' },
{ field: 'requireYear', label: '获取年度' },
{ field: 'honorCategory', label: '荣誉类别' }
];
for (let i = 0; i < honors.length; i++) {
const item = honors[i];
for (const { field, label } of requiredFields) {
if (StrUtil.isEmpty(item[field])) {
ElMessageBox.alert(
`第 ${i + 1} 条荣誉的【${label}】不能为空`,
'系统提示',
{ type: 'warning' }
);
return false;
}
}
}
return true;
};
/** 返回 */
const goBack = () => {
router.back();
};
const showSelectUser = () => {
selectUserVisible.value = true;
};
// 可选择的成员
const userIds = ref([]);
const topUnionId = ref('');
const unionId = ref('');
/** 选择成员 */
// user-select 组件回调
const handleSelect = (data) => {
// orgEditableData 原来数据
const orgEditableData = editableData.value;
// 查找已经存在的用户
const existingIds = new Set(
orgEditableData.map((item) => item.userId || item.id)
);
// 过滤掉已经选择的用户,之前有的就不做处理
const selectData = data
.filter((item) => !existingIds.has(item.id))
.map((item) => ({
...item,
userId: item.id,
oldName: item.name,
oldBirthdate: item.birthdate
}));
editableData.value = [...orgEditableData, ...selectData];
form.memberNum = editableData.value.length;
// 选完情况组件中的用户
userIds.value = [];
};
/** beforeConfirm 为确定按钮点击钩子, 可以 return false 阻止确定 */
const beforeConfirm = (data) => {
if (!data?.length) {
EleMessage.error('请至少选择一个用户');
return false;
}
};
// 可编辑的副本(用于 v-model)
const editableData = ref([]);
const removeMember = (row) => {
if (!editMember.value) {
EleMessage.warning({ message: '查看界面,无法删除', plain: true });
return false;
}
const index = editableData.value.findIndex(
(item) => item.userId === row.userId
);
if (index !== -1) {
userIds.value.splice(index, 1);
editableData.value.splice(index, 1);
form.memberNum--;
}
};
/** 表格实例 */
const tableRef = ref(null);
/** 表格列配置 */
const columns = computed(() => [
withRequiredHeader({ prop: 'name', label: '姓名', align: 'center', minWidth: 80, slot: 'name' }),
withRequiredHeader({ prop: 'birthdate', label: '出生年月', align: 'center', minWidth: 120, slot: 'birthdate', required: true }),
withRequiredHeader({ prop: 'grade', label: '学历', align: 'center', minWidth: 80, slot: 'grade', required: true }),
withRequiredHeader({ prop: 'professional', label: '职称', align: 'center', minWidth: 100, slot: 'professional', required: true }),
withRequiredHeader({ prop: 'deptName', label: '所在部门', align: 'center', minWidth: 100, slot: 'deptName' }),
withRequiredHeader({ columnKey: 'action', label: '操作', width: 80, align: 'center', slot: 'action', notRequired: true })
]);
// 自动生成映射:{ name: '姓名', birthdate: '出生年月', ... }
const fieldLabelMap = {};
columns.value.forEach((col) => {
fieldLabelMap[col.prop] = col.label;
});
/** 表格实例 */
const honorTableRef = ref(null);
/** 表格列配置 */
const honorColumns = computed(() => {
return [
{
prop: 'honorName',
label: '荣誉称号',
align: 'center',
minWidth: 120,
slot: 'honorName'
},
{
prop: 'requireYear',
label: '获取年度',
align: 'center',
minWidth: 80,
slot: 'requireYear'
},
{
prop: 'honorCategory',
label: '荣誉类别',
align: 'center',
minWidth: 100,
slot: 'honorCategory'
},
{
columnKey: 'action',
label: '操作',
width: 80,
align: 'center',
slot: 'action',
notRequired: true
}
];
});
// 可编辑的副本(用于 v-model)
const editableHonorData = ref([]);
const addHonor = () => {
editableHonorData.value.push({
id: editableHonorData.value.length + 1,
honorName: '',
honorGrade: '',
requireYear: '',
honorCategory: 1
});
};
const removeHonor = (row) => {
console.log(row);
if (!editMember.value) {
EleMessage.warning({ message: '查看界面,无法删除', plain: true });
return false;
}
const index = editableHonorData.value.findIndex(
(item) => item.id === row.id
);
if (index !== -1) {
editableHonorData.value.splice(index, 1);
}
};
const save = (isBase) => {
loading.value = true;
console.log('save', isBase);
// 1 = 基本信息,2 = 成员,3 = 荣誉
if (isBase === 2) {
if (!validateMembers()) {
loading.value = false;
return;
}
}
if (isBase === 3) {
if (!validateHonors()) {
loading.value = false;
return;
}
}
formRef.value?.validate?.((valid) => {
if (!valid && isBase === 1) {
loading.value = false;
return;
}
const updateApi = isBase === 1
? updateBrandStudioBaseInfoApi
: updateBrandStudioMemberOrHonorApi;
const params = { ...form };
params.leadIdentity = JSON.stringify(form.leadIdentity) || {};
params.brandStudioMembers = form.unionUsers || [];
params.domain = params.track;
params.id = form.id;
params.memberNum = form.memberNum;
params.brandStudioMembers = editableData.value || [];
params.brandStudioHonors = editableHonorData.value || [];
updateApi(params)
.then(() => {
EleMessage.success({ message: '保存成功', plain: true });
queryInfoById(form.id);
})
.catch((err) => {
EleMessage.error({ message: '服务错误', plain: true });
console.warn(err);
})
.finally(() => {
loading.value = false;
});
});
};
const queryInfoById = (id) => {
userIds.value = [];
getBrandStudioApplicationApi(id)
.then(({ data }) => {
const formData = {
...data,
leadIdentity: JSON.parse(data.leadIdentity)
};
form.leadHonor = formData.leadHonor;
assignFields(formData);
editableData.value =
data.brandStudioMembers?.map((item) => {
// userIds.value.push(item.userId);
return {
...item,
oldName: item.name,
oldBirthdate: item.birthdate
};
}) || [];
console.log(data.brandStudioHonors);
editableHonorData.value = data.brandStudioHonors || [];
loadUserUnion()
})
.catch((err) => {
console.log(err);
});
};
const leaderChange = (emplid) => {
console.log('emplid: ' + emplid);
const matchedItem = leaderHonors.value.find(
(item) => item.emplid === emplid
);
if (matchedItem) {
form.leadHonor = matchedItem.honorNames?.join(',') || '';
form.leadAge = matchedItem.age || 0;
form.leadPeople = matchedItem.userName || '';
} else {
form.leadHonor = '';
form.leadAge = 0;
}
};
// 获取成员当前所在工会
const loadUserUnion = async () => {
const { data } = await getUserBelongUnionApi();
unionId.value = data.unionId;
topUnionId.value = data.topUnionId;
// form.unionName = data.unionName;
// form.deptName = data.deptName;
// form.createYear = new Date().getFullYear().toString();
}
onMounted(async () => {
console.log('mounted');
await loadUserUnion()
// 获取领衔人
const res = await getLeaderHonorApi();
leaderHonors.value = res.data.map((item, index) => {
return {
id: index,
emplid: item.emplid,
userName: item.userName,
age: item.age,
honorNames: item.honorNames
};
});
});
watch(
() => route.params.id,
() => {
editMember.value = route.params.edit === 'edit';
const id = route.params.id;
if (id) {
queryInfoById(id);
isUpdate.value = true;
} else {
resetFields();
editableData.value = [];
isUpdate.value = false;
// form.unionName = route.query.unionName;
}
},
{ immediate: true }
);
</script>
<template>
<ele-page>
<ele-card style="height: 800px">
<ele-tabs
v-model="activeTab"
:items="tabsOptions"
type="indicator"
style="margin-bottom: 8px"
>
<template #label="{ item }">
<div
class="tab-item-container"
style="display: flex; align-items: center; gap: 8px"
>
<ClusterOutlined
v-if="item.index === 1"
style="width: 14px; height: 14px"
/>
<UserOutlined
v-else-if="item.index === 2"
style="width: 14px; height: 14px"
/>
<AppstoreOutlined
v-else-if="item.index === 3"
style="width: 14px; height: 14px"
/>
<span>{{ item.label }}</span>
</div>
</template>
<template #basic>
<el-form
style="padding: 20px 20px"
ref="formRef"
:model="form"
:rules="rules"
label-width="110px"
@submit.prevent=""
:disabled="true"
>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="所属单位工会" prop="unionName" required>
<el-input
v-model="form.unionName"
:disabled="true"
placeholder="请输入所属单位工会"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属单位机构" prop="deptName" required>
<el-input
v-model="form.deptName"
:disabled="true"
placeholder="请输入所属单位机构"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室名称" prop="name" required>
<el-input
v-model="form.name"
placeholder="请输入工作室名称"
maxlength="30"
show-word-limit
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建年度" required prop="createYear">
<el-date-picker
clearable
v-model="form.createYear"
type="year"
value-format="YYYY"
placeholder="请选择年度"
:disabled="true"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室领衔人" prop="leadPeople" required>
<el-select
filterable
allow-create
default-first-option
placeholder="请选择工作室领衔人"
v-model="form.leadPeople"
@change="leaderChange"
value-key="id"
clearable
>
<el-option
v-for="item in leaderHonors"
:key="item.emplid"
:value="item.emplid"
:label="`${item.emplid} - ${item.userName}`"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="领衔人年龄" required prop="leadAge">
<el-input-number
placeholder="请输入领衔人年龄"
v-model="form.leadAge"
min="0"
max="150"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="领衔人身份" prop="leadIdentity">
<el-select
placeholder="请选择领衔人身份"
v-model="form.leadIdentity"
multiple
value-key="id"
clearable
>
<el-option
v-for="item in leadPeopleDict"
:key="item.id"
:value="{
id: item.id,
dictLabel: item.dictLabel,
dictTypeId: item.dictTypeId
}"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="荣誉称号" required prop="leadHonor">
<el-input
placeholder="请输入领衔人荣誉称号"
v-model="form.leadHonor"
maxlength="40"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="工作室类型" prop="type">
<el-select
placeholder="请选择工作室类型"
v-model="form.type"
value-key="id"
clearable
>
<el-option
v-for="item in brandOfficeTypeDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属赛道" prop="track" required>
<el-select
placeholder="请选择工作室赛道"
v-model="form.track"
value-key="id"
clearable
>
<el-option
v-for="item in brandOfficeDomainDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="具体位置" prop="address" required>
<el-input
v-model="form.address"
placeholder="请输入工作室具体位置"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="基本情况介绍" required prop="introduction">
<el-input
:rows="8"
type="textarea"
v-model="form.introduction"
placeholder="请输入基本情况介绍"
clearable
maxlength="1500"
show-word-limit
/>
</el-form-item>
<div
style="flex: 1; display: flex; justify-content: flex-end"
v-if="editMember"
>
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="save(1)">
保存信息
</el-button>
</div>
</el-form>
</template>
<template #member>
<div style="padding: 20px 20px">
<div style="display: flex; justify-content: space-between">
<el-form-item
label="工作室成员"
required
prop="unionUsers"
:disabled="!editMember"
>
<span>{{ form.memberNum }}人</span>
<el-tooltip
content="团队成员数量,根据选择成员人数自动变化"
placement="top"
>
<el-icon
style="cursor: help; margin-left: 4px; color: #909399"
>
<QuestionFilled />
</el-icon>
</el-tooltip>
</el-form-item>
<div v-if="editMember">
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="showSelectUser">
选择工作室成员
</el-button>
<el-button type="primary" @click="save(2)">
保存人员
</el-button>
</div>
</div>
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="id"
:height="500"
:columns="columns"
:datasource="editableData"
:show-overflow-tooltip="true"
highlight-current-row
:export-config="{ fileName: '用户数据' }"
:style="{ paddingBottom: '16px' }"
cache-key="systemUserTable"
:tools="false"
>
<template #name="{ row }">
<span v-if="editMember && false">
<el-input class="ipt-text" v-model="row.name" maxlength="20" show-word-limit />
</span>
<span v-else>{{ row.name }}</span>
</template>
<template #birthdate="{ row }">
<span v-if="editMember">
<el-date-picker
style="width: 160px; padding: 0 20px"
v-model="row.birthdate"
type="month"
placeholder="选择年月"
format="YYYY-MM"
value-format="YYYY-MM"
size="large"
/>
</span>
<span v-else>{{ row.birthdate }}</span>
</template>
<template #grade="{ row }">
<span class="ipt-text">
<el-select
:disabled="!editMember"
placeholder="请选择学历类型"
v-model="row.grade"
value-key="id"
clearable
maxlength="30"
show-word-limit
>
<el-option
v-for="item in userEducationDict"
:key="item.id"
:value="item.dictLabel"
:label="item.dictLabel"
/>
</el-select>
</span>
</template>
<template #professional="{ row }">
<span v-if="editMember" class="ipt-text">
<el-input v-model="row.professional" maxlength="30" show-word-limit />
</span>
<span v-else>{{ row.professional }}</span>
</template>
<template #deptName="{ row }">
<span class="ipt-text" v-if="editMember && false">
<el-input v-model="row.deptName" />
</span>
<span v-else>{{ row.deptName }}</span>
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:remove'"
type="danger"
underline="never"
@click="removeMember(row)"
:icon="DeleteOutlined"
:disabled="!editMember"
>
删除
</el-link>
</template>
</ele-pro-table>
</div>
</template>
<template #honor>
<div style="padding: 20px 20px">
<div style="display: flex; justify-content: space-between">
<el-form-item label="工作室荣誉" required prop="honors" />
<div v-if="editMember">
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="addHonor">
添加荣誉
</el-button>
<el-button type="primary" @click="save(3)">
保存荣誉
</el-button>
</div>
</div>
<!-- 表格 -->
<ele-pro-table
ref="honorTableRef"
row-key="id"
:height="500"
:columns="honorColumns"
:datasource="editableHonorData"
:show-overflow-tooltip="true"
highlight-current-row
:style="{ paddingBottom: '16px' }"
cache-key="systemUserTable"
:tools="false"
>
<template #honorName="{ row }">
<span class="ipt-text">
<el-input
v-if="editMember"
v-model="row.honorName"
maxlength="30" show-word-limit
/>
<span v-else>{{ row.honorName }}</span>
</span>
</template>
<template #requireYear="{ row }">
<span v-if="editMember" class="ipt-text">
<el-date-picker
style="padding: 0 20px"
v-model="row.requireYear"
type="year"
placeholder="选择年度"
format="YYYY"
value-format="YYYY"
size="large"
/>
</span>
<span v-else>{{ row.requireYear }}</span>
</template>
<template #honorCategory="{ row }">
<span class="ipt-text">
<el-select
:disabled="!editMember"
style="padding: 0 20px"
placeholder="请选择荣誉类别"
v-model="row.honorCategory"
value-key="id"
clearable
>
<el-option
v-for="item in [
{
id: 1,
label: '外部荣誉'
},
{
id: 2,
label: '行内荣誉'
}
]"
:key="item.id"
:value="item.id"
:label="item.label"
/>
</el-select>
</span>
</template>
<template #action="{ row }">
<el-link
v-permission="'system:config:remove'"
type="danger"
underline="never"
@click="removeHonor(row)"
:icon="DeleteOutlined"
:disabled="!editMember"
>
删除
</el-link>
</template>
</ele-pro-table>
</div>
</template>
</ele-tabs>
</ele-card>
<union-user-select
clearable
multiple
view-type="picker"
v-model:visible="selectUserVisible"
v-model="userIds"
placeholder="请选择用户"
queryType="union"
:union-id="topUnionId"
@select="handleSelect"
:before-confirm="beforeConfirm"
/>
</ele-page>
</template>
<style scoped>
.ipt-text {
:deep(.el-input__inner) {
text-align: center;
}
:deep(.el-select__selected-item) {
text-align: center;
}
:deep(.el-date-picker__input) {
text-align: center;
}
}
.foot-btn {
display: flex;
justify-content: flex-end;
}
</style>