diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index ed3e2cd..3c563a0 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -7,6 +7,7 @@ "core:default", "core:window:default", "core:window:allow-start-dragging", + "core:window:allow-close", "core:window:allow-minimize", "core:window:allow-maximize", "core:window:allow-unmaximize", diff --git a/src/api/oemCardKeys.js b/src/api/oemCardKeys.js new file mode 100644 index 0000000..9a4a930 --- /dev/null +++ b/src/api/oemCardKeys.js @@ -0,0 +1,72 @@ +import { apiRequest } from "./client.js"; + +/** + * @param {string} token + * @param {RequestInit} [options] + */ +function oemRequest(token, path, options = {}) { + return apiRequest(path, { + ...options, + headers: { + Authorization: `Bearer ${token}`, + ...options.headers, + }, + }); +} + +/** + * @param {string} token + * @param {{ + * page?: number, + * pageSize?: number, + * status?: string, + * username?: string, + * oemId?: number | null, + * agentId?: number | null, + * }} [params] + */ +export function listAdminCardKeysApi( + token, + { page = 1, pageSize = 20, status = "all", username, oemId, agentId } = {}, +) { + const query = new URLSearchParams({ + page: String(page), + page_size: String(pageSize), + status, + }); + const trimmed = username?.trim(); + if (trimmed) query.set("username", trimmed); + if (oemId != null) query.set("oem_id", String(oemId)); + if (agentId != null) query.set("agent_id", String(agentId)); + return oemRequest(token, `/oem/card-keys?${query}`); +} + +/** + * @param {string} token + * @param {object} payload + */ +export function createAdminCardKeysApi(token, payload) { + return oemRequest(token, "/oem/card-keys", { + method: "POST", + body: JSON.stringify(payload), + }); +} + +/** + * @param {string} token + * @param {number} cardId + * @param {object} payload + */ +export function updateAdminCardKeyApi(token, cardId, payload) { + return oemRequest(token, `/oem/card-keys/${cardId}`, { + method: "PATCH", + body: JSON.stringify(payload), + }); +} + +/** @param {string} token @param {number} cardId */ +export function deleteAdminCardKeyApi(token, cardId) { + return oemRequest(token, `/oem/card-keys/${cardId}`, { + method: "DELETE", + }); +} diff --git a/src/api/oemUsers.js b/src/api/oemUsers.js index 4b48330..0be1dc3 100644 --- a/src/api/oemUsers.js +++ b/src/api/oemUsers.js @@ -25,7 +25,7 @@ function oemRequest(token, path, options = {}) { * roleId?: number | null, * }} [params] */ -export function listOemUsersApi( +export function listAgentUsersApi( token, { page = 1, pageSize = 20, username, oemId, agentId, roleId } = {}, ) { diff --git a/src/components/AppTitlebar.vue b/src/components/AppTitlebar.vue index d068373..fdb71d7 100644 --- a/src/components/AppTitlebar.vue +++ b/src/components/AppTitlebar.vue @@ -79,6 +79,15 @@ async function onToggleMaximize() { } } +async function onClose() { + if (!isTauri()) return; + try { + await getCurrentWindow().close(); + } catch { + /* ignore */ + } +} + async function onLogout() { if (loggingOut.value) return; loggingOut.value = true; @@ -213,6 +222,15 @@ onUnmounted(() => { :class="isMaximized ? 'pi-window-minimize' : 'pi-window-maximize'" /> + + + diff --git a/src/components/agent/AgentImageUpload.vue b/src/components/agent/AgentImageUpload.vue new file mode 100644 index 0000000..7339f74 --- /dev/null +++ b/src/components/agent/AgentImageUpload.vue @@ -0,0 +1,101 @@ + + + + + {{ label }} + + + + 未上传 + + + + + + + + + diff --git a/src/components/agent/AgentSubNav.vue b/src/components/agent/AgentSubNav.vue new file mode 100644 index 0000000..75f91ee --- /dev/null +++ b/src/components/agent/AgentSubNav.vue @@ -0,0 +1,31 @@ + + + + + + + {{ item.label }} + + + diff --git a/src/router/index.js b/src/router/index.js index 71aa5fa..626ffb5 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -264,8 +264,39 @@ const mainChildren = [ meta: { title: "代理", requiresRole: ROLE_AGENT }, + children: [ + + + + { + + path: "users", + + name: "agent-users", + + component: () => import("../views/agent/AgentUsersView.vue"), + + meta: { title: "用户管理" }, + + }, + + { + + path: "card-keys", + + name: "agent-card-keys", + + component: () => import("../views/agent/AgentCardKeysView.vue"), + + meta: { title: "卡密管理" }, + + }, + + ], + }, + ]; diff --git a/src/styles/main.css b/src/styles/main.css index 9c505a2..dcd3a14 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -420,6 +420,16 @@ body { background: rgba(255, 255, 255, 0.12); } +.app-titlebar__btn--close:hover { + background: rgba(239, 68, 68, 0.85); + color: #fff; +} + +.app-titlebar__btn--close:active { + background: rgba(220, 38, 38, 0.9); + color: #fff; +} + .admin-image-upload { display: flex; flex-direction: column; diff --git a/src/views/agent/AgentCardKeysView.vue b/src/views/agent/AgentCardKeysView.vue new file mode 100644 index 0000000..c577eab --- /dev/null +++ b/src/views/agent/AgentCardKeysView.vue @@ -0,0 +1,517 @@ + + + + + + + 卡密管理 + + + + + + {{ feedback.message }} + + + + + + + 状态 + + + + + + + + + + {{ data.serial_number }} + + + + + + + + {{ ownerLabel(data.oem_id) }} + + + + + {{ ownerLabel(data.agent_id) }} + + + + + {{ formatDateTime(data.created_at) }} + + + + + + + + + + + {{ data.username || "—" }} + + + + + {{ data.remark || "—" }} + + + + + + + + + + + + 暂无卡密 + + + + + + + + 时长(天) + + + + 生成数量 + + + + 自定义序列号(可选,仅 1 张) + + + + 预分配 + + + + + {{ form.assign_type === "oem" ? "选择 OEM" : "选择代理" }} + + + + + + + 序列号 + + + + 时长(天) + + + + + + 备注 + + + + + + + + + + + diff --git a/src/views/agent/AgentUsersView.vue b/src/views/agent/AgentUsersView.vue new file mode 100644 index 0000000..5c8e7b3 --- /dev/null +++ b/src/views/agent/AgentUsersView.vue @@ -0,0 +1,455 @@ + + + + + + + 用户管理 + + + + + + + + + + + + + + {{ feedback.message }} + + + + + + + + {{ data.phone || "—" }} + + + + + + + + + + {{ ownerLabel(data.oem_id) }} + + + + + {{ ownerLabel(data.agent_id) }} + + + + + {{ formatDateTime(data.vip_end_time) }} + + + + + {{ formatDateTime(data.created_at) }} + + + + + + + + + + + + 暂无用户 + + + + + + + 用户名 + + + + + + {{ dialogMode === "create" ? "密码" : "新密码(留空不修改)" }} + + + + + + 手机号(可选) + + + + + 角色 + + + + + VIP 到期时间 + + + + 清除 VIP 到期时间 + + + + + + + + + + + diff --git a/src/views/oem/OemCardKeysView.vue b/src/views/oem/OemCardKeysView.vue index c577eab..36e960d 100644 --- a/src/views/oem/OemCardKeysView.vue +++ b/src/views/oem/OemCardKeysView.vue @@ -6,8 +6,8 @@ import { createAdminCardKeysApi, updateAdminCardKeyApi, deleteAdminCardKeyApi, -} from "../../api/adminCardKeys.js"; -import { listAdminUsersApi } from "../../api/adminUsers.js"; +} from "../../api/oemCardKeys.js"; +import { listAgentUsersApi } from "../../api/oemUsers.js"; const auth = useAuthStore(); @@ -91,13 +91,11 @@ function showMessage(severity, message) { } async function loadFilterOptions() { - const [oemRes, agentRes] = await Promise.all([ - listAdminUsersApi(auth.token, { page: 1, pageSize: 200, roleId: ROLE_OEM }), - listAdminUsersApi(auth.token, { page: 1, pageSize: 200, roleId: ROLE_AGENT }), + const [ agentRes] = await Promise.all([ + + listAgentUsersApi(auth.token, { page: 1, pageSize: 200, roleId: ROLE_AGENT }), ]); - if (oemRes.ok && Array.isArray(oemRes.data?.items)) { - oemOptions.value = oemRes.data.items; - } + if (agentRes.ok && Array.isArray(agentRes.data?.items)) { agentOptions.value = agentRes.data.items; } diff --git a/src/views/oem/OemUsersView.vue b/src/views/oem/OemUsersView.vue index f39384a..9d16b1e 100644 --- a/src/views/oem/OemUsersView.vue +++ b/src/views/oem/OemUsersView.vue @@ -2,7 +2,7 @@ import { computed, onMounted, ref } from "vue"; import { useAuthStore, ROLE_ADMIN, ROLE_AGENT,ROLE_OEM } from "../../stores/auth.js"; import { - listOemUsersApi, + listAgentUsersApi, createOemUserApi, updateOemUserApi, deleteOemUserApi, @@ -102,13 +102,11 @@ function showMessage(severity, message) { } async function loadFilterOptions() { - const [oemRes, agentRes] = await Promise.all([ - listOemUsersApi(auth.token, { page: 1, pageSize: 200, roleId: ROLE_OEM }), - listOemUsersApi(auth.token, { page: 1, pageSize: 200, roleId: ROLE_AGENT }), + const [ agentRes] = await Promise.all([ + + listAgentUsersApi(auth.token, { page: 1, pageSize: 200, roleId: ROLE_AGENT }), ]); - if (oemRes.ok && Array.isArray(oemRes.data?.items)) { - oemOptions.value = oemRes.data.items; - } + if (agentRes.ok && Array.isArray(agentRes.data?.items)) { agentOptions.value = agentRes.data.items; } @@ -117,25 +115,27 @@ async function loadFilterOptions() { async function loadUsers() { loading.value = true; feedback.value = { severity: "", message: "" }; - const res = await listOemUsersApi(auth.token, { - page: page.value, - pageSize: pageSize.value, - username: filters.value.username, - oemId: filters.value.oem_id, - agentId: filters.value.agent_id, - }); - loading.value = false; + // const res = await listOemUsersApi(auth.token, { + // page: page.value, + // pageSize: pageSize.value, + // username: filters.value.username, + // oemId: filters.value.oem_id, + // agentId: filters.value.agent_id, + // }); + // loading.value = false; - if (!res.ok) { - showMessage("error", res.message || "加载用户列表失败"); - return; - } + // if (!res.ok) { + // showMessage("error", res.message || "加载用户列表失败"); + // return; + // } - const data = res.data; - users.value = Array.isArray(data?.items) ? data.items : []; - total.value = typeof data?.total === "number" ? data.total : 0; - if (typeof data?.page === "number") page.value = data.page; - if (typeof data?.page_size === "number") pageSize.value = data.page_size; + // const data = res.data; + // users.value = Array.isArray(data?.items) ? data.items : []; + // total.value = typeof data?.total === "number" ? data.total : 0; + users.value=[]; + total.value=0; + // if (typeof data?.page === "number") page.value = data.page; + // if (typeof data?.page_size === "number") pageSize.value = data.page_size; } async function onPage(event) {
{{ data.serial_number }}
暂无卡密
暂无用户