- 完善 user_agent_application 申请差旅报销预审槽位与消息组装 - 增强预算助理报告与风险建议卡片交互 - 重构登录页视觉样式与移动端响应式适配 - 优化个人工作台、文档中心、政策中心、员工管理等页面布局 - 拆分 travelRequestDetailPreReviewModel 为 advice/submit 模型 - 补充报销草稿、风险复核、Item Sync 与模板执行器测试覆盖
778 lines
30 KiB
Vue
778 lines
30 KiB
Vue
<template>
|
||
<section class="employee-center">
|
||
<Transition name="employee-view" mode="out-in">
|
||
<article v-if="selectedEmployee" key="detail" class="employee-detail">
|
||
<div class="detail-scroll">
|
||
<section class="detail-hero panel">
|
||
<div class="hero-profile">
|
||
<div class="hero-avatar">{{ selectedEmployee.avatar }}</div>
|
||
<div class="hero-copy">
|
||
<div class="hero-tag">{{ selectedEmployee.employeeNo }}</div>
|
||
<h2>{{ selectedEmployee.name }}</h2>
|
||
<p>
|
||
{{ selectedEmployee.department }} / {{ selectedEmployee.position }} /
|
||
{{ selectedEmployee.grade }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="hero-stats">
|
||
<div class="hero-stat">
|
||
<span>账号状态</span>
|
||
<strong>{{ selectedEmployee.status }}</strong>
|
||
</div>
|
||
<div class="hero-stat">
|
||
<span>直属上级</span>
|
||
<strong>{{ selectedEmployee.manager }}</strong>
|
||
</div>
|
||
<div class="hero-stat">
|
||
<span>财务归口</span>
|
||
<strong>{{ selectedEmployee.financeOwner }}</strong>
|
||
</div>
|
||
<div class="hero-stat">
|
||
<span>角色数量</span>
|
||
<strong>{{ roleCount }}</strong>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="detail-grid">
|
||
<section class="detail-main">
|
||
<article class="detail-card panel">
|
||
<div class="card-head">
|
||
<div>
|
||
<h3>基础信息</h3>
|
||
<p>维护员工编号、联系方式、入职日期与常用档案信息。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-grid">
|
||
<label class="field">
|
||
<span>员工姓名</span>
|
||
<input v-model="employeeForm.name" />
|
||
</label>
|
||
<label class="field">
|
||
<span>员工编号</span>
|
||
<input v-model="employeeForm.employeeNo" readonly />
|
||
</label>
|
||
<label class="field">
|
||
<span>性别</span>
|
||
<input v-model="employeeForm.gender" />
|
||
</label>
|
||
<label class="field">
|
||
<span>年龄</span>
|
||
<input
|
||
v-model="employeeForm.age"
|
||
type="number"
|
||
min="0"
|
||
max="120"
|
||
inputmode="numeric"
|
||
placeholder="请输入年龄"
|
||
@input="syncBirthDateFromAge"
|
||
/>
|
||
</label>
|
||
<label class="field">
|
||
<span>出生日期</span>
|
||
<input
|
||
v-model="employeeForm.birthDate"
|
||
type="date"
|
||
@change="syncAgeFromBirthDate"
|
||
/>
|
||
</label>
|
||
<label class="field">
|
||
<span>手机号</span>
|
||
<input v-model="employeeForm.phone" />
|
||
</label>
|
||
<label class="field">
|
||
<span>邮箱</span>
|
||
<input v-model="employeeForm.email" type="email" />
|
||
</label>
|
||
<label class="field">
|
||
<span>密码设置</span>
|
||
<input
|
||
v-model="employeeForm.password"
|
||
type="password"
|
||
autocomplete="new-password"
|
||
placeholder="留空则不修改"
|
||
/>
|
||
</label>
|
||
<label class="field">
|
||
<span>入职日期</span>
|
||
<input v-model="employeeForm.joinDate" type="date" />
|
||
</label>
|
||
<label class="field">
|
||
<span>办公地点</span>
|
||
<input v-model="employeeForm.location" />
|
||
</label>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="detail-card panel">
|
||
<div class="card-head">
|
||
<div>
|
||
<h3>组织与岗位</h3>
|
||
<p>配置部门、岗位、职级和管理归属,决定审批链路和数据访问边界。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-grid">
|
||
<label
|
||
class="field manager-picker department-picker"
|
||
:class="{ open: departmentPickerOpen }"
|
||
>
|
||
<span>所属部门</span>
|
||
<button
|
||
class="manager-picker-trigger"
|
||
type="button"
|
||
@click.stop="toggleDepartmentPicker"
|
||
>
|
||
<span class="manager-picker-label">{{ departmentDisplayLabel }}</span>
|
||
<i class="mdi mdi-chevron-down"></i>
|
||
</button>
|
||
<div
|
||
v-if="departmentPickerOpen"
|
||
class="manager-picker-panel"
|
||
@click.stop
|
||
>
|
||
<input
|
||
v-model="departmentSearchKeyword"
|
||
type="search"
|
||
placeholder="输入部门名称或编码搜索"
|
||
@keydown.enter.prevent="resolveDepartmentSelectionFromKeyword"
|
||
/>
|
||
<div class="manager-picker-options">
|
||
<button
|
||
v-for="option in filteredDepartmentOptions"
|
||
:key="option.id"
|
||
type="button"
|
||
class="manager-picker-option"
|
||
:class="{ active: employeeForm.organizationUnitCode === option.code }"
|
||
@click="selectDepartment(option)"
|
||
>
|
||
<strong>{{ option.name }}({{ option.code }})</strong>
|
||
<span v-if="option.unitType">{{ option.unitType }}</span>
|
||
</button>
|
||
<p
|
||
v-if="!filteredDepartmentOptions.length"
|
||
class="manager-picker-empty"
|
||
>
|
||
没有匹配的部门,请调整搜索关键词。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<label class="field">
|
||
<span>岗位</span>
|
||
<input v-model="employeeForm.position" />
|
||
</label>
|
||
<label class="field">
|
||
<span>职级</span>
|
||
<input v-model="employeeForm.grade" />
|
||
</label>
|
||
<label class="field manager-picker" :class="{ open: managerPickerOpen }">
|
||
<span>直属上级</span>
|
||
<button
|
||
class="manager-picker-trigger"
|
||
type="button"
|
||
@click.stop="toggleManagerPicker"
|
||
>
|
||
<span class="manager-picker-label">{{ managerDisplayLabel }}</span>
|
||
<i class="mdi mdi-chevron-down"></i>
|
||
</button>
|
||
<div
|
||
v-if="managerPickerOpen"
|
||
class="manager-picker-panel"
|
||
@click.stop
|
||
>
|
||
<input
|
||
v-model="managerSearchKeyword"
|
||
type="search"
|
||
placeholder="输入姓名、工号或部门搜索"
|
||
@keydown.enter.prevent="resolveManagerSelectionFromKeyword"
|
||
/>
|
||
<div class="manager-picker-options">
|
||
<button
|
||
type="button"
|
||
class="manager-picker-option"
|
||
:class="{ active: !hasManagerAssignment }"
|
||
@click="selectManager(null)"
|
||
>
|
||
<strong>无直属上级</strong>
|
||
<span>清空当前设置</span>
|
||
</button>
|
||
<button
|
||
v-for="option in filteredManagerOptions"
|
||
:key="option.id"
|
||
type="button"
|
||
class="manager-picker-option"
|
||
:class="{ active: employeeForm.managerEmployeeNo === option.employeeNo }"
|
||
@click="selectManager(option)"
|
||
>
|
||
<strong>{{ option.name }}({{ option.employeeNo }})</strong>
|
||
<span>{{ option.department }} / {{ option.position }}</span>
|
||
</button>
|
||
<p
|
||
v-if="!filteredManagerOptions.length"
|
||
class="manager-picker-empty"
|
||
>
|
||
没有匹配的员工,请调整搜索关键词。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<label class="field">
|
||
<span>财务归口</span>
|
||
<input v-model="employeeForm.financeOwner" />
|
||
</label>
|
||
<label class="field">
|
||
<span>成本中心</span>
|
||
<input v-model="employeeForm.costCenter" />
|
||
</label>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="detail-card panel">
|
||
<div class="card-head">
|
||
<div>
|
||
<h3>银行信息</h3>
|
||
<p>维护员工付款打款所需的户名、开户行与银行账号。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-grid">
|
||
<label class="field">
|
||
<span>户名</span>
|
||
<input v-model="employeeForm.bankAccountName" />
|
||
</label>
|
||
<label class="field">
|
||
<span>开户行</span>
|
||
<input v-model="employeeForm.bankName" />
|
||
</label>
|
||
<label class="field">
|
||
<span>银行账号</span>
|
||
<input v-model="employeeForm.bankAccountNo" inputmode="numeric" />
|
||
</label>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="detail-card panel">
|
||
<div class="card-head">
|
||
<div>
|
||
<h3>系统角色分配</h3>
|
||
<p>为员工分配管理员、财务人员、使用者、高级财务人员、预算监控员等业务角色。</p>
|
||
</div>
|
||
<span class="count-badge">{{ roleCount }} 个角色</span>
|
||
</div>
|
||
|
||
<div class="role-grid">
|
||
<label
|
||
v-for="role in roleOptions"
|
||
:key="role.id"
|
||
class="role-card"
|
||
:class="{ active: employeeForm.roleCodes.includes(role.code) }"
|
||
>
|
||
<input v-model="employeeForm.roleCodes" type="checkbox" :value="role.code" />
|
||
<div class="role-copy">
|
||
<strong>{{ role.label }}</strong>
|
||
<p>{{ role.desc }}</p>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</article>
|
||
</section>
|
||
|
||
<aside class="detail-side">
|
||
<article class="side-card panel">
|
||
<div class="card-head">
|
||
<div>
|
||
<h3>当前权限摘要</h3>
|
||
<p>角色组合带来的系统可见范围</p>
|
||
</div>
|
||
</div>
|
||
<div class="tag-list">
|
||
<span v-for="role in selectedRoleLabels" :key="role">{{ role }}</span>
|
||
</div>
|
||
<ul class="bullet-list">
|
||
<li v-for="item in selectedEmployee.permissions" :key="item">{{ item }}</li>
|
||
</ul>
|
||
</article>
|
||
|
||
<article class="side-card panel">
|
||
<div class="card-head">
|
||
<div>
|
||
<h3>最近变更</h3>
|
||
<p>仅保留最近 5 次角色与档案调整记录</p>
|
||
</div>
|
||
</div>
|
||
<div class="history-list">
|
||
<div
|
||
v-for="item in recentEmployeeHistory"
|
||
:key="`${item.occurredAt || item.time}-${item.action}`"
|
||
class="history-row"
|
||
>
|
||
<strong>{{ item.action }}</strong>
|
||
<span class="history-row-owner">{{ item.owner }}</span>
|
||
<small class="history-row-time">{{
|
||
formatEmployeeHistoryTime(item.time || item.occurredAt)
|
||
}}</small>
|
||
</div>
|
||
<p v-if="!recentEmployeeHistory.length" class="manager-picker-empty">
|
||
暂无变更记录
|
||
</p>
|
||
</div>
|
||
</article>
|
||
|
||
<article class="side-card panel publish-card">
|
||
<div>
|
||
<h3>生效状态</h3>
|
||
<p>当前修改将在保存后同步到审批、报销、知识和权限模块。</p>
|
||
</div>
|
||
<div class="publish-summary">
|
||
<span>上次同步:{{ selectedEmployee.lastSync }}</span>
|
||
<strong>{{ selectedEmployee.syncState }}</strong>
|
||
</div>
|
||
</article>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
|
||
<footer class="detail-actions">
|
||
<button class="back-action" type="button" @click="closeEmployeeDetail">
|
||
<i class="mdi mdi-arrow-left"></i>
|
||
<span>返回员工列表</span>
|
||
</button>
|
||
|
||
<div class="detail-action-group">
|
||
<button class="minor-action" type="button" :disabled="disableActionDisabled" @click="disableEmployeeAccount">
|
||
<i :class="statusActionCopy.buttonIcon"></i>
|
||
<span>{{ statusActionCopy.buttonLabel }}</span>
|
||
</button>
|
||
<button class="major-action" type="button" :disabled="actionBusy" @click="saveEmployeeChanges">
|
||
<i class="mdi mdi-check-circle-outline"></i>
|
||
<span>{{ actionState === 'save' ? '保存中...' : '保存并生效' }}</span>
|
||
</button>
|
||
</div>
|
||
</footer>
|
||
</article>
|
||
|
||
<article v-else key="list" class="employee-list panel">
|
||
<nav class="status-tabs" aria-label="员工状态筛选">
|
||
<button
|
||
v-for="tab in tabs"
|
||
:key="tab.label"
|
||
type="button"
|
||
:class="{ active: activeTab === tab.label }"
|
||
@click="activeTab = tab.label"
|
||
>
|
||
<span>{{ tab.label }}</span>
|
||
<small>{{ tab.count }}</small>
|
||
</button>
|
||
</nav>
|
||
|
||
<div class="list-toolbar">
|
||
<div class="filter-set">
|
||
<div class="list-search">
|
||
<i class="mdi mdi-magnify"></i>
|
||
<input
|
||
v-model="searchKeyword"
|
||
type="search"
|
||
placeholder="搜索姓名、工号、部门、岗位"
|
||
/>
|
||
</div>
|
||
|
||
<div class="picker-filter" :class="{ open: activeFilterPopover === 'department' }">
|
||
<button
|
||
class="picker-trigger"
|
||
type="button"
|
||
:aria-expanded="activeFilterPopover === 'department'"
|
||
aria-haspopup="dialog"
|
||
@click="toggleFilterPopover('department')"
|
||
>
|
||
<span class="picker-label">{{ selectedDepartment || '组织部门' }}</span>
|
||
<i class="mdi mdi-chevron-down"></i>
|
||
</button>
|
||
<div
|
||
v-if="activeFilterPopover === 'department'"
|
||
class="picker-popover"
|
||
role="dialog"
|
||
aria-label="选择组织部门"
|
||
>
|
||
<header>
|
||
<strong>选择组织部门</strong>
|
||
<button type="button" aria-label="关闭组织部门选择" @click="closeFilterPopover">
|
||
<i class="mdi mdi-close"></i>
|
||
</button>
|
||
</header>
|
||
<div class="picker-option-list">
|
||
<button
|
||
type="button"
|
||
class="picker-option"
|
||
:class="{ active: !selectedDepartment }"
|
||
@click="selectFilter('department', '')"
|
||
>
|
||
全部部门
|
||
</button>
|
||
<button
|
||
v-for="department in departmentOptions"
|
||
:key="department"
|
||
type="button"
|
||
class="picker-option"
|
||
:class="{ active: selectedDepartment === department }"
|
||
@click="selectFilter('department', department)"
|
||
>
|
||
{{ department }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="picker-filter" :class="{ open: activeFilterPopover === 'grade' }">
|
||
<button
|
||
class="picker-trigger"
|
||
type="button"
|
||
:aria-expanded="activeFilterPopover === 'grade'"
|
||
aria-haspopup="dialog"
|
||
@click="toggleFilterPopover('grade')"
|
||
>
|
||
<span class="picker-label">{{ selectedGrade || '职级' }}</span>
|
||
<i class="mdi mdi-chevron-down"></i>
|
||
</button>
|
||
<div
|
||
v-if="activeFilterPopover === 'grade'"
|
||
class="picker-popover"
|
||
role="dialog"
|
||
aria-label="选择职级"
|
||
>
|
||
<header>
|
||
<strong>选择职级</strong>
|
||
<button type="button" aria-label="关闭职级选择" @click="closeFilterPopover">
|
||
<i class="mdi mdi-close"></i>
|
||
</button>
|
||
</header>
|
||
<div class="picker-option-list">
|
||
<button
|
||
type="button"
|
||
class="picker-option"
|
||
:class="{ active: !selectedGrade }"
|
||
@click="selectFilter('grade', '')"
|
||
>
|
||
全部职级
|
||
</button>
|
||
<button
|
||
v-for="grade in gradeOptions"
|
||
:key="grade"
|
||
type="button"
|
||
class="picker-option"
|
||
:class="{ active: selectedGrade === grade }"
|
||
@click="selectFilter('grade', grade)"
|
||
>
|
||
{{ grade }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="picker-filter" :class="{ open: activeFilterPopover === 'role' }">
|
||
<button
|
||
class="picker-trigger"
|
||
type="button"
|
||
:aria-expanded="activeFilterPopover === 'role'"
|
||
aria-haspopup="dialog"
|
||
@click="toggleFilterPopover('role')"
|
||
>
|
||
<span class="picker-label">{{ selectedRole || '系统角色' }}</span>
|
||
<i class="mdi mdi-chevron-down"></i>
|
||
</button>
|
||
<div
|
||
v-if="activeFilterPopover === 'role'"
|
||
class="picker-popover"
|
||
role="dialog"
|
||
aria-label="选择系统角色"
|
||
>
|
||
<header>
|
||
<strong>选择系统角色</strong>
|
||
<button type="button" aria-label="关闭系统角色选择" @click="closeFilterPopover">
|
||
<i class="mdi mdi-close"></i>
|
||
</button>
|
||
</header>
|
||
<div class="picker-option-list">
|
||
<button
|
||
type="button"
|
||
class="picker-option"
|
||
:class="{ active: !selectedRole }"
|
||
@click="selectFilter('role', '')"
|
||
>
|
||
全部角色
|
||
</button>
|
||
<button
|
||
v-for="role in roleFilterOptions"
|
||
:key="role"
|
||
type="button"
|
||
class="picker-option"
|
||
:class="{ active: selectedRole === role }"
|
||
@click="selectFilter('role', role)"
|
||
>
|
||
{{ role }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toolbar-actions">
|
||
<button v-if="hasActiveFilters" class="ghost-filter-btn" type="button" @click="resetFilters">
|
||
<i class="mdi mdi-filter-remove-outline"></i>
|
||
<span>清空筛选</span>
|
||
</button>
|
||
|
||
<button class="template-btn" type="button" :disabled="importExportBusy" @click="handleDownloadTemplate">
|
||
<i class="mdi mdi-file-download-outline"></i>
|
||
<span>下载模板</span>
|
||
</button>
|
||
|
||
<button class="export-btn" type="button" :disabled="importExportBusy" @click="handleExportEmployees">
|
||
<i class="mdi mdi-export"></i>
|
||
<span>{{ actionState === 'export' ? '导出中...' : '导出员工' }}</span>
|
||
</button>
|
||
|
||
<button class="create-btn" type="button" :disabled="importExportBusy" @click="openImportFilePicker">
|
||
<i class="mdi mdi-file-upload-outline"></i>
|
||
<span>{{ actionState === 'import' ? '导入中...' : '导入员工' }}</span>
|
||
</button>
|
||
|
||
<input
|
||
ref="importFileInput"
|
||
class="import-file-input"
|
||
type="file"
|
||
accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||
@change="handleImportFileChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="hint">
|
||
<i class="mdi mdi-information-outline"></i>
|
||
点击任意员工行可进入基础信息与角色权限编辑界面;导入将按员工编号覆盖已有档案,任一行校验失败则整批不写入。
|
||
</p>
|
||
|
||
<div v-if="activeFilterTokens.length" class="active-filter-strip">
|
||
<span v-for="token in activeFilterTokens" :key="token" class="active-filter-chip">
|
||
{{ token }}
|
||
</span>
|
||
</div>
|
||
|
||
<div class="table-wrap">
|
||
<div v-if="loading" class="table-state">
|
||
<TableLoadingState
|
||
title="员工数据同步中"
|
||
message="正在加载员工档案与角色权限"
|
||
icon="mdi mdi-account-group-outline"
|
||
floating
|
||
/>
|
||
</div>
|
||
|
||
<div v-else-if="errorMessage" class="table-state error">
|
||
<i class="mdi mdi-alert-circle-outline"></i>
|
||
<p>{{ errorMessage }}</p>
|
||
<button type="button" class="state-action" @click="loadEmployees">重新加载</button>
|
||
</div>
|
||
|
||
<TableEmptyState
|
||
v-else-if="!visibleEmployees.length"
|
||
:eyebrow="employeeEmptyState.eyebrow"
|
||
:title="employeeEmptyState.title"
|
||
:description="employeeEmptyState.desc"
|
||
:icon="employeeEmptyState.icon"
|
||
:action-label="employeeEmptyState.actionLabel"
|
||
:action-icon="employeeEmptyState.actionIcon"
|
||
:tone="employeeEmptyState.tone"
|
||
:art-label="employeeEmptyState.artLabel"
|
||
:tips="employeeEmptyState.tips"
|
||
@action="handleEmployeeEmptyAction"
|
||
/>
|
||
|
||
<table v-else>
|
||
<colgroup>
|
||
<col class="col-employee">
|
||
<col class="col-employee-no">
|
||
<col class="col-department">
|
||
<col class="col-position">
|
||
<col class="col-grade">
|
||
<col class="col-role">
|
||
<col class="col-status">
|
||
<col class="col-updated">
|
||
</colgroup>
|
||
<thead>
|
||
<tr>
|
||
<th>员工</th>
|
||
<th>工号</th>
|
||
<th>部门</th>
|
||
<th>岗位</th>
|
||
<th>职级</th>
|
||
<th>系统角色</th>
|
||
<th>状态</th>
|
||
<th>最近更新</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr
|
||
v-for="employee in visibleEmployees"
|
||
:key="employee.id"
|
||
:class="{ spotlight: employee.spotlight }"
|
||
@click="openEmployeeDetail(employee)"
|
||
>
|
||
<td data-label="员工">
|
||
<div class="employee-cell">
|
||
<span class="employee-avatar">{{ employee.avatar }}</span>
|
||
<div>
|
||
<strong>{{ employee.name }}</strong>
|
||
<span>{{ employee.email }}</span>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td data-label="工号">{{ employee.employeeNo }}</td>
|
||
<td data-label="部门">{{ employee.department }}</td>
|
||
<td data-label="岗位">{{ employee.position }}</td>
|
||
<td data-label="职级"><span class="level-pill">{{ employee.grade }}</span></td>
|
||
<td data-label="系统角色">
|
||
<div class="role-stack">
|
||
<span
|
||
v-for="role in employee.roles.slice(0, 2)"
|
||
:key="role"
|
||
class="role-pill"
|
||
>
|
||
{{ role }}
|
||
</span>
|
||
<span v-if="employee.roles.length > 2" class="more-pill">
|
||
+{{ employee.roles.length - 2 }}
|
||
</span>
|
||
</div>
|
||
</td>
|
||
<td data-label="状态">
|
||
<span class="status-pill" :class="employee.statusTone">{{ employee.status }}</span>
|
||
</td>
|
||
<td class="cell-updated" data-label="最近更新">{{ employee.updatedAt }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<footer v-if="!loading && !errorMessage && totalCount" class="list-foot">
|
||
<span class="page-summary">共 {{ totalCount }} 条,目前第 {{ currentPage }} 页</span>
|
||
<div class="pager" aria-label="分页">
|
||
<button
|
||
class="page-nav"
|
||
type="button"
|
||
:disabled="currentPage === 1"
|
||
aria-label="上一页"
|
||
@click="currentPage--"
|
||
>
|
||
<i class="mdi mdi-chevron-left"></i>
|
||
</button>
|
||
<button
|
||
v-for="page in totalPages"
|
||
:key="page"
|
||
class="page-number"
|
||
:class="{ active: currentPage === page }"
|
||
type="button"
|
||
:aria-current="currentPage === page ? 'page' : undefined"
|
||
@click="currentPage = page"
|
||
>
|
||
{{ page }}
|
||
</button>
|
||
<button
|
||
class="page-nav"
|
||
type="button"
|
||
:disabled="currentPage === totalPages"
|
||
aria-label="下一页"
|
||
@click="currentPage++"
|
||
>
|
||
<i class="mdi mdi-chevron-right"></i>
|
||
</button>
|
||
</div>
|
||
<EnterpriseSelect
|
||
v-model="pageSize"
|
||
class="page-size-select"
|
||
:options="pageSizeOptions"
|
||
size="small"
|
||
@change="changePageSize"
|
||
/>
|
||
</footer>
|
||
</article>
|
||
</Transition>
|
||
|
||
<ConfirmDialog
|
||
:open="disableDialogOpen"
|
||
:badge="statusActionCopy.badge"
|
||
:badge-tone="statusActionCopy.badgeTone"
|
||
:title="statusActionCopy.title"
|
||
:description="statusActionCopy.description"
|
||
cancel-text="取消"
|
||
:confirm-text="statusActionCopy.confirmText"
|
||
:busy-text="statusActionCopy.busyText"
|
||
:confirm-tone="statusActionCopy.confirmTone"
|
||
:confirm-icon="statusActionCopy.confirmIcon"
|
||
:busy="actionState === 'disable'"
|
||
@close="closeDisableDialog"
|
||
@confirm="confirmDisableEmployeeAccount"
|
||
/>
|
||
|
||
<ConfirmDialog
|
||
:open="importConfirmDialogOpen"
|
||
badge="导入确认"
|
||
badge-tone="warning"
|
||
title="确认导入员工 Excel?"
|
||
description="系统将先校验全部数据,全部通过后才写入数据库。若存在错误,将不会修改任何现有员工信息。"
|
||
cancel-text="取消"
|
||
confirm-text="开始导入"
|
||
busy-text="导入中..."
|
||
confirm-tone="primary"
|
||
confirm-icon="mdi mdi-file-upload-outline"
|
||
:busy="actionState === 'import'"
|
||
@close="closeImportConfirmDialog"
|
||
@confirm="confirmImportEmployees"
|
||
/>
|
||
|
||
<ConfirmDialog
|
||
:open="importErrorDialogOpen"
|
||
badge="导入失败"
|
||
badge-tone="danger"
|
||
title="导入未执行"
|
||
:description="importResultMessage"
|
||
cancel-text="关闭"
|
||
confirm-text="下载模板"
|
||
confirm-tone="primary"
|
||
confirm-icon="mdi mdi-file-download-outline"
|
||
@close="closeImportErrorDialog"
|
||
@confirm="handleDownloadTemplate"
|
||
>
|
||
<div class="import-error-table-wrap">
|
||
<table class="import-error-table">
|
||
<thead>
|
||
<tr>
|
||
<th>行号</th>
|
||
<th>字段</th>
|
||
<th>工号</th>
|
||
<th>原因</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="(item, index) in importErrors" :key="`${item.row}-${item.column}-${index}`">
|
||
<td>{{ item.row || '-' }}</td>
|
||
<td>{{ item.column }}</td>
|
||
<td>{{ item.employeeNo || '-' }}</td>
|
||
<td>{{ item.message }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</ConfirmDialog>
|
||
</section>
|
||
</template>
|
||
|
||
<script src="./scripts/EmployeeManagementView.js"></script>
|
||
|
||
<style scoped>
|
||
@import "../assets/styles/views/employee-management-view.css";
|
||
</style>
|