From 073605b6d62363dd7d48f5ca047a1d401418574a Mon Sep 17 00:00:00 2001
From: zzw <228544128@qq.com>
Date: 星期日, 04 十月 2026 17:34:06 +0800
Subject: [PATCH] docs: record 2026-10-04 integration commit
---
emergency-repair-web/src/views/system/UserManage.vue | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 253 insertions(+), 0 deletions(-)
diff --git a/emergency-repair-web/src/views/system/UserManage.vue b/emergency-repair-web/src/views/system/UserManage.vue
new file mode 100644
index 0000000..2e3c9a5
--- /dev/null
+++ b/emergency-repair-web/src/views/system/UserManage.vue
@@ -0,0 +1,253 @@
+<template>
+ <div class="page">
+ <div class="page-title">
+ <div><h2>鐢ㄦ埛绠$悊</h2><p>宸︿晶鏌ヨ鐢ㄦ埛锛屽彸渚ц缃鐢ㄦ埛鎷ユ湁鐨勮鑹诧紝涓�涓敤鎴峰彲璁剧疆澶氫釜瑙掕壊</p></div>
+ </div>
+ <div class="user-layout">
+ <section class="user-left">
+ <div class="toolbar">
+ <el-button type="primary" plain icon="el-icon-s-check" :disabled="!selectedRows.length" v-permission="'system:users:assign'" @click="openBatchDialog">鎵归噺璁剧疆瑙掕壊</el-button>
+ <span v-if="selectedRows.length" class="selected-count">宸查�� {{ selectedRows.length }} 浜�</span>
+ <el-input v-model="filters.keyword" clearable placeholder="鐢ㄦ埛鍚� / 璐︽埛鍚�" style="width:190px" @keyup.enter.native="search"></el-input>
+ <el-select v-model="filters.deptId" clearable filterable placeholder="鎵�灞為儴闂�" style="width:200px">
+ <el-option v-for="item in depts" :key="item.id" :label="item.name" :value="item.id"></el-option>
+ </el-select>
+ <el-select v-model="filters.roleId" clearable placeholder="瑙掕壊" style="width:170px">
+ <el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id"></el-option>
+ </el-select>
+ <el-button type="primary" icon="el-icon-search" v-permission="'system:users:query'" @click="search">鏌ヨ</el-button>
+ <el-button icon="el-icon-refresh-left" @click="reset">閲嶇疆</el-button>
+ </div>
+ <el-table ref="userTable" row-key="userId"
+ v-loading="loading"
+ :data="rows"
+ border
+ stripe
+ highlight-current-row
+ height="calc(100vh - 250px)"
+ @selection-change="selectedRows = $event"
+ @current-change="selectUser"
+ >
+ <el-table-column type="selection" width="46" align="center"></el-table-column>
+ <el-table-column prop="nickName" label="鐢ㄦ埛鍚�" min-width="110"></el-table-column>
+ <el-table-column prop="userName" label="璐︽埛鍚�" min-width="140"></el-table-column>
+ <el-table-column prop="deptName" label="鎵�灞為儴闂�" min-width="180" show-overflow-tooltip></el-table-column>
+ <el-table-column label="瑙掕壊鏉冮檺" min-width="220">
+ <template slot-scope="{ row }">
+ <el-tag v-for="role in row.roles" :key="role.roleId" size="mini" class="role-tag">{{ role.roleName }}</el-tag>
+ <span v-if="!row.roles || !row.roles.length" class="muted">鏈垎閰�</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="phoneNumber" label="鎵嬫満鍙�" width="130"></el-table-column>
+ <el-table-column label="鐘舵��" width="80" align="center">
+ <template slot-scope="{ row }">
+ <el-tag size="mini" :type="isDisabled(row) ? 'info' : 'success'">{{ isDisabled(row) ? '鍋滅敤' : '姝e父' }}</el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-pagination
+ class="pager"
+ background
+ layout="total, sizes, prev, pager, next"
+ :total="total"
+ :current-page="current"
+ :page-size="size"
+ :page-sizes="[20, 50, 100]"
+ @current-change="handleCurrentChange"
+ @size-change="handleSizeChange"
+ ></el-pagination>
+ </section>
+ <section class="user-right">
+ <div class="panel-head">
+ <h3>瑙掕壊鎺堟潈</h3>
+ <span v-if="selected" class="selected-user">{{ selected.nickName || selected.userName }}锛坽{ selected.userName }}锛�</span>
+ </div>
+ <div v-if="!selected" class="empty-tip">璇烽�夋嫨宸︿晶鐢ㄦ埛</div>
+ <template v-else>
+ <el-checkbox-group v-model="checkedRoleIds" class="role-list">
+ <el-checkbox v-for="item in roles" :key="item.id" :label="item.id" class="role-item">
+ <span class="role-name">{{ item.name }}</span>
+ <span class="role-code">{{ item.code }}</span>
+ </el-checkbox>
+ </el-checkbox-group>
+ <el-button
+ type="primary"
+ icon="el-icon-check"
+ class="save-button"
+ v-permission="'system:users:assign'"
+ :loading="saving"
+ @click="saveRoles"
+ >淇濆瓨鎺堟潈</el-button>
+ </template>
+ </section>
+ </div>
+ <el-dialog title="鎵归噺璁剧疆瑙掕壊" :visible.sync="batchVisible" width="520px" append-to-body :close-on-click-modal="false">
+ <div class="batch-summary">宸查�夋嫨 {{ selectedRows.length }} 涓敤鎴�</div>
+ <el-radio-group v-model="batchMode" class="batch-mode">
+ <el-radio label="append">杩藉姞瑙掕壊</el-radio>
+ <el-radio label="replace">瑕嗙洊瑙掕壊</el-radio>
+ </el-radio-group>
+ <el-alert :title="batchMode === 'append' ? '淇濈暀鎵�閫夌敤鎴峰師鏈夎鑹诧紝骞惰拷鍔犲嬀閫夎鑹�' : '浣跨敤鍕鹃�夎鑹茶鐩栨墍閫夌敤鎴风殑鍘熸湁瑙掕壊'" type="warning" :closable="false" show-icon></el-alert>
+ <el-checkbox-group v-model="batchRoleIds" class="batch-role-list">
+ <el-checkbox v-for="item in roles" :key="item.id" :label="item.id" class="role-item">
+ <span class="role-name">{{ item.name }}</span>
+ <span class="role-code">{{ item.code }}</span>
+ </el-checkbox>
+ </el-checkbox-group>
+ <span slot="footer">
+ <el-button @click="batchVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" :loading="batchSaving" v-permission="'system:users:assign'" @click="saveBatchRoles">淇濆瓨</el-button>
+ </span>
+ </el-dialog>
+ </div>
+</template>
+<script>
+import api from '@/api'
+export default {
+ name: 'UserManage',
+ data() {
+ return {
+ rows: [],
+ total: 0,
+ current: 1,
+ size: 20,
+ loading: false,
+ saving: false,
+ batchSaving: false,
+ batchVisible: false,
+ batchRoleIds: [],
+ batchMode: 'append',
+ selectedRows: [],
+ depts: [],
+ roles: [],
+ selected: null,
+ checkedRoleIds: [],
+ filters: { keyword: '', deptId: null, roleId: null }
+ }
+ },
+ created() {
+ this.loadRoles()
+ this.loadDepts()
+ this.load()
+ },
+ methods: {
+ load() {
+ this.clearSelection()
+ this.loading = true
+ api.get('/system/users', {
+ params: {
+ keyword: this.filters.keyword || undefined,
+ deptId: this.filters.deptId || undefined,
+ roleId: this.filters.roleId || undefined,
+ current: this.current,
+ size: this.size
+ }
+ }).then(res => {
+ const data = res.data || {}
+ this.rows = data.records || []
+ this.total = data.total || 0
+ if (this.selected) {
+ const matched = this.rows.find(item => item.userId === this.selected.userId)
+ this.selected = matched || null
+ if (!matched) this.checkedRoleIds = []
+ }
+ }).finally(() => { this.loading = false })
+ },
+ loadRoles() {
+ api.get('/system/roles').then(res => { this.roles = res.data || [] })
+ },
+ loadDepts() {
+ api.get('/system/depts').then(res => { this.depts = res.data || [] })
+ },
+ search() {
+ this.current = 1
+ this.load()
+ },
+ reset() {
+ this.filters = { keyword: '', deptId: null, roleId: null }
+ this.search()
+ },
+ handleCurrentChange(page) {
+ this.current = page
+ this.load()
+ },
+ handleSizeChange(size) {
+ this.size = size
+ this.current = 1
+ this.load()
+ },
+ clearSelection() {
+ this.selectedRows = []
+ this.$nextTick(() => { if (this.$refs.userTable) this.$refs.userTable.clearSelection() })
+ },
+ openBatchDialog() {
+ if (!this.selectedRows.length) return
+ this.batchRoleIds = []
+ this.batchMode = 'append'
+ this.batchVisible = true
+ },
+ saveBatchRoles() {
+ if (!this.selectedRows.length) return
+ const userIds = this.selectedRows.map(item => item.userId)
+ const roleIds = this.batchRoleIds.slice()
+ if (this.batchMode === 'append' && !roleIds.length) {
+ this.$message.warning('璇烽�夋嫨瑕佽拷鍔犵殑瑙掕壊')
+ return
+ }
+ const message = this.batchMode === 'append'
+ ? '灏嗗悜 ' + userIds.length + ' 涓敤鎴疯拷鍔犳墍閫夎鑹诧紝鏄惁缁х画锛�'
+ : (roleIds.length ? '灏嗕娇鐢ㄦ墍閫夎鑹茶鐩� ' + userIds.length + ' 涓敤鎴风殑鐜版湁瑙掕壊锛屾槸鍚︾户缁紵' : '鏈�夋嫨瑙掕壊锛屽皢娓呯┖ ' + userIds.length + ' 涓敤鎴风殑鍏ㄩ儴瑙掕壊锛屾槸鍚︾户缁紵')
+ this.$confirm(message, '鎵归噺璁剧疆瑙掕壊', { type: 'warning' }).then(() => {
+ this.batchSaving = true
+ api.put('/system/users/roles/batch', { userIds, roleIds, mode: this.batchMode }).then(() => {
+ this.$message.success('鎵归噺瑙掕壊璁剧疆鎴愬姛')
+ this.batchVisible = false
+ this.load()
+ if (this.selected) this.selectUser(this.selected)
+ }).finally(() => { this.batchSaving = false })
+ }).catch(() => {})
+ },
+ selectUser(row) {
+ this.selected = row || null
+ this.checkedRoleIds = []
+ if (!row) return
+ api.get('/system/users/' + row.userId + '/roles').then(res => {
+ this.checkedRoleIds = res.data || []
+ })
+ },
+ saveRoles() {
+ if (!this.selected) return
+ this.saving = true
+ api.put('/system/users/' + this.selected.userId + '/roles', { roleIds: this.checkedRoleIds }).then(() => {
+ this.$message.success('瑙掕壊鎺堟潈宸蹭繚瀛�')
+ this.load()
+ }).finally(() => { this.saving = false })
+ },
+ isDisabled(row) {
+ return row.status === '1'
+ }
+ }
+}
+</script>
+<style scoped>
+.user-layout { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 12px; }
+.user-left, .user-right { min-width: 0; padding: 14px; border: 1px solid #dce4ec; border-radius: 4px; background: #fff; }
+.user-right { min-height: calc(100vh - 190px); }
+.toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
+.selected-count { align-self: center; color: #5c6f82; font-size: 12px; }
+.batch-summary { margin-bottom: 10px; color: #31465d; font-size: 14px; }
+.batch-role-list { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 12px; max-height: 320px; margin-top: 14px; overflow: auto; }
+.pager { margin-top: 12px; text-align: right; }
+.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid #edf1f5; }
+.panel-head h3 { margin: 0; color: #31465d; font-size: 15px; }
+.selected-user { color: #5c6f82; font-size: 13px; }
+.empty-tip { padding: 60px 0; color: #93a2b1; text-align: center; }
+.role-list { display: flex; flex-direction: column; gap: 4px; max-height: calc(100vh - 320px); overflow: auto; }
+.role-item { display: flex; align-items: center; height: 34px; margin-right: 0; }
+.role-name { color: #31465d; }
+.role-code { margin-left: 8px; color: #93a2b1; font-size: 12px; }
+.save-button { width: 100%; margin-top: 16px; }
+.role-tag { margin: 2px 4px 2px 0; }
+.muted { color: #a3afbc; }
+@media (max-width: 1100px) { .user-layout { grid-template-columns: 1fr; } }
+</style>
--
Gitblit v1.9.1