| | |
| | | <div class="page"> |
| | | <el-form :inline="true" class="search-form" @submit.prevent="doSearch"> |
| | | <el-form-item label="分组名称"> |
| | | <el-input v-model="filters.groupName" placeholder="模糊" clearable style="width: 160px" /> |
| | | <el-input v-model="filters.groupName" placeholder="请输入分组名称" clearable style="width: 160px" /> |
| | | </el-form-item> |
| | | <el-form-item label="负责人电话"> |
| | | <el-input v-model="filters.leaderMobile" placeholder="模糊" clearable style="width: 160px" /> |
| | | <el-input v-model="filters.leaderMobile" placeholder="请输入负责人电话" clearable style="width: 160px" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" native-type="submit">搜索</el-button> |
| | | <el-button @click="resetSearch">重置</el-button> |
| | | </el-form-item> |
| | | <el-form-item class="push-right"> |
| | | <el-button type="success" @click="openCreate">新建分组</el-button> |
| | | <el-button type="primary" @click="openCreate">新建分组</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <el-table :data="rows" v-loading="loading" stripe> |
| | | <el-table-column label="序号" width="64" type="index" :index="indexMethod" /> |
| | | <el-table-column prop="groupName" label="分组名称" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="leaderName" label="负责人" width="110" show-overflow-tooltip /> |
| | | <el-table-column prop="leaderMobileMasked" label="联系电话" width="140" /> |
| | | <el-table-column prop="memberCount" label="绑定人员数量" width="120" /> |
| | | <el-table-column label="创建时间" width="170"> |
| | | <template #default="{ row }">{{ formatTime(row.createTime) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="240" fixed="right"> |
| | | <template #default="{ row }"> |
| | | <div class="table-ops"> |
| | | <el-button type="primary" link @click="goDetail(row)">详情</el-button> |
| | | <el-button type="primary" link @click="openEdit(row)">编辑</el-button> |
| | | <el-button type="danger" link @click="confirmDelete(row)">删除</el-button> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div class="table-wrap"> |
| | | <el-table :data="rows" v-loading="loading" stripe height="100%"> |
| | | <el-table-column label="序号" width="64" type="index" :index="indexMethod" /> |
| | | <el-table-column prop="groupName" label="分组名称" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="leaderName" label="负责人" width="110" show-overflow-tooltip /> |
| | | <el-table-column prop="leaderMobileMasked" label="联系电话" width="140" /> |
| | | <el-table-column prop="memberCount" label="绑定人员数量" width="120" /> |
| | | <el-table-column label="创建时间" width="170"> |
| | | <template #default="{ row }">{{ formatTime(row.createTime) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="240" fixed="right"> |
| | | <template #default="{ row }"> |
| | | <div class="table-ops"> |
| | | <el-button type="primary" link @click="goDetail(row)">详情</el-button> |
| | | <el-button type="primary" link @click="openEdit(row)">编辑</el-button> |
| | | <el-button type="primary" link @click="confirmDelete(row)">删除</el-button> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <el-pagination |
| | | class="pager" |
| | | class="main-pager" |
| | | background |
| | | layout="prev, pager, next" |
| | | layout="sizes, prev, pager, next" |
| | | :total="total" |
| | | :page-size="size" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | v-model:current-page="page" |
| | | @current-change="load" |
| | | @size-change="onMainSizeChange" |
| | | /> |
| | | |
| | | <el-dialog v-model="dlg" :title="dlgTitle" width="860px" destroy-on-close class="group-dialog"> |
| | |
| | | <template #label> |
| | | <span class="req-label">负责人</span> |
| | | </template> |
| | | <el-select v-model="form.leaderMpUserId" placeholder="请输入" class="fullw"> |
| | | <el-select |
| | | v-model="form.leaderMpUserId" |
| | | placeholder="请选择负责人" |
| | | class="fullw" |
| | | @visible-change="onLeaderVisibleChange" |
| | | > |
| | | <el-option |
| | | v-for="m in memberList" |
| | | :key="m.id" |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <el-table :data="memberList" stripe border> |
| | | <el-table :data="memberList" stripe border :max-height="memberTableMaxHeight"> |
| | | <el-table-column label="序号" width="64" type="index" /> |
| | | <el-table-column prop="realName" label="姓名" width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="staffNo" label="编号" width="140" show-overflow-tooltip /> |
| | |
| | | <div class="table-ops"> |
| | | <el-tooltip v-if="row.id === form.leaderMpUserId" content="需先更换负责人后再移除" placement="top"> |
| | | <span class="btn-wrap"> |
| | | <el-button type="danger" link disabled>移除</el-button> |
| | | <el-button type="primary" link disabled>移除</el-button> |
| | | </span> |
| | | </el-tooltip> |
| | | <el-button v-else type="danger" link @click="removeMember(row.id)">移除</el-button> |
| | | <el-button v-else type="primary" link @click="removeMember(row.id)">移除</el-button> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <div class="picker-toolbar"> |
| | | <el-form :inline="true" @submit.prevent="pickerSearch"> |
| | | <el-form-item label="姓名"> |
| | | <el-input v-model="pickerFilters.realName" placeholder="模糊" clearable style="width: 140px" /> |
| | | <el-input v-model="pickerFilters.realName" placeholder="请输入姓名" clearable style="width: 140px" /> |
| | | </el-form-item> |
| | | <el-form-item label="联系电话"> |
| | | <el-input v-model="pickerFilters.mobile" placeholder="模糊" clearable style="width: 140px" /> |
| | | <el-input v-model="pickerFilters.mobile" placeholder="请输入联系电话" clearable style="width: 140px" /> |
| | | </el-form-item> |
| | | <el-form-item label="部门"> |
| | | <el-tree-select |
| | |
| | | <el-table |
| | | ref="pickerTableRef" |
| | | :data="pickerRows" |
| | | row-key="id" |
| | | v-loading="pickerLoading" |
| | | stripe |
| | | border |
| | | @selection-change="onPickerSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="48" :selectable="pickerRowSelectable" /> |
| | | <el-table-column type="selection" width="48" :selectable="pickerRowSelectable" :reserve-selection="true" /> |
| | | <el-table-column prop="realName" label="姓名" width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="staffNo" label="编号" width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="mobileMasked" label="联系电话" width="140" /> |
| | |
| | | <el-pagination |
| | | class="pager" |
| | | background |
| | | layout="prev, pager, next" |
| | | layout="sizes, prev, pager, next" |
| | | :total="pickerTotal" |
| | | :page-size="pickerSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | v-model:current-page="pickerPage" |
| | | @current-change="loadPicker" |
| | | @size-change="onPickerSizeChange" |
| | | /> |
| | | |
| | | <template #footer> |
| | |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { useRouter } from "vue-router"; |
| | | import http from "../api/http"; |
| | | import { normalizeFormData } from "../utils/formHelper"; |
| | | |
| | | type GroupRow = { |
| | | id: number; |
| | |
| | | load(); |
| | | } |
| | | |
| | | function onMainSizeChange(nextSize: number) { |
| | | size.value = nextSize; |
| | | page.value = 1; |
| | | load(); |
| | | } |
| | | |
| | | function goDetail(r: GroupRow) { |
| | | router.push({ name: "staff-group-detail", params: { id: r.id }, query: { uuid: r.uuid } }); |
| | | } |
| | |
| | | |
| | | const membersMap = ref(new Map<number, StaffRow>()); |
| | | const memberList = computed(() => Array.from(membersMap.value.values())); |
| | | const memberTableMaxHeight = computed(() => (memberList.value.length > 20 ? 560 : undefined)); |
| | | |
| | | async function refreshLeaderMobile() { |
| | | const id = form.leaderMpUserId; |
| | |
| | | membersMap.value.delete(id); |
| | | } |
| | | |
| | | function onLeaderVisibleChange(visible: boolean) { |
| | | if (!visible) { |
| | | return; |
| | | } |
| | | if (memberList.value.length > 0) { |
| | | return; |
| | | } |
| | | ElMessage.warning("请先选择人员,再从已有人员列表中选择负责人"); |
| | | } |
| | | |
| | | async function submitForm() { |
| | | if (!form.groupName.trim()) { |
| | | ElMessage.warning("请填写分组名称"); |
| | |
| | | saving.value = true; |
| | | try { |
| | | if (!editing.value) { |
| | | await http.post("/sys/staff-groups", { |
| | | const payload = normalizeFormData({ |
| | | groupName: form.groupName.trim(), |
| | | leaderMpUserId: form.leaderMpUserId, |
| | | memberIds, |
| | | }); |
| | | await http.post("/sys/staff-groups", payload); |
| | | ElMessage.success("已创建"); |
| | | } else { |
| | | await http.put(`/sys/staff-groups/${editing.value.id}`, { |
| | | const payload = normalizeFormData({ |
| | | uuid: form.uuid, |
| | | groupName: form.groupName.trim(), |
| | | leaderMpUserId: form.leaderMpUserId, |
| | | memberIds, |
| | | }); |
| | | await http.put(`/sys/staff-groups/${editing.value.id}`, payload); |
| | | ElMessage.success("已保存"); |
| | | } |
| | | dlg.value = false; |
| | |
| | | }); |
| | | |
| | | const pickedMap = ref(new Map<number, StaffRow>()); |
| | | const restoringPickerSelection = ref(false); |
| | | |
| | | const deptOptions = ref<DeptTree[]>([]); |
| | | |
| | |
| | | loadPicker(); |
| | | } |
| | | |
| | | function onPickerSizeChange(nextSize: number) { |
| | | pickerSize.value = nextSize; |
| | | pickerPage.value = 1; |
| | | loadPicker(); |
| | | } |
| | | |
| | | function pickerParams() { |
| | | return { |
| | | page: pickerPage.value, |
| | |
| | | realName: pickerFilters.realName?.trim() || undefined, |
| | | mobile: pickerFilters.mobile?.trim() || undefined, |
| | | status: 1, |
| | | excludeStaffGroupId: editing.value?.id, |
| | | }; |
| | | } |
| | | |
| | |
| | | pickerRows.value = (data.result || []) as StaffRow[]; |
| | | pickerTotal.value = Number(data.total) || 0; |
| | | await nextTick(); |
| | | // re-apply selection |
| | | restoringPickerSelection.value = true; |
| | | pickerTableRef.value?.clearSelection?.(); |
| | | for (const r of pickerRows.value) { |
| | | if (pickedMap.value.has(r.id)) { |
| | | pickerTableRef.value?.toggleRowSelection?.(r, true); |
| | | } |
| | | } |
| | | restoringPickerSelection.value = false; |
| | | } catch (e: unknown) { |
| | | ElMessage.error(e instanceof Error ? e.message : "加载失败"); |
| | | } finally { |
| | | restoringPickerSelection.value = false; |
| | | pickerLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | function onPickerSelectionChange(cur: StaffRow[]) { |
| | | if (restoringPickerSelection.value) { |
| | | return; |
| | | } |
| | | const idsOnPage = new Set(pickerRows.value.map((x) => x.id)); |
| | | const selectedOnPage = new Set(cur.map((x) => x.id)); |
| | | for (const rid of idsOnPage) { |
| | |
| | | <style scoped> |
| | | .search-form { |
| | | margin-bottom: 12px; |
| | | flex: none; |
| | | } |
| | | .push-right { |
| | | margin-left: auto; |
| | | } |
| | | .pager { |
| | | .page { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: calc(100vh - 190px); |
| | | min-height: 420px; |
| | | } |
| | | .table-wrap { |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | .main-pager { |
| | | margin-top: 12px; |
| | | justify-content: flex-end; |
| | | flex: none; |
| | | } |
| | | .member-actions { |
| | | display: flex; |
| | |
| | | } |
| | | |
| | | .req-label { |
| | | position: relative; |
| | | padding-left: 10px; |
| | | color: #364867; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .req-label::before { |
| | | content: "*"; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 0; |
| | | color: #ff5b5b; |
| | | } |
| | | |
| | | .phone-item { |