Compare commits
4
Commits
efea624376
...
7c937a090f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c937a090f | ||
|
|
bb1fdb5547 | ||
|
|
e5473eb499 | ||
|
|
f0ba55fe25 |
@@ -0,0 +1,38 @@
|
|||||||
|
# 2026-08-06 工作日志
|
||||||
|
|
||||||
|
## TypeScript enum 重构(全量消除 enum)
|
||||||
|
用户决定改用纯联合类型,自行修改依赖方。全项目 18 处 enum 分两类处理:
|
||||||
|
|
||||||
|
### 改为纯联合类型(字符串枚举,仅作类型用)
|
||||||
|
- `Attachment.ts` AttachmentBizType、`Template.ts` TemplateBizType
|
||||||
|
- `Model.ts` RunEnv / OrderType / ImageType(ImageType 值为 ir-auto 等,与 key 不同)
|
||||||
|
- `Comment.ts` CommentReplyBizType / CommentBizType
|
||||||
|
- `User.ts` UserAttachType(在 UserAPI.ts 有值引用,已同步改为字符串字面量,import 改 type-only)
|
||||||
|
|
||||||
|
### 改为 const 对象 + 派生类型(数字枚举或运行时值引用,纯联合不可行)
|
||||||
|
- `User.ts` LoginType、`Article.ts` ArticleType / ArticleAttachType / ArticleSoftwareDownloadType / ArticleSoftwareRuntime(数字枚举,保留数值避免破坏数据契约)
|
||||||
|
- `deviceStore.ts` Breakpoints(数字,用于比较 width < Breakpoints.SM)
|
||||||
|
- `IOSize.ts` Unit(用 Object.keys/values + Unit.KB 值引用)
|
||||||
|
- `Prismjs.ts` PrismjsType / PrismjsViewer(Map 键值引用)
|
||||||
|
- `directives/Popup.ts` PopupType(switch case 值引用)
|
||||||
|
|
||||||
|
### 结论
|
||||||
|
- `vue-tsc --noEmit` 通过(exit 0)
|
||||||
|
- ESLint 因项目缺 eslint.config.js(仍用旧 .eslintrc,ESLint9 不支持)无法运行,属项目既有配置问题
|
||||||
|
- 判断依据:纯联合无运行时存在,凡 `Object.keys(enum)`、`map.set(Enum.X)`、`case Enum.X`、`Enum.X` 作值的都无法纯联合,必须 const 对象
|
||||||
|
|
||||||
|
## Storage.ts 设计修正
|
||||||
|
- `getString` 返回 `string | null`(对齐 `localStorage.getItem` 契约),不再 throw
|
||||||
|
- `getJSON` 返回 `unknown`,键不存在时返回 `null`,值无法解析时才抛错
|
||||||
|
- `getObject` 返回 `T | null`,不再 throw(修正原 JSDoc 已写 `T | undefined` 但代码 throw 的矛盾)
|
||||||
|
- `is` / `not` 键不存在时自然返回 `false` / `true`,不再崩
|
||||||
|
- `has` 去掉死代码 `!== undefined`(localStorage.getItem 只返回 string|null),单次查询
|
||||||
|
- `vue-tsc --noEmit` 通过
|
||||||
|
|
||||||
|
## Toolkit.ts debounce 修复
|
||||||
|
- **Bug 修复**:`cancel()` 原先只清定时器不重置 `leading` 状态,导致立即模式下 cancel 后首次调用丢失 leading 触发
|
||||||
|
- **类型修复**:返回类型从 `T & { cancel(): void }` 改为 `((...args: Parameters<T>) => void) & { cancel(): void }`,防抖函数实际返回 void 而非 T 的返回值
|
||||||
|
- 补充完整 JSDoc(含 `@example`),说明立即模式(leading + trailing)和延迟模式(trailing only)的行为差异
|
||||||
|
- 状态变量 `immediate` → `leading`,参数 `defaultImmediate` → `immediate`,语义更清晰
|
||||||
|
- 去掉 `eslint-disable-next-line` 和多余注释
|
||||||
|
- `vue-tsc --noEmit` 通过
|
||||||
+10
-10
@@ -1,5 +1,5 @@
|
|||||||
import {Attachment, BizUpdateReq, TempFileResp} from "../types";
|
import { Attachment, BizIdUpdate, TempFile } from "../types";
|
||||||
import {axios} from "./BaseAPI";
|
import { axios } from "./BaseAPI";
|
||||||
import CommonAPI from "./CommonAPI";
|
import CommonAPI from "./CommonAPI";
|
||||||
|
|
||||||
const BASE_URI = "/attach";
|
const BASE_URI = "/attach";
|
||||||
@@ -11,7 +11,7 @@ const BASE_URI = "/attach";
|
|||||||
* @param ttl 有效期
|
* @param ttl 有效期
|
||||||
* @returns 临时附件列表
|
* @returns 临时附件列表
|
||||||
*/
|
*/
|
||||||
async function uploadTemp(file: File | File[], ttl?: string): Promise<TempFileResp[]> {
|
async function upload(file: File | File[], ttl?: string): Promise<TempFile[]> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const fileList = Array.isArray(file) ? file : [file];
|
const fileList = Array.isArray(file) ? file : [file];
|
||||||
for (const item of fileList) {
|
for (const item of fileList) {
|
||||||
@@ -28,8 +28,8 @@ async function uploadTemp(file: File | File[], ttl?: string): Promise<TempFileRe
|
|||||||
*
|
*
|
||||||
* @param req 更新请求
|
* @param req 更新请求
|
||||||
*/
|
*/
|
||||||
async function updateByBiz(req: BizUpdateReq): Promise<void> {
|
async function updateByBizId(req: BizIdUpdate): Promise<void> {
|
||||||
return await axios.post(`${BASE_URI}/update/biz`, req);
|
return await axios.post(`${BASE_URI}/update/biz/id`, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,8 +39,8 @@ async function updateByBiz(req: BizUpdateReq): Promise<void> {
|
|||||||
* @param bizId 业务 ID
|
* @param bizId 业务 ID
|
||||||
* @param attachTypeList 附件类型列表
|
* @param attachTypeList 附件类型列表
|
||||||
*/
|
*/
|
||||||
async function listByBiz(bizType: string, bizId: string, attachTypeList?: string[]): Promise<Attachment[]> {
|
async function listByBizId(bizType: string, bizId: string, attachTypeList?: string[]): Promise<Attachment[]> {
|
||||||
return await axios.get(`${BASE_URI}/list/biz`, {
|
return await axios.get(`${BASE_URI}/list/biz/id`, {
|
||||||
params: {
|
params: {
|
||||||
bizType,
|
bizType,
|
||||||
bizId,
|
bizId,
|
||||||
@@ -50,9 +50,9 @@ async function listByBiz(bizType: string, bizId: string, attachTypeList?: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
uploadTemp,
|
upload,
|
||||||
updateByBiz,
|
updateByBizId,
|
||||||
listByBiz,
|
listByBizId,
|
||||||
getReadURL: CommonAPI.getAttachmentReadAPI,
|
getReadURL: CommonAPI.getAttachmentReadAPI,
|
||||||
getTempReadURL: CommonAPI.getAttachmentTempReadAPI
|
getTempReadURL: CommonAPI.getAttachmentTempReadAPI
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-11
@@ -1,4 +1,4 @@
|
|||||||
import { CaptchaData, Comment, CommentReply, Page, PageResult } from "../types";
|
import { CaptchaData, Comment, Page, PageResult } from "../types";
|
||||||
import { axios } from "./BaseAPI";
|
import { axios } from "./BaseAPI";
|
||||||
|
|
||||||
const BASE_URI = "/comment";
|
const BASE_URI = "/comment";
|
||||||
@@ -11,17 +11,7 @@ async function create(captchaData: CaptchaData<Comment>): Promise<void> {
|
|||||||
return axios.post(`${BASE_URI}/create`, captchaData);
|
return axios.post(`${BASE_URI}/create`, captchaData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pageReply(page: Page<CommentReply>): Promise<PageResult<CommentReply>> {
|
|
||||||
return axios.post(`${BASE_URI}/reply/list`, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createReply(captchaData: CaptchaData<CommentReply>): Promise<void> {
|
|
||||||
return axios.post(`${BASE_URI}/reply/create`, captchaData);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
page,
|
page,
|
||||||
create,
|
create,
|
||||||
createReply,
|
|
||||||
pageReply
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {CaptchaResult, Setting, TemplateBizType} from "../types";
|
import { CaptchaResult, Setting } from "../types";
|
||||||
import {axios} from "./BaseAPI";
|
import { axios } from "./BaseAPI";
|
||||||
|
|
||||||
function getBaseURI(): string {
|
function getBaseURI(): string {
|
||||||
const baseURL = axios.defaults.baseURL;
|
const baseURL = axios.defaults.baseURL;
|
||||||
@@ -19,10 +19,6 @@ const getAttachmentReadAPI = (id: string) => `${getBaseURI()}/attach/read/${id}`
|
|||||||
|
|
||||||
const getAttachmentTempReadAPI = (id: string) => `${getBaseURI()}/attach/temp/read?id=${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}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function settingMap(map: Record<string, string[]>): Promise<Map<string, Map<string, Setting>>> {
|
async function settingMap(map: Record<string, string[]>): Promise<Map<string, Map<string, Setting>>> {
|
||||||
const raw = await axios.post("/setting/map", map);
|
const raw = await axios.post("/setting/map", map);
|
||||||
const moduleMap = new Map<string, Map<string, Setting>>();
|
const moduleMap = new Map<string, Map<string, Setting>>();
|
||||||
@@ -47,6 +43,5 @@ export default {
|
|||||||
captcha,
|
captcha,
|
||||||
getAttachmentReadAPI,
|
getAttachmentReadAPI,
|
||||||
getAttachmentTempReadAPI,
|
getAttachmentTempReadAPI,
|
||||||
getTemplate,
|
|
||||||
settingMap
|
settingMap
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { axios } from "./BaseAPI";
|
import { axios } from "./BaseAPI";
|
||||||
import type { Page, PageResult, Permission, PermissionPayload } from "../types";
|
import type { ModuleCode, Page, PageResult, Permission, PermissionPayload } from "../types";
|
||||||
|
|
||||||
const BASE_URI = "/user/permission";
|
const BASE_URI = "/user/permission";
|
||||||
|
|
||||||
@@ -21,9 +21,17 @@ async function remove(id: string): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 查询当前登录用户在指定模块内可转授的权限。 */
|
||||||
|
async function grantableList(moduleCode: ModuleCode): Promise<Permission[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/grantable/list`, undefined, {
|
||||||
|
params: { moduleCode }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
list,
|
list,
|
||||||
create,
|
create,
|
||||||
update,
|
update,
|
||||||
remove
|
remove,
|
||||||
|
grantableList
|
||||||
};
|
};
|
||||||
|
|||||||
+70
-2
@@ -1,5 +1,5 @@
|
|||||||
import { axios } from "./BaseAPI";
|
import { axios } from "./BaseAPI";
|
||||||
import type { ModuleCode, Page, PageResult, Permission, Role, RolePayload, UserRoleAuthorizeReq } from "../types";
|
import type { ModuleCode, Page, PageResult, Permission, Role, RolePayload, AuthorizeUserRole } from "../types";
|
||||||
|
|
||||||
const BASE_URI = "/user/role";
|
const BASE_URI = "/user/role";
|
||||||
|
|
||||||
@@ -27,6 +27,65 @@ async function remove(id: string): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 查询当前登录用户在模块内可管理的角色。 */
|
||||||
|
async function manageableList(moduleCode: ModuleCode): Promise<Role[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/manageable/list`, undefined, {
|
||||||
|
params: { moduleCode }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询当前登录用户在模块内可授权给账号的角色。 */
|
||||||
|
async function grantableList(moduleCode: ModuleCode): Promise<Role[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/grantable/list`, undefined, {
|
||||||
|
params: { moduleCode }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询当前登录用户直属角色详情,不按可管理范围过滤。 */
|
||||||
|
async function currentDetailList(moduleCode?: ModuleCode): Promise<Role[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/current/detail/list`, undefined, {
|
||||||
|
params: { moduleCode }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询当前登录用户可以给目标角色配置的直接权限 P 候选项。 */
|
||||||
|
async function grantablePermissionList(roleId: string): Promise<Permission[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/permission/grantable/list`, undefined, {
|
||||||
|
params: { roleId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询目标角色当前可继续转授的权限 D。 */
|
||||||
|
async function delegationList(roleId: string): Promise<Permission[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/delegation/list`, undefined, {
|
||||||
|
params: { roleId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询当前登录用户可以给目标角色配置的 D 候选项。 */
|
||||||
|
async function grantableDelegationList(roleId: string): Promise<Permission[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/delegation/grantable/list`, undefined, {
|
||||||
|
params: { roleId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 覆盖保存目标角色可继续转授的权限 D。 */
|
||||||
|
async function updateDelegation(req: Pick<RolePayload, "id" | "delegationPermissionIdList">): Promise<void> {
|
||||||
|
await axios.post(`${BASE_URI}/delegation/update`, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询目标角色可选管理父级。 */
|
||||||
|
async function parentGrantableList(roleId: string): Promise<Role[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/parent/grantable/list`, undefined, {
|
||||||
|
params: { roleId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存目标角色管理父级。 */
|
||||||
|
async function updateParent(req: Pick<RolePayload, "id" | "parentRoleId">): Promise<void> {
|
||||||
|
await axios.post(`${BASE_URI}/parent/update`, req);
|
||||||
|
}
|
||||||
|
|
||||||
async function authorizedList(userId: string, moduleCode?: ModuleCode): Promise<Role[]> {
|
async function authorizedList(userId: string, moduleCode?: ModuleCode): Promise<Role[]> {
|
||||||
return axios.post(`${BASE_URI}/authorized/list`, undefined, {
|
return axios.post(`${BASE_URI}/authorized/list`, undefined, {
|
||||||
params: {
|
params: {
|
||||||
@@ -36,7 +95,7 @@ async function authorizedList(userId: string, moduleCode?: ModuleCode): Promise<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function authorize(req: UserRoleAuthorizeReq): Promise<void> {
|
async function authorize(req: AuthorizeUserRole): Promise<void> {
|
||||||
return axios.post(`${BASE_URI}/authorized/create`, req);
|
return axios.post(`${BASE_URI}/authorized/create`, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +111,15 @@ export default {
|
|||||||
create,
|
create,
|
||||||
update,
|
update,
|
||||||
remove,
|
remove,
|
||||||
|
manageableList,
|
||||||
|
grantableList,
|
||||||
|
currentDetailList,
|
||||||
|
grantablePermissionList,
|
||||||
|
delegationList,
|
||||||
|
grantableDelegationList,
|
||||||
|
updateDelegation,
|
||||||
|
parentGrantableList,
|
||||||
|
updateParent,
|
||||||
authorizedList,
|
authorizedList,
|
||||||
authorize,
|
authorize,
|
||||||
authorizedPermission
|
authorizedPermission
|
||||||
|
|||||||
+8
-6
@@ -1,5 +1,4 @@
|
|||||||
import type {Attachment, CaptchaData, LoginRequest, LoginResponse, RegisterRequest, UpdatePasswordRequest, User} from "../types";
|
import type {Attachment, CaptchaData, Gender, LoginRequest, LoginResponse, RegisterRequest, UpdatePasswordRequest, User, UserAttachType} from "../types";
|
||||||
import {UserAttachType} from "../types";
|
|
||||||
import {axios} from "./BaseAPI";
|
import {axios} from "./BaseAPI";
|
||||||
import CommonAPI from "./CommonAPI";
|
import CommonAPI from "./CommonAPI";
|
||||||
|
|
||||||
@@ -7,6 +6,9 @@ const BASE_URI = "/user";
|
|||||||
type UserAttachmentOwner = {
|
type UserAttachmentOwner = {
|
||||||
attachmentList?: readonly Attachment[];
|
attachmentList?: readonly Attachment[];
|
||||||
};
|
};
|
||||||
|
type UpdateCurrentRequest = Omit<Partial<User>, "gender"> & {
|
||||||
|
gender?: Gender | "";
|
||||||
|
};
|
||||||
|
|
||||||
async function register(req: CaptchaData<RegisterRequest>): Promise<LoginResponse> {
|
async function register(req: CaptchaData<RegisterRequest>): Promise<LoginResponse> {
|
||||||
return await axios.post(`${BASE_URI}/register`, req);
|
return await axios.post(`${BASE_URI}/register`, req);
|
||||||
@@ -35,7 +37,7 @@ async function logout(): Promise<void> {
|
|||||||
return await axios.post(`${BASE_URI}/logout`);
|
return await axios.post(`${BASE_URI}/logout`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateCurrent(req: Partial<User>): Promise<LoginResponse> {
|
async function updateCurrent(req: UpdateCurrentRequest): Promise<LoginResponse> {
|
||||||
return await axios.post(`${BASE_URI}/current/update`, req);
|
return await axios.post(`${BASE_URI}/current/update`, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,17 +57,17 @@ async function view(id: string): Promise<User> {
|
|||||||
|
|
||||||
function getAvatarURL(user?: UserAttachmentOwner) {
|
function getAvatarURL(user?: UserAttachmentOwner) {
|
||||||
if (user?.attachmentList) {
|
if (user?.attachmentList) {
|
||||||
return findAttachmentByType(user.attachmentList, [UserAttachType.AVATAR, UserAttachType.DEFAULT_AVATAR]);
|
return findAttachmentByType(user.attachmentList, "AVATAR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWrapperURL(user?: UserAttachmentOwner) {
|
function getWrapperURL(user?: UserAttachmentOwner) {
|
||||||
if (user?.attachmentList) {
|
if (user?.attachmentList) {
|
||||||
return findAttachmentByType(user.attachmentList, [UserAttachType.WRAPPER, UserAttachType.DEFAULT_WRAPPER]);
|
return findAttachmentByType(user.attachmentList, "WRAPPER");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findAttachmentByType(attachmentList: readonly Attachment[], types: UserAttachType[]) {
|
function findAttachmentByType(attachmentList: readonly Attachment[], ...types: UserAttachType[]) {
|
||||||
for (let i = 0; i < attachmentList.length; i++) {
|
for (let i = 0; i < attachmentList.length; i++) {
|
||||||
const attachType = attachmentList[i].attachType as UserAttachType | undefined;
|
const attachType = attachmentList[i].attachType as UserAttachType | undefined;
|
||||||
const id = attachmentList[i].id;
|
const id = attachmentList[i].id;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import EmptyTips from "./empty-tips";
|
|||||||
import MarkdownView from "./markdown-view";
|
import MarkdownView from "./markdown-view";
|
||||||
import BEFlowerFall from "./background-effect/flower-fall";
|
import BEFlowerFall from "./background-effect/flower-fall";
|
||||||
import MarkdownEditor from "./markdown-editor";
|
import MarkdownEditor from "./markdown-editor";
|
||||||
|
import PassTimeLabel from "./passtime-label";
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
Icon,
|
Icon,
|
||||||
@@ -20,7 +21,8 @@ export default [
|
|||||||
EmptyTips,
|
EmptyTips,
|
||||||
MarkdownView,
|
MarkdownView,
|
||||||
BEFlowerFall,
|
BEFlowerFall,
|
||||||
MarkdownEditor
|
MarkdownEditor,
|
||||||
|
PassTimeLabel
|
||||||
];
|
];
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -33,5 +35,6 @@ export {
|
|||||||
EmptyTips,
|
EmptyTips,
|
||||||
MarkdownView,
|
MarkdownView,
|
||||||
BEFlowerFall,
|
BEFlowerFall,
|
||||||
MarkdownEditor
|
MarkdownEditor,
|
||||||
|
PassTimeLabel
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "../../utils/Toolkit";
|
||||||
|
|
||||||
|
export const PassTimeLabel = Toolkit.withInstall(view);
|
||||||
|
export default PassTimeLabel;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<span
|
||||||
|
v-if="timestamp"
|
||||||
|
class="tui-passtime-label"
|
||||||
|
:class="{ underline }"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
v-text="content"
|
||||||
|
@click="toggle"
|
||||||
|
@keydown.enter.space.prevent="toggle"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Time from "../../utils/Time";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "PassTimeLabel"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
timestamp?: number;
|
||||||
|
onlyTime?: boolean;
|
||||||
|
onlyDate?: boolean;
|
||||||
|
underline?: boolean;
|
||||||
|
}>(), {
|
||||||
|
onlyTime: false,
|
||||||
|
onlyDate: false,
|
||||||
|
underline: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const { timestamp, onlyTime, onlyDate } = toRefs(props);
|
||||||
|
const showingDetail = ref(false);
|
||||||
|
|
||||||
|
const content = computed(() => {
|
||||||
|
if (!showingDetail.value) {
|
||||||
|
return Time.toPassedDateTime(timestamp.value);
|
||||||
|
}
|
||||||
|
if (onlyTime.value) {
|
||||||
|
return Time.toTime(timestamp.value);
|
||||||
|
}
|
||||||
|
if (onlyDate.value) {
|
||||||
|
return Time.toDate(timestamp.value);
|
||||||
|
}
|
||||||
|
return Time.toDateTime(timestamp.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggle(): void {
|
||||||
|
showingDetail.value = !showingDetail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-passtime-label {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: .16rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -32,23 +32,23 @@ const isShortScreen = ref(false);
|
|||||||
const isMobileLayout = ref(false);
|
const isMobileLayout = ref(false);
|
||||||
|
|
||||||
/** 断点配置,单位:px */
|
/** 断点配置,单位:px */
|
||||||
enum Breakpoints {
|
const Breakpoints = {
|
||||||
|
|
||||||
/** 超小设备 */
|
/** 超小设备 */
|
||||||
XS = 480,
|
XS: 480,
|
||||||
|
|
||||||
/** 手机 */
|
/** 手机 */
|
||||||
SM = 650,
|
SM: 650,
|
||||||
|
|
||||||
/** 平板 */
|
/** 平板 */
|
||||||
MD = 768,
|
MD: 768,
|
||||||
|
|
||||||
/** 笔记本 */
|
/** 笔记本 */
|
||||||
LG = 1024,
|
LG: 1024,
|
||||||
|
|
||||||
/** 大屏幕 */
|
/** 大屏幕 */
|
||||||
XL = 1440
|
XL: 1440
|
||||||
}
|
} as const;
|
||||||
|
|
||||||
Resizer.addListener("DEVICE_SIZE", (width, height) => {
|
Resizer.addListener("DEVICE_SIZE", (width, height) => {
|
||||||
screenWidth.value = width;
|
screenWidth.value = width;
|
||||||
|
|||||||
+7
-68
@@ -2,12 +2,12 @@ import type { Attachment } from "./Attachment";
|
|||||||
import type { Model } from "./Model";
|
import type { Model } from "./Model";
|
||||||
|
|
||||||
// 文章
|
// 文章
|
||||||
export type Article<E extends ArticleMusicExtendData | ArticleSoftwareExtendData> = {
|
export type Article<A> = {
|
||||||
title?: string;
|
title?: string;
|
||||||
type: ArticleType;
|
type: ArticleType;
|
||||||
digest?: string;
|
digest?: string;
|
||||||
data?: string;
|
data?: string;
|
||||||
extendData?: E;
|
args?: A;
|
||||||
reads: number;
|
reads: number;
|
||||||
likes: number;
|
likes: number;
|
||||||
showComment: boolean;
|
showComment: boolean;
|
||||||
@@ -15,74 +15,13 @@ export type Article<E extends ArticleMusicExtendData | ArticleSoftwareExtendData
|
|||||||
canRanking: boolean;
|
canRanking: boolean;
|
||||||
} & Model;
|
} & Model;
|
||||||
|
|
||||||
export type ArticleView<E extends ArticleMusicExtendData | ArticleSoftwareExtendData> = {
|
export type ArticleView<A> = {
|
||||||
comments?: number;
|
comments?: number;
|
||||||
attachmentList: Attachment[];
|
attachmentList: Attachment[];
|
||||||
} & Article<E>;
|
} & Article<A>;
|
||||||
|
|
||||||
export enum ArticleType {
|
export const ArticleType = "COMMON";
|
||||||
|
|
||||||
/** 公版 */
|
export type ArticleType = (typeof ArticleType)[keyof typeof ArticleType];
|
||||||
PUBLIC,
|
|
||||||
|
|
||||||
/** 音乐 */
|
export const ArticleAttachType = "COVER";
|
||||||
MUSIC,
|
|
||||||
|
|
||||||
/** 软件 */
|
|
||||||
SOFTWARE
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ArticleAttachType {
|
|
||||||
|
|
||||||
COVER,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ArticleMusicExtendData = {
|
|
||||||
title: string;
|
|
||||||
list: ArticleMusicItem[];
|
|
||||||
info: {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ArticleSoftwareExtendData = {
|
|
||||||
url?: string;
|
|
||||||
downloads: ArticleSoftwareDownload[];
|
|
||||||
format: string;
|
|
||||||
runtime: ArticleSoftwareRuntime[];
|
|
||||||
size: number;
|
|
||||||
version: string;
|
|
||||||
password?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ArticleSoftwareDownloadType {
|
|
||||||
|
|
||||||
TIMI_MONGO,
|
|
||||||
|
|
||||||
TIMI_COS,
|
|
||||||
|
|
||||||
URL
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ArticleSoftwareRuntime {
|
|
||||||
|
|
||||||
JVM,
|
|
||||||
|
|
||||||
WINDOWS,
|
|
||||||
|
|
||||||
LINUX,
|
|
||||||
|
|
||||||
MAC_OS
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ArticleSoftwareDownload = {
|
|
||||||
type: ArticleSoftwareDownloadType;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ArticleMusicItem = {
|
|
||||||
title: string;
|
|
||||||
audio?: string;
|
|
||||||
video?: string;
|
|
||||||
}
|
|
||||||
|
|||||||
+6
-17
@@ -1,10 +1,10 @@
|
|||||||
import { Model } from "./Model";
|
import { Model } from "./Model";
|
||||||
|
|
||||||
export type Attachment = {
|
export type Attachment = {
|
||||||
bizType: AttachmentBizType;
|
bizType?: AttachmentBizType;
|
||||||
bizId: string;
|
bizId?: string;
|
||||||
attachType?: string;
|
attachType?: string;
|
||||||
mongoId: string;
|
mongoId?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
mimeType?: string;
|
mimeType?: string;
|
||||||
@@ -17,26 +17,15 @@ export type Attachment = {
|
|||||||
tempFileId?: string;
|
tempFileId?: string;
|
||||||
} & Model;
|
} & Model;
|
||||||
|
|
||||||
export type TempFileResp = {
|
export type TempFile = {
|
||||||
id: string;
|
id: string;
|
||||||
expireAt?: number;
|
expireAt?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BizUpdateReq = {
|
export type BizIdUpdate = {
|
||||||
bizType: AttachmentBizType;
|
bizType: AttachmentBizType;
|
||||||
bizId: string;
|
bizId: string;
|
||||||
items: Partial<Attachment>[];
|
items: Partial<Attachment>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum AttachmentBizType {
|
export type AttachmentBizType = | "USER" | "GAO_CUSTOMER" | "GAO_REGISTER_RECORD" | "TEMP_FILE" | "MIRROR";
|
||||||
|
|
||||||
USER = "USER",
|
|
||||||
|
|
||||||
GAO_CUSTOMER = "GAO_CUSTOMER",
|
|
||||||
|
|
||||||
GAO_REGISTER_RECORD = "GAO_REGISTER_RECORD",
|
|
||||||
|
|
||||||
TEMP_FILE = "TEMP_FILE",
|
|
||||||
|
|
||||||
MIRROR = "MIRROR"
|
|
||||||
}
|
|
||||||
|
|||||||
+3
-32
@@ -13,10 +13,10 @@ export type Comment = {
|
|||||||
user?: User;
|
user?: User;
|
||||||
|
|
||||||
/** 回复列表 */
|
/** 回复列表 */
|
||||||
replies?: CommentReply[];
|
replies?: Comment[];
|
||||||
|
|
||||||
/** 回复分页 */
|
/** 回复分页 */
|
||||||
repliesPage?: Page<CommentReply>;
|
repliesPage?: Page<Comment>;
|
||||||
|
|
||||||
/** 用于绑定组件当前页下标 */
|
/** 用于绑定组件当前页下标 */
|
||||||
repliesCurrent?: number;
|
repliesCurrent?: number;
|
||||||
@@ -31,33 +31,4 @@ export type Comment = {
|
|||||||
repository?: object;
|
repository?: object;
|
||||||
} & Model
|
} & Model
|
||||||
|
|
||||||
export type CommentReply = {
|
export type CommentBizType = "ARTICLE" | "GIT_ISSUE" | "GIT_MERGE";
|
||||||
replyId?: number;
|
|
||||||
commentId?: number;
|
|
||||||
senderId?: number;
|
|
||||||
senderNick?: string;
|
|
||||||
receiverId?: number;
|
|
||||||
receiverNick?: string;
|
|
||||||
content?: string;
|
|
||||||
|
|
||||||
comment?: Comment;
|
|
||||||
sender?: User;
|
|
||||||
receiver?: User;
|
|
||||||
} & Model;
|
|
||||||
|
|
||||||
export enum CommentReplyBizType {
|
|
||||||
|
|
||||||
COMMENT = "COMMENT",
|
|
||||||
|
|
||||||
SENDER = "SENDER",
|
|
||||||
|
|
||||||
RECEIVER = "RECEIVER"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CommentBizType {
|
|
||||||
ARTICLE = "ARTICLE",
|
|
||||||
|
|
||||||
GIT_ISSUE = "GIT_ISSUE",
|
|
||||||
|
|
||||||
GIT_MERGE = "GIT_MERGE",
|
|
||||||
}
|
|
||||||
|
|||||||
+7
-17
@@ -1,9 +1,3 @@
|
|||||||
export enum RunEnv {
|
|
||||||
DEV = "DEV",
|
|
||||||
DEV_SSL = "DEV_SSL",
|
|
||||||
PROD = "PROD"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 基本实体模型
|
// 基本实体模型
|
||||||
export type Model = {
|
export type Model = {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -23,17 +17,17 @@ export type ApiResponse<T> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Page<T> = {
|
export type Page<T> = {
|
||||||
index: number;
|
|
||||||
size: number;
|
|
||||||
orderMap?: { [K in keyof T]?: OrderType };
|
orderMap?: { [K in keyof T]?: OrderType };
|
||||||
equalsExample?: Partial<T>;
|
equalsExample?: Partial<T>;
|
||||||
likesExample?: Partial<T>;
|
likesExample?: Partial<T>;
|
||||||
|
} & BasePage;
|
||||||
|
|
||||||
|
export type BasePage = {
|
||||||
|
index: number;
|
||||||
|
size: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrderType {
|
export type OrderType = "ASC" | "DESC";
|
||||||
ASC = "ASC",
|
|
||||||
DESC = "DESC"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PageResult<T> = {
|
export type PageResult<T> = {
|
||||||
total: number;
|
total: number;
|
||||||
@@ -53,11 +47,7 @@ export type CaptchaResult = {
|
|||||||
data: string;
|
data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImageType {
|
export type ImageType = "IR-AUTO" | "IR-SMOOTH" | "IR-PIXELATED";
|
||||||
AUTO = "ir-auto",
|
|
||||||
SMOOTH = "ir-smooth",
|
|
||||||
PIXELATED = "ir-pixelated"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type KeyValue<V, K = string> = {
|
export type KeyValue<V, K = string> = {
|
||||||
key: K;
|
key: K;
|
||||||
|
|||||||
@@ -7,11 +7,18 @@ export interface Permission {
|
|||||||
nameLangId?: string;
|
nameLangId?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
builtin?: boolean;
|
||||||
|
protectedPermission?: boolean;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
|
createdBy?: string;
|
||||||
createdAt?: number;
|
createdAt?: number;
|
||||||
updatedAt?: number;
|
updatedAt?: number;
|
||||||
deletedAt?: number;
|
deletedAt?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AuthOwnerType = "SYSTEM" | "MODULE" | "TENANT" | "STORE" | "USER";
|
||||||
|
|
||||||
export type PermissionPayload = Pick<Permission, "moduleCode" | "code" | "nameLangId" | "name" | "description"> & {
|
export type PermissionPayload = Pick<Permission, "moduleCode" | "code" | "nameLangId" | "name" | "description"> & {
|
||||||
id?: string;
|
id?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
+20
-10
@@ -1,31 +1,41 @@
|
|||||||
import type { ModuleCode, Permission } from "./Permission";
|
import type { AuthOwnerType, ModuleCode, Permission } from "./Permission";
|
||||||
|
import { Model } from "./Model";
|
||||||
|
|
||||||
export interface Role {
|
export type Role = {
|
||||||
id: string;
|
|
||||||
moduleCode: ModuleCode;
|
moduleCode: ModuleCode;
|
||||||
code: string;
|
code: string;
|
||||||
nameLangId?: string;
|
nameLangId?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
builtin?: boolean;
|
||||||
|
protectedRole?: boolean;
|
||||||
|
parentRoleId?: string;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
|
createdBy?: string;
|
||||||
|
|
||||||
permissionList?: Permission[];
|
permissionList?: Permission[];
|
||||||
allPermissionList?: Permission[];
|
allPermissionList?: Permission[];
|
||||||
childRoleList?: Role[];
|
childRoleList?: Role[];
|
||||||
|
parentRole?: Role;
|
||||||
|
delegationPermissionList?: Permission[];
|
||||||
permissionIdList?: string[];
|
permissionIdList?: string[];
|
||||||
childRoleIdList?: string[];
|
childRoleIdList?: string[];
|
||||||
createdAt?: number;
|
delegationPermissionIdList?: string[];
|
||||||
updatedAt?: number;
|
} & Model;
|
||||||
deletedAt?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RolePayload = Pick<Role, "moduleCode" | "code" | "nameLangId" | "name" | "description"> & {
|
export type RolePayload = Partial<Pick<Role, "moduleCode" | "code" | "nameLangId" | "name" | "description">> & {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
parentRoleId?: string;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
permissionIdList?: string[];
|
permissionIdList?: string[];
|
||||||
childRoleIdList?: string[];
|
childRoleIdList?: string[];
|
||||||
|
delegationPermissionIdList?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface UserRoleAuthorizeReq {
|
export interface AuthorizeUserRole {
|
||||||
userId: string;
|
userId: string;
|
||||||
moduleCode?: ModuleCode;
|
moduleCode?: ModuleCode;
|
||||||
roleIdList?: string[];
|
roleIdList?: string[];
|
||||||
roleCodeList?: string[];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export enum TemplateBizType {
|
|
||||||
|
|
||||||
GIT = "GIT",
|
|
||||||
|
|
||||||
FOREVER_MC = "FOREVER_MC"
|
|
||||||
}
|
|
||||||
+7
-19
@@ -1,6 +1,9 @@
|
|||||||
import { ImageType, Model } from "./Model";
|
import { ImageType, Model } from "./Model";
|
||||||
import { Attachment } from "./Attachment";
|
import { Attachment } from "./Attachment";
|
||||||
import type { Setting } from "./Setting";
|
import type { Setting } from "./Setting";
|
||||||
|
import { Role } from "./Role";
|
||||||
|
|
||||||
|
export type Gender = "MALE" | "FEMALE";
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -13,7 +16,7 @@ export type User = {
|
|||||||
wrapperType?: ImageType;
|
wrapperType?: ImageType;
|
||||||
avatarType?: ImageType;
|
avatarType?: ImageType;
|
||||||
exp?: number;
|
exp?: number;
|
||||||
sex?: number;
|
gender?: Gender;
|
||||||
birthdate?: number;
|
birthdate?: number;
|
||||||
qq?: string;
|
qq?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@@ -21,7 +24,9 @@ export type User = {
|
|||||||
lastLoginAt?: number;
|
lastLoginAt?: number;
|
||||||
unmuteAt?: number;
|
unmuteAt?: number;
|
||||||
unbanAt?: number;
|
unbanAt?: number;
|
||||||
|
|
||||||
roleList?: string[];
|
roleList?: string[];
|
||||||
|
roleEntityList?: Role[];
|
||||||
permissionList?: string[];
|
permissionList?: string[];
|
||||||
attachmentList?: Attachment[];
|
attachmentList?: Attachment[];
|
||||||
settingList?: Setting[];
|
settingList?: Setting[];
|
||||||
@@ -32,18 +37,7 @@ export type User = {
|
|||||||
phoneNoVerified?: boolean;
|
phoneNoVerified?: boolean;
|
||||||
} & Model;
|
} & Model;
|
||||||
|
|
||||||
export enum UserAttachType {
|
export type UserAttachType = "AVATAR" | "WRAPPER";
|
||||||
|
|
||||||
AVATAR = "AVATAR",
|
|
||||||
|
|
||||||
WRAPPER = "WRAPPER",
|
|
||||||
|
|
||||||
LICENSE = "LICENSE",
|
|
||||||
|
|
||||||
DEFAULT_AVATAR = "DEFAULT_AVATAR",
|
|
||||||
|
|
||||||
DEFAULT_WRAPPER = "DEFAULT_WRAPPER"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RegisterRequest = {
|
export type RegisterRequest = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -76,12 +70,6 @@ export type LoginUser = {
|
|||||||
user?: User;
|
user?: User;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum LoginType {
|
|
||||||
ALERT,
|
|
||||||
IFRAME,
|
|
||||||
REDIRECT
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UserLevelType = {
|
export type UserLevelType = {
|
||||||
exp: number; // 经验数值,和 UserData.exp 一样
|
exp: number; // 经验数值,和 UserData.exp 一样
|
||||||
value: number; // 经验对应等级,[0, 8]
|
value: number; // 经验对应等级,[0, 8]
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export * from "./Model";
|
|||||||
export * from "./Permission";
|
export * from "./Permission";
|
||||||
export * from "./Role";
|
export * from "./Role";
|
||||||
export * from "./User";
|
export * from "./User";
|
||||||
export * from "./Template";
|
|
||||||
export * from "./Comment";
|
export * from "./Comment";
|
||||||
export * from "./Developer";
|
export * from "./Developer";
|
||||||
|
|
||||||
|
|||||||
+12
-35
@@ -1,28 +1,7 @@
|
|||||||
import Text from "./Text";
|
import Text from "./Text";
|
||||||
|
|
||||||
export enum Unit {
|
export const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB"] as const;
|
||||||
|
export type Unit = (typeof UNITS)[number];
|
||||||
/** B */
|
|
||||||
B = "B",
|
|
||||||
|
|
||||||
/** KB */
|
|
||||||
KB = "KB",
|
|
||||||
|
|
||||||
/** MB */
|
|
||||||
MB = "MB",
|
|
||||||
|
|
||||||
/** GB */
|
|
||||||
GB = "GB",
|
|
||||||
|
|
||||||
/** TB */
|
|
||||||
TB = "TB",
|
|
||||||
|
|
||||||
/** PB */
|
|
||||||
PB = "PB",
|
|
||||||
|
|
||||||
/** EB */
|
|
||||||
EB = "EB"
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 储存单位 */
|
/** 储存单位 */
|
||||||
export default class IOSize {
|
export default class IOSize {
|
||||||
@@ -48,8 +27,6 @@ export default class IOSize {
|
|||||||
/** 1 EB */
|
/** 1 EB */
|
||||||
public static EB = IOSize.PB << 10;
|
public static EB = IOSize.PB << 10;
|
||||||
|
|
||||||
public static Unit = Unit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>格式化一个储存容量,保留两位小数
|
* <p>格式化一个储存容量,保留两位小数
|
||||||
* <pre>
|
* <pre>
|
||||||
@@ -66,7 +43,7 @@ export default class IOSize {
|
|||||||
if (size === undefined || size === null) {
|
if (size === undefined || size === null) {
|
||||||
return "0 B";
|
return "0 B";
|
||||||
}
|
}
|
||||||
const units = Object.keys(Unit);
|
const units = Object.values(UNITS);
|
||||||
if (0 < size) {
|
if (0 < size) {
|
||||||
for (let i = 0; i < units.length; i++, size /= 1024) {
|
for (let i = 0; i < units.length; i++, size /= 1024) {
|
||||||
const unit = units[i];
|
const unit = units[i];
|
||||||
@@ -118,31 +95,31 @@ export default class IOSize {
|
|||||||
let unit: Unit;
|
let unit: Unit;
|
||||||
|
|
||||||
// 先尝试精确匹配枚举
|
// 先尝试精确匹配枚举
|
||||||
if (Object.values(Unit).includes(unitStr as Unit)) {
|
if (Object.values(UNITS).includes(unitStr as Unit)) {
|
||||||
unit = unitStr as Unit;
|
unit = unitStr as Unit;
|
||||||
} else {
|
} else {
|
||||||
// 处理单字母单位缩写(K/M/G/T/P/E)
|
// 处理单字母单位缩写(K/M/G/T/P/E)
|
||||||
switch (unitStr.charAt(0)) {
|
switch (unitStr.charAt(0)) {
|
||||||
case "K":
|
case "K":
|
||||||
unit = Unit.KB;
|
unit = "KB";
|
||||||
break;
|
break;
|
||||||
case "M":
|
case "M":
|
||||||
unit = Unit.MB;
|
unit = "MB";
|
||||||
break;
|
break;
|
||||||
case "G":
|
case "G":
|
||||||
unit = Unit.GB;
|
unit = "GB";
|
||||||
break;
|
break;
|
||||||
case "T":
|
case "T":
|
||||||
unit = Unit.TB;
|
unit = "TB";
|
||||||
break;
|
break;
|
||||||
case "P":
|
case "P":
|
||||||
unit = Unit.PB;
|
unit = "PB";
|
||||||
break;
|
break;
|
||||||
case "E":
|
case "E":
|
||||||
unit = Unit.EB;
|
unit = "EB";
|
||||||
break;
|
break;
|
||||||
case "B":
|
case "B":
|
||||||
unit = Unit.B;
|
unit = "B";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Unknown unit: " + unitStr);
|
throw new Error("Unknown unit: " + unitStr);
|
||||||
@@ -173,7 +150,7 @@ export default class IOSize {
|
|||||||
if (val === undefined || val === null) {
|
if (val === undefined || val === null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const units = Object.values(Unit);
|
const units = Object.values(UNITS);
|
||||||
const ordinal = units.indexOf(unit);
|
const ordinal = units.indexOf(unit);
|
||||||
return Math.round(val * Math.pow(1024, ordinal));
|
return Math.round(val * Math.pow(1024, ordinal));
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-25
@@ -1,23 +1,25 @@
|
|||||||
export enum PrismjsType {
|
export const PrismjsType = {
|
||||||
PlainText = "PlainText",
|
PlainText: "PlainText",
|
||||||
Markdown = "Markdown",
|
Markdown: "Markdown",
|
||||||
JavaScript = "JavaScript",
|
JavaScript: "JavaScript",
|
||||||
TypeScript = "TypeScript",
|
TypeScript: "TypeScript",
|
||||||
Initialization = "Initialization",
|
Initialization: "Initialization",
|
||||||
PHP = "PHP",
|
PHP: "PHP",
|
||||||
SQL = "SQL",
|
SQL: "SQL",
|
||||||
XML = "XML",
|
XML: "XML",
|
||||||
CSS = "CSS",
|
CSS: "CSS",
|
||||||
VUE = "VUE",
|
VUE: "VUE",
|
||||||
LESS = "LESS",
|
LESS: "LESS",
|
||||||
Markup = "Markup",
|
Markup: "Markup",
|
||||||
YAML = "YAML",
|
YAML: "YAML",
|
||||||
Json = "Json",
|
Json: "Json",
|
||||||
Java = "Java",
|
Java: "Java",
|
||||||
Properties = "Properties",
|
Properties: "Properties",
|
||||||
NginxConf = "NginxConf",
|
NginxConf: "NginxConf",
|
||||||
ApacheConf = "ApacheConf"
|
ApacheConf: "ApacheConf"
|
||||||
}
|
} as const;
|
||||||
|
|
||||||
|
export type PrismjsType = (typeof PrismjsType)[keyof typeof PrismjsType];
|
||||||
|
|
||||||
export type PrismjsProperties = {
|
export type PrismjsProperties = {
|
||||||
|
|
||||||
@@ -26,14 +28,16 @@ export type PrismjsProperties = {
|
|||||||
viewer: PrismjsViewer;
|
viewer: PrismjsViewer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PrismjsViewer {
|
export const PrismjsViewer = {
|
||||||
|
|
||||||
MARKDOWN = "MARKDOWN",
|
MARKDOWN: "MARKDOWN",
|
||||||
|
|
||||||
CODE = "CODE",
|
CODE: "CODE",
|
||||||
|
|
||||||
TEXT = "TEXT",
|
TEXT: "TEXT"
|
||||||
}
|
} as const;
|
||||||
|
|
||||||
|
export type PrismjsViewer = (typeof PrismjsViewer)[keyof typeof PrismjsViewer];
|
||||||
|
|
||||||
export default class Prismjs {
|
export default class Prismjs {
|
||||||
|
|
||||||
|
|||||||
+15
-20
@@ -1,7 +1,7 @@
|
|||||||
export default class Storage {
|
export default class Storage {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取为布尔值
|
* 获取为布尔值,键不存在时返回 false
|
||||||
*
|
*
|
||||||
* @param key 键
|
* @param key 键
|
||||||
* @returns 布尔值
|
* @returns 布尔值
|
||||||
@@ -11,7 +11,7 @@ export default class Storage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取为布尔值并取反
|
* 获取为布尔值并取反,键不存在时返回 true
|
||||||
*
|
*
|
||||||
* @param key 键
|
* @param key 键
|
||||||
* @returns 布尔值
|
* @returns 布尔值
|
||||||
@@ -25,13 +25,11 @@ export default class Storage {
|
|||||||
*
|
*
|
||||||
* @template T 对象类型
|
* @template T 对象类型
|
||||||
* @param key 键
|
* @param key 键
|
||||||
* @returns {T | undefined} 返回对象
|
* @returns 对象,键不存在时返回 null
|
||||||
*/
|
*/
|
||||||
public static getObject<T>(key: string): T {
|
public static getObject<T>(key: string): T | null {
|
||||||
if (this.has(key)) {
|
const value = this.getJSON(key);
|
||||||
return this.getJSON(key) as T;
|
return value === null ? null : value as T;
|
||||||
}
|
|
||||||
throw Error(`not found ${key}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,24 +52,21 @@ export default class Storage {
|
|||||||
* 获取为 JSON
|
* 获取为 JSON
|
||||||
*
|
*
|
||||||
* @param key 键
|
* @param key 键
|
||||||
* @returns JSON 对象
|
* @returns JSON 对象,键不存在时返回 null;值无法解析时抛错
|
||||||
*/
|
*/
|
||||||
public static getJSON(key: string) {
|
public static getJSON(key: string): unknown {
|
||||||
return JSON.parse(this.getString(key));
|
const value = this.getString(key);
|
||||||
|
return value === null ? null : JSON.parse(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取为字符串(其他获取方式一般经过这个方法,找不到配置或配置值无效时会抛错)
|
* 获取为字符串
|
||||||
*
|
*
|
||||||
* @param key 键
|
* @param key 键
|
||||||
* @returns 字符串
|
* @returns 字符串,键不存在时返回 null
|
||||||
*/
|
*/
|
||||||
public static getString(key: string): string {
|
public static getString(key: string): string | null {
|
||||||
const value = localStorage.getItem(key);
|
return localStorage.getItem(key);
|
||||||
if (value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
throw new Error(`not found: ${key}, ${value}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,7 +76,7 @@ export default class Storage {
|
|||||||
* @returns true 为存在
|
* @returns true 为存在
|
||||||
*/
|
*/
|
||||||
public static has(key: string): boolean {
|
public static has(key: string): boolean {
|
||||||
return localStorage.getItem(key) !== undefined && localStorage.getItem(key) !== null;
|
return localStorage.getItem(key) !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+46
-16
@@ -232,43 +232,73 @@ export default class Toolkit {
|
|||||||
return Object.keys(e)[Object.values(e).indexOf(value)];
|
return Object.keys(e)[Object.values(e).indexOf(value)];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 防抖
|
/**
|
||||||
// eslint-disable-next-line
|
* 防抖函数
|
||||||
public static debounce<T extends (...args: any[]) => any>(callback: T, defaultImmediate = true, delay = 600): T & {
|
*
|
||||||
cancel(): void
|
* 支持两种模式:
|
||||||
} {
|
* - **立即模式**(`immediate = true`,默认):首次调用立即执行,连续调用在延迟结束后再执行最后一次(leading + trailing)
|
||||||
let timerId: ReturnType<typeof setTimeout> | null = null; // 存储定时器
|
* - **延迟模式**(`immediate = false`):所有调用在延迟结束后执行最后一次(trailing only)
|
||||||
let immediate = defaultImmediate;
|
*
|
||||||
// 定义一个 cancel 办法,用于勾销防抖
|
* @param callback 回调函数
|
||||||
|
* @param immediate 是否首次调用立即执行,默认 true
|
||||||
|
* @param delay 延迟毫秒数,默认 600
|
||||||
|
* @returns 防抖后的函数,附带 `cancel()` 方法用于取消 pending 执行并重置状态
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // 立即模式(默认):首次立即触发,连续调用后在延迟结束时再触发一次
|
||||||
|
* const fn = Toolkit.debounce((val: string) => {
|
||||||
|
* console.log(val);
|
||||||
|
* });
|
||||||
|
* fn("a"); // 立即输出 "a"
|
||||||
|
* fn("b"); // 600ms 后输出 "b"(trailing)
|
||||||
|
* fn.cancel(); // 取消 pending 的 trailing 调用,重置为初始状态
|
||||||
|
*
|
||||||
|
* // 延迟模式:连续调用只在停止后触发最后一次
|
||||||
|
* const fn2 = Toolkit.debounce((val: string) => {
|
||||||
|
* console.log(val);
|
||||||
|
* }, false);
|
||||||
|
* fn2("a"); // 不输出
|
||||||
|
* fn2("b"); // 600ms 后输出 "b"
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public static debounce<T extends (...args: any[]) => any>(
|
||||||
|
callback: T,
|
||||||
|
immediate = true,
|
||||||
|
delay = 300
|
||||||
|
): ((...args: Parameters<T>) => void) & { cancel(): void } {
|
||||||
|
let timerId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
let leading = immediate;
|
||||||
|
|
||||||
const cancel = (): void => {
|
const cancel = (): void => {
|
||||||
if (timerId) {
|
if (timerId) {
|
||||||
clearTimeout(timerId);
|
clearTimeout(timerId);
|
||||||
timerId = null;
|
timerId = null;
|
||||||
}
|
}
|
||||||
|
leading = immediate;
|
||||||
};
|
};
|
||||||
|
|
||||||
const debounced = function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
|
const debounced = function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
|
||||||
const context = this;
|
const context = this;
|
||||||
if (timerId) {
|
if (timerId) {
|
||||||
cancel();
|
clearTimeout(timerId);
|
||||||
|
timerId = null;
|
||||||
}
|
}
|
||||||
if (immediate) {
|
if (leading) {
|
||||||
callback.apply(context, args);
|
callback.apply(context, args);
|
||||||
immediate = false;
|
leading = false;
|
||||||
timerId = setTimeout(() => {
|
timerId = setTimeout(() => {
|
||||||
immediate = defaultImmediate;
|
leading = immediate;
|
||||||
}, delay);
|
}, delay);
|
||||||
} else {
|
} else {
|
||||||
// 设置定时器,在延迟时间后执行指标函数
|
|
||||||
timerId = setTimeout(() => {
|
timerId = setTimeout(() => {
|
||||||
callback.apply(context, args);
|
callback.apply(context, args);
|
||||||
immediate = defaultImmediate;
|
leading = immediate;
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 将 cancel 方法附加到 debounced 函数上
|
|
||||||
(debounced as any).cancel = cancel;
|
(debounced as any).cancel = cancel;
|
||||||
return debounced as T & { cancel(): void };
|
return debounced as ((...args: Parameters<T>) => void) & { cancel(): void };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static toUserLevel(exp?: number): UserLevelType {
|
public static toUserLevel(exp?: number): UserLevelType {
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import type { Directive, DirectiveBinding } from "vue";
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
import Toolkit from "../Toolkit";
|
import Toolkit from "../Toolkit";
|
||||||
|
|
||||||
export enum PopupType {
|
export type PopupType = "TEXT" | "IMG" | "HTML" | "ELEMENT";
|
||||||
TEXT,
|
|
||||||
IMG,
|
|
||||||
HTML,
|
|
||||||
EL
|
|
||||||
}
|
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
export type PopupConfig = {
|
export type PopupConfig = {
|
||||||
@@ -30,7 +25,7 @@ const VPopup: Directive = {
|
|||||||
config = binding.value as PopupConfig;
|
config = binding.value as PopupConfig;
|
||||||
} else {
|
} else {
|
||||||
config = {
|
config = {
|
||||||
type: PopupType.TEXT,
|
type: "TEXT",
|
||||||
value: binding.value as any as string,
|
value: binding.value as any as string,
|
||||||
canShow: () => true
|
canShow: () => true
|
||||||
};
|
};
|
||||||
@@ -58,24 +53,24 @@ const VPopup: Directive = {
|
|||||||
popup.appendChild(el);
|
popup.appendChild(el);
|
||||||
}
|
}
|
||||||
switch (config.type) {
|
switch (config.type) {
|
||||||
case PopupType.TEXT:
|
case "TEXT":
|
||||||
// 文本
|
// 文本
|
||||||
el = document.createElement("div");
|
el = document.createElement("div");
|
||||||
el.className = "text";
|
el.className = "text";
|
||||||
el.textContent = config.value as string;
|
el.textContent = config.value as string;
|
||||||
popup.appendChild(el);
|
popup.appendChild(el);
|
||||||
break;
|
break;
|
||||||
case PopupType.IMG:
|
case "IMG":
|
||||||
// 图片
|
// 图片
|
||||||
el = document.createElement("img");
|
el = document.createElement("img");
|
||||||
(el as HTMLImageElement).src = config.value as string;
|
(el as HTMLImageElement).src = config.value as string;
|
||||||
popup.appendChild(el);
|
popup.appendChild(el);
|
||||||
break;
|
break;
|
||||||
case PopupType.HTML:
|
case "HTML":
|
||||||
// HTML 字符串
|
// HTML 字符串
|
||||||
popup.appendChild(Toolkit.toDOM(config.value as string));
|
popup.appendChild(Toolkit.toDOM(config.value as string));
|
||||||
break;
|
break;
|
||||||
case PopupType.EL:
|
case "ELEMENT":
|
||||||
// DOM 节点
|
// DOM 节点
|
||||||
if (config.value instanceof HTMLElement) {
|
if (config.value instanceof HTMLElement) {
|
||||||
const valueEl = config.value as HTMLElement;
|
const valueEl = config.value as HTMLElement;
|
||||||
|
|||||||
Reference in New Issue
Block a user