feat(performance、workReport、workbench): 添加绩效表预填功能并优化工作台组件
This commit is contained in:
@@ -65,6 +65,11 @@ type MonthlyResultResponse = Omit<Api.Performance.Sheet.MonthlyResult, 'sheetId'
|
|||||||
employeeId: StringIdResponse;
|
employeeId: StringIdResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PrefillResponse = Omit<Api.Performance.Sheet.PrefillData, 'sourceSheetId' | 'cellValues'> & {
|
||||||
|
sourceSheetId?: StringIdResponse | null;
|
||||||
|
cellValues?: Record<string, string | number | null | undefined> | null;
|
||||||
|
};
|
||||||
|
|
||||||
type TeamSummaryResponse = Omit<
|
type TeamSummaryResponse = Omit<
|
||||||
Api.Performance.Team.Summary,
|
Api.Performance.Team.Summary,
|
||||||
| 'expectedSheetCount'
|
| 'expectedSheetCount'
|
||||||
@@ -185,6 +190,28 @@ function normalizeMonthlyResult(response: MonthlyResultResponse): Api.Performanc
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizePrefillData(response: PrefillResponse): Api.Performance.Sheet.PrefillData {
|
||||||
|
const cellValues = Object.fromEntries(
|
||||||
|
Object.entries(response.cellValues || {}).flatMap(([address, value]) => {
|
||||||
|
const normalizedAddress = String(address || '')
|
||||||
|
.trim()
|
||||||
|
.toUpperCase();
|
||||||
|
|
||||||
|
if (!normalizedAddress || value === null || value === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [[normalizedAddress, String(value)]];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceSheetId: normalizeNullableStringId(response.sourceSheetId),
|
||||||
|
sourcePeriodMonth: response.sourcePeriodMonth ?? null,
|
||||||
|
cellValues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeTeamSummary(response: TeamSummaryResponse): Api.Performance.Team.Summary {
|
function normalizeTeamSummary(response: TeamSummaryResponse): Api.Performance.Team.Summary {
|
||||||
return {
|
return {
|
||||||
...response,
|
...response,
|
||||||
@@ -464,6 +491,17 @@ export async function fetchPerformanceMonthlyResult(employeeId: string, periodMo
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchPerformanceSheetPrefill(employeeId: string, periodMonth: string) {
|
||||||
|
const result = await request<PrefillResponse>({
|
||||||
|
...safeJsonRequestConfig,
|
||||||
|
url: `${SHEET_PREFIX}/prefill`,
|
||||||
|
method: 'get',
|
||||||
|
params: { employeeId, periodMonth }
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapServiceResult(result as ServiceRequestResult<PrefillResponse>, normalizePrefillData);
|
||||||
|
}
|
||||||
|
|
||||||
export function fetchPerformanceSheetStatusDict() {
|
export function fetchPerformanceSheetStatusDict() {
|
||||||
return request<Api.Performance.Sheet.StatusDict[]>({
|
return request<Api.Performance.Sheet.StatusDict[]>({
|
||||||
...safeJsonRequestConfig,
|
...safeJsonRequestConfig,
|
||||||
|
|||||||
10
src/typings/api/performance.d.ts
vendored
10
src/typings/api/performance.d.ts
vendored
@@ -21,6 +21,10 @@ declare namespace Api {
|
|||||||
actualScoreTotalCell?: string | null;
|
actualScoreTotalCell?: string | null;
|
||||||
baseScoreTotalCell?: string | null;
|
baseScoreTotalCell?: string | null;
|
||||||
extraScoreTotalCell?: string | null;
|
extraScoreTotalCell?: string | null;
|
||||||
|
resultDescriptionCells?: string[] | null;
|
||||||
|
actualScoreCells?: string[] | null;
|
||||||
|
baseScoreCells?: string[] | null;
|
||||||
|
extraScoreCells?: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Template {
|
interface Template {
|
||||||
@@ -172,6 +176,12 @@ declare namespace Api {
|
|||||||
extraScoreTotal?: string | number | null;
|
extraScoreTotal?: string | number | null;
|
||||||
statusCode?: Common.SheetStatusCode | null;
|
statusCode?: Common.SheetStatusCode | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PrefillData {
|
||||||
|
sourceSheetId?: string | null;
|
||||||
|
sourcePeriodMonth?: string | null;
|
||||||
|
cellValues: Record<string, string>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Team {
|
namespace Team {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
createPerformanceSheet,
|
createPerformanceSheet,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
fetchPerformanceSheet,
|
fetchPerformanceSheet,
|
||||||
|
fetchPerformanceSheetPrefill,
|
||||||
fetchPerformanceTemplateCurrent,
|
fetchPerformanceTemplateCurrent,
|
||||||
sendPerformanceSheet,
|
sendPerformanceSheet,
|
||||||
updatePerformanceSheetExcel,
|
updatePerformanceSheetExcel,
|
||||||
@@ -54,12 +55,16 @@ const { createRequiredRule } = useFormRules();
|
|||||||
|
|
||||||
const containerRef = ref<HTMLDivElement>();
|
const containerRef = ref<HTMLDivElement>();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const prefillLoading = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const sending = ref(false);
|
const sending = ref(false);
|
||||||
const currentSheet = ref<Api.Performance.Sheet.Sheet | null>(null);
|
const currentSheet = ref<Api.Performance.Sheet.Sheet | null>(null);
|
||||||
const currentTemplate = ref<Api.Performance.Template.Template | null>(null);
|
const currentTemplate = ref<Api.Performance.Template.Template | null>(null);
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref('');
|
||||||
|
const prefillSourcePeriodMonth = ref('');
|
||||||
const viewportWidth = ref(typeof window === 'undefined' ? 1920 : window.innerWidth);
|
const viewportWidth = ref(typeof window === 'undefined' ? 1920 : window.innerWidth);
|
||||||
|
const loadedCreateContextKey = ref('');
|
||||||
|
const createContextInitializing = ref(false);
|
||||||
|
|
||||||
const createForm = reactive({
|
const createForm = reactive({
|
||||||
periodMonth: createDefaultPeriodMonth(),
|
periodMonth: createDefaultPeriodMonth(),
|
||||||
@@ -380,6 +385,10 @@ function getActiveWorkbook() {
|
|||||||
return univerAPI?.getActiveWorkbook?.();
|
return univerAPI?.getActiveWorkbook?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCreateContextKey() {
|
||||||
|
return `${createForm.periodMonth || ''}::${createForm.employeeId || ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
function parseCellAddress(address?: string | null) {
|
function parseCellAddress(address?: string | null) {
|
||||||
const text = String(address || '')
|
const text = String(address || '')
|
||||||
.trim()
|
.trim()
|
||||||
@@ -415,6 +424,29 @@ function readCell(address?: string | null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function writeCell(address: string, value: string) {
|
||||||
|
const position = parseCellAddress(address);
|
||||||
|
const workbook = getActiveWorkbook();
|
||||||
|
const sheet = workbook?.getActiveSheet?.();
|
||||||
|
if (!position || !sheet) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const range = sheet.getRange(position.row, position.column);
|
||||||
|
range?.setValue?.(value);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPrefillValues(cellValues: Record<string, string>) {
|
||||||
|
Object.entries(cellValues).forEach(([address, value]) => {
|
||||||
|
if (!address) return;
|
||||||
|
|
||||||
|
writeCell(address, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function readScores() {
|
function readScores() {
|
||||||
const mapping = currentTemplate.value?.scoreCellMapping;
|
const mapping = currentTemplate.value?.scoreCellMapping;
|
||||||
|
|
||||||
@@ -451,9 +483,36 @@ function createInitialFileName() {
|
|||||||
return '绩效表.xlsx';
|
return '绩效表.xlsx';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function applyCreatePrefill() {
|
||||||
|
prefillSourcePeriodMonth.value = '';
|
||||||
|
|
||||||
|
if (!isCreateMode.value || !createForm.employeeId || !createForm.periodMonth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefillLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { error, data } = await fetchPerformanceSheetPrefill(createForm.employeeId, createForm.periodMonth);
|
||||||
|
if (error || !data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefillSourcePeriodMonth.value = data.sourcePeriodMonth ?? '';
|
||||||
|
|
||||||
|
if (Object.keys(data.cellValues).length) {
|
||||||
|
applyPrefillValues(data.cellValues);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
prefillLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadWorkbook() {
|
async function loadWorkbook() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
prefillLoading.value = false;
|
||||||
errorMessage.value = '';
|
errorMessage.value = '';
|
||||||
|
prefillSourcePeriodMonth.value = '';
|
||||||
currentSheet.value = null;
|
currentSheet.value = null;
|
||||||
currentTemplate.value = null;
|
currentTemplate.value = null;
|
||||||
|
|
||||||
@@ -505,6 +564,14 @@ async function loadWorkbook() {
|
|||||||
const snapshot = await transformExcelToUniver(file);
|
const snapshot = await transformExcelToUniver(file);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
createWorkbook(applyCreateEmployeeName(snapshot));
|
createWorkbook(applyCreateEmployeeName(snapshot));
|
||||||
|
|
||||||
|
if (isCreateMode.value) {
|
||||||
|
await nextTick();
|
||||||
|
await applyCreatePrefill();
|
||||||
|
loadedCreateContextKey.value = getCreateContextKey();
|
||||||
|
} else {
|
||||||
|
loadedCreateContextKey.value = '';
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : 'Excel 解析失败';
|
errorMessage.value = error instanceof Error ? error.message : 'Excel 解析失败';
|
||||||
} finally {
|
} finally {
|
||||||
@@ -546,6 +613,7 @@ function createInitialPeriodMonth() {
|
|||||||
function initializeCreateForm() {
|
function initializeCreateForm() {
|
||||||
createForm.periodMonth = createInitialPeriodMonth();
|
createForm.periodMonth = createInitialPeriodMonth();
|
||||||
createForm.employeeId = props.initialEmployeeId || '';
|
createForm.employeeId = props.initialEmployeeId || '';
|
||||||
|
prefillSourcePeriodMonth.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeSave(): Promise<PerformanceSheetSavePayload | null> {
|
async function executeSave(): Promise<PerformanceSheetSavePayload | null> {
|
||||||
@@ -643,31 +711,48 @@ watch(visible, async isVisible => {
|
|||||||
disposeUniver();
|
disposeUniver();
|
||||||
currentSheet.value = null;
|
currentSheet.value = null;
|
||||||
currentTemplate.value = null;
|
currentTemplate.value = null;
|
||||||
|
loadedCreateContextKey.value = '';
|
||||||
|
createContextInitializing.value = false;
|
||||||
|
prefillLoading.value = false;
|
||||||
|
prefillSourcePeriodMonth.value = '';
|
||||||
initializeCreateForm();
|
initializeCreateForm();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCreateMode.value) {
|
createContextInitializing.value = isCreateMode.value;
|
||||||
initializeCreateForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
await nextTick();
|
try {
|
||||||
await loadWorkbook();
|
if (isCreateMode.value) {
|
||||||
|
initializeCreateForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
await loadWorkbook();
|
||||||
|
} finally {
|
||||||
|
createContextInitializing.value = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => createForm.employeeId,
|
() => [createForm.employeeId, createForm.periodMonth] as const,
|
||||||
async (employeeId, previousEmployeeId) => {
|
async ([employeeId, periodMonth], [previousEmployeeId, previousPeriodMonth]) => {
|
||||||
if (!visible.value || !isCreateMode.value || !employeeId || employeeId === previousEmployeeId) {
|
if (!visible.value || !isCreateMode.value || createContextInitializing.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workbook = getActiveWorkbook();
|
const currentKey = `${periodMonth || ''}::${employeeId || ''}`;
|
||||||
if (!workbook) return;
|
const previousKey = `${previousPeriodMonth || ''}::${previousEmployeeId || ''}`;
|
||||||
|
if (
|
||||||
|
currentKey === previousKey ||
|
||||||
|
currentKey === loadedCreateContextKey.value ||
|
||||||
|
loading.value ||
|
||||||
|
prefillLoading.value
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const snapshot = workbook.save();
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
createWorkbook(applyCreateEmployeeName(snapshot));
|
await loadWorkbook();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -701,14 +786,24 @@ onMounted(() => {
|
|||||||
type="month"
|
type="month"
|
||||||
value-format="YYYY-MM"
|
value-format="YYYY-MM"
|
||||||
placeholder="选择绩效月份"
|
placeholder="选择绩效月份"
|
||||||
|
:disabled="loading || prefillLoading || saving || sending"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="下属" prop="employeeId" class="performance-excel-editor__form-item">
|
<ElFormItem label="下属" prop="employeeId" class="performance-excel-editor__form-item">
|
||||||
<ElSelect v-model="createForm.employeeId" filterable placeholder="选择下属" style="width: 200px">
|
<ElSelect
|
||||||
|
v-model="createForm.employeeId"
|
||||||
|
filterable
|
||||||
|
placeholder="选择下属"
|
||||||
|
style="width: 200px"
|
||||||
|
:disabled="loading || prefillLoading || saving || sending"
|
||||||
|
>
|
||||||
<ElOption v-for="opt in props.subordinateOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
<ElOption v-for="opt in props.subordinateOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
|
<div v-if="prefillLoading || prefillSourcePeriodMonth" class="performance-excel-editor__prefill-tip">
|
||||||
|
{{ prefillLoading ? '正在自动带出上一份绩效数据...' : `已自动带出 ${prefillSourcePeriodMonth} 的绩效数据` }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-loading="loading" class="performance-excel-editor">
|
<div v-loading="loading" class="performance-excel-editor">
|
||||||
@@ -727,14 +822,26 @@ onMounted(() => {
|
|||||||
<template v-if="canSave || showApprovalFooter" #footer>
|
<template v-if="canSave || showApprovalFooter" #footer>
|
||||||
<div class="performance-excel-editor__footer">
|
<div class="performance-excel-editor__footer">
|
||||||
<template v-if="canSave">
|
<template v-if="canSave">
|
||||||
<ElButton v-if="!props.hideDraftAction" :loading="saving" :disabled="sending" @click="handleSaveDraft">
|
<ElButton
|
||||||
|
v-if="!props.hideDraftAction"
|
||||||
|
:loading="saving"
|
||||||
|
:disabled="sending || loading || prefillLoading"
|
||||||
|
@click="handleSaveDraft"
|
||||||
|
>
|
||||||
保存草稿
|
保存草稿
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton type="primary" :loading="sending" :disabled="saving" @click="handleSaveAndSend">发送绩效</ElButton>
|
<ElButton
|
||||||
|
type="primary"
|
||||||
|
:loading="sending"
|
||||||
|
:disabled="saving || loading || prefillLoading"
|
||||||
|
@click="handleSaveAndSend"
|
||||||
|
>
|
||||||
|
发送绩效
|
||||||
|
</ElButton>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="showApprovalFooter">
|
<template v-else-if="showApprovalFooter">
|
||||||
<ElButton @click="handleClose">退出审批</ElButton>
|
<ElButton @click="handleClose">退出</ElButton>
|
||||||
<ElButton type="primary" @click="handleStartApproval">开始审批</ElButton>
|
<ElButton type="primary" @click="handleStartApproval">审批</ElButton>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -763,6 +870,12 @@ onMounted(() => {
|
|||||||
margin-right: 24px;
|
margin-right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.performance-excel-editor__prefill-tip {
|
||||||
|
margin-top: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.performance-excel-editor__footer {
|
.performance-excel-editor__footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -864,8 +864,8 @@ watch(
|
|||||||
</ElForm>
|
</ElForm>
|
||||||
|
|
||||||
<div class="form-actions approval-form-actions">
|
<div class="form-actions approval-form-actions">
|
||||||
<ElButton @click="emit('back')">退出审批</ElButton>
|
<ElButton @click="emit('back')">退出</ElButton>
|
||||||
<ElButton type="primary" @click="openAuditDialog">开始审批</ElButton>
|
<ElButton type="primary" @click="openAuditDialog">审批</ElButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BusinessFormDialog
|
<BusinessFormDialog
|
||||||
|
|||||||
@@ -539,8 +539,8 @@ function notifyTitleSaved(item: WorkItem) {
|
|||||||
<ElButton type="primary" @click="emit('submit')">提交审批</ElButton>
|
<ElButton type="primary" @click="emit('submit')">提交审批</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="scene === 'approval'" class="form-actions approval-form-actions">
|
<div v-else-if="scene === 'approval'" class="form-actions approval-form-actions">
|
||||||
<ElButton @click="emit('back')">退出审批</ElButton>
|
<ElButton @click="emit('back')">退出</ElButton>
|
||||||
<ElButton type="primary" @click="emit('requestApprove')">开始审批</ElButton>
|
<ElButton type="primary" @click="emit('requestApprove')">审批</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1500,8 +1500,8 @@ function syncRichSupport(item: PlanItem, event: Event) {
|
|||||||
<ElButton type="primary" @click="emit('submit')">提交审批</ElButton>
|
<ElButton type="primary" @click="emit('submit')">提交审批</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="scene === 'approval'" class="form-actions approval-form-actions">
|
<div v-else-if="scene === 'approval'" class="form-actions approval-form-actions">
|
||||||
<ElButton @click="emit('back')">退出审批</ElButton>
|
<ElButton @click="emit('back')">退出</ElButton>
|
||||||
<ElButton type="primary" @click="emit('requestApprove')">开始审批</ElButton>
|
<ElButton type="primary" @click="emit('requestApprove')">审批</ElButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BusinessFormDialog
|
<BusinessFormDialog
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ const TASK_ACTION_ICONS: Partial<Record<Api.Project.ProjectTaskActionCode, Compo
|
|||||||
cancel: markRaw(IconMdiCloseCircleOutline)
|
cancel: markRaw(IconMdiCloseCircleOutline)
|
||||||
};
|
};
|
||||||
|
|
||||||
function normalizeTaskTodoDeadline(deadline: string | null) {
|
function normalizeTodoDeadline(deadline: string | null) {
|
||||||
if (!deadline) return null;
|
if (!deadline) return null;
|
||||||
|
|
||||||
if (/^\d{4}-\d{2}-\d{2}$/.test(deadline)) {
|
if (/^\d{4}-\d{2}-\d{2}$/.test(deadline)) {
|
||||||
@@ -831,7 +831,7 @@ async function loadMyTaskItems() {
|
|||||||
category: 'task' as const,
|
category: 'task' as const,
|
||||||
title: task.taskTitle,
|
title: task.taskTitle,
|
||||||
createdTime: task.createTime,
|
createdTime: task.createTime,
|
||||||
deadline: normalizeTaskTodoDeadline(task.plannedEndDate),
|
deadline: normalizeTodoDeadline(task.plannedEndDate),
|
||||||
source: task.projectName,
|
source: task.projectName,
|
||||||
progressRate: task.progressRate,
|
progressRate: task.progressRate,
|
||||||
priority: mapTaskTodoPriority(task.priority),
|
priority: mapTaskTodoPriority(task.priority),
|
||||||
@@ -861,7 +861,7 @@ async function loadPersonalTodoItems() {
|
|||||||
category: 'personal',
|
category: 'personal',
|
||||||
title: item.taskTitle,
|
title: item.taskTitle,
|
||||||
createdTime: item.createTime,
|
createdTime: item.createTime,
|
||||||
deadline: item.plannedEndDate,
|
deadline: normalizeTodoDeadline(item.plannedEndDate),
|
||||||
source: '我的事项',
|
source: '我的事项',
|
||||||
progressRate: item.progressRate,
|
progressRate: item.progressRate,
|
||||||
routeKey: 'personal-center_my-item'
|
routeKey: 'personal-center_my-item'
|
||||||
|
|||||||
Reference in New Issue
Block a user