13 changed files with 258 additions and 92 deletions
-
4.env
-
10src/App.vue
-
1src/assets/menuIcon/n_user.svg
-
1src/assets/menuIcon/s_user.svg
-
2src/eventBus.ts
-
14src/router/index.ts
-
51src/services/axios.ts
-
64src/services/httpRequest.ts
-
21src/services/user/userManager.ts
-
6src/views/components/menu.ts
-
54src/views/login/index.vue
-
63src/views/userManage/UserManage.vue
-
7vite.config.ts
@ -1,3 +1,3 @@ |
|||||
VITE_API_HOST=window.location.hostname |
|
||||
VITE_API_PORT=80 |
|
||||
|
VITE_API_HOST=127.0.0.1 |
||||
|
VITE_API_PORT=8080 |
||||
VITE_WS_PATH=/api/v1/app/ws/state |
VITE_WS_PATH=/api/v1/app/ws/state |
@ -0,0 +1 @@ |
|||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#EEEFF8" fill-opacity="1"/></g><g><path d="M29.44471,32.3043C29.44472,37.9952,34.0687,42.608599999999996,39.772800000000004,42.608599999999996C45.4768,42.608599999999996,50.100899999999996,37.9952,50.100899999999996,32.3043C50.100899999999996,26.6134,45.4768,22,39.772800000000004,22C34.0687,22,29.44472,26.6134,29.44471,32.3043ZM55.678,46.5004L51.0002,46.5004C50.2702,46.5004,49.678200000000004,45.9097,49.678200000000004,45.1814C49.678200000000004,44.4531,50.2702,43.8625,51.0002,43.8625L55.678,43.8625C56.408,43.8625,57,44.4531,57,45.1814C57,45.9097,56.408,46.5004,55.678,46.5004ZM55.678,52.2502L47.580799999999996,52.2502C46.8508,52.2502,46.2588,51.6595,46.2588,50.931200000000004C46.2588,50.2029,46.8508,49.612300000000005,47.580799999999996,49.612300000000005L55.678,49.612300000000005C56.408,49.612300000000005,57,50.2029,57,50.931200000000004C57,51.6595,56.408,52.2502,55.678,52.2502ZM55.678,58L47.580799999999996,58C46.8508,58,46.2588,57.4094,46.2588,56.681C46.2588,55.9527,46.8508,55.3621,47.580799999999996,55.3621L55.678,55.3621C56.408,55.3621,57,55.9527,57,56.681C57,57.4094,56.408,58,55.678,58ZM41.260000000000005,52.9303C41.260000000000005,48.602900000000005,43.934200000000004,44.898700000000005,47.723299999999995,43.3724C48.3662,43.113600000000005,48.4289,42.229,47.825,41.8898C45.435,40.546099999999996,42.6911,39.782,39.772800000000004,39.782C30.50933,39.782,23,47.4797,23,56.9757C23,57.0475,23.000413211,57.1188,23.00123932,57.1901C23.0066098,57.6398,23.376768,58,23.827485,58L41.2753,58C41.865700000000004,58,42.262299999999996,57.4015,42.037099999999995,56.857C41.5364,55.6469,41.260000000000005,54.3209,41.260000000000005,52.9303Z" fill="#4F85FB" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg> |
@ -0,0 +1 @@ |
|||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><g><g><ellipse cx="40" cy="40" rx="40" ry="40" fill="#479CF1" fill-opacity="1"/></g><g><path d="M29.44471,32.3043C29.44472,37.9952,34.0687,42.608599999999996,39.772800000000004,42.608599999999996C45.4768,42.608599999999996,50.100899999999996,37.9952,50.100899999999996,32.3043C50.100899999999996,26.6134,45.4768,22,39.772800000000004,22C34.0687,22,29.44472,26.6134,29.44471,32.3043ZM55.678,46.5004L51.0002,46.5004C50.2702,46.5004,49.678200000000004,45.9097,49.678200000000004,45.1814C49.678200000000004,44.4531,50.2702,43.8625,51.0002,43.8625L55.678,43.8625C56.408,43.8625,57,44.4531,57,45.1814C57,45.9097,56.408,46.5004,55.678,46.5004ZM55.678,52.2502L47.580799999999996,52.2502C46.8508,52.2502,46.2588,51.6595,46.2588,50.931200000000004C46.2588,50.2029,46.8508,49.612300000000005,47.580799999999996,49.612300000000005L55.678,49.612300000000005C56.408,49.612300000000005,57,50.2029,57,50.931200000000004C57,51.6595,56.408,52.2502,55.678,52.2502ZM55.678,58L47.580799999999996,58C46.8508,58,46.2588,57.4094,46.2588,56.681C46.2588,55.9527,46.8508,55.3621,47.580799999999996,55.3621L55.678,55.3621C56.408,55.3621,57,55.9527,57,56.681C57,57.4094,56.408,58,55.678,58ZM41.260000000000005,52.9303C41.260000000000005,48.602900000000005,43.934200000000004,44.898700000000005,47.723299999999995,43.3724C48.3662,43.113600000000005,48.4289,42.229,47.825,41.8898C45.435,40.546099999999996,42.6911,39.782,39.772800000000004,39.782C30.50933,39.782,23,47.4797,23,56.9757C23,57.0475,23.000413211,57.1188,23.00123932,57.1901C23.0066098,57.6398,23.376768,58,23.827485,58L41.2753,58C41.865700000000004,58,42.262299999999996,57.4015,42.037099999999995,56.857C41.5364,55.6469,41.260000000000005,54.3209,41.260000000000005,52.9303Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg> |
@ -1,6 +1,6 @@ |
|||||
import mitt from 'mitt' |
import mitt from 'mitt' |
||||
type Events = { |
type Events = { |
||||
menuId: number, |
|
||||
|
invalidToken: void, |
||||
} |
} |
||||
|
|
||||
export const eventBus = mitt<Events>() |
export const eventBus = mitt<Events>() |
@ -0,0 +1,64 @@ |
|||||
|
import { Subject } from "rxjs"; |
||||
|
|
||||
|
export interface BaseResponse<T = unknown> { |
||||
|
success: boolean; |
||||
|
code: string; |
||||
|
msg: string; |
||||
|
data: T; |
||||
|
} |
||||
|
|
||||
|
type HttpReqParam = { |
||||
|
url: string; |
||||
|
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE"; |
||||
|
params?: Record<string, any>; |
||||
|
encode?: "form" | "json"; // 入参编码类型
|
||||
|
headers?: Record<string, any>; |
||||
|
}; |
||||
|
|
||||
|
export type ApiException = "invalidToken" | "serverError"; |
||||
|
|
||||
|
const exceptionSub = new Subject<ApiException>(); |
||||
|
export const exceptionOb = exceptionSub.asObservable(); |
||||
|
|
||||
|
function extHandle(res: BaseResponse) { |
||||
|
if (res.code === "A0230") { |
||||
|
// 访问令牌无效或已过期
|
||||
|
exceptionSub.next("invalidToken"); |
||||
|
} |
||||
|
return { |
||||
|
...res, |
||||
|
success: res.code === "00000", |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export default async function httpRequest<T>({ url, method = "GET", params = {}, encode = "json", headers = {} }: HttpReqParam) { |
||||
|
const token = sessionStorage.getItem("token"); |
||||
|
if (token) { |
||||
|
headers = { Authorization: token, ...headers }; |
||||
|
} |
||||
|
if (method === "GET") { |
||||
|
const query = urlEncode(params); |
||||
|
const _url = query ? url + "?" + query : url; |
||||
|
const res = await fetch(_url, { headers }); |
||||
|
return res.json().then(res => extHandle(res) as T); |
||||
|
} else { |
||||
|
const body = encode === "json" ? JSON.stringify(params) : urlEncode(params); |
||||
|
const _headers = |
||||
|
encode === "json" |
||||
|
? { "Content-Type": "application/json; charset=utf-8", ...headers } |
||||
|
: { "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", ...headers }; |
||||
|
const res = await fetch(url, { method, headers: _headers, body }); |
||||
|
return res.json().then(res => extHandle(res) as T); |
||||
|
} |
||||
|
} |
||||
|
export function urlEncode(params?: Record<string, any>) { |
||||
|
let query = ""; |
||||
|
if (params && Object.keys(params).length > 0) { |
||||
|
const qs = []; |
||||
|
for (let attr in params) { |
||||
|
qs.push(`${attr}=${encodeURIComponent(params[attr])}`); |
||||
|
} |
||||
|
query = qs.join("&"); |
||||
|
} |
||||
|
return query; |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
import httpRequest, { type BaseResponse } from "../httpRequest"; |
||||
|
|
||||
|
export function login(params: { username: string; password: string }) { |
||||
|
return httpRequest<BaseResponse<string>>({ url: `/api/auth/login`, method: "POST", params }); |
||||
|
} |
||||
|
|
||||
|
export type User = { |
||||
|
id: number; |
||||
|
username: string; |
||||
|
nickname: string; // 用户显示
|
||||
|
password: string; |
||||
|
role: number; // 1: admin
|
||||
|
}; |
||||
|
|
||||
|
export function getUserList(params: { pageNum: number; pageSize: number }) { |
||||
|
return httpRequest<BaseResponse<{ list: User[]; total: number }>>({ url: "/api/user/list", params }); |
||||
|
} |
||||
|
|
||||
|
export function getCurrentUser() { |
||||
|
return httpRequest<BaseResponse<User>>({ url: "/api/user/current" }); |
||||
|
} |
@ -0,0 +1,63 @@ |
|||||
|
<template> |
||||
|
<div class="component-page"> |
||||
|
<section class="flex items-center h-20 gap-3 pl-3"> |
||||
|
<button class="btn-light px-3 py-1 text-xs" @click="addUser">新增用户</button> |
||||
|
<button :disabled="opDisable" class="btn-light px-3 py-1 text-xs disabled:btn-light-disabled">删除用户</button> |
||||
|
</section> |
||||
|
|
||||
|
<section> |
||||
|
<header class="h-10 flex items-center bg-[#000]/[0.02] text-xs pr-3 text-text"> |
||||
|
<div class="w-10 self-stretch flex justify-center items-center"> |
||||
|
<img src="@/assets/Icon-unselect.svg" alt="icon" /> |
||||
|
</div> |
||||
|
<p class="w-16">用户名称</p> |
||||
|
<p>权限</p> |
||||
|
</header> |
||||
|
|
||||
|
<div v-for="user in userList" class="h-10 flex items-center text-xs pr-3 text-[#6e6e6e] border-b border-b-[#f8f8f8]"> |
||||
|
<div class="w-10 self-stretch flex justify-center items-center"> |
||||
|
<img :src="isSelect ? icon_select : icon_unselect" alt="" /> |
||||
|
</div> |
||||
|
<p class="w-16">{{ user.nickname }}</p> |
||||
|
<p class="flex-auto">{{ user.role === 1 ? "管理员" : "用户" }}</p> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<van-overlay :show="showEditDialog"> |
||||
|
<div class="flex justify-center items-center h-full"></div> |
||||
|
</van-overlay> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import icon_unselect from "@/assets/Icon-unselect.svg"; |
||||
|
import icon_select from "@/assets/Icon-select.svg"; |
||||
|
import { showToast } from "vant"; |
||||
|
import { onMounted, ref } from "vue"; |
||||
|
import { getCurrentUser, getUserList, type User } from "@/services/user/userManager"; |
||||
|
|
||||
|
const isSelect = ref<boolean>(false); |
||||
|
const opDisable = ref<boolean>(true); |
||||
|
const showEditDialog = ref<boolean>(false); |
||||
|
|
||||
|
const userList = ref<User[]>([]); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
getUserList({ pageNum: 1, pageSize: 9999 }).then(res => { |
||||
|
if (res.success) { |
||||
|
userList.value = res.data.list; |
||||
|
} else { |
||||
|
showToast(res.msg); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
function addUser() { |
||||
|
// TEST |
||||
|
getCurrentUser().then(res => { |
||||
|
if (res.success) { |
||||
|
console.log("current user:", res.data) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
</script> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue