Files
cn-rdms-web/src/views/personal-center/my-performance/modules/performance-template-dialog.vue

514 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="tsx">
import { computed, reactive, ref, watch } from 'vue';
import type { UploadFile, UploadFiles } from 'element-plus';
import { ElButton, ElTag } from 'element-plus';
import {
activatePerformanceTemplate,
fetchPerformanceTemplatePage,
uploadFile,
uploadPerformanceTemplate
} from '@/service/api';
import { useUIPaginatedTable } from '@/hooks/common/table';
import { useAuth } from '@/hooks/business/auth';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import BusinessTableActionCell, { type BusinessTableAction } from '@/components/custom/business-table-action-cell';
import { PerformancePermission, formatDateTime } from './performance-shared';
defineOptions({ name: 'PerformanceTemplateDialog' });
const visible = defineModel<boolean>('visible', { default: false });
const emit = defineEmits<{
updated: [];
}>();
type TemplatePageResponse = Awaited<ReturnType<typeof fetchPerformanceTemplatePage>>;
const { hasAuth } = useAuth();
const canQueryTemplate = computed(() => hasAuth(PerformancePermission.TemplateQuery));
const canUpdateTemplate = computed(() => hasAuth(PerformancePermission.TemplateUpdate));
const searchParams = reactive<Api.Performance.Template.SearchParams>({
pageNo: 1,
pageSize: 10,
templateName: undefined,
activeFlag: undefined
});
const uploadForm = reactive({
templateName: '',
remark: '',
activeFlag: true,
file: null as File | null
});
const uploading = ref(false);
const activatingId = ref('');
const { columns, columnChecks, data, loading, getDataByPage, mobilePagination } = useUIPaginatedTable<
TemplatePageResponse,
Api.Performance.Template.Template
>({
paginationProps: {
currentPage: searchParams.pageNo,
pageSize: searchParams.pageSize
},
api: () => fetchPerformanceTemplatePage(searchParams),
transform: response => {
if (!response.error && response.data) {
return {
data: response.data.list,
pageNum: searchParams.pageNo ?? 1,
pageSize: searchParams.pageSize ?? 10,
total: response.data.total
};
}
return {
data: [],
pageNum: 1,
pageSize: 10,
total: 0
};
},
onPaginationParamsChange: params => {
searchParams.pageNo = params.currentPage ?? 1;
searchParams.pageSize = params.pageSize ?? 10;
},
columns: () => [
{ prop: 'index', type: 'index', label: '序号', width: 64 },
{ prop: 'templateName', label: '模板名称', minWidth: 170, showOverflowTooltip: true },
// { prop: 'fileName', label: '文件名', minWidth: 200, showOverflowTooltip: true },
// { prop: 'versionNo', label: '版本', width: 80 },
{
prop: 'activeFlag',
label: '状态',
width: 100,
formatter: row => <ElTag type={row.activeFlag ? 'success' : 'info'}>{row.activeFlag ? '已启用' : '已禁用'}</ElTag>
},
{ prop: 'uploadUserName', label: '上传人', width: 110 },
{
prop: 'uploadTime',
label: '上传时间',
width: 180,
formatter: row => formatDateTime(row.uploadTime)
},
{
prop: 'operate',
label: '操作',
width: 110,
align: 'center',
fixed: 'right',
formatter: row => <BusinessTableActionCell actions={getTemplateActions(row)} />
}
]
});
const selectedFileName = computed(() => uploadForm.file?.name || '');
async function loadTemplatePage(page = 1) {
if (!canQueryTemplate.value) return;
await getDataByPage(page);
}
function getTemplateActions(row: Api.Performance.Template.Template): BusinessTableAction[] {
if (!canUpdateTemplate.value) return [];
return [
{
key: 'activate',
label: row.activeFlag ? '已启用' : '启用',
buttonType: 'primary',
disabled: row.activeFlag || Boolean(activatingId.value),
onClick: () => handleActivate(row)
}
];
}
function handleFileChange(file: UploadFile, _files: UploadFiles) {
const rawFile = file.raw;
if (!rawFile) return;
uploadForm.file = rawFile;
if (!uploadForm.templateName) {
uploadForm.templateName = rawFile.name.replace(/\.[^.]+$/u, '');
}
}
async function handleUploadTemplate() {
if (!canUpdateTemplate.value) return;
if (!uploadForm.file) {
window.$message?.warning('请选择 Excel 模板文件');
return;
}
if (!uploadForm.templateName.trim()) {
window.$message?.warning('请输入模板名称');
return;
}
uploading.value = true;
const fileResult = await uploadFile(uploadForm.file, 'performance/templates');
if (fileResult.error || !fileResult.data) {
uploading.value = false;
return;
}
const result = await uploadPerformanceTemplate({
templateName: uploadForm.templateName.trim(),
fileId: fileResult.data.id,
fileName: uploadForm.file.name,
activeFlag: uploadForm.activeFlag,
remark: uploadForm.remark.trim() || undefined
});
uploading.value = false;
if (result.error) return;
window.$message?.success('绩效模板已上传');
Object.assign(uploadForm, {
templateName: '',
remark: '',
activeFlag: true,
file: null
});
await loadTemplatePage(1);
emit('updated');
}
async function handleActivate(row: Api.Performance.Template.Template) {
if (!canUpdateTemplate.value) return;
activatingId.value = row.id;
const { error } = await activatePerformanceTemplate(row.id);
activatingId.value = '';
if (error) return;
window.$message?.success('绩效模板已启用');
await loadTemplatePage(searchParams.pageNo ?? 1);
emit('updated');
}
watch(visible, isVisible => {
if (isVisible && canQueryTemplate.value) {
loadTemplatePage(1);
}
});
</script>
<template>
<BusinessFormDialog
v-model="visible"
title="绩效模板"
preset="lg"
append-to-body
:show-footer="false"
max-body-height="76vh"
>
<div class="performance-template-dialog">
<ElCard v-if="canUpdateTemplate" shadow="never">
<ElForm :model="uploadForm" label-position="top" class="performance-template-dialog__upload-form">
<div class="performance-template-dialog__upload-grid">
<ElFormItem label="模板名称" class="performance-template-dialog__field">
<ElInput v-model="uploadForm.templateName" placeholder="请输入模板名称" />
</ElFormItem>
<ElFormItem class="performance-template-dialog__field">
<template #label>
<div class="performance-template-dialog__label">
<span>Excel 文件</span>
</div>
</template>
<div class="performance-template-dialog__file-row">
<ElUpload
class="performance-template-dialog__upload-trigger"
:auto-upload="false"
:show-file-list="false"
accept=".xlsx,.xls"
:limit="1"
:on-change="handleFileChange"
>
<ElButton plain class="performance-template-dialog__upload-button">
<template #icon>
<icon-mdi-upload class="text-icon" />
</template>
选择文件
</ElButton>
</ElUpload>
<div class="performance-template-dialog__file-name-wrapper">
<ElTooltip
:disabled="!selectedFileName"
:content="selectedFileName"
placement="top"
effect="light"
popper-class="performance-template-dialog__file-tooltip"
>
<div
class="performance-template-dialog__file-name"
:class="{ 'performance-template-dialog__file-name--placeholder': !selectedFileName }"
>
<span class="performance-template-dialog__file-name-text">
{{ selectedFileName || '未选择文件' }}
</span>
</div>
</ElTooltip>
</div>
<ElTooltip placement="top" effect="light">
<template #content>支持 .xlsx.xls选择后会在这里显示文件名</template>
<button type="button" class="performance-template-dialog__hint-button" aria-label="Excel 文件说明">
<icon-mdi-information-outline />
</button>
</ElTooltip>
</div>
</ElFormItem>
<ElFormItem
label="上传后启用"
class="performance-template-dialog__field performance-template-dialog__switch-field"
>
<div class="performance-template-dialog__switch-box">
<span>上传后立即切换为当前模板</span>
<ElSwitch v-model="uploadForm.activeFlag" />
</div>
</ElFormItem>
<ElFormItem
label="备注"
class="performance-template-dialog__field performance-template-dialog__field--full"
>
<ElInput v-model="uploadForm.remark" type="textarea" :rows="3" maxlength="500" show-word-limit />
</ElFormItem>
</div>
<div class="performance-template-dialog__actions">
<ElButton type="primary" :loading="uploading" @click="handleUploadTemplate">上传模板</ElButton>
</div>
</ElForm>
</ElCard>
<ElCard v-if="canQueryTemplate" shadow="never" body-class="business-table-card-body">
<template #header>
<div class="flex items-center justify-between gap-12px">
<p class="text-16px font-600">模板列表</p>
<ElSpace wrap alignment="center">
<ElButton @click="loadTemplatePage(searchParams.pageNo ?? 1)">
<template #icon>
<icon-mdi-refresh class="text-icon" :class="{ 'animate-spin': loading }" />
</template>
刷新
</ElButton>
<TableColumnSetting v-model:columns="columnChecks" />
</ElSpace>
</div>
</template>
<div class="performance-template-dialog__table">
<ElTable v-loading="loading" height="100%" border :data="data">
<template v-for="col in columns" :key="String(col.prop)">
<ElTableColumn v-bind="col" />
</template>
</ElTable>
</div>
<div class="mt-16px flex justify-end">
<ElPagination
v-if="mobilePagination.total"
layout="total,prev,pager,next,sizes"
v-bind="mobilePagination"
@current-change="mobilePagination['current-change']"
@size-change="mobilePagination['size-change']"
/>
</div>
</ElCard>
<ElEmpty v-if="!canQueryTemplate && !canUpdateTemplate" :image-size="80" description="当前账号没有绩效模板权限" />
</div>
</BusinessFormDialog>
</template>
<style scoped lang="scss">
.performance-template-dialog {
display: grid;
gap: 16px;
}
.performance-template-dialog__upload-form {
display: grid;
gap: 16px;
}
.performance-template-dialog__upload-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.performance-template-dialog__field {
margin-bottom: 0;
}
.performance-template-dialog__field :deep(.el-form-item__label) {
display: inline-flex;
align-items: center;
min-height: 22px;
padding-bottom: 6px;
line-height: 22px;
}
.performance-template-dialog__field :deep(.el-form-item__content) {
min-height: 36px;
align-items: stretch;
width: 100%;
}
.performance-template-dialog__field--full {
grid-column: 1 / -1;
}
.performance-template-dialog__label {
display: inline-flex;
align-items: center;
gap: 6px;
}
.performance-template-dialog__file-row {
display: grid;
grid-template-columns: 112px minmax(0, 1fr) 24px;
align-items: stretch;
gap: 6px;
min-height: 36px;
width: 100% !important;
min-width: 0 !important;
}
.performance-template-dialog__upload-trigger {
display: block;
}
.performance-template-dialog__upload-trigger :deep(.el-upload) {
display: block;
}
.performance-template-dialog__upload-button {
width: 100%;
height: 36px;
padding: 0 12px;
}
.performance-template-dialog__file-name-wrapper {
display: block;
width: 100%;
min-width: 0;
flex: 1 1 auto;
}
.performance-template-dialog__file-name-wrapper :deep(.el-tooltip__trigger) {
display: block;
width: 100% !important;
min-width: 0 !important;
}
.performance-template-dialog__file-name {
display: flex;
align-items: center;
min-width: 0;
width: 100% !important;
height: 36px;
padding: 0 12px;
overflow: hidden;
border: 1px solid var(--el-border-color);
border-radius: 8px;
background: var(--el-fill-color-blank);
color: var(--el-text-color-regular);
font-size: 13px;
}
.performance-template-dialog__file-name-text {
display: block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.performance-template-dialog__file-name--placeholder {
color: var(--el-text-color-placeholder);
}
.performance-template-dialog__hint-button {
width: 24px;
height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: none;
border-radius: 6px;
background: transparent;
color: var(--el-text-color-secondary);
cursor: pointer;
transition:
background-color 0.2s ease,
color 0.2s ease;
}
.performance-template-dialog__hint-button:hover {
background: var(--el-fill-color-light);
color: var(--el-color-primary);
}
.performance-template-dialog__switch-field {
align-self: stretch;
}
.performance-template-dialog__switch-box {
height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 14px;
border: 1px solid var(--el-border-color);
border-radius: 8px;
background: var(--el-fill-color-blank);
color: var(--el-text-color-regular);
font-size: 13px;
line-height: 1;
}
.performance-template-dialog__actions {
display: flex;
justify-content: flex-end;
}
.performance-template-dialog__table {
height: 360px;
}
@media (max-width: 1080px) {
.performance-template-dialog__upload-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.performance-template-dialog__switch-field {
grid-column: 1 / -1;
}
}
@media (max-width: 768px) {
.performance-template-dialog__upload-grid {
grid-template-columns: 1fr;
}
.performance-template-dialog__field--full,
.performance-template-dialog__switch-field {
grid-column: auto;
}
.performance-template-dialog__switch-box {
height: 36px;
}
}
</style>