feat(performance): 优化绩效模板管理功能
- 新增绩效模板上传范围接口,限制组织负责人权限 - 实现单人上传和批量导入两种模板上传方式 - 添加模板年份、员工筛选等搜索条件 - 集成文件上传验证和批量导入结果展示 - 重构模板对话框UI布局,提升用户体验 - 更新API类型定义和数据转换逻辑
This commit is contained in:
@@ -19,6 +19,8 @@ type TemplateResponse = Omit<Api.Performance.Template.Template, 'id' | 'fileId'
|
||||
id: StringIdResponse;
|
||||
fileId: StringIdResponse;
|
||||
uploadUserId: StringIdResponse;
|
||||
employeeId?: StringIdResponse | null;
|
||||
employeeDeptId?: StringIdResponse | null;
|
||||
activeFlag?: boolean | number | string | null;
|
||||
};
|
||||
|
||||
@@ -27,6 +29,34 @@ type TemplatePageResponse = {
|
||||
list: TemplateResponse[];
|
||||
};
|
||||
|
||||
type TemplateUploadScopeResponse = {
|
||||
orgLeader?: boolean | number | string | null;
|
||||
employeeCount?: number | string | null;
|
||||
employees?: Array<{
|
||||
employeeId: StringIdResponse;
|
||||
employeeName: string;
|
||||
employeeDeptId?: StringIdResponse | null;
|
||||
employeeDeptName?: string | null;
|
||||
}> | null;
|
||||
};
|
||||
|
||||
type TemplateBatchImportResponse = {
|
||||
successCount?: number | string | null;
|
||||
failureCount?: number | string | null;
|
||||
successes?: Array<{
|
||||
employeeId: StringIdResponse;
|
||||
employeeName: string;
|
||||
templateId: StringIdResponse;
|
||||
sourceFileName: string;
|
||||
sheetName?: string | null;
|
||||
}> | null;
|
||||
failures?: Array<{
|
||||
sourceFileName: string;
|
||||
sheetName?: string | null;
|
||||
reason: string;
|
||||
}> | null;
|
||||
};
|
||||
|
||||
type SheetResponse = Omit<
|
||||
Api.Performance.Sheet.Sheet,
|
||||
'id' | 'employeeId' | 'employeeDeptId' | 'managerId' | 'templateId' | 'fileId'
|
||||
@@ -135,6 +165,8 @@ function normalizeTemplate(response: TemplateResponse): Api.Performance.Template
|
||||
...response,
|
||||
id: normalizeStringId(response.id),
|
||||
fileId: normalizeStringId(response.fileId),
|
||||
employeeId: normalizeNullableStringId(response.employeeId),
|
||||
employeeDeptId: normalizeNullableStringId(response.employeeDeptId),
|
||||
uploadUserId: normalizeStringId(response.uploadUserId),
|
||||
activeFlag: normalizeBooleanFlag(response.activeFlag),
|
||||
remark: response.remark ?? null,
|
||||
@@ -142,6 +174,40 @@ function normalizeTemplate(response: TemplateResponse): Api.Performance.Template
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTemplateUploadScope(response: TemplateUploadScopeResponse): Api.Performance.Template.UploadScope {
|
||||
return {
|
||||
orgLeader: normalizeBooleanFlag(response.orgLeader),
|
||||
employeeCount: normalizeTotal(response.employeeCount),
|
||||
employees: (response.employees || []).map(item => ({
|
||||
employeeId: normalizeStringId(item.employeeId),
|
||||
employeeName: item.employeeName,
|
||||
employeeDeptId: normalizeNullableStringId(item.employeeDeptId),
|
||||
employeeDeptName: item.employeeDeptName ?? null
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTemplateBatchImportResult(
|
||||
response: TemplateBatchImportResponse
|
||||
): Api.Performance.Template.BatchImportResult {
|
||||
return {
|
||||
successCount: normalizeTotal(response.successCount),
|
||||
failureCount: normalizeTotal(response.failureCount),
|
||||
successes: (response.successes || []).map(item => ({
|
||||
employeeId: normalizeStringId(item.employeeId),
|
||||
employeeName: item.employeeName,
|
||||
templateId: normalizeStringId(item.templateId),
|
||||
sourceFileName: item.sourceFileName,
|
||||
sheetName: item.sheetName ?? null
|
||||
})),
|
||||
failures: (response.failures || []).map(item => ({
|
||||
sourceFileName: item.sourceFileName,
|
||||
sheetName: item.sheetName ?? null,
|
||||
reason: item.reason
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSheet(response: SheetResponse): Api.Performance.Sheet.Sheet {
|
||||
return {
|
||||
...response,
|
||||
@@ -304,15 +370,21 @@ function createTemplateQuery(params: Api.Performance.Template.SearchParams = {})
|
||||
query.append('pageNo', String(params.pageNo ?? 1));
|
||||
query.append('pageSize', String(params.pageSize ?? 10));
|
||||
appendValue(query, 'templateName', params.templateName);
|
||||
appendValue(query, 'employeeId', params.employeeId);
|
||||
appendValue(query, 'employeeName', params.employeeName);
|
||||
appendValue(query, 'templateYear', params.templateYear);
|
||||
appendValue(query, 'uploadUserName', params.uploadUserName);
|
||||
appendValue(query, 'activeFlag', params.activeFlag);
|
||||
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
export async function fetchPerformanceTemplateCurrent() {
|
||||
export async function fetchPerformanceTemplateCurrent(employeeId?: string | null) {
|
||||
const query = new URLSearchParams();
|
||||
appendValue(query, 'employeeId', employeeId);
|
||||
const result = await request<TemplateResponse | null>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${TEMPLATE_PREFIX}/current`,
|
||||
url: query.toString() ? `${TEMPLATE_PREFIX}/current?${query.toString()}` : `${TEMPLATE_PREFIX}/current`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
@@ -335,10 +407,20 @@ export async function fetchPerformanceTemplatePage(params: Api.Performance.Templ
|
||||
}));
|
||||
}
|
||||
|
||||
export async function uploadPerformanceTemplate(data: Api.Performance.Template.UploadParams) {
|
||||
export async function fetchPerformanceTemplateUploadScope() {
|
||||
const result = await request<TemplateUploadScopeResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${TEMPLATE_PREFIX}/upload-scope`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
return mapServiceResult(result as ServiceRequestResult<TemplateUploadScopeResponse>, normalizeTemplateUploadScope);
|
||||
}
|
||||
|
||||
export async function uploadSinglePerformanceTemplate(data: Api.Performance.Template.SingleUploadParams) {
|
||||
const result = await request<StringIdResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${TEMPLATE_PREFIX}/upload`,
|
||||
url: `${TEMPLATE_PREFIX}/upload-single`,
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
@@ -346,6 +428,20 @@ export async function uploadPerformanceTemplate(data: Api.Performance.Template.U
|
||||
return mapServiceResult(result as ServiceRequestResult<StringIdResponse>, normalizeStringId);
|
||||
}
|
||||
|
||||
export async function batchImportPerformanceTemplates(data: Api.Performance.Template.BatchImportParams) {
|
||||
const result = await request<TemplateBatchImportResponse>({
|
||||
...safeJsonRequestConfig,
|
||||
url: `${TEMPLATE_PREFIX}/batch-import`,
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
|
||||
return mapServiceResult(
|
||||
result as ServiceRequestResult<TemplateBatchImportResponse>,
|
||||
normalizeTemplateBatchImportResult
|
||||
);
|
||||
}
|
||||
|
||||
export function activatePerformanceTemplate(id: string) {
|
||||
return request<boolean>({
|
||||
...safeJsonRequestConfig,
|
||||
|
||||
63
src/typings/api/performance.d.ts
vendored
63
src/typings/api/performance.d.ts
vendored
@@ -32,6 +32,11 @@ declare namespace Api {
|
||||
templateName: string;
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
employeeId?: string | null;
|
||||
employeeName?: string | null;
|
||||
employeeDeptId?: string | null;
|
||||
employeeDeptName?: string | null;
|
||||
templateYear?: number | null;
|
||||
versionNo: number;
|
||||
activeFlag: boolean;
|
||||
uploadUserId: string;
|
||||
@@ -44,17 +49,67 @@ declare namespace Api {
|
||||
type SearchParams = CommonType.RecordNullable<
|
||||
Common.PageParams & {
|
||||
templateName: string;
|
||||
employeeId: string;
|
||||
employeeName: string;
|
||||
templateYear: number;
|
||||
uploadUserName: string;
|
||||
activeFlag: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
interface UploadParams {
|
||||
interface ScopeEmployee {
|
||||
employeeId: string;
|
||||
employeeName: string;
|
||||
employeeDeptId?: string | null;
|
||||
employeeDeptName?: string | null;
|
||||
}
|
||||
|
||||
interface UploadScope {
|
||||
orgLeader: boolean;
|
||||
employeeCount: number;
|
||||
employees: ScopeEmployee[];
|
||||
}
|
||||
|
||||
interface SingleUploadParams {
|
||||
employeeId: string;
|
||||
templateName: string;
|
||||
fileId: string;
|
||||
fileName: string;
|
||||
activeFlag?: boolean | null;
|
||||
templateYear?: number | null;
|
||||
sourceFileId: string;
|
||||
sourceFileName: string;
|
||||
remark?: string | null;
|
||||
}
|
||||
|
||||
interface BatchImportFileItem {
|
||||
sourceFileId: string;
|
||||
sourceFileName: string;
|
||||
}
|
||||
|
||||
interface BatchImportParams {
|
||||
templateYear?: number | null;
|
||||
remark?: string | null;
|
||||
files: BatchImportFileItem[];
|
||||
}
|
||||
|
||||
interface BatchImportSuccessItem {
|
||||
employeeId: string;
|
||||
employeeName: string;
|
||||
templateId: string;
|
||||
sourceFileName: string;
|
||||
sheetName?: string | null;
|
||||
}
|
||||
|
||||
interface BatchImportFailureItem {
|
||||
sourceFileName: string;
|
||||
sheetName?: string | null;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
interface BatchImportResult {
|
||||
successCount: number;
|
||||
failureCount: number;
|
||||
successes: BatchImportSuccessItem[];
|
||||
failures: BatchImportFailureItem[];
|
||||
}
|
||||
}
|
||||
|
||||
namespace Sheet {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
fetchGetMySubordinateTree,
|
||||
fetchPerformanceSheetPage,
|
||||
fetchPerformanceSheetStatusDict,
|
||||
fetchPerformanceTemplateUploadScope,
|
||||
fetchTeamPerformanceSummary,
|
||||
formatToYYYYMM,
|
||||
resendPerformanceSheet,
|
||||
@@ -111,6 +112,8 @@ const deptOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
const directSubordinateOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
const statusOptions = ref<Array<{ label: string; value: string }>>([]);
|
||||
const currentDeptId = ref<string>('');
|
||||
const templateUploadScope = ref<Api.Performance.Template.UploadScope | null>(null);
|
||||
const templateUploadScopeLoading = ref(false);
|
||||
|
||||
const templateVisible = ref(false);
|
||||
const excelVisible = ref(false);
|
||||
@@ -144,9 +147,10 @@ const canSignOff = computed(
|
||||
() => hasAuth(PerformancePermission.SheetImport) && hasAuth(PerformancePermission.SheetSignOff)
|
||||
);
|
||||
const canExport = computed(() => hasAuth(PerformancePermission.SheetExport));
|
||||
const canManageTemplate = computed(
|
||||
const hasTemplatePermission = computed(
|
||||
() => hasAuth(PerformancePermission.TemplateQuery) || hasAuth(PerformancePermission.TemplateUpdate)
|
||||
);
|
||||
const canManageTemplate = computed(() => hasTemplatePermission.value && Boolean(templateUploadScope.value?.orgLeader));
|
||||
const isTeamMode = computed(() => teamViewMode.value === 'team');
|
||||
const allSubordinateUserIds = computed(() => collectSubordinateUserIds(subordinateTree.value));
|
||||
const subordinateOptions = computed(() => {
|
||||
@@ -656,6 +660,19 @@ async function loadStatusOptions() {
|
||||
statusOptions.value = getPerformanceStatusOptions(statusDict);
|
||||
}
|
||||
|
||||
async function loadTemplateUploadScope() {
|
||||
if (!hasTemplatePermission.value) {
|
||||
templateUploadScope.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
templateUploadScopeLoading.value = true;
|
||||
const { error, data: scopeData } = await fetchPerformanceTemplateUploadScope();
|
||||
templateUploadScopeLoading.value = false;
|
||||
|
||||
templateUploadScope.value = error || !scopeData ? null : scopeData;
|
||||
}
|
||||
|
||||
async function handleTeamViewModeChange(mode: TeamViewMode) {
|
||||
teamViewMode.value = mode;
|
||||
|
||||
@@ -694,7 +711,12 @@ watch(excelVisible, isVisible => {
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadDeptOptions(), loadDirectSubordinateOptions(), loadStatusOptions()]);
|
||||
await Promise.all([
|
||||
loadDeptOptions(),
|
||||
loadDirectSubordinateOptions(),
|
||||
loadStatusOptions(),
|
||||
loadTemplateUploadScope()
|
||||
]);
|
||||
|
||||
if (canUseTeamDashboard.value) {
|
||||
await loadSubordinateTree();
|
||||
@@ -773,7 +795,12 @@ onMounted(async () => {
|
||||
</template>
|
||||
下签
|
||||
</ElButton>
|
||||
<ElButton v-if="isTeamMode && canManageTemplate" plain @click="templateVisible = true">
|
||||
<ElButton
|
||||
v-if="isTeamMode && canManageTemplate"
|
||||
plain
|
||||
:loading="templateUploadScopeLoading"
|
||||
@click="templateVisible = true"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-mdi-file-cog-outline class="text-icon" />
|
||||
</template>
|
||||
@@ -821,6 +848,7 @@ onMounted(async () => {
|
||||
<PerformanceTemplateDialog
|
||||
v-if="templateVisible"
|
||||
v-model:visible="templateVisible"
|
||||
:scope="templateUploadScope"
|
||||
@updated="reloadAfterMutation"
|
||||
/>
|
||||
|
||||
|
||||
@@ -520,7 +520,12 @@ async function loadWorkbook() {
|
||||
let sourceFileId = '';
|
||||
|
||||
if (isCreateMode.value) {
|
||||
const templateResult = await fetchPerformanceTemplateCurrent();
|
||||
if (!createForm.employeeId) {
|
||||
errorMessage.value = '请选择下属后再加载绩效模板';
|
||||
return;
|
||||
}
|
||||
|
||||
const templateResult = await fetchPerformanceTemplateCurrent(createForm.employeeId);
|
||||
if (templateResult.error || !templateResult.data) {
|
||||
errorMessage.value = '当前没有可用的绩效模板';
|
||||
return;
|
||||
@@ -533,7 +538,7 @@ async function loadWorkbook() {
|
||||
|
||||
const [sheetResult, templateResult] = await Promise.all([
|
||||
fetchPerformanceSheet(props.rowData.id),
|
||||
fetchPerformanceTemplateCurrent()
|
||||
fetchPerformanceTemplateCurrent(props.rowData.employeeId)
|
||||
]);
|
||||
|
||||
if (sheetResult.error || !sheetResult.data) {
|
||||
|
||||
@@ -1,181 +1,254 @@
|
||||
<script setup lang="tsx">
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import type { UploadFile, UploadFiles } from 'element-plus';
|
||||
import { ElButton, ElTag } from 'element-plus';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import type { UploadFile, UploadFiles, UploadRawFile } from 'element-plus';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
activatePerformanceTemplate,
|
||||
batchImportPerformanceTemplates,
|
||||
fetchPerformanceTemplatePage,
|
||||
uploadFile,
|
||||
uploadPerformanceTemplate
|
||||
uploadSinglePerformanceTemplate
|
||||
} 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' });
|
||||
|
||||
interface Props {
|
||||
scope?: Api.Performance.Template.UploadScope | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
scope: null
|
||||
});
|
||||
|
||||
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 employeeOptions = computed(() => props.scope?.employees || []);
|
||||
const isOrgLeader = computed(() => Boolean(props.scope?.orgLeader));
|
||||
const currentYear = dayjs().year();
|
||||
|
||||
const searchParams = reactive<Api.Performance.Template.SearchParams>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
templateName: undefined,
|
||||
employeeId: undefined,
|
||||
employeeName: undefined,
|
||||
templateYear: undefined,
|
||||
uploadUserName: undefined,
|
||||
activeFlag: undefined
|
||||
});
|
||||
|
||||
const uploadForm = reactive({
|
||||
const singleForm = reactive({
|
||||
employeeId: '',
|
||||
templateName: '',
|
||||
templateYear: currentYear,
|
||||
remark: '',
|
||||
activeFlag: true,
|
||||
file: null as File | null
|
||||
file: null as UploadRawFile | null
|
||||
});
|
||||
|
||||
const uploading = ref(false);
|
||||
const batchForm = reactive({
|
||||
templateYear: currentYear,
|
||||
remark: '',
|
||||
files: [] as UploadRawFile[]
|
||||
});
|
||||
|
||||
const tableData = ref<Api.Performance.Template.Template[]>([]);
|
||||
const total = ref(0);
|
||||
const loading = ref(false);
|
||||
const singleUploading = ref(false);
|
||||
const batchUploading = 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
|
||||
};
|
||||
}
|
||||
const singleFileName = computed(() => singleForm.file?.name || '');
|
||||
const batchFileNames = computed(() => batchForm.files.map(file => file.name));
|
||||
|
||||
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)} />
|
||||
}
|
||||
]
|
||||
});
|
||||
function resetSingleForm() {
|
||||
Object.assign(singleForm, {
|
||||
employeeId: '',
|
||||
templateName: '',
|
||||
templateYear: currentYear,
|
||||
remark: '',
|
||||
file: null
|
||||
});
|
||||
}
|
||||
|
||||
const selectedFileName = computed(() => uploadForm.file?.name || '');
|
||||
function resetBatchForm() {
|
||||
Object.assign(batchForm, {
|
||||
templateYear: currentYear,
|
||||
remark: '',
|
||||
files: []
|
||||
});
|
||||
}
|
||||
|
||||
async function loadTemplatePage(page = 1) {
|
||||
async function loadTemplatePage(page = searchParams.pageNo ?? 1) {
|
||||
if (!canQueryTemplate.value) return;
|
||||
await getDataByPage(page);
|
||||
}
|
||||
|
||||
function getTemplateActions(row: Api.Performance.Template.Template): BusinessTableAction[] {
|
||||
if (!canUpdateTemplate.value) return [];
|
||||
searchParams.pageNo = page;
|
||||
loading.value = true;
|
||||
const { error, data } = await fetchPerformanceTemplatePage(searchParams);
|
||||
loading.value = false;
|
||||
|
||||
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 模板文件');
|
||||
if (error || !data) {
|
||||
tableData.value = [];
|
||||
total.value = 0;
|
||||
return;
|
||||
}
|
||||
if (!uploadForm.templateName.trim()) {
|
||||
|
||||
tableData.value = data.list;
|
||||
total.value = data.total;
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
loadTemplatePage(1);
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
Object.assign(searchParams, {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
templateName: undefined,
|
||||
employeeId: undefined,
|
||||
employeeName: undefined,
|
||||
templateYear: undefined,
|
||||
uploadUserName: undefined,
|
||||
activeFlag: undefined
|
||||
});
|
||||
loadTemplatePage(1);
|
||||
}
|
||||
|
||||
function handleSingleFileChange(file: UploadFile) {
|
||||
const rawFile = file.raw;
|
||||
if (!rawFile) return;
|
||||
|
||||
singleForm.file = rawFile;
|
||||
if (!singleForm.templateName) {
|
||||
singleForm.templateName = rawFile.name.replace(/\.[^.]+$/u, '');
|
||||
}
|
||||
}
|
||||
|
||||
function handleBatchFileChange(_file: UploadFile, files: UploadFiles) {
|
||||
batchForm.files = files.reduce<UploadRawFile[]>((list, item) => {
|
||||
if (item.raw) {
|
||||
list.push(item.raw);
|
||||
}
|
||||
return list;
|
||||
}, []);
|
||||
}
|
||||
|
||||
async function uploadSourceFile(file: File) {
|
||||
return uploadFile(file, 'performance/template-sources');
|
||||
}
|
||||
|
||||
async function handleSingleUpload() {
|
||||
if (!canUpdateTemplate.value || !isOrgLeader.value) return;
|
||||
|
||||
if (!singleForm.employeeId) {
|
||||
window.$message?.warning('请选择员工');
|
||||
return;
|
||||
}
|
||||
if (!singleForm.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;
|
||||
if (!singleForm.file) {
|
||||
window.$message?.warning('请选择 Excel 文件');
|
||||
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
|
||||
singleUploading.value = true;
|
||||
const fileResult = await uploadSourceFile(singleForm.file);
|
||||
if (fileResult.error || !fileResult.data) {
|
||||
singleUploading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await uploadSinglePerformanceTemplate({
|
||||
employeeId: singleForm.employeeId,
|
||||
templateName: singleForm.templateName.trim(),
|
||||
templateYear: singleForm.templateYear || undefined,
|
||||
sourceFileId: fileResult.data.id,
|
||||
sourceFileName: singleForm.file.name,
|
||||
remark: singleForm.remark.trim() || undefined
|
||||
});
|
||||
uploading.value = false;
|
||||
singleUploading.value = false;
|
||||
|
||||
if (result.error) return;
|
||||
|
||||
window.$message?.success('绩效模板已上传');
|
||||
Object.assign(uploadForm, {
|
||||
templateName: '',
|
||||
remark: '',
|
||||
activeFlag: true,
|
||||
file: null
|
||||
});
|
||||
window.$message?.success('绩效模板上传成功');
|
||||
resetSingleForm();
|
||||
await loadTemplatePage(1);
|
||||
emit('updated');
|
||||
}
|
||||
|
||||
async function handleBatchImport() {
|
||||
if (!canUpdateTemplate.value || !isOrgLeader.value) return;
|
||||
|
||||
if (!batchForm.files.length) {
|
||||
window.$message?.warning('请选择要导入的 Excel 文件');
|
||||
return;
|
||||
}
|
||||
|
||||
batchUploading.value = true;
|
||||
const uploadResults = await Promise.all(
|
||||
batchForm.files.map(async file => {
|
||||
const fileResult = await uploadSourceFile(file);
|
||||
return { file, fileResult };
|
||||
})
|
||||
);
|
||||
|
||||
const failedUpload = uploadResults.find(item => item.fileResult.error || !item.fileResult.data);
|
||||
if (failedUpload) {
|
||||
batchUploading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const files: Api.Performance.Template.BatchImportFileItem[] = uploadResults.map(item => ({
|
||||
sourceFileId: item.fileResult.data!.id,
|
||||
sourceFileName: item.file.name
|
||||
}));
|
||||
|
||||
const result = await batchImportPerformanceTemplates({
|
||||
templateYear: batchForm.templateYear || undefined,
|
||||
remark: batchForm.remark.trim() || undefined,
|
||||
files
|
||||
});
|
||||
batchUploading.value = false;
|
||||
|
||||
if (result.error || !result.data) return;
|
||||
|
||||
await showBatchImportResult(result.data);
|
||||
resetBatchForm();
|
||||
await loadTemplatePage(1);
|
||||
emit('updated');
|
||||
}
|
||||
|
||||
async function showBatchImportResult(result: Api.Performance.Template.BatchImportResult) {
|
||||
const lines = [`成功 ${result.successCount} 条`, `失败 ${result.failureCount} 条`];
|
||||
|
||||
result.failures.slice(0, 20).forEach(item => {
|
||||
const sheetName = item.sheetName ? ` / ${item.sheetName}` : '';
|
||||
lines.push(`${item.sourceFileName}${sheetName}:${item.reason}`);
|
||||
});
|
||||
|
||||
if (result.failures.length > 20) {
|
||||
lines.push(`其余 ${result.failures.length - 20} 条失败明细请查看后端日志或再次筛查源文件`);
|
||||
}
|
||||
|
||||
await ElMessageBox.alert(lines.join('\n'), '批量导入结果', {
|
||||
confirmButtonText: '知道了'
|
||||
});
|
||||
}
|
||||
|
||||
async function handleActivate(row: Api.Performance.Template.Template) {
|
||||
if (!canUpdateTemplate.value) return;
|
||||
|
||||
@@ -191,7 +264,13 @@ async function handleActivate(row: Api.Performance.Template.Template) {
|
||||
}
|
||||
|
||||
watch(visible, isVisible => {
|
||||
if (isVisible && canQueryTemplate.value) {
|
||||
if (!isVisible) return;
|
||||
|
||||
if (!singleForm.employeeId && employeeOptions.value.length === 1) {
|
||||
singleForm.employeeId = employeeOptions.value[0].employeeId;
|
||||
}
|
||||
|
||||
if (canQueryTemplate.value) {
|
||||
loadTemplatePage(1);
|
||||
}
|
||||
});
|
||||
@@ -204,125 +283,217 @@ watch(visible, isVisible => {
|
||||
preset="lg"
|
||||
append-to-body
|
||||
:show-footer="false"
|
||||
max-body-height="76vh"
|
||||
max-body-height="78vh"
|
||||
>
|
||||
<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="请输入模板名称" />
|
||||
<ElAlert
|
||||
v-if="canUpdateTemplate && !isOrgLeader"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="当前账号不是组织负责人,不能上传绩效模板"
|
||||
/>
|
||||
|
||||
<div v-if="canUpdateTemplate && isOrgLeader" class="performance-template-dialog__upload-sections">
|
||||
<ElCard shadow="never">
|
||||
<template #header>
|
||||
<div class="performance-template-dialog__section-title">单人上传</div>
|
||||
</template>
|
||||
|
||||
<ElForm :model="singleForm" label-position="top" class="performance-template-dialog__form">
|
||||
<div class="performance-template-dialog__grid">
|
||||
<ElFormItem label="员工">
|
||||
<ElSelect v-model="singleForm.employeeId" filterable placeholder="请选择员工">
|
||||
<ElOption
|
||||
v-for="item in employeeOptions"
|
||||
:key="item.employeeId"
|
||||
:label="
|
||||
item.employeeDeptName ? `${item.employeeName}(${item.employeeDeptName})` : item.employeeName
|
||||
"
|
||||
:value="item.employeeId"
|
||||
/>
|
||||
</ElSelect>
|
||||
</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">
|
||||
<ElFormItem label="模板名称">
|
||||
<ElInput v-model="singleForm.templateName" placeholder="请输入模板名称" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="模板年份">
|
||||
<ElInputNumber v-model="singleForm.templateYear" :min="2000" :max="2100" controls-position="right" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="Excel 文件" class="performance-template-dialog__full">
|
||||
<div class="performance-template-dialog__file-line">
|
||||
<ElUpload
|
||||
class="performance-template-dialog__upload-trigger"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept=".xlsx,.xls"
|
||||
:limit="1"
|
||||
:on-change="handleFileChange"
|
||||
accept=".xlsx"
|
||||
@change="handleSingleFileChange"
|
||||
>
|
||||
<ElButton plain class="performance-template-dialog__upload-button">
|
||||
<ElButton>
|
||||
<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 class="performance-template-dialog__file-text">
|
||||
{{ singleFileName || '未选择文件,仅支持 .xlsx' }}
|
||||
</span>
|
||||
</div>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<ElTooltip placement="top" effect="light">
|
||||
<template #content>仅支持 .xlsx格式,选择后会在这里显示文件名</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 label="备注" class="performance-template-dialog__full">
|
||||
<ElInput v-model="singleForm.remark" type="textarea" :rows="2" maxlength="500" show-word-limit />
|
||||
</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>
|
||||
<ElButton type="primary" :loading="singleUploading" @click="handleSingleUpload">上传模板</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
</ElCard>
|
||||
|
||||
<ElCard v-if="canQueryTemplate" shadow="never" body-class="business-table-card-body">
|
||||
<ElCard shadow="never">
|
||||
<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)">
|
||||
<div class="performance-template-dialog__section-title">批量导入</div>
|
||||
</template>
|
||||
|
||||
<ElForm :model="batchForm" label-position="top" class="performance-template-dialog__form">
|
||||
<div class="performance-template-dialog__grid">
|
||||
<ElFormItem label="模板年份">
|
||||
<ElInputNumber v-model="batchForm.templateYear" :min="2000" :max="2100" controls-position="right" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="Excel 文件" class="performance-template-dialog__full">
|
||||
<div class="performance-template-dialog__file-line">
|
||||
<ElUpload
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
multiple
|
||||
accept=".xlsx"
|
||||
@change="handleBatchFileChange"
|
||||
>
|
||||
<ElButton>
|
||||
<template #icon>
|
||||
<icon-mdi-refresh class="text-icon" :class="{ 'animate-spin': loading }" />
|
||||
<icon-mdi-upload class="text-icon" />
|
||||
</template>
|
||||
刷新
|
||||
选择多个文件
|
||||
</ElButton>
|
||||
<TableColumnSetting v-model:columns="columnChecks" />
|
||||
</ElSpace>
|
||||
</ElUpload>
|
||||
<div class="performance-template-dialog__batch-files">
|
||||
<span v-if="!batchFileNames.length" class="performance-template-dialog__file-text">
|
||||
未选择文件,仅支持 .xlsx
|
||||
</span>
|
||||
<ElTag v-for="name in batchFileNames" :key="name" effect="plain">{{ name }}</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="备注" class="performance-template-dialog__full">
|
||||
<ElInput v-model="batchForm.remark" type="textarea" :rows="2" maxlength="500" show-word-limit />
|
||||
</ElFormItem>
|
||||
</div>
|
||||
|
||||
<div class="performance-template-dialog__actions">
|
||||
<ElButton type="primary" :loading="batchUploading" @click="handleBatchImport">批量导入</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
</ElCard>
|
||||
</div>
|
||||
|
||||
<ElCard v-if="canQueryTemplate" shadow="never">
|
||||
<template #header>
|
||||
<div class="performance-template-dialog__section-title">模板列表</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" />
|
||||
<ElForm :model="searchParams" inline label-position="top" class="performance-template-dialog__search">
|
||||
<ElFormItem label="模板名称">
|
||||
<ElInput v-model="searchParams.templateName" clearable placeholder="请输入模板名称" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="员工姓名">
|
||||
<ElInput v-model="searchParams.employeeName" clearable placeholder="请输入员工姓名" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="模板年份">
|
||||
<ElInputNumber
|
||||
v-model="searchParams.templateYear"
|
||||
:min="2000"
|
||||
:max="2100"
|
||||
controls-position="right"
|
||||
placeholder="年份"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="上传人">
|
||||
<ElInput v-model="searchParams.uploadUserName" clearable placeholder="请输入上传人" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="状态">
|
||||
<ElSelect v-model="searchParams.activeFlag" clearable placeholder="全部">
|
||||
<ElOption :value="true" label="已启用" />
|
||||
<ElOption :value="false" label="未启用" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem class="performance-template-dialog__search-actions">
|
||||
<ElButton type="primary" @click="handleSearch">查询</ElButton>
|
||||
<ElButton @click="handleReset">重置</ElButton>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
<ElTable v-loading="loading" :data="tableData" border>
|
||||
<ElTableColumn type="index" label="序号" width="64" />
|
||||
<ElTableColumn prop="templateName" label="模板名称" min-width="180" show-overflow-tooltip />
|
||||
<ElTableColumn prop="employeeName" label="员工" min-width="110" show-overflow-tooltip />
|
||||
<ElTableColumn prop="employeeDeptName" label="部门" min-width="120" show-overflow-tooltip />
|
||||
<ElTableColumn prop="templateYear" label="年份" width="90" />
|
||||
<ElTableColumn label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<ElTag :type="row.activeFlag ? 'success' : 'info'">{{ row.activeFlag ? '已启用' : '未启用' }}</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="uploadUserName" label="上传人" width="110" />
|
||||
<ElTableColumn label="上传时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.uploadTime) }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="操作" width="100" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<ElButton
|
||||
v-if="canUpdateTemplate"
|
||||
link
|
||||
type="primary"
|
||||
:disabled="row.activeFlag || activatingId === row.id"
|
||||
@click="handleActivate(row)"
|
||||
>
|
||||
启用
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
</div>
|
||||
|
||||
<div class="mt-16px flex justify-end">
|
||||
<div class="performance-template-dialog__pagination">
|
||||
<ElPagination
|
||||
v-if="mobilePagination.total"
|
||||
layout="total,prev,pager,next,sizes"
|
||||
v-bind="mobilePagination"
|
||||
@current-change="mobilePagination['current-change']"
|
||||
@size-change="mobilePagination['size-change']"
|
||||
:current-page="searchParams.pageNo || 1"
|
||||
:page-size="searchParams.pageSize || 10"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next, sizes"
|
||||
@current-change="loadTemplatePage"
|
||||
@size-change="
|
||||
size => {
|
||||
searchParams.pageSize = size;
|
||||
loadTemplatePage(1);
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<ElEmpty v-if="!canQueryTemplate && !canUpdateTemplate" :image-size="80" description="当前账号没有绩效模板权限" />
|
||||
<ElEmpty
|
||||
v-if="!canQueryTemplate && !canUpdateTemplate"
|
||||
:image-size="80"
|
||||
description="当前账号没有绩效模板相关权限"
|
||||
/>
|
||||
</div>
|
||||
</BusinessFormDialog>
|
||||
</template>
|
||||
@@ -333,148 +504,47 @@ watch(visible, isVisible => {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.performance-template-dialog__upload-form {
|
||||
.performance-template-dialog__upload-sections {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.performance-template-dialog__upload-grid {
|
||||
.performance-template-dialog__section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.performance-template-dialog__form {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.performance-template-dialog__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 {
|
||||
.performance-template-dialog__full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.performance-template-dialog__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.performance-template-dialog__file-row {
|
||||
.performance-template-dialog__file-line {
|
||||
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;
|
||||
grid-template-columns: auto 1fr;
|
||||
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;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.performance-template-dialog__file-text {
|
||||
color: var(--el-text-color-secondary);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.performance-template-dialog__batch-files {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.performance-template-dialog__actions {
|
||||
@@ -482,32 +552,31 @@ watch(visible, isVisible => {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.performance-template-dialog__table {
|
||||
height: 360px;
|
||||
.performance-template-dialog__search {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
.performance-template-dialog__search-actions {
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.performance-template-dialog__upload-grid {
|
||||
.performance-template-dialog__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.performance-template-dialog__grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.performance-template-dialog__field--full,
|
||||
.performance-template-dialog__switch-field {
|
||||
.performance-template-dialog__full {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.performance-template-dialog__switch-box {
|
||||
height: 36px;
|
||||
.performance-template-dialog__file-line {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user