| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- <template>
- <PageShell>
- <div class="evaluation-page">
- <div class="evaluation-card">
- <EvaluationFilter
- :evaluation-name="queryParams.evaluationName || ''"
- :grade="queryParams.grade || ''"
- :position-type="queryParams.positionType || ''"
- :status="queryParams.status || ''"
- :date-range="dateRange"
- :grade-options="gradeOptions"
- :position-type-options="positionTypeOptions"
- @search="handleQuery"
- @reset="resetQuery"
- @update:evaluation-name="queryParams.evaluationName = $event"
- @update:grade="queryParams.grade = $event"
- @update:position-type="queryParams.positionType = $event"
- @update:status="queryParams.status = $event"
- @update:date-range="dateRange = $event"
- />
- <div class="evaluation-toolbar">
- <el-button class="evaluation-toolbar-button" @click="handleExport">导出数据</el-button>
- <el-button class="evaluation-toolbar-button" @click="getList">刷新</el-button>
- <el-button class="evaluation-toolbar-button" @click="handleOpenSyncDialog">同步至员工</el-button>
- </div>
- <EvaluationTable
- :loading="loading"
- :list="evaluationList"
- :total="total"
- :page-num="queryParams.pageNum"
- :page-size="queryParams.pageSize"
- :grade-options="gradeOptions"
- :position-type-options="positionTypeOptions"
- :switch-loading-map="switchLoadingMap"
- :selected-ids="selectedEvaluationIds"
- @update:page-num="queryParams.pageNum = $event"
- @update:page-size="queryParams.pageSize = $event"
- @update:selected-ids="selectedEvaluationIds = $event"
- @pagination="getList"
- @toggle-status="handleToggleStatus"
- @view-apply-list="handleViewApplyList"
- />
- </div>
- <el-dialog v-model="syncDialog.visible" title="同步测评" width="460px" append-to-body>
- <div class="sync-dialog-content">
- <div class="sync-dialog-row">
- <span class="sync-dialog-label">同步员工</span>
- <el-select v-model="syncDialog.employeeIds" multiple collapse-tags collapse-tags-tooltip placeholder="请选择" class="sync-dialog-select" :loading="syncOptionLoading">
- <el-option v-for="item in employeeOptions" :key="item.id" :label="item.label" :value="item.id" />
- </el-select>
- </div>
- </div>
- <template #footer>
- <div class="sync-dialog-footer">
- <el-button @click="syncDialog.visible = false">取消</el-button>
- <el-button type="primary" :loading="syncLoading" @click="handleConfirmSync">确定</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </PageShell>
- </template>
- <script setup lang="ts">
- import { computed, getCurrentInstance, onMounted, reactive, ref, type ComponentInternalInstance } from 'vue';
- import { useRouter } from 'vue-router';
- import PageShell from '@/components/PageShell/index.vue';
- import { getEvaluationParticipantStats, getEvaluationSyncEmployeeOptions, listEvaluation, syncEvaluationToEmployees, updateEvaluationStatus } from '@/api/main/evaluation';
- import { EvaluationSyncEmployeeOption, MainExamEvaluationQuery, MainExamEvaluationVO } from '@/api/main/evaluation/types';
- import { getDicts } from '@/api/system/dict/data';
- import type { DictDataVO } from '@/api/system/dict/data/types';
- import EvaluationFilter from './components/EvaluationFilter.vue';
- import EvaluationTable from './components/EvaluationTable.vue';
- type FilterOption = { label: string; value: string };
- type SyncEmployeeOption = EvaluationSyncEmployeeOption & { label: string };
- const router = useRouter();
- const { proxy } = getCurrentInstance() as ComponentInternalInstance;
- const modal = proxy?.$modal as any;
- const loading = ref(false);
- const total = ref(0);
- const evaluationList = ref<MainExamEvaluationVO[]>([]);
- const dateRange = ref<[string, string] | []>([]);
- const gradeOptions = ref<FilterOption[]>([]);
- const positionTypeOptions = ref<FilterOption[]>([]);
- const employeeOptions = ref<SyncEmployeeOption[]>([]);
- const syncLoading = ref(false);
- const syncOptionLoading = ref(false);
- const selectedEvaluationIds = ref<Array<string | number>>([]);
- const switchLoadingMap = reactive<Record<string, boolean>>({});
- const syncDialog = reactive({
- visible: false,
- employeeIds: [] as Array<string | number>
- });
- const queryParams = reactive<MainExamEvaluationQuery>({
- pageNum: 1,
- pageSize: 10,
- evaluationName: '',
- grade: '',
- positionType: '',
- status: ''
- });
- const normalizedQuery = computed(() => ({
- ...queryParams,
- params: {
- beginTime: dateRange.value?.[0] || undefined,
- endTime: dateRange.value?.[1] || undefined
- }
- }));
- const mapDictOptions = (list: DictDataVO[] = []): FilterOption[] =>
- list.map((item) => ({
- label: item.dictLabel,
- value: item.dictValue
- }));
- const mergeFieldOptions = (baseOptions: FilterOption[], rows: MainExamEvaluationVO[], field: 'grade' | 'positionType') => {
- const optionMap = new Map(baseOptions.map((item) => [item.value, item]));
- rows.forEach((item) => {
- const value = item[field];
- if (value && !optionMap.has(value)) {
- optionMap.set(value, { label: value, value });
- }
- });
- return Array.from(optionMap.values());
- };
- const loadFilterOptions = async () => {
- const [gradeRes, positionTypeRes] = await Promise.allSettled([getDicts('main_position_level'), getDicts('main_position_type')]);
- if (gradeRes.status === 'fulfilled') {
- gradeOptions.value = mapDictOptions(gradeRes.value.data || []);
- }
- if (positionTypeRes.status === 'fulfilled') {
- positionTypeOptions.value = mapDictOptions(positionTypeRes.value.data || []);
- }
- };
- const loadParticipantStats = async (rows: MainExamEvaluationVO[]) => {
- const ids = rows.map((item) => item.id).filter((id): id is string | number => id !== undefined && id !== null);
- if (!ids.length) {
- return rows.map((item) => ({
- ...item,
- participantCount: 0,
- totalCount: 0
- }));
- }
- const res = await getEvaluationParticipantStats(ids);
- const statsMap = res.data || {};
- return rows.map((item) => {
- const stats = statsMap[String(item.id)] || statsMap[item.id as keyof typeof statsMap];
- return {
- ...item,
- participantCount: Number(stats?.participantCount || 0),
- totalCount: Number(stats?.totalCount || 0)
- };
- });
- };
- const loadSyncEmployeeOptions = async () => {
- syncOptionLoading.value = true;
- try {
- const res = await getEvaluationSyncEmployeeOptions();
- const rows = res.data || [];
- employeeOptions.value = rows.map((item) => ({
- ...item,
- label: [item.name, item.mobile, item.studentNo].filter(Boolean).join(' / ')
- }));
- } finally {
- syncOptionLoading.value = false;
- }
- };
- const getList = async () => {
- loading.value = true;
- try {
- const res = await listEvaluation(normalizedQuery.value);
- evaluationList.value = await loadParticipantStats(res.rows || []);
- total.value = res.total || 0;
- gradeOptions.value = mergeFieldOptions(gradeOptions.value, evaluationList.value, 'grade');
- positionTypeOptions.value = mergeFieldOptions(positionTypeOptions.value, evaluationList.value, 'positionType');
- } finally {
- loading.value = false;
- }
- };
- const handleQuery = () => {
- queryParams.pageNum = 1;
- getList();
- };
- const resetQuery = () => {
- queryParams.evaluationName = '';
- queryParams.grade = '';
- queryParams.position = '';
- queryParams.positionType = '';
- queryParams.status = '';
- dateRange.value = [];
- queryParams.pageNum = 1;
- queryParams.pageSize = 10;
- getList();
- };
- const handleToggleStatus = async (row: MainExamEvaluationVO, value: boolean | string | number) => {
- const rowKey = String(row.id);
- try {
- switchLoadingMap[rowKey] = true;
- await updateEvaluationStatus(row.id, value ? '0' : '1');
- modal?.msgSuccess('状态更新成功');
- await getList();
- } finally {
- switchLoadingMap[rowKey] = false;
- }
- };
- const handleViewApplyList = (row: MainExamEvaluationVO) => {
- router.push({
- path: '/evaluation/apply-list',
- query: {
- evaluationId: String(row.id),
- evaluationName: row.evaluationName || '',
- backPath: '/evaluation'
- }
- });
- };
- const handleExport = () => {
- proxy?.download(
- '/main/examEvaluation/export',
- {
- ...normalizedQuery.value
- },
- `evaluation_${new Date().getTime()}.xlsx`
- );
- };
- const handleOpenSyncDialog = () => {
- if (!selectedEvaluationIds.value.length) {
- modal?.msgWarning('请先勾选测评');
- return;
- }
- syncDialog.employeeIds = [];
- syncDialog.visible = true;
- if (!employeeOptions.value.length) {
- loadSyncEmployeeOptions();
- }
- };
- const handleConfirmSync = async () => {
- if (!syncDialog.employeeIds.length) {
- modal?.msgWarning('请选择员工');
- return;
- }
- syncLoading.value = true;
- try {
- await syncEvaluationToEmployees({
- evaluationIds: selectedEvaluationIds.value,
- studentIds: syncDialog.employeeIds
- });
- syncDialog.visible = false;
- selectedEvaluationIds.value = [];
- modal?.msgSuccess('同步成功');
- await getList();
- } finally {
- syncLoading.value = false;
- }
- };
- onMounted(() => {
- loadFilterOptions().finally(() => {
- getList();
- });
- });
- </script>
- <style scoped lang="scss">
- .evaluation-page {
- min-height: 100%;
- }
- .evaluation-card {
- display: flex;
- flex-direction: column;
- min-height: calc(100vh - 180px);
- padding: 16px;
- border-radius: 8px;
- background: #fff;
- box-sizing: border-box;
- }
- .evaluation-toolbar {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: 12px;
- margin-bottom: 16px;
- }
- .evaluation-toolbar-button {
- color: #303133;
- background: #fff;
- border-color: #303133;
- }
- .evaluation-toolbar-button:hover,
- .evaluation-toolbar-button:focus {
- color: #303133;
- background: #fff;
- border-color: #303133;
- }
- .sync-dialog-content {
- padding: 10px 4px 24px;
- }
- .sync-dialog-row {
- display: flex;
- align-items: center;
- gap: 16px;
- }
- .sync-dialog-label {
- width: 56px;
- color: #303133;
- flex-shrink: 0;
- }
- .sync-dialog-select {
- flex: 1;
- }
- .sync-dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 12px;
- }
- </style>
|