update
This commit is contained in:
@@ -7,7 +7,7 @@ import { axios } from "./BaseAPI";
|
||||
* @param id 文章 ID
|
||||
* @returns 文章数据
|
||||
*/
|
||||
async function view(id: number): Promise<ArticleView<any>> {
|
||||
async function view(id: string): Promise<ArticleView<any>> {
|
||||
return axios.get(`/article/${id}`);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ async function view(id: number): Promise<ArticleView<any>> {
|
||||
* @param id 文章 ID
|
||||
* @returns 最新喜欢数量
|
||||
*/
|
||||
async function like(id: number): Promise<number> {
|
||||
async function like(id: string): Promise<number> {
|
||||
return axios.get(`/article/like/${id}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import {Attachment, BizUpdateReq, TempFileResp} from "../types";
|
||||
import {axios} from "./BaseAPI";
|
||||
import CommonAPI from "./CommonAPI";
|
||||
|
||||
const BASE_URI = "/attach";
|
||||
|
||||
/**
|
||||
* 上传临时附件
|
||||
*
|
||||
* @param file 文件或文件列表
|
||||
* @param ttl 有效期
|
||||
* @returns 临时附件列表
|
||||
*/
|
||||
async function uploadTemp(file: File | File[], ttl?: string): Promise<TempFileResp[]> {
|
||||
const formData = new FormData();
|
||||
const fileList = Array.isArray(file) ? file : [file];
|
||||
for (const item of fileList) {
|
||||
formData.append("file", item);
|
||||
}
|
||||
if (ttl) {
|
||||
formData.append("ttl", ttl);
|
||||
}
|
||||
return await axios.post(`${BASE_URI}/temp/upload`, formData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按业务差分更新附件
|
||||
*
|
||||
* @param req 更新请求
|
||||
*/
|
||||
async function updateByBiz(req: BizUpdateReq): Promise<void> {
|
||||
return await axios.post(`${BASE_URI}/update/biz`, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按业务查询附件列表
|
||||
*
|
||||
* @param bizType 业务类型
|
||||
* @param bizId 业务 ID
|
||||
* @param attachTypeList 附件类型列表
|
||||
*/
|
||||
async function listByBiz(bizType: string, bizId: string, attachTypeList?: string[]): Promise<Attachment[]> {
|
||||
return await axios.get(`${BASE_URI}/list/biz`, {
|
||||
params: {
|
||||
bizType,
|
||||
bizId,
|
||||
attachTypeList
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
uploadTemp,
|
||||
updateByBiz,
|
||||
listByBiz,
|
||||
getReadURL: CommonAPI.getAttachmentReadAPI,
|
||||
getTempReadURL: CommonAPI.getAttachmentTempReadAPI
|
||||
};
|
||||
+19
-3
@@ -1,9 +1,23 @@
|
||||
import {Setting, TemplateBizType} from "../types";
|
||||
import {CaptchaResult, Setting, TemplateBizType} from "../types";
|
||||
import {axios} from "./BaseAPI";
|
||||
|
||||
const getCaptchaAPI = () => axios.defaults.baseURL + "/captcha";
|
||||
function getBaseURI(): string {
|
||||
const baseURL = axios.defaults.baseURL;
|
||||
if (!baseURL || baseURL === "undefined") {
|
||||
return "";
|
||||
}
|
||||
return baseURL.replace(/\/$/, "");
|
||||
}
|
||||
|
||||
const getAttachmentReadAPI = (mongoId: string) => `${axios.defaults.baseURL}/attachment/read/${mongoId}`;
|
||||
const getCaptchaAPI = () => `${getBaseURI()}/captcha`;
|
||||
|
||||
async function captcha(width: number, height: number): Promise<CaptchaResult> {
|
||||
return await axios.get(`/captcha?width=${width}&height=${height}`);
|
||||
}
|
||||
|
||||
const getAttachmentReadAPI = (id: string) => `${getBaseURI()}/attach/read/${id}`;
|
||||
|
||||
const getAttachmentTempReadAPI = (id: string) => `${getBaseURI()}/attach/temp/read?id=${id}`;
|
||||
|
||||
async function getTemplate(bizType: TemplateBizType, code: string): Promise<string> {
|
||||
return axios.get(`/template?bizType=${bizType}&bizCode=${code}`);
|
||||
@@ -30,7 +44,9 @@ async function settingMap(map: Record<string, string[]>): Promise<Map<string, Ma
|
||||
|
||||
export default {
|
||||
getCaptchaAPI,
|
||||
captcha,
|
||||
getAttachmentReadAPI,
|
||||
getAttachmentTempReadAPI,
|
||||
getTemplate,
|
||||
settingMap
|
||||
};
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { axios } from "./BaseAPI";
|
||||
import type { Page, PageResult, Permission, PermissionPayload } from "../types";
|
||||
|
||||
const BASE_URI = "/user/permission";
|
||||
|
||||
async function list(req: Page<Permission>): Promise<PageResult<Permission>> {
|
||||
return axios.post(`${BASE_URI}/list`, req);
|
||||
}
|
||||
|
||||
async function create(req: PermissionPayload): Promise<void> {
|
||||
return axios.post(`${BASE_URI}/create`, req);
|
||||
}
|
||||
|
||||
async function update(req: PermissionPayload): Promise<void> {
|
||||
return axios.post(`${BASE_URI}/update`, req);
|
||||
}
|
||||
|
||||
async function remove(id: string): Promise<void> {
|
||||
return axios.post(`${BASE_URI}/delete`, undefined, {
|
||||
params: { id }
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
list,
|
||||
create,
|
||||
update,
|
||||
remove
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import { axios } from "./BaseAPI";
|
||||
import type { Page, PageResult, Permission, Role, RolePayload, UserRoleAuthorizeReq } from "../types";
|
||||
|
||||
const BASE_URI = "/user/role";
|
||||
|
||||
async function list(req: Page<Role>): Promise<PageResult<Role>> {
|
||||
return axios.post(`${BASE_URI}/list`, req);
|
||||
}
|
||||
|
||||
async function detail(id: string): Promise<Role> {
|
||||
return axios.post(`${BASE_URI}/detail`, undefined, {
|
||||
params: { id }
|
||||
});
|
||||
}
|
||||
|
||||
async function create(req: RolePayload): Promise<void> {
|
||||
return axios.post(`${BASE_URI}/create`, req);
|
||||
}
|
||||
|
||||
async function update(req: RolePayload): Promise<void> {
|
||||
return axios.post(`${BASE_URI}/update`, req);
|
||||
}
|
||||
|
||||
async function remove(id: string): Promise<void> {
|
||||
return axios.post(`${BASE_URI}/delete`, undefined, {
|
||||
params: { id }
|
||||
});
|
||||
}
|
||||
|
||||
async function authorizedList(userId: string): Promise<Role[]> {
|
||||
return axios.post(`${BASE_URI}/authorized/list`, undefined, {
|
||||
params: { userId }
|
||||
});
|
||||
}
|
||||
|
||||
async function authorize(req: UserRoleAuthorizeReq): Promise<void> {
|
||||
return axios.post(`${BASE_URI}/authorized/create`, req);
|
||||
}
|
||||
|
||||
async function authorizedPermission(userId: string): Promise<Permission[]> {
|
||||
return axios.post(`${BASE_URI}/authorized/permission`, undefined, {
|
||||
params: { userId }
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
list,
|
||||
detail,
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
authorizedList,
|
||||
authorize,
|
||||
authorizedPermission
|
||||
};
|
||||
+33
-33
@@ -1,30 +1,21 @@
|
||||
import {
|
||||
Attachment,
|
||||
CaptchaData,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
RegisterRequest,
|
||||
UserAttachType,
|
||||
UserProfileView,
|
||||
UserView
|
||||
} from "../types";
|
||||
import { axios } from "./BaseAPI";
|
||||
import {Attachment, CaptchaData, LoginRequest, LoginResponse, RegisterRequest, UpdatePasswordRequest, User, UserAttachType} from "../types";
|
||||
import {axios} from "./BaseAPI";
|
||||
import CommonAPI from "./CommonAPI";
|
||||
|
||||
const BASE_URI = "/user";
|
||||
|
||||
async function register(captchaData: CaptchaData<RegisterRequest>): Promise<LoginResponse> {
|
||||
return axios.post(`${BASE_URI}/register`, captchaData);
|
||||
async function register(req: CaptchaData<RegisterRequest>): Promise<LoginResponse> {
|
||||
return await axios.post(`${BASE_URI}/register`, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @param captchaData 验证码登录对象
|
||||
* @param req 验证码登录对象
|
||||
* @returns LoginResponse
|
||||
*/
|
||||
async function login(captchaData: CaptchaData<LoginRequest>): Promise<LoginResponse> {
|
||||
return axios.post(`${BASE_URI}/login`, captchaData);
|
||||
async function login(req: CaptchaData<LoginRequest>): Promise<LoginResponse> {
|
||||
return await axios.post(`${BASE_URI}/login`, req);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,12 +23,20 @@ async function login(captchaData: CaptchaData<LoginRequest>): Promise<LoginRespo
|
||||
*
|
||||
* @returns true 为已登录
|
||||
*/
|
||||
async function login4Token(): Promise<LoginResponse> {
|
||||
return axios.post(`${BASE_URI}/login/token`);
|
||||
async function loginByToken(): Promise<LoginResponse> {
|
||||
return await axios.post(`${BASE_URI}/login/token`);
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
return axios.post(`${BASE_URI}/logout`);
|
||||
return await axios.post(`${BASE_URI}/logout`);
|
||||
}
|
||||
|
||||
async function updateCurrent(req: User): Promise<LoginResponse> {
|
||||
return await axios.post(`${BASE_URI}/current/update`, req);
|
||||
}
|
||||
|
||||
async function updatePassword(req: UpdatePasswordRequest): Promise<void> {
|
||||
return await axios.post(`${BASE_URI}/update/password`, req);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,29 +45,28 @@ async function logout(): Promise<void> {
|
||||
* @param id 用户 ID
|
||||
* @returns 用户数据
|
||||
*/
|
||||
async function view(id: number): Promise<UserView> {
|
||||
return axios.post(`${BASE_URI}/view/${id}`);
|
||||
async function view(id: string): Promise<User> {
|
||||
return await axios.post(`${BASE_URI}/view/${id}`);
|
||||
}
|
||||
|
||||
function getAvatarURL(profile?: UserProfileView) {
|
||||
if (profile && profile.attachmentList) {
|
||||
return findAttachmentByType(profile.attachmentList, [UserAttachType.AVATAR, UserAttachType.DEFAULT_AVATAR]);
|
||||
function getAvatarURL(user?: User) {
|
||||
if (user?.attachmentList) {
|
||||
return findAttachmentByType(user.attachmentList, [UserAttachType.AVATAR, UserAttachType.DEFAULT_AVATAR]);
|
||||
}
|
||||
}
|
||||
|
||||
function getWrapperURL(profile?: UserProfileView) {
|
||||
if (profile && profile.attachmentList) {
|
||||
return findAttachmentByType(profile.attachmentList, [UserAttachType.WRAPPER, UserAttachType.DEFAULT_WRAPPER]);
|
||||
function getWrapperURL(user?: User) {
|
||||
if (user?.attachmentList) {
|
||||
return findAttachmentByType(user.attachmentList, [UserAttachType.WRAPPER, UserAttachType.DEFAULT_WRAPPER]);
|
||||
}
|
||||
}
|
||||
|
||||
function findAttachmentByType(attachmentList: Attachment[], types: UserAttachType[]) {
|
||||
for (let i = 0; i < attachmentList.length; i++) {
|
||||
const attachType = (<any>UserAttachType)[attachmentList[i].attachType!];
|
||||
for (let type of types) {
|
||||
if (attachType === type) {
|
||||
return CommonAPI.getAttachmentReadAPI(attachmentList[i].mongoId);
|
||||
}
|
||||
const attachType = attachmentList[i].attachType as UserAttachType | undefined;
|
||||
const id = attachmentList[i].id;
|
||||
if (id && attachType && types.includes(attachType)) {
|
||||
return CommonAPI.getAttachmentReadAPI(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,8 +74,10 @@ function findAttachmentByType(attachmentList: Attachment[], types: UserAttachTyp
|
||||
export default {
|
||||
register,
|
||||
login,
|
||||
login4Token,
|
||||
loginByToken,
|
||||
logout,
|
||||
updateCurrent,
|
||||
updatePassword,
|
||||
|
||||
view,
|
||||
getAvatarURL,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
export { default as ArticleAPI } from "./ArticleAPI";
|
||||
export { default as AttachmentAPI } from "./AttachmentAPI";
|
||||
export { default as CommentAPI } from "./CommentAPI";
|
||||
export { default as CommonAPI } from "./CommonAPI";
|
||||
export { default as DeveloperAPI } from "./DeveloperAPI";
|
||||
export { default as PermissionAPI } from "./PermissionAPI";
|
||||
export { default as RoleAPI } from "./RoleAPI";
|
||||
export { default as UserAPI } from "./UserAPI";
|
||||
|
||||
@@ -1,46 +1,103 @@
|
||||
<template>
|
||||
<img
|
||||
<button
|
||||
class="tui-captcha ir-pixelated"
|
||||
v-if="src"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:src="src"
|
||||
alt="验证码"
|
||||
@click="update()"
|
||||
/>
|
||||
type="button"
|
||||
:style="captchaStyle"
|
||||
:disabled="isLoading"
|
||||
:title="title"
|
||||
@click="update"
|
||||
>
|
||||
<img v-if="src" :src="src" :alt="title" />
|
||||
<span v-else v-text="placeholder" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Toolkit from "../../utils/Toolkit";
|
||||
import {CommonAPI} from "../../api";
|
||||
import type {CaptchaResult} from "../../types";
|
||||
|
||||
defineOptions({
|
||||
name: "Captcha"
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
width: number,
|
||||
height: number,
|
||||
from: string,
|
||||
api: string,
|
||||
}>(), {});
|
||||
const { width, height, from, api } = toRefs(props);
|
||||
width?: number,
|
||||
height?: number,
|
||||
title?: string,
|
||||
placeholder?: string,
|
||||
}>(), {
|
||||
width: 90,
|
||||
height: 40,
|
||||
title: "验证码",
|
||||
placeholder: "刷新"
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
update: [result: CaptchaResult],
|
||||
error: [error: unknown]
|
||||
}>();
|
||||
|
||||
const { width, height, title, placeholder } = toRefs(props);
|
||||
|
||||
const src = ref("");
|
||||
function update() {
|
||||
src.value = `${api.value}?from=${from.value}&width=${width.value}&height=${height.value}&r=${Toolkit.random(0, 999999)}`;
|
||||
const captchaId = ref("");
|
||||
const isLoading = ref(false);
|
||||
const captchaStyle = computed(() => {
|
||||
return {
|
||||
width: `${width.value / 16}rem`,
|
||||
height: `${height.value / 16}rem`
|
||||
};
|
||||
});
|
||||
|
||||
async function update(): Promise<void> {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const result = await CommonAPI.captcha(width.value, height.value);
|
||||
captchaId.value = result.id;
|
||||
src.value = result.data;
|
||||
emit("update", result);
|
||||
} catch (error) {
|
||||
emit("error", error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
onMounted(update);
|
||||
|
||||
onMounted(async () => {
|
||||
await update();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
captchaId,
|
||||
src,
|
||||
isLoading,
|
||||
update
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tui-captcha {
|
||||
cursor: var(--tui-cur-pointer);
|
||||
border: 1px solid gray;
|
||||
display: block;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
background: #f8fafc;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
color: #64748b;
|
||||
flex: 0 0 auto;
|
||||
pointer-events: all;
|
||||
place-items: center;
|
||||
border-radius: .5rem;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: .64;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+180
-64
@@ -1,79 +1,195 @@
|
||||
import { LoginResponse, LoginToken, LoginUser, UserToken } from "../types";
|
||||
import {defineStore} from "pinia";
|
||||
import {computed, reactive, readonly, ref} from "vue";
|
||||
import {CaptchaData, LoginRequest, LoginResponse, LoginUser} from "../types";
|
||||
import Cooker from "../utils/Cooker";
|
||||
import Storage from "../utils/Storage";
|
||||
import UserAPI from "../api/UserAPI";
|
||||
import {UserAPI} from "../api";
|
||||
|
||||
const loginUser = reactive<LoginUser>({
|
||||
token: undefined,
|
||||
user: undefined
|
||||
});
|
||||
|
||||
function isLogged(): boolean {
|
||||
return !!loginUser.user;
|
||||
/** 用户 Store 配置项 */
|
||||
interface UserStoreOptions {
|
||||
/** localStorage 储存 key,默认 "loginUser" */
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
async function login4Token(token: LoginToken): Promise<LoginResponse | null> {
|
||||
loginUser.token = token;
|
||||
// 未过期
|
||||
try {
|
||||
const resp = await UserAPI.login4Token();
|
||||
await updateToken(resp);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
await logout();
|
||||
/** 访问控制匹配模式 */
|
||||
export type AccessMatchMode = "and" | "or";
|
||||
|
||||
let _storageKey = "loginUser";
|
||||
|
||||
function normalizeAccessList(list?: string[]): string[] {
|
||||
if (!list?.length) {
|
||||
return [];
|
||||
}
|
||||
return null;
|
||||
return list
|
||||
.map(item => item?.trim())
|
||||
.filter((item): item is string => !!item);
|
||||
}
|
||||
|
||||
async function login4Storage(): Promise<LoginResponse | null> {
|
||||
if (!loginUser.user && (loginUser.token || Storage.has("token"))) {
|
||||
// 未登录,储存有令牌
|
||||
const token = Storage.getJSON("token") as UserToken;
|
||||
if (new Date().getTime() < token.expireAt) {
|
||||
return await login4Token(token);
|
||||
function matchAccess(sourceList?: string[], targetList?: string | string[], mode: AccessMatchMode = "or"): boolean {
|
||||
const normalizedTargetList = normalizeAccessList(Array.isArray(targetList) ? targetList : [targetList || ""]);
|
||||
if (!normalizedTargetList.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const sourceSet = new Set(normalizeAccessList(sourceList));
|
||||
if (!sourceSet.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode === "and") {
|
||||
return normalizedTargetList.every(item => sourceSet.has(item));
|
||||
}
|
||||
return normalizedTargetList.some(item => sourceSet.has(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置用户 Store,应在使用 useUserStore 之前调用
|
||||
* @param options 配置项
|
||||
*/
|
||||
function configureUserStore(options: UserStoreOptions): void {
|
||||
if (options.storageKey) {
|
||||
_storageKey = options.storageKey;
|
||||
}
|
||||
}
|
||||
|
||||
const useUserStore = defineStore("user", () => {
|
||||
type LoginUserState = LoginUser & Record<string, unknown>;
|
||||
|
||||
const loginUser = reactive<LoginUserState>({});
|
||||
const isRestored = ref(false);
|
||||
|
||||
const token = computed(() => loginUser.token ?? "");
|
||||
const isLoggedIn = computed(() => isValidLoginUser(loginUser));
|
||||
|
||||
function resetLoginUser(nextUser?: LoginUserState): void {
|
||||
Object.keys(loginUser).forEach((key) => {
|
||||
delete loginUser[key];
|
||||
});
|
||||
if (nextUser) {
|
||||
Object.assign(loginUser, nextUser);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function updateToken(loginResponse: LoginResponse): Promise<void> {
|
||||
loginUser.token = {
|
||||
id: loginResponse.id,
|
||||
value: loginResponse.token,
|
||||
expireAt: loginResponse.expireAt
|
||||
};
|
||||
loginUser.user = await UserAPI.view(loginResponse.id);
|
||||
|
||||
Storage.setJSON("token", {
|
||||
value: loginUser.token.value,
|
||||
expireAt: loginUser.token.expireAt
|
||||
} as UserToken);
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
await UserAPI.logout();
|
||||
|
||||
loginUser.token = undefined;
|
||||
loginUser.user = undefined;
|
||||
|
||||
Storage.remove("token");
|
||||
}
|
||||
|
||||
async function reloadProfile() {
|
||||
if (loginUser.token && loginUser.token.id) {
|
||||
loginUser.user = await UserAPI.view(loginUser.token.id);
|
||||
function isValidLoginUser(user?: LoginUserState | null): user is LoginResponse {
|
||||
return !!user?.token && !!user.expireAt && new Date().getTime() < user.expireAt;
|
||||
}
|
||||
}
|
||||
|
||||
const userStore = {
|
||||
loginUser,
|
||||
isLogged,
|
||||
login4Token,
|
||||
login4Storage,
|
||||
updateToken,
|
||||
logout,
|
||||
reloadProfile
|
||||
};
|
||||
function isLogged(): boolean {
|
||||
return isLoggedIn.value;
|
||||
}
|
||||
|
||||
function getLoginUser<T extends LoginUser = LoginUser>(): T | null {
|
||||
if (!isLogged()) {
|
||||
return null;
|
||||
}
|
||||
return loginUser as T;
|
||||
}
|
||||
|
||||
function getRoleList(): string[] {
|
||||
return normalizeAccessList(loginUser.user?.roleList);
|
||||
}
|
||||
|
||||
function getPermissionList(): string[] {
|
||||
return normalizeAccessList(loginUser.user?.permissionList);
|
||||
}
|
||||
|
||||
function hasRoles(roleList: string | string[], mode: AccessMatchMode = "or"): boolean {
|
||||
return matchAccess(getRoleList(), roleList, mode);
|
||||
}
|
||||
|
||||
function hasPermissions(permissionList: string | string[], mode: AccessMatchMode = "or"): boolean {
|
||||
return matchAccess(getPermissionList(), permissionList, mode);
|
||||
}
|
||||
|
||||
async function login<T extends LoginUser = LoginUser>(req: CaptchaData<LoginRequest>): Promise<T> {
|
||||
const result = await UserAPI.login(req);
|
||||
const nextUser = result as unknown as T;
|
||||
await updateLoginUser(nextUser);
|
||||
return nextUser;
|
||||
}
|
||||
|
||||
async function loginByToken<T extends LoginUser = LoginUser>(token: string): Promise<T | null> {
|
||||
loginUser.token = token;
|
||||
// 未过期
|
||||
try {
|
||||
const result = await UserAPI.loginByToken();
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
const nextUser = result as unknown as T;
|
||||
await updateLoginUser(nextUser);
|
||||
return nextUser;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
clearLoginUser();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function loginByStorage<T extends LoginUser = LoginUser>(): Promise<T | null> {
|
||||
if (loginUser.token) {
|
||||
isRestored.value = true;
|
||||
return getLoginUser<T>();
|
||||
}
|
||||
|
||||
if (!Storage.has(_storageKey)) {
|
||||
isRestored.value = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
// 未登录,储存有令牌。
|
||||
const storageUser = Storage.getJSON(_storageKey) as T;
|
||||
if (isValidLoginUser(storageUser)) {
|
||||
const result = await loginByToken<T>(storageUser.token);
|
||||
isRestored.value = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
clearLoginUser();
|
||||
isRestored.value = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
async function updateLoginUser<T extends LoginUser = LoginUser>(user: T): Promise<void> {
|
||||
resetLoginUser(user as LoginUserState);
|
||||
Storage.setJSON(_storageKey, user);
|
||||
}
|
||||
|
||||
function clearLoginUser(): void {
|
||||
resetLoginUser();
|
||||
Cooker.remove("Token");
|
||||
Storage.remove(_storageKey);
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
try {
|
||||
await UserAPI.logout();
|
||||
} finally {
|
||||
clearLoginUser();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
isLoggedIn,
|
||||
isRestored,
|
||||
getRoleList,
|
||||
getPermissionList,
|
||||
hasRoles,
|
||||
hasPermissions,
|
||||
isLogged,
|
||||
loginUser: readonly(loginUser),
|
||||
getLoginUser,
|
||||
login,
|
||||
loginByToken,
|
||||
loginByStorage,
|
||||
updateLoginUser,
|
||||
clearLoginUser,
|
||||
logout
|
||||
};
|
||||
});
|
||||
|
||||
export {
|
||||
userStore
|
||||
useUserStore,
|
||||
configureUserStore
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AttachmentView } from "./Attachment";
|
||||
import type { Attachment } from "./Attachment";
|
||||
import type { Model } from "./Model";
|
||||
|
||||
// 文章
|
||||
@@ -17,7 +17,7 @@ export type Article<E extends ArticleMusicExtendData | ArticleSoftwareExtendData
|
||||
|
||||
export type ArticleView<E extends ArticleMusicExtendData | ArticleSoftwareExtendData> = {
|
||||
comments?: number;
|
||||
attachmentList: AttachmentView[];
|
||||
attachmentList: Attachment[];
|
||||
} & Article<E>;
|
||||
|
||||
export enum ArticleType {
|
||||
|
||||
+29
-11
@@ -2,23 +2,41 @@ import { Model } from "./Model";
|
||||
|
||||
export type Attachment = {
|
||||
bizType: AttachmentBizType;
|
||||
bizId: number;
|
||||
bizId: string;
|
||||
attachType?: string;
|
||||
mongoId: string;
|
||||
lidTitle?: number;
|
||||
name?: string;
|
||||
size: number;
|
||||
} & Model
|
||||
|
||||
export type AttachmentView = {
|
||||
title?: string;
|
||||
} & Attachment
|
||||
name?: string;
|
||||
mimeType?: string;
|
||||
metadata?: unknown;
|
||||
size?: number;
|
||||
md5?: string;
|
||||
uploaderIp?: string;
|
||||
isDestroyed?: boolean;
|
||||
destroyAt?: number;
|
||||
tempFileId?: string;
|
||||
} & Model;
|
||||
|
||||
export type TempFileResp = {
|
||||
id: string;
|
||||
expireAt?: number;
|
||||
};
|
||||
|
||||
export type BizUpdateReq = {
|
||||
bizType: AttachmentBizType;
|
||||
bizId: string;
|
||||
items: Partial<Attachment>[];
|
||||
};
|
||||
|
||||
export enum AttachmentBizType {
|
||||
|
||||
GIT_ISSUE,
|
||||
USER = "USER",
|
||||
|
||||
GIT_MERGE,
|
||||
GAO_CUSTOMER = "GAO_CUSTOMER",
|
||||
|
||||
GIT_RELEASE
|
||||
GAO_REGISTER_RECORD = "GAO_REGISTER_RECORD",
|
||||
|
||||
TEMP_FILE = "TEMP_FILE",
|
||||
|
||||
MIRROR = "MIRROR"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Model, Page } from "./Model";
|
||||
import { UserView } from "./User";
|
||||
import { User } from "./User";
|
||||
|
||||
/** 评论 */
|
||||
export type Comment = {
|
||||
@@ -10,7 +10,7 @@ export type Comment = {
|
||||
content?: string;
|
||||
|
||||
/** 所属用户 */
|
||||
user?: UserView;
|
||||
user?: User;
|
||||
|
||||
/** 回复列表 */
|
||||
replies?: CommentReply[];
|
||||
@@ -41,8 +41,8 @@ export type CommentReply = {
|
||||
content?: string;
|
||||
|
||||
comment?: Comment;
|
||||
sender?: UserView;
|
||||
receiver?: UserView;
|
||||
sender?: User;
|
||||
receiver?: User;
|
||||
} & Model;
|
||||
|
||||
export enum CommentReplyBizType {
|
||||
|
||||
+6
-19
@@ -6,7 +6,7 @@ export enum RunEnv {
|
||||
|
||||
// 基本实体模型
|
||||
export type Model = {
|
||||
id?: number;
|
||||
id?: string;
|
||||
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
@@ -39,28 +39,15 @@ export type PageResult<T> = {
|
||||
|
||||
// 携带验证码的请求体
|
||||
export type CaptchaData<T> = {
|
||||
from: string;
|
||||
captchaId: string;
|
||||
captcha: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export enum CaptchaFrom {
|
||||
|
||||
LOGIN = "LOGIN",
|
||||
|
||||
REGISTER = "REGISTER",
|
||||
|
||||
/** 评论 */
|
||||
COMMENT = "COMMENT",
|
||||
|
||||
/** 评论回复 */
|
||||
COMMENT_REPLY = "COMMENT_REPLY",
|
||||
|
||||
/** Git 反馈 */
|
||||
GIT_ISSUE = "GIT_ISSUE",
|
||||
|
||||
/** Git 合并请求 */
|
||||
GIT_MERGE = "GIT_MERGE",
|
||||
// 图形验证码
|
||||
export type CaptchaResult = {
|
||||
id: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export enum ImageType {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export type ModuleCode = "CORE" | "GAO";
|
||||
|
||||
export interface Permission {
|
||||
id: string;
|
||||
moduleCode: ModuleCode;
|
||||
code: string;
|
||||
nameLangId?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
deletedAt?: number;
|
||||
}
|
||||
|
||||
export type PermissionPayload = Pick<Permission, "moduleCode" | "code" | "nameLangId" | "name" | "description"> & {
|
||||
id?: string;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { ModuleCode, Permission } from "./Permission";
|
||||
|
||||
export interface Role {
|
||||
id: string;
|
||||
moduleCode: ModuleCode;
|
||||
code: string;
|
||||
nameLangId?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
permissionList?: Permission[];
|
||||
allPermissionList?: Permission[];
|
||||
childRoleList?: Role[];
|
||||
permissionIdList?: string[];
|
||||
childRoleIdList?: string[];
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
deletedAt?: number;
|
||||
}
|
||||
|
||||
export type RolePayload = Pick<Role, "moduleCode" | "code" | "nameLangId" | "name" | "description"> & {
|
||||
id?: string;
|
||||
permissionIdList?: string[];
|
||||
childRoleIdList?: string[];
|
||||
};
|
||||
|
||||
export interface UserRoleAuthorizeReq {
|
||||
userId: string;
|
||||
moduleCode?: ModuleCode;
|
||||
roleIdList?: string[];
|
||||
roleCodeList?: string[];
|
||||
}
|
||||
+48
-47
@@ -1,79 +1,80 @@
|
||||
import { ImageType, Model } from "./Model";
|
||||
import { AttachmentView } from "./Attachment";
|
||||
import { Attachment } from "./Attachment";
|
||||
import type { Setting } from "./Setting";
|
||||
|
||||
export type User = {
|
||||
name: string;
|
||||
nick?: string;
|
||||
password?: string;
|
||||
email?: string;
|
||||
emailVerifyAt: number;
|
||||
unmuteAt?: number;
|
||||
unbanAt?: number;
|
||||
} & Model;
|
||||
|
||||
export type UserView = {
|
||||
profile: UserProfileView
|
||||
} & User;
|
||||
|
||||
export enum UserAttachType {
|
||||
|
||||
AVATAR,
|
||||
|
||||
WRAPPER,
|
||||
|
||||
DEFAULT_AVATAR,
|
||||
|
||||
DEFAULT_WRAPPER,
|
||||
}
|
||||
|
||||
export type UserProfile = {
|
||||
userId: number;
|
||||
avatarType: ImageType;
|
||||
wrapperType: ImageType;
|
||||
|
||||
exp: number;
|
||||
emailVerifyAt?: number;
|
||||
phoneNo?: string;
|
||||
phoneNoVerifyAt?: number;
|
||||
wrapperType?: ImageType;
|
||||
avatarType?: ImageType;
|
||||
exp?: number;
|
||||
sex?: number;
|
||||
birthdate?: number;
|
||||
qq?: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
lastLoginIP?: string;
|
||||
lastLoginAt?: number;
|
||||
updatedAt?: number;
|
||||
}
|
||||
unmuteAt?: number;
|
||||
unbanAt?: number;
|
||||
roleList?: string[];
|
||||
permissionList?: string[];
|
||||
attachmentList?: Attachment[];
|
||||
settingList?: Setting[];
|
||||
tempFileIdList?: string[];
|
||||
muting?: boolean;
|
||||
banning?: boolean;
|
||||
emailVerified?: boolean;
|
||||
phoneNoVerified?: boolean;
|
||||
} & Model;
|
||||
|
||||
export type UserProfileView = {
|
||||
attachmentList?: AttachmentView[]
|
||||
} & UserProfile
|
||||
export enum UserAttachType {
|
||||
|
||||
export type UserToken = {
|
||||
value: string;
|
||||
expireAt: number;
|
||||
AVATAR = "AVATAR",
|
||||
|
||||
WRAPPER = "WRAPPER",
|
||||
|
||||
LICENSE = "LICENSE",
|
||||
|
||||
DEFAULT_AVATAR = "DEFAULT_AVATAR",
|
||||
|
||||
DEFAULT_WRAPPER = "DEFAULT_WRAPPER"
|
||||
}
|
||||
|
||||
export type RegisterRequest = {
|
||||
name: string;
|
||||
password: string;
|
||||
email?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type LoginRequest = {
|
||||
user: string;
|
||||
password: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type UpdatePasswordRequest = {
|
||||
oldValue: string;
|
||||
newValue: string;
|
||||
};
|
||||
|
||||
// 登录返回
|
||||
export type LoginResponse = {
|
||||
id: number;
|
||||
id: string;
|
||||
token: string;
|
||||
expireAt: number;
|
||||
}
|
||||
user?: User;
|
||||
};
|
||||
|
||||
export type LoginUser = {
|
||||
token?: LoginToken;
|
||||
user?: UserView
|
||||
}
|
||||
|
||||
export type LoginToken = {
|
||||
id?: number;
|
||||
} & UserToken;
|
||||
id?: string;
|
||||
token?: string;
|
||||
expireAt?: number;
|
||||
user?: User;
|
||||
};
|
||||
|
||||
export enum LoginType {
|
||||
ALERT,
|
||||
|
||||
@@ -5,6 +5,8 @@ export * from "./Article";
|
||||
export * from "./Setting";
|
||||
export * from "./Attachment";
|
||||
export * from "./Model";
|
||||
export * from "./Permission";
|
||||
export * from "./Role";
|
||||
export * from "./User";
|
||||
export * from "./Template";
|
||||
export * from "./Comment";
|
||||
|
||||
Reference in New Issue
Block a user