diff --git a/src/modules/user/UserFormDialog.vue b/src/modules/user/UserFormDialog.vue new file mode 100644 index 0000000..0eacb9f --- /dev/null +++ b/src/modules/user/UserFormDialog.vue @@ -0,0 +1,255 @@ + + + diff --git a/src/modules/user/UserListView.vue b/src/modules/user/UserListView.vue index 3c5128c..0720561 100644 --- a/src/modules/user/UserListView.vue +++ b/src/modules/user/UserListView.vue @@ -5,13 +5,14 @@ * 按 ui-preview-v2.html 模板: 9 列 + 6 搜索 + 4 操作 + 状态 tab */ import { onMounted, ref, reactive, computed } from 'vue' -import { httpGet, httpPost } from '@/utils/request' +import { http, httpGet, httpPost } from '@/utils/request' import { ElMessage, ElMessageBox } from 'element-plus' import { Plus } from '@element-plus/icons-vue' import DataTable from '@/components/DataTable/DataTable.vue' import PageHeader from '@/components/PageHeader/PageHeader.vue' import SearchBar from '@/components/SearchBar/SearchBar.vue' import ListTabs from '@/components/ListTabs/ListTabs.vue' +import UserFormDialog from './UserFormDialog.vue' interface FrontUser { id: string @@ -33,6 +34,12 @@ const total = ref(0) const enabledStats = reactive({ normal: 0, disabled: 0 }) const loading = ref(false) const activeTab = ref('all') + +// 表单弹窗状态 +const dialogVisible = ref(false) +const editingUserId = ref(null) +const exporting = ref(false) + const query = reactive({ page: 1, limit: 10, @@ -192,7 +199,62 @@ function onReset() { } function onEdit(row: FrontUser) { - ElMessageBox.alert(`用户详情编辑待 W17 完善 (当前用户: ${row.nickName}, ID: ${row.id})`, '修改用户', { type: 'info' }) + editingUserId.value = row.id + dialogVisible.value = true +} + +function onCreate() { + editingUserId.value = null + dialogVisible.value = true +} + +function onDialogSuccess() { + load() +} + +/** + * 导出当前搜索条件下的用户列表为 Excel + * 后端: GET /user/user/export (返 Excel 文件流, Content-Type: application/vnd.ms-excel) + * 走 http 实例 (自动带 Bearer token), responseType: 'blob' 接二进制流 + */ +async function exportExcel() { + exporting.value = true + try { + const params = buildSearchParams() + if (query.enabled !== '') params.enabled = Number(query.enabled) + const resp = await http.get('/user/user/export', { + params, + responseType: 'blob' + }) + const blob = resp.data + if (!blob || blob.size === 0) { + ElMessage.error('导出内容为空') + return + } + // 优先从响应头取文件名 (后端 ExcelUtils 会设置) + const dispo = resp.headers?.['content-disposition'] || resp.headers?.['Content-Disposition'] + let filename = `用户列表_${new Date().toISOString().slice(0, 10)}.xls` + if (typeof dispo === 'string') { + const m = dispo.match(/filename\*?=(?:UTF-8\u0027\u0027)?["']?([^;"']+)["']?/i) + if (m && m[1]) { + try { filename = decodeURIComponent(m[1]) } catch { filename = m[1] } + } + } + const url = URL.createObjectURL(new Blob([blob])) + const a = document.createElement('a') + a.href = url + a.download = filename + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + ElMessage.success(`已导出 ${filename}`) + } catch (e: any) { + const msg = e?.msg || e?.message || '导出失败' + ElMessage.error(msg) + } finally { + exporting.value = false + } } async function toggleEnabled(row: FrontUser) { @@ -230,12 +292,17 @@ onMounted(load)