Files
tuanwei-admin/src/views/gxmu/TYGLForm/index.vue
T
2026-07-20 11:50:47 +08:00

514 lines
14 KiB
Vue

<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:scroll="{ x: 1900 }"
tool-class="ele-toolbar-form"
>
<template #toolbar>
<a-space wrap>
<a-button
v-if="canShowAddButton"
class="gxmu-toolbar-btn gxmu-add-btn"
@click="openEdit()"
>
<template #icon>
<plus-outlined />
</template>
<span>添加</span>
</a-button>
<a-button
v-if="hasPermission('gxmu:TYGLForm:import')"
class="gxmu-toolbar-btn gxmu-import-btn"
@click="openImport"
>
<template #icon>
<UploadOutlined />
</template>
<span>导入</span>
</a-button>
<a-button
v-if="hasPermission('gxmu:TYGLForm:import')"
class="gxmu-toolbar-btn"
:loading="exporting"
@click="exportData"
>
<template #icon>
<DownloadOutlined />
</template>
<span>导出</span>
</a-button>
<a-input-search
allow-clear
v-model:value="searchText"
placeholder="请输入姓名/手机号/学院/班级/身份证号"
@search="reload"
@pressEnter="reload"
/>
<a-select
allow-clear
v-model:value="nationFilter"
:options="nationOptions"
placeholder="全部民族"
style="width: 160px"
@change="reload"
/>
<a-select
allow-clear
v-model:value="leaguePositionFilter"
:options="leaguePositionOptions"
placeholder="全部团内职务"
style="width: 180px"
@change="reload"
/>
<a-button @click="resetFilters">重置筛选</a-button>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'currentReviewStatus'">
<ReviewStatusTag :review-list="record.reviewList" />
</template>
<template v-if="column.key === 'action'">
<div class="action-links">
<a @click="openDetail(record)">详情</a>
<a
v-if="hasPermission('gxmu:TYGLForm:edit')"
@click="openEdit(record)"
>
编辑
</a>
<a @click="openMemberRecords(record)">团员记录</a>
<a @click="openGrowthArchives(record)">成长档案</a>
<a-popconfirm
v-if="hasPermission('gxmu:TYGLForm:remove')"
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</div>
</template>
</template>
</ele-pro-table>
</a-card>
</div>
<edit-modal v-model:visible="showEdit" :data="current" @done="reload" />
<import-modal v-model:visible="showImport" @done="reload" />
<member-records-modal
v-model:visible="showMemberRecords"
:data="recordCurrent"
@done="reload"
/>
<growth-archives-modal
v-model:visible="showGrowthArchives"
:data="archiveCurrent"
@done="reload"
/>
<ModuleDataModal
v-model:visible="showDetail"
module="gxmu_tygl_form"
module-name="团员管理"
:pk="detailCurrent?.id"
/>
<ReviewHistoryModal
v-model:visible="showReviewHistory"
:module="REVIEW_MODULE"
:pk="reviewCurrent?.id"
:review-list="reviewCurrent?.reviewList"
/>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { message } from 'ant-design-vue';
import {
DownloadOutlined,
UploadOutlined,
PlusOutlined
} from '@ant-design/icons-vue';
import EditModal from './components/edit.vue';
import ImportModal from './components/import.vue';
import MemberRecordsModal from './components/memberRecords.vue';
import GrowthArchivesModal from './components/growthArchives.vue';
import ReviewHistoryModal from '../components/reviewHistoryModal.vue';
import ModuleDataModal from '../reviewList/components/moduleDataModal.vue';
import ReviewStatusTag from '../components/reviewStatusTag.vue';
import type { EleProTable } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import {
exportTyglForm,
getTyglForm,
pageTyglForm,
removeTyglForm
} from '@/api/gxmu/tygl-form';
import type { TyglForm, TyglFormParam } from '@/api/gxmu/tygl-form/model';
import { listDictData } from '@/api/system/dict-data';
import { hasAnyRole, hasPermission } from '@/utils/permission';
import { useUserStore } from '@/store/modules/user';
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
const userStore = useUserStore();
const addRoleCodes = [
'superAdmin',
'SchoolYouthLeagueCommittee',
'PartyCommitteesOfSecondaryColleges',
'YouthLeagueCommitteeOfSecondaryColleges',
'YouthLeagueBranchesOfSecondaryColleges'
];
const studentRoleCodes = ['sutdent', 'student'];
const searchText = ref('');
const nationFilter = ref<string>();
const leaguePositionFilter = ref<string>();
const nationOptions = ref<{ label: string; value: string }[]>([]);
const leaguePositionOptions = ref<{ label: string; value: string }[]>([]);
const showEdit = ref(false);
const showImport = ref(false);
const current = ref<TyglForm>();
const showMemberRecords = ref(false);
const recordCurrent = ref<TyglForm>();
const showGrowthArchives = ref(false);
const archiveCurrent = ref<TyglForm>();
const showDetail = ref(false);
const detailCurrent = ref<TyglForm | null>(null);
const REVIEW_MODULE: string | undefined = undefined;
const showReviewHistory = ref(false);
const reviewCurrent = ref<TyglForm | null>(null);
const exporting = ref(false);
const studentRecordCount = ref(0);
const isStudentOnlyUser = computed(() => {
const roles = (userStore.roles ?? []).filter(
(role): role is string => !!role
);
return roles.length === 1 && studentRoleCodes.includes(roles[0]);
});
const canShowAddButton = computed(() => {
if (hasAnyRole(addRoleCodes)) {
return true;
}
return isStudentOnlyUser.value && studentRecordCount.value === 0;
});
const columns = ref<ColumnItem[]>([
{
title: '序号',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 90
},
{
title: '姓名',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 120
},
{
title: '性别',
dataIndex: 'gender',
key: 'gender',
align: 'center',
width: 80
},
{
title: '民族',
dataIndex: 'nation',
key: 'nation',
align: 'center',
width: 100
},
{
title: '学院',
dataIndex: 'college',
key: 'college',
align: 'center',
width: 180,
ellipsis: true
},
{
title: '班级',
dataIndex: 'className',
key: 'className',
align: 'center',
width: 180,
ellipsis: true
},
{
title: '政治面貌',
dataIndex: 'politics',
key: 'politics',
align: 'center',
width: 130
},
{
title: '手机号码',
dataIndex: 'phone',
key: 'phone',
align: 'center',
width: 140
},
{
title: '团内职务',
dataIndex: 'leaguePosition',
key: 'leaguePosition',
align: 'center',
width: 160,
ellipsis: true
},
{
title: '所属团支部',
dataIndex: 'branchOrganizationName',
key: 'branchOrganizationName',
align: 'center',
width: 220,
ellipsis: true
},
{
title: '出生日期',
dataIndex: 'birthDate',
key: 'birthDate',
align: 'center',
width: 120
},
{
title: '团籍是否在本组织',
dataIndex: 'archiveInCurrentOrg',
key: 'archiveInCurrentOrg',
align: 'center',
width: 170
},
{
title: '入团年月',
dataIndex: 'joinMonth',
key: 'joinMonth',
align: 'center',
width: 120
},
{
title: '操作',
key: 'action',
fixed: 'right',
align: 'center',
width: 260
}
]);
const buildQueryParams = (): TyglFormParam => {
return {
keywords: searchText.value.trim() || undefined,
nation: nationFilter.value,
leaguePosition: leaguePositionFilter.value
} as TyglFormParam;
};
const datasource: DatasourceFunction = ({ page, limit }) => {
return pageTyglForm({
page,
limit,
...buildQueryParams()
} as TyglFormParam)
.then((result) => {
if (isStudentOnlyUser.value) {
studentRecordCount.value = Number(result?.count ?? 0);
}
return result;
})
.catch((e) => {
message.error(e.message);
return { list: [], count: 0 };
});
};
const reload = () => {
loadStudentRecordCount();
tableRef.value?.reload();
};
const loadStudentRecordCount = async () => {
if (!isStudentOnlyUser.value) {
studentRecordCount.value = 0;
return;
}
try {
const result = await pageTyglForm({
page: 1,
limit: 1
} as TyglFormParam);
studentRecordCount.value = Number(result?.count ?? 0);
} catch (e: any) {
message.error(e.message || '获取团员数据失败');
}
};
const loadDictOptions = async (dictId: number) => {
const result = await listDictData({ dictId });
return result
.filter((item) => !!item.dictDataName)
.map((item) => ({
label: item.dictDataName as string,
value: item.dictDataName as string
}));
};
const loadFilterOptions = async () => {
try {
const [nationList, leaguePositionList] = await Promise.all([
loadDictOptions(173),
loadDictOptions(174)
]);
nationOptions.value = nationList;
leaguePositionOptions.value = leaguePositionList;
} catch (e: any) {
message.error(e.message || '筛选字典加载失败');
}
};
const resetFilters = () => {
searchText.value = '';
nationFilter.value = undefined;
leaguePositionFilter.value = undefined;
reload();
};
const resolveExportUrl = (payload: any) => {
return String(
payload?.data?.url ||
payload?.data?.downloadUrl ||
payload?.data?.path ||
payload?.url ||
payload?.downloadUrl ||
payload?.path ||
''
);
};
const exportData = async () => {
if (exporting.value) {
return;
}
exporting.value = true;
const hide = message.loading('导出中..', 0);
try {
const result = await exportTyglForm(buildQueryParams());
const url = resolveExportUrl(result);
if (!url) {
throw new Error('导出地址缺失');
}
window.open(url, '_blank');
hide();
message.success('导出成功');
} catch (e: any) {
hide();
message.error(e.message || '导出失败');
} finally {
exporting.value = false;
}
};
const openImport = () => {
showImport.value = true;
};
const openEdit = (record?: TyglForm) => {
if (!record && isStudentOnlyUser.value && studentRecordCount.value > 0) {
message.warning('当前学生用户已有团员数据,不能重复新增');
return;
}
current.value = record
? (JSON.parse(JSON.stringify(record)) as TyglForm)
: undefined;
showEdit.value = true;
};
const loadTyglDetail = (
record: TyglForm,
callback: (data: TyglForm) => void
) => {
if (!record.id) {
message.error('缺少团员记录ID');
return;
}
const hide = message.loading('加载中..', 0);
getTyglForm(record.id)
.then((data) => {
hide();
callback(JSON.parse(JSON.stringify(data)) as TyglForm);
})
.catch((e) => {
hide();
message.error(e.message);
});
};
const openMemberRecords = (record: TyglForm) => {
loadTyglDetail(record, (data) => {
recordCurrent.value = data;
showMemberRecords.value = true;
});
};
const openGrowthArchives = (record: TyglForm) => {
loadTyglDetail(record, (data) => {
archiveCurrent.value = data;
showGrowthArchives.value = true;
});
};
const openDetail = (record: TyglForm) => {
detailCurrent.value = record;
showDetail.value = true;
};
const openReviewHistory = (record: TyglForm) => {
reviewCurrent.value = record;
showReviewHistory.value = true;
};
const remove = (record: TyglForm) => {
const hide = message.loading('请求中..', 0);
removeTyglForm(record.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
onMounted(() => {
loadStudentRecordCount();
loadFilterOptions();
});
</script>
<script lang="ts">
export default {
name: 'TYGLForm'
};
</script>
<style lang="less" scoped>
@import '../toolbar.less';
.action-links {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 8px 12px;
line-height: 1.5;
}
</style>