Initial project
This commit is contained in:
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = tab
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
15
.eslintignore
Normal file
15
.eslintignore
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
*.sh
|
||||||
|
node_modules
|
||||||
|
*.md
|
||||||
|
*.woff
|
||||||
|
*.ttf
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
dist
|
||||||
|
/public
|
||||||
|
/docs
|
||||||
|
.husky
|
||||||
|
.local
|
||||||
|
.eslintrc.js
|
||||||
|
dist
|
||||||
|
pnpm-lock.yaml
|
||||||
78
.eslintrc.js
Normal file
78
.eslintrc.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
module.exports = {
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"./.eslintrc-auto-import.json"
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
".eslintrc.{js}"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "script"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint",
|
||||||
|
"vue"
|
||||||
|
],
|
||||||
|
"rules": { // 注释是解释使用该设置的效果,而不是设置属性本身
|
||||||
|
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
|
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||||
|
// 其他
|
||||||
|
"camelcase": 2, // 变量驼峰式命名
|
||||||
|
"quotes": ["error", "double"], // 强制双引字符串
|
||||||
|
"eqeqeq": ["error", "always"], // 强制全等比较
|
||||||
|
"semi": ["error", "always"], // 强制语句分号结束
|
||||||
|
"max-len": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"code": 180
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// 逗号
|
||||||
|
"comma-style": [2, "last"], // 逗号出现在行末 [first, last]
|
||||||
|
"comma-dangle": [2, "never"], // 数组或对象不可带最后一个逗号 [never, always, always-multiline]
|
||||||
|
// 空格
|
||||||
|
"no-trailing-spaces": "error", // 禁止行末存在空格
|
||||||
|
"comma-spacing": [2, { "before": false, "after": true }], // 逗号后需要空格
|
||||||
|
"semi-spacing": ["error", { "before": false, "after": true }], // 分号后需要空格
|
||||||
|
"computed-property-spacing": [2, "never"], // 以方括号取对象属性时,[ 后面和 ] 前面需要空格, [never, always]
|
||||||
|
"space-before-function-paren": ["error", { // 函数括号前空格
|
||||||
|
"anonymous": "always", // 针对匿名函数表达式,比如 function () {}
|
||||||
|
"named": "never", // 针对命名函数表达式,比如 function foo() {}
|
||||||
|
"asyncArrow": "always" // 针对异步的箭头函数表达式,比如 async () => {}
|
||||||
|
}],
|
||||||
|
// 缩进
|
||||||
|
"no-mixed-spaces-and-tabs": "off", // 允许混合缩进
|
||||||
|
"no-tabs": ["error", { allowIndentationTabs: true }], // 使用 Tab 缩进
|
||||||
|
"indent": ["error", "tab", { // Tab 缩进相关
|
||||||
|
SwitchCase: 1 // Switch Case 缩进一级
|
||||||
|
}],
|
||||||
|
// 框架
|
||||||
|
"@typescript-eslint/ban-types": "off", // TS 允许空对象
|
||||||
|
"@typescript-eslint/no-empty-function": "off", // TS 允许空函数
|
||||||
|
"@typescript-eslint/no-explicit-any": "off", // TS 允许 any 类型
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": "off", // TS 允许显式模块边界类型?
|
||||||
|
"vue/no-multiple-template-root": "off", // Vue3 支持多个根节点
|
||||||
|
"@typescript-eslint/no-this-alias": "off", // 允许 this 变量本地化
|
||||||
|
"vue/no-v-model-argument": "off", // 允许 v-model 支持参数
|
||||||
|
"vue/multi-word-component-names": "off" // 允许单个词语的组件名
|
||||||
|
}
|
||||||
|
};
|
||||||
144
.gitignore
vendored
144
.gitignore
vendored
@ -1,138 +1,28 @@
|
|||||||
# ---> Node
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
node_modules
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
# Gatsby files
|
# Editor directories and files
|
||||||
.cache/
|
.vscode/*
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
!.vscode/extensions.json
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
.idea
|
||||||
# public
|
.DS_Store
|
||||||
|
*.suo
|
||||||
# vuepress build output
|
*.ntvs*
|
||||||
.vuepress/dist
|
*.njsproj
|
||||||
|
*.sln
|
||||||
# vuepress v2.x temp and cache directory
|
*.sw?
|
||||||
.temp
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# vitepress build output
|
|
||||||
**/.vitepress/dist
|
|
||||||
|
|
||||||
# vitepress cache directory
|
|
||||||
**/.vitepress/cache
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
|
/.eslintrc-auto-import.json
|
||||||
|
/components.d.ts
|
||||||
|
/examples/auto-imports.d.ts
|
||||||
|
|||||||
19
README.md
19
README.md
@ -1,3 +1,18 @@
|
|||||||
# timi-web
|
# Vue 3 + TypeScript + Vite
|
||||||
|
|
||||||
Timi 前端通用组件库
|
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||||
|
|
||||||
|
## Type Support For `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||||
|
|
||||||
|
1. Disable the built-in TypeScript Extension
|
||||||
|
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||||
|
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||||
|
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||||
|
|||||||
21
examples/Root.vue
Normal file
21
examples/Root.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<div class="root">
|
||||||
|
<h2 v-popup="`提示`">test popup</h2>
|
||||||
|
<icon name="ZIP" :scale="2" disabled />
|
||||||
|
<markdown-editor class="editor" v-model:data="data" :minRows="12" :max-rows="24" />
|
||||||
|
</div>
|
||||||
|
<popup />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Icon, Popup } from "timi-web";
|
||||||
|
import MarkdownEditor from "~/components/markdown-editor/index.vue";
|
||||||
|
|
||||||
|
const data = ref("");
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.root {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
10
examples/main.ts
Normal file
10
examples/main.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import Root from "./Root.vue";
|
||||||
|
import TimiWebUI, { axios, VPopup } from "timi-web"; // 本地开发
|
||||||
|
|
||||||
|
axios.defaults.baseURL = "http://localhost:8091";
|
||||||
|
|
||||||
|
const app = createApp(Root);
|
||||||
|
app.use(TimiWebUI);
|
||||||
|
app.directive("popup", VPopup);
|
||||||
|
app.mount("#root");
|
||||||
7
examples/vite-env.d.ts
vendored
Normal file
7
examples/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
declare module "*.vue" {
|
||||||
|
import type { DefineComponent } from "vue";
|
||||||
|
const component: DefineComponent<{}, {}, any>;
|
||||||
|
export default component;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "prismjs";
|
||||||
12
index.html
Normal file
12
index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + Vue + TS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/examples/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
66
package.json
Normal file
66
package.json
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"name": "timi-web",
|
||||||
|
"main": "./dist/timi-web.umd.js",
|
||||||
|
"types": "./dist/src/index.d.ts",
|
||||||
|
"module": "./dist/timi-web.mjs",
|
||||||
|
"style": "./dist/style.css",
|
||||||
|
"private": false,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"dev:doc": "pnpm run -C docs dev",
|
||||||
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"build:doc": "pnpm run -C docs build"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**",
|
||||||
|
"src/**",
|
||||||
|
"examples/**",
|
||||||
|
"README.md",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/timi-web.mjs",
|
||||||
|
"require": "./dist/timi-web.umd.js"
|
||||||
|
},
|
||||||
|
"./style.css": "./dist/timi-web.css"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "1.8.2",
|
||||||
|
"less": "4.3.0",
|
||||||
|
"marked": "^15.0.11",
|
||||||
|
"marked-gfm-heading-id": "^4.1.1",
|
||||||
|
"marked-highlight": "^2.2.1",
|
||||||
|
"marked-mangle": "^1.1.10",
|
||||||
|
"prismjs": "1.30.0",
|
||||||
|
"terser": "^5.39.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/marked": "^6.0.0",
|
||||||
|
"@types/node": "^22.15.2",
|
||||||
|
"@types/prismjs": "1.26.5",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.31.0",
|
||||||
|
"@typescript-eslint/parser": "^8.31.0",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
|
"eslint": "^9.25.1",
|
||||||
|
"eslint-config-prettier": "^10.1.2",
|
||||||
|
"eslint-define-config": "^2.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.2.6",
|
||||||
|
"eslint-plugin-vue": "^10.0.0",
|
||||||
|
"prettier": "^3.5.3",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"unplugin-auto-import": "^19.1.2",
|
||||||
|
"unplugin-vue-components": "^28.5.0",
|
||||||
|
"vite": "6.2.6",
|
||||||
|
"vite-plugin-dts": "^4.5.3",
|
||||||
|
"vite-plugin-prismjs": "^0.0.11",
|
||||||
|
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-tsc": "^2.2.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
3598
pnpm-lock.yaml
generated
Normal file
3598
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
src/api/CommentAPI.ts
Normal file
36
src/api/CommentAPI.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
axios,
|
||||||
|
CaptchaData,
|
||||||
|
Comment,
|
||||||
|
CommentPage,
|
||||||
|
CommentReply,
|
||||||
|
CommentReplyPage,
|
||||||
|
CommentReplyView,
|
||||||
|
CommentView,
|
||||||
|
PageResult
|
||||||
|
} from "~/index";
|
||||||
|
|
||||||
|
const BASE_URI = "/comment";
|
||||||
|
|
||||||
|
async function page(commentPage: CommentPage): Promise<PageResult<CommentView>> {
|
||||||
|
return axios.post(`${BASE_URI}/list`, commentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function create(captchaData: CaptchaData<Comment>): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/create`, captchaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pageReply(commentReplyPage: CommentReplyPage): Promise<PageResult<CommentReplyView>> {
|
||||||
|
return axios.post(`${BASE_URI}/reply/list`, commentReplyPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createReply(captchaData: CaptchaData<CommentReply>): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/reply/create`, captchaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
page,
|
||||||
|
create,
|
||||||
|
createReply,
|
||||||
|
pageReply
|
||||||
|
};
|
||||||
28
src/api/CommonAPI.ts
Normal file
28
src/api/CommonAPI.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { axios, SettingKey, TemplateBizType, Toolkit } from "~/index";
|
||||||
|
|
||||||
|
const getCaptchaAPI = () => axios.defaults.baseURL + "/captcha";
|
||||||
|
|
||||||
|
const getAttachmentReadAPI = (mongoId: string) => `${axios.defaults.baseURL}/attachment/read/${mongoId}`;
|
||||||
|
|
||||||
|
async function getTemplate(bizType: TemplateBizType, code: string): Promise<string> {
|
||||||
|
return axios.get(`/template?bizType=${bizType}&bizCode=${code}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSetting(key: string, args?: { [key: string]: any }): Promise<string> {
|
||||||
|
return axios.get(`/setting/${key}?${Toolkit.toURLArgs(args)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listSetting(keyMap: Map<string, object | undefined>): Promise<Map<string, string>> {
|
||||||
|
const result = await axios.post("/setting/map", Toolkit.toObject(keyMap));
|
||||||
|
const map = new Map<string, string>();
|
||||||
|
Object.entries(result).forEach(([key, value]) => map.set(key, value as string));
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getCaptchaAPI,
|
||||||
|
getAttachmentReadAPI,
|
||||||
|
getTemplate,
|
||||||
|
getSetting,
|
||||||
|
listSetting
|
||||||
|
};
|
||||||
11
src/api/DeveloperAPI.ts
Normal file
11
src/api/DeveloperAPI.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { axios, Developer } from "~/index";
|
||||||
|
|
||||||
|
const BASE_URI = "/git/developer";
|
||||||
|
|
||||||
|
async function get(): Promise<Developer> {
|
||||||
|
return axios.post(`${BASE_URI}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
get
|
||||||
|
};
|
||||||
85
src/api/UserAPI.ts
Normal file
85
src/api/UserAPI.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
Attachment,
|
||||||
|
axios,
|
||||||
|
CaptchaData,
|
||||||
|
CommonAPI,
|
||||||
|
LoginRequest,
|
||||||
|
LoginResponse,
|
||||||
|
RegisterRequest,
|
||||||
|
UserAttachType,
|
||||||
|
UserProfileView,
|
||||||
|
UserView
|
||||||
|
} from "~/index";
|
||||||
|
|
||||||
|
const BASE_URI = "/user";
|
||||||
|
|
||||||
|
async function register(captchaData: CaptchaData<RegisterRequest>): Promise<LoginResponse> {
|
||||||
|
return axios.post(`${BASE_URI}/register`, captchaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录
|
||||||
|
*
|
||||||
|
* @param captchaData 验证码登录对象
|
||||||
|
* @returns LoginResponse
|
||||||
|
*/
|
||||||
|
async function login(captchaData: CaptchaData<LoginRequest>): Promise<LoginResponse> {
|
||||||
|
return axios.post(`${BASE_URI}/login`, captchaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证是否已登录
|
||||||
|
*
|
||||||
|
* @returns true 为已登录
|
||||||
|
*/
|
||||||
|
async function login4Token(): Promise<LoginResponse> {
|
||||||
|
return axios.post(`${BASE_URI}/login/token`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout(): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/logout`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户数据
|
||||||
|
*
|
||||||
|
* @param id 用户 ID
|
||||||
|
* @returns 用户数据
|
||||||
|
*/
|
||||||
|
async function view(id: number): Promise<UserView> {
|
||||||
|
return axios.post(`${BASE_URI}/view/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAvatarURL(profile?: UserProfileView) {
|
||||||
|
if (profile && profile.attachmentList) {
|
||||||
|
return findAttachmentByType(profile.attachmentList, [UserAttachType.AVATAR, UserAttachType.DEFAULT_AVATAR]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWrapperURL(profile?: UserProfileView) {
|
||||||
|
if (profile && profile.attachmentList) {
|
||||||
|
return findAttachmentByType(profile.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
register,
|
||||||
|
login,
|
||||||
|
login4Token,
|
||||||
|
logout,
|
||||||
|
|
||||||
|
view,
|
||||||
|
getAvatarURL,
|
||||||
|
getWrapperURL
|
||||||
|
};
|
||||||
151
src/assets/icon.json
Normal file
151
src/assets/icon.json
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
{
|
||||||
|
"play": "M3 12h2v-10h-2v10zM7 12h2v-1h1v-1h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v-1h-1v-1h-2z",
|
||||||
|
"tomcat": "M1 0v2h1v1h1v3h1v-1h1v1h2v1h3v-2h1v-1h1v-1h2v-1h1v-1h-3v1h-2v1h-4v-1h1v-1h-2v1h-1v-1h-2v-1zM2 9h1v-3h-1v3zM3 11h1v-2h-1v2zM4 12h2v-1h-2v1zM6 11h2v-2h-2v2zM11 9h1v-1h2v1h1v-3h-1v-1h-2v1h-1z",
|
||||||
|
"underline": "M4 2h8v-1h-8v1zM4 12h2v-7h4v7h2v-7h-1v-1h-1v-1h-4v1h-1v1h-1z",
|
||||||
|
"upload": "M2 5h2v-2h8v2h2v-4h-12zM4 8v1h1v1h1v1h1v1h2v-1h1v-1h1v-1h1v-1h-3v-4h-2v4z",
|
||||||
|
"server_0": "M5 14h6v-6h-2v-1h5v-2h1v-4h-4v4h1v1h-3v-1h1v-4h-4v4h1v1h-3v-1h1v-4h-4v4h1v2h5v1h-2z",
|
||||||
|
"fontsize": "M2 9v3h10v-3h-1v1h-3v-7h3v4h-1v-1h-1v2h5v-2h-1v1h-1v-4h2v-1h-9v1h2v7h-4v-1z",
|
||||||
|
"spec_weaken": "M1 3h2v-3h-2v3zM4 5h2v-5h-2v5zM7 8h2v-8h-2v8zM10 3h2v-3h-2v3zM13 5h2v-5h-2v5zM4 14h2v-3h2v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h2z",
|
||||||
|
"play_next": "M3 12h2v-1h1v-1h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v-1h-1v-1h-2zM10 12h2v-10h-2v10z",
|
||||||
|
"branch": "M1 14h4v-1h9v-2h-9v-1h-1v-2h6v1h4v-4h-4v1h-6v-3h6v1h4v-4h-4v1h-8v9h-1z",
|
||||||
|
"redis": "M7 14h2v-1h2v-1h2v-1h2v-2h-2v-1h-2v-1h-2v-1h-2v1h-2v1h-2v1h-2v2h2v1h2v1h2zM1 8h2v-1h2v-1h2v-1h2v1h2v1h2v1h2v-2h-2v-1h-2v-1h-2v-1h-2v1h-2v1h-2v1h-2zM1 5h2v-1h2v-1h2v-1h2v1h2v1h2v1h2v-2h-2v-1h-2v-1h-2v-1h-2v1h-2v1h-2v1h-2z",
|
||||||
|
"music": "M2 1v4h2v7h10v-10h-4v4h2v4h-6v-9z",
|
||||||
|
"java": "M3 2h10v-1h-10v1zM5 4h5v-1h-5v1zM4 6h7v-1h-7v1zM7 7h1v-1h-1v1zM6 9h1v-2h-1v2zM8 9h-1v2h1v1h2v-1h-1v-1h-1zM9 7h-1v2h2v-1h-1zM10 10h1v-1h-1v1zM11 7h1v-1h-1v1zM12 6h1v-2h-1v2zM11 4h1v-1h-1v1z",
|
||||||
|
"pen": "M11 8v-2h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-4v4h1v1h1v1h1v1h1v1h1v1h1v1h2v-1h1v-1zM15 11v-1h-1v-1h-1v-1h-1v1h-1v1h-1v1h-1v1h1v1h1v1h1v-1h1v-1h1v-1z",
|
||||||
|
"text": "M12 11v1h-1v1h-8v-12h10v10h-1zM9 5h-5v1h5v-1zM11 7h-7v1h7v-1zM11 9h-7v1h7v-1z",
|
||||||
|
"mail_0": "M1 1v9h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h2v1h1v1h1v1h1v1h1v1h1v1h1v-9zM1 12v-1h2v-1h1v-1h1v-1h1v-1h1v-1h2v1h1v1h1v1h1v1h1v1h2v1z",
|
||||||
|
"zip": "M12 11v1h-1v1h-8v-5h-2v-8h8v1h4v10h-1zM8 1h-6v6h6v-6zM3 6h4v-1h-1v-1h-1v1h-2zM4 4h1v-1h2v-1h-4v1h1z",
|
||||||
|
"filter_0": "M9 13h-7v-2h1v-1h1v-1h1v-8h1v1h1v1h1v1h1v1h-2v5h-1v1h3zM15 13h-2v-1h-1v-1h-1v-1h-1v-5h2v4h1v1h1v1h1z",
|
||||||
|
"version": "M3 13h2v-6h1v2h1v2h1v2h2v-2h-1v-2h-1v-2h-1v-2h-1v-2h-1v-2h-2zM10 3h2v-2h-2v2z",
|
||||||
|
"arrow_0_w": "M14 6h-8v-1h1v-1h1v-2h-2v1h-1v1h-1v1h-1v1h-1v2h1v1h1v1h1v1h1v1h2v-2h-1v-1h-1v-1h8z",
|
||||||
|
"extend": "M1 0v6h2v-4h4v-2zM9 14h6v-6h-2v4h-4z",
|
||||||
|
"sampling_rate": "M0 12h7v-3h-2v1h-3v-2h5v-6h-7v3h2v-1h3v2h-5zM16 6v6h-7v-10h2v3h1v-1h1v-1h1v-1h2v2h-1v1h-1v1h2zM11 8v2h3v-2h-3z",
|
||||||
|
"fail": "M3 2v2h1v1h1v1h1v2h-1v1h-1v1h-1v2h2v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-2h-2v1h-1v1h-1v1h-2v-1h-1v-1h-1v-1zM10 8h1v1h1v1h1v2h-2v-1h-1v-1h-1v-1h1z",
|
||||||
|
"proxy": "M15 8v5h-5v-2h-2v-3h-1v2h-6v-6h6v2h1v-3h2v-2h5v5h-5v-2h-1v6h1v-2h5zM11 12h3v-3h-3v3zM11 5h3v-3h-3v3z",
|
||||||
|
"folder": "M2 2h12v8h-7v1h-1v1h-4z",
|
||||||
|
"system": "M12 11v1h-1v1h-8v-12h10v10h-1zM4 9h3v-3h-3v3zM8 3h-3v2h1v-1h2v-1zM8 8h3v-2h-1v1h-2v1zM12 2h-3v3h3v-3z",
|
||||||
|
"arrow_0_s": "M9 13v-8h1v1h1v1h2v-2h-1v-1h-1v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h-1v1h-1v2h2v-1h1v-1h1v8z",
|
||||||
|
"stop": "M3 12h10v-10h-10v10z",
|
||||||
|
"arrow_0_n": "M7 1v8h-1v-1h-1v-1h-2v2h1v1h1v1h1v1h1v1h2v-1h1v-1h1v-1h1v-1h1v-2h-2v1h-1v1h-1v-8z",
|
||||||
|
"spec_1": "M1 6h2v-5h-2v5zM4 12h2v-11h-2v11zM7 9h2v-8h-2v8zM10 4h2v-3h-2v3zM13 6h2v-5h-2v5z",
|
||||||
|
"thumbtack": "M7 1c0 1.30 0 2.70 0 4h-5v1h3v6h-2v1h10v-1h-2v-6h3v-1h-5c0-1.30 0-2.70 0-4-0.70 0-1.30 0-2 0z",
|
||||||
|
"spec_0": "M1 10h2v-6h-2v6zM4 14h2v-14h-2v14zM7 11h2v-8h-2v8zM10 9h2v-4h-2v4zM13 11h2v-8h-2v8z",
|
||||||
|
"java_0": "M3 2h10v-1h-10v1zM5 4h5v-1h-5v1zM4 6h7v-1h-7v1zM7 7h1v-1h-1v1zM6 9h1v-2h-1v2zM8 9h-1v2h1v1h2v-1h-1v-1h-1zM9 7h-1v2h2v-1h-1zM10 10h1v-1h-1v1zM11 7h1v-1h-1v1zM12 6h1v-2h-1v2zM11 4h1v-1h-1v1zM1 15h14v-1h-14v1zM1 0h14v-1h-14v1zM0 14h1v-14h-1v14zM15 14h1v-14h-1v14z",
|
||||||
|
"arrow_0_e": "M2 8h8v1h-1v1h-1v2h2v-1h1v-1h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v-1h-1v-1h-2v2h1v1h1v1h-8z",
|
||||||
|
"magnifier": "M4 14h8v-2h-8v2zM2 12h2v-8h-2v8zM4 4h8v-2h-8v2zM12 12h2v-8h-2v8zM13 3h2v-1h1v-2h-2v1h-1z",
|
||||||
|
"server": "M1 12v-11h14v11h-14zM14 2h-12v4h12v-4zM14 7h-12v4h12v-4zM3 10h2v-2h-2v2zM6 10h7v-2h-7v2zM3 5h2v-2h-2v2zM6 5h7v-2h-7v2z",
|
||||||
|
"cut": "M1 7h4v-1h-4v1zM6 7h4v-1h-4v1zM11 7h4v-1h-4v1zM3 5h10v-5h-10v5zM3 8h10v4h-1v1h-1v1h-8z",
|
||||||
|
"flag": "M1 0v12h2v2h10v-2h2v-12h-2v2h-2v2h-2v2h-2v-2h-2v-2h-2v-2z",
|
||||||
|
"sd_card": "M4 13v-4h-1v-2h1v-2h-1v-4h10v12h-9zM6 10h-1v2h1v-2zM8 10h-1v2h1v-2zM10 10h-1v2h1v-2zM12 10h-1v2h1v-2z",
|
||||||
|
"log": "M6 6h1v-2h-1v2zM14 11v1h-1v1h-1v1h-9v-6h-2v-6h2v-2h12v11h-1zM2 3v4h1v-3h1v-1h-2zM14 1h-10v1h9v6h-9v5h7v-3h3v-9zM8 6v-2h-1v-1h-1v1h-1v2h1v1h1v-1h1zM11 4v1h1v-2h-2v1h-1v2h1v1h2v-1h-2v-2h1z",
|
||||||
|
"mic": "M1 0v3h1v1h1v1h1v1h1v2h1v-1h1v-1h1v-1h1v-1h-2v-1h-1v-1h-1v-1h-1v-1zM8 8h-1v1h-1v2h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h-2v1h-1v1h-1zM8 11v2h1v1h4v-1h1v-1h1v-4h-1v-1h-2v1h-1v1h-1v1h-1v1z",
|
||||||
|
"save": "M12 11v1h-1v1h-8v-12h10v10h-1zM8 12h1v-2h-1v2zM5 12h2v-2h-2v2zM11 2h-6v3h6v-3z",
|
||||||
|
"play_prev": "M12 2h-2v1h-1v1h-1v1h-1v1h-1v2h1v1h1v1h1v1h1v1h2zM3 12h2v-10h-2v10z",
|
||||||
|
"min": "M3 8h10v-1h-10v1z",
|
||||||
|
"file": "M12 11v1h-1v1h-8v-12h10v10h-1z",
|
||||||
|
"writing": "M7 13v1h-5v-12h1v-1h1v-1h1v1h1v1h1v1h7v10h-7zM6 4h-3v1h3v-1zM6 11h-3v2h3v-2zM12 5h-5v1h4v1h-4v1h4v2h-4v1h5v-6z",
|
||||||
|
"power": "M4 3h8v-2h-8v2zM2 9h2v-6h-2v6zM4 11h2v-2h-2v2zM7 13h2v-6h-2v6zM10 11h2v-2h-2v2zM12 9h2v-6h-2v6z",
|
||||||
|
"copy": "M12 12v1h-1v1h-7v-2h-2v-12h9v2h2v10h-1zM12 3h-1v7h-1v1h-1v1h-4v1h5v-1h1v-1h1v-8z",
|
||||||
|
"close": "M7 8h2v-2h-2v2zM6 9h1v-1h-1v1zM5 10h1v-1h-1v1zM4 11h1v-1h-1v1zM3 12h1v-1h-1v1zM3 3h1v-1h-1v1zM4 4h1v-1h-1v1zM5 5h1v-1h-1v1zM6 6h1v-1h-1v1zM9 9h1v-1h-1v1zM10 10h1v-1h-1v1zM11 11h1v-1h-1v1zM12 12h1v-1h-1v1zM9 6h1v-1h-1v1zM10 5h1v-1h-1v1zM11 4h1v-1h-1v1zM12 3h1v-1h-1v1z",
|
||||||
|
"table": "M1 13v-12h14v12h-14zM5 2h-3v2h3v-2zM5 5h-3v2h3v-2zM5 8h-3v2h3v-2zM14 2h-8v2h8v-2zM14 5h-8v2h8v-2zM14 8h-8v2h8v-2z",
|
||||||
|
"b": "M13 9v2h-1v1h-1v1h-8v-12h8v1h1v1h1v2h-1v4h1zM10 4h-1v-1h-3v3h3v-1h1v-1zM10 9h-1v-1h-3v3h3v-1h1v-1z",
|
||||||
|
"question": "M3 11h2v-3h-2v3zM5 13h6v-2h-6v2zM11 11h2v-4h-2v4zM11 7h-2v-1h-2v-3h2v2h2zM7 2h2v-2h-2v2z",
|
||||||
|
"star": "M4 8v1h5v1h1v2h1v2h1v-5h3v-1h-1v-1h-1v-1h-1v-6h-1v1h-1v1h-2v-1h-1v-1h-1v3h1v3h-1v1h-1v1zM6 13h3v-1h-3v1zM5 11h3v-1h-3v1zM2 3h3v-1h-3v1zM3 5h3v-1h-3v1z",
|
||||||
|
"max": "M3 12v-10h10v10h-10zM12 3h-8v8h8v-8z",
|
||||||
|
"refresh": "M11 9h2v3h-10v-4h-2v-1h1v-1h1v-1h2v1h1v1h1v1h-2v2h6zM11 8h2v-1h1v-1h1v-1h-2v-4h-10v3h2v-1h6v2h-2v1h1v1h1z",
|
||||||
|
"arrow_5_v": "M8 5v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h2v6h2v-6zM10 3v6h-2v1h1v1h1v1h2v-1h1v-1h1v-1h-2v-6z",
|
||||||
|
"link_0": "M1 11h10v-4h-2v2h-6v-4h1v-2h-3zM5 7h2v-2h6v4h-1v2h3v-8h-10z",
|
||||||
|
"arrow_1_w": "M11 2h-2v1h-1v1h-1v1h-1v1h-1v2h1v1h1v1h1v1h1v1h2v-2h-1v-1h-1v-1h-1v-2h1v-1h1v-1h1z",
|
||||||
|
"filter": "M10 14h-10v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-8h1v1h1v1h1v1h1v5h1v1h1v1h1v1h1v1h1v1h1v1h-2v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-5h-1v-1h-1v6h-1v1h-1v1h-1v1h-1v1h7z",
|
||||||
|
"insert_before": "M1 10h5v1h1v-1h1v-1h1c0-0.30 0-0.70 0-1-2 0-4 0-6 0 0-1.30 0-2.70 0-4-0.70 0-1.30 0-2 0 0 2 0 4 0 6zM5 7h3v-3h-3v3zM9 7h6v-3h-6v3z",
|
||||||
|
"arrow_1_s": "M13 10v-2h-1v-1h-1v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h-1v1h-1v2h2v-1h1v-1h1v-1h2v1h1v1h1v1z",
|
||||||
|
"hide": "M3 1v12h8v-1h1v-1h1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1zM5 2h2v-1h-2v1zM8 2h2v-1h-2v1zM11 1v1h1v1h1v-2zM12 6h1v-2h-1v2zM12 9h1v-2h-1v2z",
|
||||||
|
"t": "M2 13h12v-4h-1v1h-1v1h-3v-9h3v-1h-8v1h3v9h-3v-1h-1v-1h-1z",
|
||||||
|
"firewall": "M15 12v1h-3v1h-8v-1h-3v-1h-1v-7h1v-2h1v-1h2v-1h2v-1h4v1h2v1h2v1h1v2h1v7h-1zM15 6h-1v-2h-1v-1h-2v-1h-2v-1h-1v6c-2.30 0-4.70 0-7 0 0 1.30 0 2.70 0 4h1v1h3v1c1 0 2 0 3 0 0-2 0-4 0-6h7v-1z",
|
||||||
|
"arrow_5_h": "M6 6h-1v1h-1v1h-1v2h1v1h1v1h1v-2h6v-2h-6zM4 5h6v2h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v2h-6z",
|
||||||
|
"rename": "M2 9v-4h12v4h-12zM13 6h-10v2h10v-2zM8 12v-1h1v-8h-1v-1h3v1h-1v8h1v1z",
|
||||||
|
"arrow_1_n": "M3 4v2h1v1h1v1h1v1h1v1h2v-1h1v-1h1v-1h1v-1h1v-2h-2v1h-1v1h-1v1h-2v-1h-1v-1h-1v-1z",
|
||||||
|
"arrow_1_e": "M5 12h2v-1h1v-1h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v-1h-1v-1h-2v2h1v1h1v1h1v2h-1v1h-1v1h-1z",
|
||||||
|
"wrap": "M1 13h14v-2h-14v2zM1 9h5v-2h-5v2zM1 5h5v-2h-5v2zM13 9h2v-6h-4v-2h-1v1h-1v1h-1v2h1v1h1v1h1v-2h2z",
|
||||||
|
"die": "M15 9v2h-2v1h-1v2h-2v-2h-1v2h-2v-2h-1v2h-2v-2h-1v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v1h2v2h-2v1h2v2h-2v1h2zM11 4h-6v6h6v-6zM6 9h4v-4h-4v4z",
|
||||||
|
"keep_end": "M1 4h14v-2h-14v2zM1 8h5v-2h-5v2zM1 12h5v-2h-5v2zM10 12h2v-4h2v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h2z",
|
||||||
|
"lib": "M1 11h2v-10h-2v10zM4 11h2v-10h-2v10zM7 13h2v-12h-2v12zM10 11h2v-10h-2v10zM13 9h2v-8h-2v8z",
|
||||||
|
"mail": "M1 10h2v-1h2v-1h6v1h2v1h2v-8h-14zM1 12h14v-1h-3v-1h-2v-1h-4v1h-2v1h-3z",
|
||||||
|
"italic": "M4 13h8v-2h-3v-4h-1v-4h3v-2h-8v2h3v4h1v4h-3z",
|
||||||
|
"insert_after": "M1 10c0.70 0 1.30 0 2 0 0-1.30 0-2.70 0-4 2 0 4 0 6 0 0-0.30 0-0.70 0-1h-1v-1h-1v-1h-1v1h-5c0 2 0 4 0 6zM5 10h3v-3h-3v3zM9 10h6v-3h-6v3z",
|
||||||
|
"translate": "M9 10v1h3v-3h-1v2zM5 7h1v-2h2v-1h-3zM9 7h4v-1h-3v-1h2v-1h-2v-1h3v-1h-4zM6 12v1h-1v-1h-2v-3h2v-1h1v1h2v3h-2zM5 10h-1v1h1v-1zM7 10h-1v1h1v-1z",
|
||||||
|
"spec": "M2 9h1v-4h-1v4zM4 12h1v-10h-1v10zM6 10h1v-6h-1v6zM8 11h1v-8h-1v8zM10 8h1v-2h-1v2zM12 9h1v-4h-1v4z",
|
||||||
|
"download": "M2 5h2v-2h8v2h2v-4h-12zM4 8h3v4h2v-4h3v-1h-1v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h-1z",
|
||||||
|
"apache": "M1 0v2h1v2h1v2h1v2h1v1h1v1h1v1h1v1h2v1h2v1h3v-3h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-2v-1h-2v-1h-2v-1h-1v-2h-1v-1h-1z",
|
||||||
|
"function": "M2 6h5v-5h-5v5zM2 13h5v-5h-5v5zM9 6h5v-5h-5v5zM9 10h-1v1h1v1h1v1h1v1h1v-1h1v-1h1v-1h1v-1h-1v-1h-1v-1h-1v-1h-1v1h-1v1h-1z",
|
||||||
|
"tile": "M2 6h5v-5h-5v5zM2 13h5v-5h-5v5zM9 13h5v-5h-5v5zM9 6h5v-5h-5v5z",
|
||||||
|
"spec_boost": "M1 4h2v-4h-2v4zM4 0v10h-2v1h1v1h1v1h2v-1h1v-1h1v-1h-2v-10zM7 8h2v-8h-2v8zM10 3h2v-3h-2v3zM13 5h2v-5h-2v5z",
|
||||||
|
"lrc": "M0 0v4h2v10h4v-2h-2v-12zM8 14h8v-14h-10v2h8v10h-6zM6 10h7v-2h-7v2zM6 6h5v-2h-5v2z",
|
||||||
|
"export": "M8 0v1h-5v12h6v-4h5v2h-1v1h-1v1h-1v1h-9v-14zM6 3v3h4v2h1v-1h1v-1h1v-1h1v-1h-1v-1h-1v-1h-1v-1h-1v2z",
|
||||||
|
"info": "M1 12h2v-8h-2v8zM3 14h10v-2h-10v2zM13 12h2v-8h-2v8zM3 4h4v-2h-4v2zM9 4h4v-2h-4v2zM7 2h2v-2h-2v2zM7 11h2v-3h-2v3zM7 7h2v-2h-2v2z",
|
||||||
|
"image": "M1 12v-10h14v10h-14zM14 6h-1v1h-1v1h-1v-1h-1v-1h-2v1h-1v-1h-1v-1h-2v-1h-2v7h12v-5zM3 10h2v-2h-2v2z",
|
||||||
|
"tar": "M12 11v1h-1v1h-8v-5h-2v-8h8v1h4v10h-1zM8 1h-6v6h6v-6zM3 6h4v-1h-1v-3h-2v3h-1z",
|
||||||
|
"nginx": "M13 11v1h-2v1h-2v1h-2v-1h-2v-1h-2v-1h-2v-8h2v-1h2v-1h2v-1h2v1h2v1h2v1h2v8h-2zM12 5h-3v1h-1v1h-1v1h-1v-3h-2v4h3v-1h1v-1h1v-1h1v3h2v-4z",
|
||||||
|
"maven": "M1 14h14v-2h-14v2zM1 10h1v-1h1v-1h1v1h1v1h1v-6h-1v2h-1v-1h-1v1h-1v-2h-1zM7 10h1v-3h1v3h1v-4h-1v-2h-1v2h-1zM11 4v6h1v-1h1v-1h1v2h1v-6h-1v1h-1v1h-1v-2zM1 2h14v-2h-14v2z",
|
||||||
|
"arrow_2_w": "M15 9v-4h-7v-4h-1v1h-1v1h-1v1h-1v1h-1v1h-1v2h1v1h1v1h1v1h1v1h1v1h1v-4z",
|
||||||
|
"to_top": "M9 2h-2v4h-3v1h1v1h1v1h1v1h2v-1h1v-1h1v-1h1v-1h-3zM12 11h-8v1h8z",
|
||||||
|
"clock": "M2 11h2v-8h-2v8zM4 13h8v-2h-8v2zM12 11h2v-8h-2v8zM4 3h8v-2h-8v2zM7 10h2v-3h1v-1h1v-2h-2v1h-1v1h-1z",
|
||||||
|
"duplicate": "M3 14v-3h-2v-11h11v1h3v13h-12zM13 3h-1v8h-7v1h8v-9z",
|
||||||
|
"girl": "M11 6v1h1v4h-1v1h-1v1h-4v-1h-1v-1h-1v-4h1v-1h2c0-0.70 0-1.30 0-2-0.70 0-1.30 0-2 0 0-0.30 0-0.70 0-1 0.70 0 1.30 0 2 0v-2h2v2c0.70 0 1.30 0 2 0 0 0.30 0 0.70 0 1-0.70 0-1.30 0-2 0 0 0.70 0 1.30 0 2h2zM7 7v1h-1v2h1v1h2v-1h1v-2h-1v-1h-2z",
|
||||||
|
"list": "M2 12h2v-2h-2v2zM5 12h9v-2h-9v2zM2 8h2v-2h-2v2zM5 8h9v-2h-9v2zM2 4h2v-2h-2v2zM5 4h9v-2h-9v2z",
|
||||||
|
"arrow_2_s": "M6 13h4v-7h4v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h-1v1h-1v1h-1v1h4z",
|
||||||
|
"plus": "M3 8h4v4h2v-4h4v-2h-4v-4h-2v4h-4z",
|
||||||
|
"pause": "M4 12h3v-10h-3v10zM9 12h3v-10h-3v10z",
|
||||||
|
"tool": "M1 0v3h1v1h1v1h2v1h1v2h-4v1h-1v2h2v-1h2v2h-1v2h2v-1h1v-4h2v1h1v1h1v1h1v1h1v1h2v-2h-1v-1h-1v-1h-1v-1h-1v-1h-1v-2h1v-1h1v-1h1v-1h1v-1h1v-2h-2v1h-1v1h-1v1h-1v1h-1v1h-2v-1h-1v-2h-1v-1h-1v-1z",
|
||||||
|
"trash": "M7 13h2v-2h5v-2h-12v2h5zM3 8h2v-6h6v6h2v-8h-10zM6 8h4v-5h-4v5z",
|
||||||
|
"volume": "M0 9h3v1h1v1h1v1h1v1h2v-12h-2v1h-1v1h-1v1h-1v1h-3zM9 10h1v-1h1v-4h-1v-1h-1zM9 13v-2h2v-1h1v-1h1v-4h-1v-1h-1v-1h-2v-2h2v1h1v1h1v1h1v6h-1v1h-1v1h-1v1z",
|
||||||
|
"disk": "M14 9c0 0.30 0 0.70 0 1h-1v1h-1v1h-8v-1h-1v-1c-0.30 0-0.70 0-1 0 0-0.30 0-0.70 0-1-0.30 0-0.70 0-1 0 0-2.30 0-4.70 0-7h14c0 2.30 0 4.70 0 7-0.30 0-0.70 0-1 0zM14 3h-12v4h12v-4zM3 6h7v-2h-7v2zM11 6h2v-2h-2v2z",
|
||||||
|
"task": "M5 14h6v-4h-6v4zM12 12v-3h-8v3h-3v-12h14v12h-3zM5 2h-2v2h2v-2zM5 5h-2v2h2v-2zM13 2h-7v2h7v-2zM13 5h-7v2h7v-2z",
|
||||||
|
"transfer": "M3 8v1h1v1h1v1h1v1h1v-11h-2v7zM9 12v-11h1v1h1v1h1v1h1v1h-2v7z",
|
||||||
|
"remote_control": "M7 6v6h-7v-10h2v3h1v-1h1v-1h1v-1h2v2h-1v1h-1v1h2zM2 8v2h3v-2h-3zM9 12h7v-4h-2v2h-3v-6h3v2h2v-4h-7z",
|
||||||
|
"info_0": "M3 14h10v-2h-10v2zM1 12h2v-10h-2v10zM3 2h10v-2h-10v2zM13 12h2v-10h-2v10zM7 8h2v-5h-2v5zM7 11h2v-2h-2v2z",
|
||||||
|
"arrow_2_n": "M10 1h-4v7h-4v1h1v1h1v1h1v1h1v1h1v1h2v-1h1v-1h1v-1h1v-1h1v-1h1v-1h-4z",
|
||||||
|
"success": "M3 8h2v-1h1v-1h2v1h1v1h1v1h1v1h1v1h2v-2h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h-1z",
|
||||||
|
"arrow_2_e": "M2 5v4h7v4h1v-1h1v-1h1v-1h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v4z",
|
||||||
|
"frame": "M2 12v-10h12v10h-12zM5 3h-2v5h2v-5zM13 3h-7v5h7v-5zM13 9h-10v2h10v-2zM9 7h3v-3h-1v2h-2z",
|
||||||
|
"full": "M0 13h4v-2h-2v-2h-2zM12 13h4v-4h-2v2h-2zM0 5h2v-2h2v-2h-4zM14 5h2v-4h-4v2h2z",
|
||||||
|
"arrow_4_en": "M13 12v-6h-1v1h-1v1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1c-0.70 0-1.30 0-2 0 0 0.70 0 1.30 0 2h1v1h1v1h1v1h1v1h1v1h1v1h-1v1h-1v1h6z",
|
||||||
|
"private": "M3 2h2v-2h-2v2zM1 2h2.10l-0.10 10h-2zM3 14h10v-2h-10v2zM13 12h2v-2h-2v2zM4 11h4v-2h-4v2zM4 8h2v-2h-2v2zM14 6v2h-1v1h-4v-1h-1v-2h-1v-5h1v-1h6v1h1v5h-1zM10 7h2v-1h-2v1zM13 2h-4v2h4v-2z",
|
||||||
|
"memory": "M16 8v3h-16v-3h1v-1h-1v-6h8v1h2v-1h6v6h-1v1h1zM5 5h-2v3h2v-3zM9 5h-2v3h2v-3zM13 5h-2v3h2v-3z",
|
||||||
|
"code": "M0 8h1v1h1v1h1v1h2v-1h-1v-1h-1v-1h-1v-2h1v-1h1v-1h1v-1h-2v1h-1v1h-1v1h-1zM16 8h-1v1h-1v1h-1v1h-2v-1h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v-1h2v1h1v1h1v1h1zM9 13h1v-3h-1v-4h-1v-3h-1v-2h-1v3h1v4h1v3h1z",
|
||||||
|
"arrow_4_es": "M13 2h-6v1h1v1h1v1h-1v1h-1v1h-1v1h-1v1h-1v1h-1c0 0.70 0 1.30 0 2 0.70 0 1.30 0 2 0v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v1h1v1h1v-6z",
|
||||||
|
"hammer": "M1 8v1h1v1h1v1h1v1h1v1h5v-1h-1v-1h-1v-1h-1v-1h1v-1h1v-1h2v-1h1v-1h1v-1h1v-1h1v-1h1v-1h-1v-1h-1v-1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v2h-1v1h-1v1h-1v-1h-1v-1h-1v1h-1v1z",
|
||||||
|
"import": "M8 0v1h-5v12h6v-4h5v2h-1v1h-1v1h-1v1h-9v-14zM10 8h-1v-1h-1v-1h-1v-1h-1v-1h1v-1h1v-1h1v-1h1v2h4v3h-4z",
|
||||||
|
"import_0": "M1 5h2v-3h10v3h2v-5h-14zM2 9h6v-3h-2v-1h1v-1h1v-1h2v1h1v1h1v1h-2v5h-8z",
|
||||||
|
"volume_mute": "M0 9h3v1h1v1h1v1h1v1h2v-12h-2v1h-1v1h-1v1h-1v1h-3zM9 11h2v-1h1v-1h1v1h1v1h2v-2h-1v-1h-1v-2h1v-1h1v-2h-2v1h-1v1h-1v-1h-1v-1h-2v2h1v1h1v2h-1v1h-1z",
|
||||||
|
"book": "M7 0v1h-7v10h2v-8h12v8h2v-10h-7v-1zM3 13h4v-9h-4v9zM9 13h4v-9h-4v9z",
|
||||||
|
"link": "M1 6h6v-6h-6v6zM3 7v5h5v-2h-3v-3zM9 14h6v-6h-6v6zM11 7h2v-5h-5v2h3z",
|
||||||
|
"back": "M4 4h7v5h-4v-3h-1v1h-1v1h-1v1h-1v2h1v1h1v1h1v1h1v-3h6v-9h-9z",
|
||||||
|
"full_exit": "M2 13v-2h-2v-2h4v4zM14 13h-2v-4h4v2h-2zM0 5h4v-4h-2v2h-2zM12 5h4v-2h-2v-2h-2z",
|
||||||
|
"video": "M0 13h16v-13h-2v11h-12v-9h10v-2h-12zM6 10h1v-1h1v-1h1v-1h1v-1h-1v-1h-1v-1h-1v-1h-1z",
|
||||||
|
"paste": "M12 11v1h-1v1h-8v-4h-1v-9h7v1h4v10h-1zM12 2h-3v5h-1v1h-1v1h-3v3h6v-1h1v-1h1v-8z",
|
||||||
|
"to_bottom": "M1 14h14v-2h-14v2zM1 9h14v-2h-14v2zM1 4h3v2h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v2h-3zM8 4h7v-2h-7v2z",
|
||||||
|
"delete": "M12 8v2h-1v2h-1v1h-6v-5h-3v-2h3v-5h6v1h1v2h1v2h3v2h-3zM10 4h-1v-1h-3v3h4v-2zM10 8h-4v3h3v-1h1v-2z",
|
||||||
|
"boy": "M8 13c0 0 0-1.70 0-2 0.70 0 1.30 0 2 0v-1h-1v-1h-1v1h-4v-1h-1v-1h-1v-5h1v-1h1v-1h5v1h1v1h1v4h-1v1h1v1h1c0-0.70 0-1.30 0-2 0.30 0 2 0 2 0v6h-6zM9 4h-1v-1h-3v1h-1v3h1v1h3v-1h1v-3z",
|
||||||
|
"random": "M11 10v2h2v1h-1v1h-1v1h-2v-1h-1v-1h-1v-1h2v-2h-4v-4h-5v-2h5v-2h-2v-1h1v-1h1v-1h2v1h1v1h1v1h-2v2h4v4h5v2h-5zM9 6h-2v2h2v-2z",
|
||||||
|
"database": "M5 14h6v-1h2v-1h2v-2h-2v-1h-2v-1h-6v1h-2v1h-2v2h2v1h2zM1 9h2v-1h2v-1h6v1h2v1h2v-3h-2v-1h-2v-1h-6v1h-2v1h-2zM1 5h2v-1h2v-1h6v1h2v1h2v-3h-2v-1h-2v-1h-6v1h-2v1h-2z",
|
||||||
|
"repeat": "M6 4v-2h-4v10h4v2h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v2h-2v-6zM7 4h1v1h1v1h1v-2h2v6h-2v2h4v-10h-4v-2h-1v1h-1v1h-1z",
|
||||||
|
"play_list": "M1 14h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1zM6 12h9v-2h-9v2zM1 7h14v-2h-14v2zM1 2h14v-2h-14v2z",
|
||||||
|
"play_0": "M6 12c0.70 0 1.30 0 2 0v-1h1v-1h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v-1h-1v-1c-0.70 0-1.30 0-2 0 0 3.30 0 6.70 0 10z",
|
||||||
|
"lang": "M1 14v-2h-1v-12h14v1h2v13h-15zM7 6h-2v-1h-1v1h-2v3h2v1h1v-1h2v-3zM12 6h-3v-1h2v-1h-2v-1h3v-1h-4v5h4v-1zM15 2h-1v10h-12v1h13v-11zM3 8h1v-1h-1v1zM5 8h1v-1h-1v1z",
|
||||||
|
"arrow_3_w": "M10 8v-2h1v-1h1v-1h1v-2h-2v1h-1v1h-1v1h-1v1h-1v2h1v1h1v1h1v1h1v1h2v-2h-1v-1h-1v-1zM8 12v-2h-1v-1h-1v-1h-1v-2h1v-1h1v-1h1v-2h-2v1h-1v1h-1v1h-1v1h-1v2h1v1h1v1h1v1h1v1z",
|
||||||
|
"resources": "M1 8v4h2v2h4v-6zM1 6h6v-6h-4v2h-2zM9 8v6h4v-2h2v-4zM9 6h6v-4h-2v-2h-4z",
|
||||||
|
"terminal": "M1 12v-10h14v10h-14zM6 6h-1v-1h-1v-1h-1v1h1v1h1v1h-1v1h-1v1h1v-1h1v-1h1v-1zM13 4h-6v1h6v-1z",
|
||||||
|
"message": "M15 11v1h-1v1h-12v-1h-1v-1h-1v-7h1v-1h1v-1h1v-1h1v-1h1v1h1v1h8v1h1v1h1v7h-1zM14 4h-8v-1h-1v-1h-1v1h-1v1h-1v7h12v-7zM4 10h8v-2h-8v2zM4 7h4v-2h-4v2z",
|
||||||
|
"arrow_3_s": "M7 9h2v1h1v1h1v1h2v-2h-1v-1h-1v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h-1v1h-1v2h2v-1h1v-1h1zM3 7h2v-1h1v-1h1v-1h2v1h1v1h1v1h2v-2h-1v-1h-1v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h-1v1h-1z",
|
||||||
|
"home": "M7 1h-4v5h-2v1h1v1h1v1h1v1h1v1h1v1h1v1h2v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h-2v-5h-4v3h-2z",
|
||||||
|
"arrow_3_n": "M9 4h-2v-1h-1v-1h-1v-1h-2v2h1v1h1v1h1v1h1v1h2v-1h1v-1h1v-1h1v-1h1v-2h-2v1h-1v1h-1zM13 6h-2v1h-1v1h-1v1h-2v-1h-1v-1h-1v-1h-2v2h1v1h1v1h1v1h1v1h2v-1h1v-1h1v-1h1v-1h1z",
|
||||||
|
"popup": "M7 13h-5v-12h12v5h-2v-3h-8v8h3zM8 13h6v-6h-2v2h-1v-1h-1v-1h-1v-1h-2v2h1v1h1v1h1v1h-2z",
|
||||||
|
"arrow_4_wn": "M3 12h6v-1h-1v-1h-1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1c0-0.70 0-1.30 0-2-0.70 0-1.30 0-2 0v1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v-1h-1v-1h-1v6z",
|
||||||
|
"money": "M7 1v2h-3v1h3v2h-2v-1h-1v1h-1v1h1v1h1v-1h2v2h-1v1h-1v1h-1v1h-1v1h2v-1h1v-1h1v-1h2v1h1v1h1v1h2v-1h-1v-1h-1v-1h-1v-1h-1v-2h3v-1h-3v-2h2v1h1v-1h1v-1h-1v-1h-1v1h-2v-2z",
|
||||||
|
"unlink": "M4 0v2h1v2h1v2h1v2h1v2h1v2h1v2h2v-2h-1v-2h-1v-2h-1v-2h-1v-2h-1v-2h-1v-2zM4 3h-3v8h6v-2h-4v-4h1zM9 3v2h4v4h-1v2h3v-8z",
|
||||||
|
"arrow_4_ws": "M3 2v6h1v-1h1v-1h1v1h1v1h1v1h1v1h1v1h1v1c0.70 0 1.30 0 2 0 0-0.70 0-1.30 0-2h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h1v-1h1v-1h-6z",
|
||||||
|
"folder_add": "M7 10v1h-1v1h-4v-10h12v8h-7zM13 5h-1v-1h-1v1h-1v1h1v1h1v-1h1v-1z",
|
||||||
|
"user": "M6 14h4v-2h2v-4h-2v-2h-4v2h-2v4h2zM3 5h10v-2h2v-3h-14v3h2z",
|
||||||
|
"arrow_3_e": "M6 6v2h-1v1h-1v1h-1v2h2v-1h1v-1h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v-1h-1v-1h-2v2h1v1h1v1zM8 2v2h1v1h1v1h1v2h-1v1h-1v1h-1v2h2v-1h1v-1h1v-1h1v-1h1v-2h-1v-1h-1v-1h-1v-1h-1v-1z"
|
||||||
|
}
|
||||||
BIN
src/assets/img/default.png
Normal file
BIN
src/assets/img/default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 183 B |
BIN
src/assets/img/input.png
Normal file
BIN
src/assets/img/input.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 B |
BIN
src/assets/img/link.png
Normal file
BIN
src/assets/img/link.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 207 B |
213
src/assets/style/timi-web.less
Normal file
213
src/assets/style/timi-web.less
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
@import url(~/assets/style/variable);
|
||||||
|
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 10px !important;
|
||||||
|
height: 10px !important;
|
||||||
|
cursor: var(--tui-cur-default);
|
||||||
|
background: #CFD2E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-corner {
|
||||||
|
background: #CFD2E0;
|
||||||
|
cursor: var(--tui-cur-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
cursor: var(--tui-cur-default);
|
||||||
|
background: #525870;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::selection {
|
||||||
|
color: #FFF;
|
||||||
|
background: #525870 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
cursor: var(--tui-cur-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 100% !important;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
overflow-y: scroll !important;
|
||||||
|
font-family: var(--tui-font);
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: @tuiColors[blue];
|
||||||
|
cursor: var(--tui-cur-pointer);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select, textarea {
|
||||||
|
resize: none;
|
||||||
|
outline: none;
|
||||||
|
display: block;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
label,
|
||||||
|
select,
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="file"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
cursor: var(--tui-cur-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea,
|
||||||
|
input[type="text"],
|
||||||
|
input[type="date"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="password"] {
|
||||||
|
cursor: var(--tui-cur-text);
|
||||||
|
-webkit-appearance: none;
|
||||||
|
|
||||||
|
&:-webkit-autofill,
|
||||||
|
&:-webkit-autofill:hover,
|
||||||
|
&:-webkit-autofill:focus,
|
||||||
|
&:-webkit-autofill:active {
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
tab-size: 4;
|
||||||
|
font-family: var(--tui-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gray-filter {
|
||||||
|
filter: grayscale(1);
|
||||||
|
-webkit-filter: grayscale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold {
|
||||||
|
font-weight: bold !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.italic {
|
||||||
|
font-style: italic !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
text-decoration: line-through !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underline {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-underline {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-underline:hover {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文本适当对齐 */
|
||||||
|
.justify-text {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模糊玻璃效果:白色 */
|
||||||
|
.glass-white {
|
||||||
|
color: var(--eui-black, #000);
|
||||||
|
background: rgba(255, 255, 255, .8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模糊玻璃效果:黑色 */
|
||||||
|
.glass-black {
|
||||||
|
color: var(--eui-white, #FFF);
|
||||||
|
background: rgba(0, 0, 0, .8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-space {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 强制换行 */
|
||||||
|
.break-all,
|
||||||
|
.break-all textarea {
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文本溢出截断 */
|
||||||
|
.clip-text {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 禁止选择 */
|
||||||
|
.diselect {
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectable {
|
||||||
|
-webkit-touch-callout: default;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
-moz-user-select: text;
|
||||||
|
-ms-user-select: text;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ir-default,
|
||||||
|
.ir-pixelated {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ir-auto {
|
||||||
|
image-rendering: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ir-smooth {
|
||||||
|
image-rendering: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-default {
|
||||||
|
cursor: var(--tui-cur-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-pointer {
|
||||||
|
cursor: var(--tui-cur-pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cur-text {
|
||||||
|
cursor: var(--tui-cur-text);
|
||||||
|
}
|
||||||
58
src/assets/style/variable.less
Normal file
58
src/assets/style/variable.less
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
@tuiColors: {
|
||||||
|
red: #F33;
|
||||||
|
pink: #FF7A9B;
|
||||||
|
black: #111;
|
||||||
|
blue: #006EFF;
|
||||||
|
light-blue: #00A6FF;
|
||||||
|
green: GREEN;
|
||||||
|
orange: #E7913B;
|
||||||
|
gray: #666;
|
||||||
|
light-gray: #AAA;
|
||||||
|
dark-white: #E7EAEF;
|
||||||
|
yellow: #FF0;
|
||||||
|
purple: PURPLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--tui-font: SimSun, PingFang SC, Microsoft YaHei, Arial Regular;
|
||||||
|
--tui-cur-default: url("../img/default.png"), default;
|
||||||
|
--tui-cur-pointer: url("../img/link.png"), pointer;
|
||||||
|
--tui-cur-text: url("../img/input.png"), text;
|
||||||
|
|
||||||
|
--tui-shadow: 3px 3px 0 var(--tui-shadow-color);
|
||||||
|
--tui-bezier: cubic-bezier(.19, .1, .22, 1);
|
||||||
|
--tui-shadow-color: rgba(0, 0, 0, .2);
|
||||||
|
|
||||||
|
each(@tuiColors, {
|
||||||
|
--tui-@{key}: @value;
|
||||||
|
});
|
||||||
|
--tui-border: 1px solid var(--tui-light-gray);
|
||||||
|
|
||||||
|
/* 等级对应颜色 */
|
||||||
|
--tui-level-0: #BFBFBF;
|
||||||
|
--tui-level-1: #BFBFBF;
|
||||||
|
--tui-level-2: #95DDB2;
|
||||||
|
--tui-level-3: #92D1E5;
|
||||||
|
--tui-level-4: #FFB37C;
|
||||||
|
--tui-level-5: #FF6C00;
|
||||||
|
--tui-level-6: #F00;
|
||||||
|
--tui-level-7: #E52FEC;
|
||||||
|
--tui-level-8: #841CF9;
|
||||||
|
--tui-level-9: #151515;
|
||||||
|
|
||||||
|
--tui-page-padding: .5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字体颜色
|
||||||
|
each(@tuiColors, {
|
||||||
|
.@{key} {
|
||||||
|
color: @value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 背景颜色
|
||||||
|
each(@tuiColors, {
|
||||||
|
.bg-@{key} {
|
||||||
|
background: @value;
|
||||||
|
}
|
||||||
|
});
|
||||||
BIN
src/components/background-effect/flower-fall/assets/petal.png
Normal file
BIN
src/components/background-effect/flower-fall/assets/petal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 370 B |
5
src/components/background-effect/flower-fall/index.ts
Normal file
5
src/components/background-effect/flower-fall/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
export const BEFlowerFall = Toolkit.withInstall(view);
|
||||||
|
export default BEFlowerFall;
|
||||||
155
src/components/background-effect/flower-fall/index.vue
Normal file
155
src/components/background-effect/flower-fall/index.vue
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tui-background tui-be-flower-fall">
|
||||||
|
<div :ref="el => fillNodes(el as HTMLElement, i - 1)" class="leaf" v-for="i in leafSize" :key="i"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
import Resizer from "~/utils/Resizer";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "BEFlowerFall"
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 叶子对象 */
|
||||||
|
class Leaf {
|
||||||
|
|
||||||
|
/** 横轴 */
|
||||||
|
x = 0;
|
||||||
|
|
||||||
|
/** 纵轴 */
|
||||||
|
y = 0;
|
||||||
|
|
||||||
|
/** 背景偏移 */
|
||||||
|
bgI = 0;
|
||||||
|
|
||||||
|
/** 跳跃状态 */
|
||||||
|
jump = false;
|
||||||
|
|
||||||
|
/** 透明度 */
|
||||||
|
opacity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const docEl = document.documentElement;
|
||||||
|
const leafF = ref<number>(0);
|
||||||
|
const leafs: Leaf[] = [];
|
||||||
|
const leafEls: HTMLElement[] = [];
|
||||||
|
const leafSize = 16;
|
||||||
|
const leafTimer = ref();
|
||||||
|
const leafPause = ref<boolean>(false);
|
||||||
|
const leafVector = ref<number>(0);
|
||||||
|
|
||||||
|
const fillNodes = (el: HTMLElement, index: number) => {
|
||||||
|
if (el) {
|
||||||
|
leafEls[index] = el;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResize = (width: number) => leafPause.value = width < 1200;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
// 生成叶子
|
||||||
|
for (let i = 0; i < leafSize; i++) {
|
||||||
|
leafs[i] = new Leaf();
|
||||||
|
leafs[i].x = Toolkit.random(0, docEl.clientWidth);
|
||||||
|
leafs[i].y = Toolkit.random(-docEl.clientHeight, -32);
|
||||||
|
}
|
||||||
|
// 掉落叶子
|
||||||
|
clearInterval(leafTimer.value);
|
||||||
|
leafTimer.value = setInterval(() => {
|
||||||
|
// 随机上方位置向下飘落,维护一个向量值作为风向,可以向左向右
|
||||||
|
//
|
||||||
|
// 暂定 2 秒刷新风力向量,向量偏转范围 -10 - +10,分别表示左右向
|
||||||
|
// -5 - +5 有固定 10% 几率增加或减小,同时增减或同时无增减都不改变向量
|
||||||
|
// -10 - -5 有 0%, 2%, 4%... 几率再次减小
|
||||||
|
// +5 - +10 有 8%, 6%, 4%... 几率再次增加
|
||||||
|
// 降低垂直飘落的几率,扩大向量影响范围
|
||||||
|
|
||||||
|
if (!leafPause.value) {
|
||||||
|
if (leafF.value % 10 === 0) {
|
||||||
|
// 刷新风力向量
|
||||||
|
let add = false,
|
||||||
|
sub = false;
|
||||||
|
if (-5 <= leafVector.value) {
|
||||||
|
sub = Math.random() < 0.1;
|
||||||
|
if (5 < leafVector.value) {
|
||||||
|
add = Math.random() < (10 - leafVector.value) / 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (leafVector.value <= 5) {
|
||||||
|
add = Math.random() < 0.1;
|
||||||
|
if (leafVector.value < -5) {
|
||||||
|
sub = Math.random() < (leafVector.value + 10) / 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (add !== sub) {
|
||||||
|
leafVector.value = leafVector.value + (add ? 1 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 叶子飘落
|
||||||
|
for (let i = 0; i < leafSize; i++) {
|
||||||
|
const leaf = leafs[i];
|
||||||
|
|
||||||
|
if (leafVector.value < 0) {
|
||||||
|
leaf.x += Toolkit.random(leafVector.value * 4 - 10, -10);
|
||||||
|
} else {
|
||||||
|
if (leafVector.value === 0) {
|
||||||
|
leaf.x += Toolkit.random(-8, 8);
|
||||||
|
} else {
|
||||||
|
leaf.x += Toolkit.random(10, 10 + leafVector.value * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 跳跃机制
|
||||||
|
if (leafVector.value !== 0 && leaf.jump) {
|
||||||
|
leaf.y += Toolkit.random(-12, Math.abs(leafVector.value));
|
||||||
|
leaf.jump = Math.random() < 0.4; // 二次跳跃几率
|
||||||
|
} else {
|
||||||
|
leaf.y += Toolkit.random(10 + Math.abs(leafVector.value) * 5, 10);
|
||||||
|
leaf.jump = Math.random() < 0.03; // 跳跃几率
|
||||||
|
}
|
||||||
|
// 操作 DOM
|
||||||
|
const el = leafEls[i];
|
||||||
|
el.style.top = leaf.y + "px";
|
||||||
|
el.style.left = leaf.x + "px";
|
||||||
|
el.style.backgroundPosition = "0 -" + Toolkit.random(0, 3) * 32 + "px";
|
||||||
|
el.style.opacity = leaf.opacity.toString();
|
||||||
|
|
||||||
|
// y 掉出最大值或没有掉出最大值 X 却不在宽度内的,视为掉出屏幕,重新飘落
|
||||||
|
if ((0 < leaf.y && (leaf.x < 0 || docEl.clientWidth < leaf.x)) || docEl.clientHeight < leaf.y) {
|
||||||
|
// 根据风向反向偏移起始 X 轴
|
||||||
|
const offset = -leafVector.value * 100;
|
||||||
|
// 飘落越出屏幕,重新计算位置
|
||||||
|
leafs[i].x = Toolkit.random(offset, docEl.clientWidth + offset);
|
||||||
|
leafs[i].y = Toolkit.random(-docEl.clientHeight * 0.5, -32);
|
||||||
|
// 随机透明度
|
||||||
|
leafs[i].opacity = Toolkit.random(40, 100) * 0.01;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 帧循环
|
||||||
|
leafF.value++;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
onResize(Resizer.getWidth());
|
||||||
|
Resizer.addListener("TUI_BE_FLOWER_FALL", onResize);
|
||||||
|
});
|
||||||
|
onUnmounted(() => Resizer.removeListener("TUI_BE_FLOWER_FALL"));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@import "../style";
|
||||||
|
|
||||||
|
.tui-be-flower-fall {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
|
||||||
|
.leaf {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
position: absolute;
|
||||||
|
background: url("assets/petal.png") no-repeat;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
src/components/background-effect/style.less
Normal file
8
src/components/background-effect/style.less
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.tui-background {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: -1;
|
||||||
|
position: fixed;
|
||||||
|
overflow: hidden;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
5
src/components/captcha/index.ts
Normal file
5
src/components/captcha/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
export const Captcha = Toolkit.withInstall(view);
|
||||||
|
export default Captcha;
|
||||||
46
src/components/captcha/index.vue
Normal file
46
src/components/captcha/index.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<img
|
||||||
|
class="tui-captcha ir-pixelated"
|
||||||
|
v-if="src"
|
||||||
|
:width="width"
|
||||||
|
:height="height"
|
||||||
|
:src="src"
|
||||||
|
alt="验证码"
|
||||||
|
@click="update()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "Captcha"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
from: string,
|
||||||
|
api: string,
|
||||||
|
}>(), {});
|
||||||
|
const { width, height, from, api } = 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)}`;
|
||||||
|
}
|
||||||
|
onMounted(update);
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
update
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-captcha {
|
||||||
|
cursor: var(--tui-cur-pointer);
|
||||||
|
border: 1px solid gray;
|
||||||
|
display: block;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
BIN
src/components/copyright/assets/bottom.png
Normal file
BIN
src/components/copyright/assets/bottom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 786 B |
5
src/components/copyright/index.ts
Normal file
5
src/components/copyright/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
export const Copyright = Toolkit.withInstall(view);
|
||||||
|
export default Copyright;
|
||||||
45
src/components/copyright/index.vue
Normal file
45
src/components/copyright/index.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tui-copyright">
|
||||||
|
<p>朝朝频顾惜,夜夜不相忘</p>
|
||||||
|
<p v-if="icp" class="selectable">
|
||||||
|
<a href="https://beian.miit.gov.cn/" v-text="icp" :title="icp" target="_blank"></a>
|
||||||
|
</p>
|
||||||
|
<p v-if="author && domain">
|
||||||
|
<span v-text="`Copyright © 2017 - ${new Date().getFullYear()} ${domain}`"></span>
|
||||||
|
<span v-text="`All Rights Reserved ${author} 版权所有`"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({
|
||||||
|
name: "Copyright"
|
||||||
|
});
|
||||||
|
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
icp?: string;
|
||||||
|
domain?: string;
|
||||||
|
author?: string;
|
||||||
|
}>(), {
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-copyright {
|
||||||
|
color: #777;
|
||||||
|
padding: 1rem 0 3rem 0;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
background: url("assets/bottom.png") repeat-x left bottom;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
5
src/components/empty-tips/index.ts
Normal file
5
src/components/empty-tips/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
export const EmptyTips = Toolkit.withInstall(view);
|
||||||
|
export default EmptyTips;
|
||||||
27
src/components/empty-tips/index.vue
Normal file
27
src/components/empty-tips/index.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="showOn" class="tui-empty-tips gray" v-text="content"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "EmptyTips"
|
||||||
|
});
|
||||||
|
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
showOn: boolean;
|
||||||
|
content?: string,
|
||||||
|
}>(), {
|
||||||
|
showOn: true,
|
||||||
|
content: "没有更多数据"
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-empty-tips {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem 0;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
5
src/components/icon/index.ts
Normal file
5
src/components/icon/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
export const Icon = Toolkit.withInstall(view);
|
||||||
|
export default Icon;
|
||||||
62
src/components/icon/index.vue
Normal file
62
src/components/icon/index.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
v-if="name && d"
|
||||||
|
class="tui-icon"
|
||||||
|
:class="{ 'disable': disable || disabled }"
|
||||||
|
:width="16 * scale"
|
||||||
|
:height="16 * scale"
|
||||||
|
:fill="fill"
|
||||||
|
>
|
||||||
|
<path class="path" :d="d" />
|
||||||
|
</svg>
|
||||||
|
<div v-else class="tui-icon empty"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import IconMapper from "~/utils/IconMapper";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "Icon"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
name?: string,
|
||||||
|
scale?: number,
|
||||||
|
fill?: string,
|
||||||
|
disable?: boolean,
|
||||||
|
disabled?: boolean,
|
||||||
|
}>(), {
|
||||||
|
scale: 1,
|
||||||
|
fill: "#333",
|
||||||
|
});
|
||||||
|
|
||||||
|
const { name, scale, fill, disable, disabled } = toRefs(props);
|
||||||
|
const d = ref();
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
if (name.value) {
|
||||||
|
d.value = IconMapper.getInstance().getSVG(name.value, scale.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(name, update);
|
||||||
|
watch(scale, update);
|
||||||
|
onMounted(update);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-icon {
|
||||||
|
display: block;
|
||||||
|
transform: rotateX(180deg) translateY(1px);
|
||||||
|
|
||||||
|
&.disable {
|
||||||
|
opacity: .7;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.empty {
|
||||||
|
width: v-bind("16 * scale + 'px'");
|
||||||
|
height: v-bind("16 * scale + 'px'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
37
src/components/index.ts
Normal file
37
src/components/index.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/** 导出所有组件 */
|
||||||
|
import Icon from "./icon";
|
||||||
|
import Popup from "./popup";
|
||||||
|
import Captcha from "./captcha";
|
||||||
|
import Loading from "./loading";
|
||||||
|
import UserLevel from "./user-level";
|
||||||
|
import Copyright from "./copyright";
|
||||||
|
import EmptyTips from "./empty-tips";
|
||||||
|
import MarkdownView from "./markdown-view";
|
||||||
|
import BEFlowerFall from "./background-effect/flower-fall";
|
||||||
|
import MarkdownEditor from "./markdown-editor";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
Icon,
|
||||||
|
Popup,
|
||||||
|
Captcha,
|
||||||
|
Loading,
|
||||||
|
UserLevel,
|
||||||
|
Copyright,
|
||||||
|
EmptyTips,
|
||||||
|
MarkdownView,
|
||||||
|
BEFlowerFall,
|
||||||
|
MarkdownEditor
|
||||||
|
];
|
||||||
|
|
||||||
|
export {
|
||||||
|
Icon,
|
||||||
|
Popup,
|
||||||
|
Captcha,
|
||||||
|
Loading,
|
||||||
|
UserLevel,
|
||||||
|
Copyright,
|
||||||
|
EmptyTips,
|
||||||
|
MarkdownView,
|
||||||
|
BEFlowerFall,
|
||||||
|
MarkdownEditor
|
||||||
|
};
|
||||||
5
src/components/loading/index.ts
Normal file
5
src/components/loading/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
export const Loading = Toolkit.withInstall(view);
|
||||||
|
export default Loading;
|
||||||
153
src/components/loading/index.vue
Normal file
153
src/components/loading/index.vue
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="visible" class="tui-loading" :class="{ 'filled': filled }" :style="style">
|
||||||
|
<div class="icon">
|
||||||
|
<div
|
||||||
|
class="line"
|
||||||
|
:class="lineClass(i)"
|
||||||
|
v-for="i in 4"
|
||||||
|
:key="i"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div v-if="tips" class="text" v-text="tips"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Toolkit } from "~/index";
|
||||||
|
defineOptions({
|
||||||
|
name: "Loading"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
tips?: string | boolean;
|
||||||
|
delay?: number;
|
||||||
|
showOn?: boolean;
|
||||||
|
size?: number;
|
||||||
|
filled?: boolean;
|
||||||
|
}>(), {
|
||||||
|
tips: "加载中..",
|
||||||
|
delay: 260,
|
||||||
|
showOn: true,
|
||||||
|
size: 24,
|
||||||
|
filled: false
|
||||||
|
});
|
||||||
|
const { tips, delay, showOn, size, filled } = toRefs(props);
|
||||||
|
|
||||||
|
// ---------- 尺寸参数 ----------
|
||||||
|
|
||||||
|
const style = computed(() => {
|
||||||
|
const base = Math.max(8, size.value);
|
||||||
|
const height = Math.round(base);
|
||||||
|
const width = Math.round(base * (8 / 24));
|
||||||
|
const gap = Math.round(base * (3 / 24));
|
||||||
|
return {
|
||||||
|
"--loading-height": `${height}px`,
|
||||||
|
"--rect-width": `${width}px`,
|
||||||
|
"--rect-gap": `${gap}px`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------- 显示控制 ----------
|
||||||
|
|
||||||
|
const visible = ref<boolean>(false);
|
||||||
|
watch(showOn, async () => await start());
|
||||||
|
|
||||||
|
// ---------- 动画 ----------
|
||||||
|
|
||||||
|
const activeI = ref<number>(0);
|
||||||
|
const timer = ref();
|
||||||
|
const lineClass = (i: number): string => {
|
||||||
|
let clazz = "i" + i;
|
||||||
|
if (activeI.value === i) {
|
||||||
|
clazz += " active";
|
||||||
|
}
|
||||||
|
return clazz;
|
||||||
|
};
|
||||||
|
const start = async () => {
|
||||||
|
if (timer.value) {
|
||||||
|
clearInterval(timer.value);
|
||||||
|
}
|
||||||
|
if (showOn.value) {
|
||||||
|
if (0 < delay.value) {
|
||||||
|
await Toolkit.sleep(delay.value);
|
||||||
|
}
|
||||||
|
if (!showOn.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visible.value = true;
|
||||||
|
|
||||||
|
// 动画
|
||||||
|
let direction = 1;
|
||||||
|
timer.value = setInterval(() => {
|
||||||
|
activeI.value = activeI.value + direction;
|
||||||
|
if (4 < activeI.value || activeI.value < 1) {
|
||||||
|
direction *= -1;
|
||||||
|
}
|
||||||
|
}, 120);
|
||||||
|
} else {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onMounted(start);
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer.value) {
|
||||||
|
clearInterval(timer.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-loading {
|
||||||
|
--h1: calc(var(--loading-height) * (9 / 24));
|
||||||
|
--h2: calc(var(--loading-height) * (14 / 24));
|
||||||
|
--h3: calc(var(--loading-height) * (19 / 24));
|
||||||
|
--h4: var(--loading-height);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&.filled {
|
||||||
|
color: #FFF;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.line {
|
||||||
|
width: var(--rect-width);
|
||||||
|
background: #BBB;
|
||||||
|
margin-right: var(--rect-gap);
|
||||||
|
|
||||||
|
&.i1 {
|
||||||
|
height: calc(var(--h1))
|
||||||
|
}
|
||||||
|
|
||||||
|
&.i2 {
|
||||||
|
height: calc(var(--h2))
|
||||||
|
}
|
||||||
|
|
||||||
|
&.i3 {
|
||||||
|
height: calc(var(--h3))
|
||||||
|
}
|
||||||
|
|
||||||
|
&.i4 {
|
||||||
|
height: var(--h4)
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #53BD93;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
112
src/components/markdown-editor/CalcTextareaHeight.ts
Normal file
112
src/components/markdown-editor/CalcTextareaHeight.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// from element ui
|
||||||
|
// https://github.com/ElemeFE/element/blob/dev/packages/input/src/calcTextareaHeight.js
|
||||||
|
|
||||||
|
let tempTextArea: HTMLTextAreaElement | null;
|
||||||
|
|
||||||
|
const HIDDEN_STYLE = `
|
||||||
|
height:0 !important;
|
||||||
|
visibility:hidden !important;
|
||||||
|
overflow:hidden !important;
|
||||||
|
position:absolute !important;
|
||||||
|
z-index:-1000 !important;
|
||||||
|
top:0 !important;
|
||||||
|
right:0 !important
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CONTEXT_STYLE = [
|
||||||
|
"letter-spacing",
|
||||||
|
"line-height",
|
||||||
|
"padding-top",
|
||||||
|
"padding-bottom",
|
||||||
|
"font-family",
|
||||||
|
"font-weight",
|
||||||
|
"font-size",
|
||||||
|
"text-rendering",
|
||||||
|
"text-transform",
|
||||||
|
"width",
|
||||||
|
"text-indent",
|
||||||
|
"padding-left",
|
||||||
|
"padding-right",
|
||||||
|
"border-width",
|
||||||
|
"box-sizing"
|
||||||
|
];
|
||||||
|
|
||||||
|
type NodeStyling = {
|
||||||
|
contextStyle: string;
|
||||||
|
paddingSize: number;
|
||||||
|
borderSize: number;
|
||||||
|
boxSizing: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Result = {
|
||||||
|
height: number;
|
||||||
|
minHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateNodeStyling(targetElement: HTMLTextAreaElement): NodeStyling {
|
||||||
|
const style = window.getComputedStyle(targetElement);
|
||||||
|
|
||||||
|
const boxSizing = style.getPropertyValue("box-sizing");
|
||||||
|
|
||||||
|
const paddingSize = (
|
||||||
|
parseFloat(style.getPropertyValue("padding-bottom")) +
|
||||||
|
parseFloat(style.getPropertyValue("padding-top"))
|
||||||
|
);
|
||||||
|
|
||||||
|
const borderSize = (
|
||||||
|
parseFloat(style.getPropertyValue("border-bottom-width")) +
|
||||||
|
parseFloat(style.getPropertyValue("border-top-width"))
|
||||||
|
);
|
||||||
|
|
||||||
|
const contextStyle = CONTEXT_STYLE
|
||||||
|
.map(name => `${name}:${style.getPropertyValue(name)}`)
|
||||||
|
.join(";");
|
||||||
|
|
||||||
|
return {contextStyle, paddingSize, borderSize, boxSizing};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function calc(el: HTMLTextAreaElement, minRows = 1, maxRows?: number) {
|
||||||
|
if (!tempTextArea) {
|
||||||
|
tempTextArea = document.createElement("textarea") as HTMLTextAreaElement;
|
||||||
|
document.body.appendChild(tempTextArea);
|
||||||
|
}
|
||||||
|
const {paddingSize, borderSize, boxSizing, contextStyle} = calculateNodeStyling(el);
|
||||||
|
|
||||||
|
tempTextArea.setAttribute("style", `${contextStyle};${HIDDEN_STYLE}`);
|
||||||
|
tempTextArea.value = el.value || el.placeholder || "";
|
||||||
|
|
||||||
|
let height = tempTextArea.scrollHeight;
|
||||||
|
const result: Result = {
|
||||||
|
height: 0,
|
||||||
|
minHeight: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if (boxSizing === "border-box") {
|
||||||
|
height = height + borderSize;
|
||||||
|
} else if (boxSizing === "content-box") {
|
||||||
|
height = height - paddingSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
tempTextArea.value = "";
|
||||||
|
const singleRowHeight = tempTextArea.scrollHeight - paddingSize;
|
||||||
|
|
||||||
|
if (minRows) {
|
||||||
|
let minHeight = singleRowHeight * minRows;
|
||||||
|
if (boxSizing === "border-box") {
|
||||||
|
minHeight = minHeight + paddingSize + borderSize;
|
||||||
|
}
|
||||||
|
height = Math.max(minHeight, height);
|
||||||
|
result.minHeight = minHeight;
|
||||||
|
}
|
||||||
|
if (maxRows) {
|
||||||
|
let maxHeight = singleRowHeight * maxRows;
|
||||||
|
if (boxSizing === "border-box") {
|
||||||
|
maxHeight = maxHeight + paddingSize + borderSize;
|
||||||
|
}
|
||||||
|
height = Math.min(maxHeight, height);
|
||||||
|
}
|
||||||
|
result.height = height;
|
||||||
|
tempTextArea.parentNode && tempTextArea.parentNode.removeChild(tempTextArea);
|
||||||
|
tempTextArea = null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
5
src/components/markdown-editor/index.ts
Normal file
5
src/components/markdown-editor/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
export const MarkdownEditor = Toolkit.withInstall(view);
|
||||||
|
export default MarkdownEditor;
|
||||||
192
src/components/markdown-editor/index.vue
Normal file
192
src/components/markdown-editor/index.vue
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="root"
|
||||||
|
class="tui-markdown-editor diselect"
|
||||||
|
:class="{ 'fold': isFold }"
|
||||||
|
>
|
||||||
|
<div class="editor">
|
||||||
|
<div class="header">
|
||||||
|
<icon class="icon" name="WRITING" />
|
||||||
|
<slot name="editorHeader">
|
||||||
|
<h4 class="title">
|
||||||
|
<span>源码</span>
|
||||||
|
<span class="light-gray word-space">Markdown</span>
|
||||||
|
</h4>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<slot name="editor">
|
||||||
|
<textarea ref="textArea" class="text-area" v-model="_data"></textarea>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div class="preview" :class="{ 'showing': showingPreview }">
|
||||||
|
<div class="header">
|
||||||
|
<icon
|
||||||
|
v-if="isFold"
|
||||||
|
class="icon cur-pointer"
|
||||||
|
:name="showingPreview ? 'ARROW_1_E' : 'ARROW_1_W'"
|
||||||
|
@click="showingPreview = !showingPreview"
|
||||||
|
/>
|
||||||
|
<slot name="previewHeader">
|
||||||
|
<h4 class="title">预览</h4>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div ref="previewContent" class="content">
|
||||||
|
<markdown-view :content="_data" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Icon, MarkdownView } from "~/index";
|
||||||
|
import calcHeight from "./CalcTextareaHeight";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "MarkdownEditor"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
data?: string,
|
||||||
|
minRows?: number,
|
||||||
|
maxRows?: number
|
||||||
|
}>(), {
|
||||||
|
data: "",
|
||||||
|
minRows: 8,
|
||||||
|
maxRows: 32
|
||||||
|
});
|
||||||
|
const { data, minRows, maxRows } = toRefs(props);
|
||||||
|
|
||||||
|
const _data = ref(data.value);
|
||||||
|
const textArea = ref<HTMLTextAreaElement>();
|
||||||
|
const textAreaHeight = ref(30);
|
||||||
|
const previewContent = ref<HTMLDivElement>();
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:data"]);
|
||||||
|
|
||||||
|
watch(_data, () => {
|
||||||
|
emit("update:data", _data.value);
|
||||||
|
calcTextAreaHeight();
|
||||||
|
});
|
||||||
|
watch(data, () => {
|
||||||
|
_data.value = data.value;
|
||||||
|
calcTextAreaHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 自适应折叠预览
|
||||||
|
const root = ref<HTMLDivElement>();
|
||||||
|
const isFold = ref(false);
|
||||||
|
const showingPreview = ref(false);
|
||||||
|
onMounted(() => {
|
||||||
|
if (root.value) {
|
||||||
|
const foldObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||||
|
if (entries && 0 < entries.length) {
|
||||||
|
isFold.value = entries[0].contentRect.width < 650;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
foldObserver.observe(root.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 自适应高度
|
||||||
|
const calcTextAreaHeight = () => {
|
||||||
|
if (textArea.value) {
|
||||||
|
textAreaHeight.value = calcHeight(textArea.value, minRows.value, maxRows.value).height;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
calcTextAreaHeight();
|
||||||
|
if (textArea.value) {
|
||||||
|
const textareaHeightObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {
|
||||||
|
if (entries && 0 < entries.length) {
|
||||||
|
if (previewContent.value) {
|
||||||
|
previewContent.value.style.height = entries[0].contentRect.height + "px";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
textareaHeightObserver.observe(textArea.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
textArea
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-markdown-editor {
|
||||||
|
width: 100%;
|
||||||
|
border: var(--tui-border);
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--tui-dark-white);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
line-height: 30px;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
border-right: var(--tui-border);
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.text-area {
|
||||||
|
width: calc(100% - .5rem * 2);
|
||||||
|
height: v-bind("textAreaHeight + 'px'");
|
||||||
|
resize: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
padding: .5rem;
|
||||||
|
font-size: 14px;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
width: 50%;
|
||||||
|
background: #FFF;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fold {
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
left: calc(100% - 5rem);
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
border-left: var(--tui-border);
|
||||||
|
transition: left .5s var(--tui-bezier);
|
||||||
|
box-shadow: -2px 0 0 var(--tui-shadow-color);
|
||||||
|
|
||||||
|
&.showing {
|
||||||
|
left: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
6
src/components/markdown-view/index.ts
Normal file
6
src/components/markdown-view/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
import "./style.less";
|
||||||
|
|
||||||
|
export const MarkdownView = Toolkit.withInstall(view);
|
||||||
|
export default MarkdownView;
|
||||||
42
src/components/markdown-view/index.vue
Normal file
42
src/components/markdown-view/index.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="tui-markdown-view selectable break-all line-numbers"
|
||||||
|
v-html="markdownHTML"
|
||||||
|
:data-max-height="maxHeight"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Prism from "prismjs";
|
||||||
|
import Markdown from "~/utils/Markdown";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "MarkdownView"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
content?: string;
|
||||||
|
showCodeBorder?: boolean;
|
||||||
|
maxHeight?: string;
|
||||||
|
}>(), {
|
||||||
|
content: "",
|
||||||
|
showCodeBorder: true,
|
||||||
|
maxHeight: "400px"
|
||||||
|
});
|
||||||
|
const { content } = toRefs(props);
|
||||||
|
const markdownHTML = ref("");
|
||||||
|
|
||||||
|
const doRender = async () => {
|
||||||
|
markdownHTML.value = await Markdown.getInstance().toHTML(content.value);
|
||||||
|
await nextTick();
|
||||||
|
Prism.highlightAll();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => props.content, doRender);
|
||||||
|
onMounted(doRender);
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-markdown-view {
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
341
src/components/markdown-view/style.less
Normal file
341
src/components/markdown-view/style.less
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
@import url(~/assets/style/variable);
|
||||||
|
|
||||||
|
.tui-markdown-view {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin: 2rem 0 .5rem 0;
|
||||||
|
padding: .25rem 1rem;
|
||||||
|
position: relative;
|
||||||
|
border-left: .4rem solid var(--tui-light-blue);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:first-child,
|
||||||
|
h1:first-child,
|
||||||
|
h2:first-child,
|
||||||
|
h3:first-child,
|
||||||
|
h4:first-child,
|
||||||
|
h5:first-child,
|
||||||
|
h6:first-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:hover a.anchor,
|
||||||
|
h2:hover a.anchor,
|
||||||
|
h3:hover a.anchor,
|
||||||
|
h4:hover a.anchor,
|
||||||
|
h5:hover a.anchor,
|
||||||
|
h6:hover a.anchor {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 a,
|
||||||
|
h3 a {
|
||||||
|
color: #34495E;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: .8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
color: #777;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
ul,
|
||||||
|
ol,
|
||||||
|
dl,
|
||||||
|
table,
|
||||||
|
blockquote {
|
||||||
|
margin: .5rem 0;
|
||||||
|
min-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-indent: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote p {
|
||||||
|
text-indent: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: .2em .5em;
|
||||||
|
background: #EEE;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li > ol,
|
||||||
|
li > ul {
|
||||||
|
margin: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li > p {
|
||||||
|
text-indent: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 2px;
|
||||||
|
margin: 16px 0;
|
||||||
|
border: 0 none;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #E7E7E7;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 p,
|
||||||
|
h2 p,
|
||||||
|
h3 p,
|
||||||
|
h4 p,
|
||||||
|
h5 p,
|
||||||
|
h6 p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li p.first {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul:first-child,
|
||||||
|
ol:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul:last-child,
|
||||||
|
ol:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 520px;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding: 2px 10px;
|
||||||
|
background: rgba(153, 153, 153, .1);
|
||||||
|
border-left: 4px solid #EDEDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
min-width: 240px;
|
||||||
|
max-width: 100%;
|
||||||
|
word-break: initial;
|
||||||
|
border-spacing: 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead {
|
||||||
|
background: #F2F2F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border-top: 1px solid #DFE2E5;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:nth-child(2n) {
|
||||||
|
background-color: #FAFAFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr th {
|
||||||
|
border: 1px solid #DFE2E5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px 13px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr td {
|
||||||
|
border: 1px solid #DFE2E5;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 13px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
text-align: left;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr th:first-child,
|
||||||
|
table tr td:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr th:last-child,
|
||||||
|
table tr td:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt {
|
||||||
|
color: #e96900;
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
background: #F8F8F8;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border {
|
||||||
|
border: 1px solid #525870;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media {
|
||||||
|
margin: .5rem auto;
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
+ .media-tips {
|
||||||
|
color: #777;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代码
|
||||||
|
pre[class*="language-"] {
|
||||||
|
border: 1px solid #B8BBC9;
|
||||||
|
padding: 0 !important;
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
background: transparent;
|
||||||
|
max-height: var(data-max-height);
|
||||||
|
transition: max-height .5s var(--tui-bezier);
|
||||||
|
font-family: var(--td-font-family);
|
||||||
|
line-height: 1;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: #333;
|
||||||
|
background: transparent;
|
||||||
|
text-shadow: none !important;
|
||||||
|
font-family: var(--td-font-family);
|
||||||
|
|
||||||
|
.line-numbers-rows {
|
||||||
|
left: 0;
|
||||||
|
float: left;
|
||||||
|
z-index: 1;
|
||||||
|
position: sticky;
|
||||||
|
background: rgba(242, 242, 242, .9);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
>span::before {
|
||||||
|
padding-right: .2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.codes {
|
||||||
|
position: absolute;
|
||||||
|
min-width: calc(100% - 4.6em);
|
||||||
|
padding-right: .5em;
|
||||||
|
|
||||||
|
.token.namespace {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.constant {
|
||||||
|
color: #FF7A9B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.annotation {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.function {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.class-name {
|
||||||
|
color: #FF461F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.generics .class-name {
|
||||||
|
color: #895532;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.string {
|
||||||
|
color: #55AA55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.number {
|
||||||
|
color: #EB9354;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.keyword,
|
||||||
|
.token.boolean {
|
||||||
|
color: #177CB0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/components/popup/index.ts
Normal file
5
src/components/popup/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
export const Popup = Toolkit.withInstall(view);
|
||||||
|
export default Popup;
|
||||||
26
src/components/popup/index.vue
Normal file
26
src/components/popup/index.vue
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<div id="tui-popup"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineOptions({
|
||||||
|
name: "Popup"
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
#tui-popup {
|
||||||
|
border: var(--tui-border);
|
||||||
|
z-index: 20;
|
||||||
|
position: fixed;
|
||||||
|
font-size: 13px;
|
||||||
|
visibility: hidden;
|
||||||
|
box-shadow: 2px 2px 0 rgba(50, 50, 50, .2);
|
||||||
|
background: #F4F4F4;
|
||||||
|
|
||||||
|
:deep(.text) {
|
||||||
|
padding: 3px 6px 5px 6px;
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
BIN
src/components/user-level/icon.png
Normal file
BIN
src/components/user-level/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 745 B |
5
src/components/user-level/index.ts
Normal file
5
src/components/user-level/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import Toolkit from "~/utils/Toolkit";
|
||||||
|
|
||||||
|
export const UserLevel = Toolkit.withInstall(view);
|
||||||
|
export default UserLevel;
|
||||||
25
src/components/user-level/index.vue
Normal file
25
src/components/user-level/index.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tui-user-level" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineOptions({
|
||||||
|
name: "UserLevel"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
value: number;
|
||||||
|
}>(), {});
|
||||||
|
const { value } = toRefs(props);
|
||||||
|
|
||||||
|
const offset = computed(() => -9 * value.value + "px");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-user-level {
|
||||||
|
width: 19px;
|
||||||
|
height: 9px;
|
||||||
|
background: url("./icon.png");
|
||||||
|
background-position-y: v-bind(offset);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
90
src/index.ts
Normal file
90
src/index.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import type { App } from "vue";
|
||||||
|
|
||||||
|
import components from "./components";
|
||||||
|
|
||||||
|
import Network from "./utils/Network";
|
||||||
|
import UserAPI from "./api/UserAPI";
|
||||||
|
import CommonAPI from "./api/CommonAPI";
|
||||||
|
import CommentAPI from "./api/CommentAPI";
|
||||||
|
import DeveloperAPI from "./api/DeveloperAPI";
|
||||||
|
|
||||||
|
import Time from "./utils/Time";
|
||||||
|
import IOSize from "./utils/IOSize";
|
||||||
|
import Events from "./utils/Events";
|
||||||
|
import Cooker from "./utils/Cooker";
|
||||||
|
import Toolkit from "./utils/Toolkit";
|
||||||
|
import Resizer from "./utils/Resizer";
|
||||||
|
import Storage from "./utils/Storage";
|
||||||
|
import Prismjs from "./utils/Prismjs";
|
||||||
|
import Markdown from "./utils/Markdown";
|
||||||
|
import Scroller from "./utils/Scroller";
|
||||||
|
import IconMapper from "./utils/IconMapper";
|
||||||
|
import SettingMapper from "./utils/SettingMapper";
|
||||||
|
|
||||||
|
import VPopup from "./utils/directives/Popup";
|
||||||
|
import VDraggable from "./utils/directives/Draggable";
|
||||||
|
|
||||||
|
import { userStore } from "./store/user";
|
||||||
|
import { deviceStore } from "./store/device";
|
||||||
|
|
||||||
|
|
||||||
|
import "./assets/style/variable.less";
|
||||||
|
import "./assets/style/timi-web.less";
|
||||||
|
|
||||||
|
export * from "./components";
|
||||||
|
|
||||||
|
export * from "./types/Model";
|
||||||
|
export * from "./types/User";
|
||||||
|
export * from "./types/Comment";
|
||||||
|
export * from "./types/Setting";
|
||||||
|
export * from "./types/Template";
|
||||||
|
export * from "./types/Developer";
|
||||||
|
export * from "./types/Attachment";
|
||||||
|
|
||||||
|
export * from "./utils/Prismjs";
|
||||||
|
|
||||||
|
export * from "./utils/directives/Popup";
|
||||||
|
|
||||||
|
export type { ScrollListener } from "./utils/Scroller";
|
||||||
|
export type { DraggableConfig } from "./utils/directives/Draggable";
|
||||||
|
export type { PopupConfig } from "./utils/directives/Popup";
|
||||||
|
|
||||||
|
const install = function (app: App) {
|
||||||
|
components.forEach(component => {
|
||||||
|
app.use(component as unknown as { install: () => any });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const axios = Network.axios;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
axios,
|
||||||
|
Network,
|
||||||
|
|
||||||
|
UserAPI,
|
||||||
|
CommonAPI,
|
||||||
|
CommentAPI,
|
||||||
|
DeveloperAPI,
|
||||||
|
|
||||||
|
userStore,
|
||||||
|
deviceStore,
|
||||||
|
|
||||||
|
Time,
|
||||||
|
Events,
|
||||||
|
IOSize,
|
||||||
|
Cooker,
|
||||||
|
Toolkit,
|
||||||
|
Resizer,
|
||||||
|
Storage,
|
||||||
|
Prismjs,
|
||||||
|
Markdown,
|
||||||
|
Scroller,
|
||||||
|
IconMapper,
|
||||||
|
SettingMapper,
|
||||||
|
|
||||||
|
VPopup,
|
||||||
|
VDraggable
|
||||||
|
};
|
||||||
110
src/store/device.ts
Normal file
110
src/store/device.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { Resizer } from "~/index";
|
||||||
|
|
||||||
|
// true 为手机
|
||||||
|
const isPhone = ref(false);
|
||||||
|
// true 为平板
|
||||||
|
const isTablet = ref(false);
|
||||||
|
// true 为桌面
|
||||||
|
const isDesktop = ref(false);
|
||||||
|
// true 为大屏幕
|
||||||
|
const isLargeScreen = ref(false);
|
||||||
|
|
||||||
|
// true 为竖屏
|
||||||
|
const isVertical = ref(false);
|
||||||
|
// true 为横屏
|
||||||
|
const isHorizontal = ref(false);
|
||||||
|
// 宽高比
|
||||||
|
const aspectRatio = ref(0);
|
||||||
|
// true 为超宽屏
|
||||||
|
const isUltrawide = ref(false);
|
||||||
|
// true 为近似方屏
|
||||||
|
const isSquare = ref(false);
|
||||||
|
|
||||||
|
// 当前断点
|
||||||
|
const currentBreakpoint = ref<"xs" | "sm" | "md" | "lg" | "xl">("lg");
|
||||||
|
// 当前屏幕宽度
|
||||||
|
const screenWidth = ref(0);
|
||||||
|
// 当前屏幕高度
|
||||||
|
const screenHeight = ref(0);
|
||||||
|
// 短屏幕(高度小于 500)
|
||||||
|
const isShortScreen = ref(false);
|
||||||
|
// true 为启用移动端布局
|
||||||
|
const isMobileLayout = ref(false);
|
||||||
|
|
||||||
|
/** 断点配置,单位:px */
|
||||||
|
enum Breakpoints {
|
||||||
|
|
||||||
|
/** 超小设备 */
|
||||||
|
XS = 480,
|
||||||
|
|
||||||
|
/** 手机 */
|
||||||
|
SM = 650,
|
||||||
|
|
||||||
|
/** 平板 */
|
||||||
|
MD = 768,
|
||||||
|
|
||||||
|
/** 笔记本 */
|
||||||
|
LG = 1024,
|
||||||
|
|
||||||
|
/** 大屏幕 */
|
||||||
|
XL = 1440
|
||||||
|
}
|
||||||
|
|
||||||
|
Resizer.addListener("DEVICE_SIZE", (width, height) => {
|
||||||
|
screenWidth.value = width;
|
||||||
|
screenHeight.value = height;
|
||||||
|
|
||||||
|
// 设备类型
|
||||||
|
isPhone.value = width < Breakpoints.SM;
|
||||||
|
isTablet.value = width >= Breakpoints.SM && width < Breakpoints.LG;
|
||||||
|
isDesktop.value = width >= Breakpoints.LG;
|
||||||
|
isLargeScreen.value = width >= Breakpoints.XL;
|
||||||
|
|
||||||
|
// 屏幕方向
|
||||||
|
isVertical.value = width < height;
|
||||||
|
isHorizontal.value = !isVertical.value;
|
||||||
|
|
||||||
|
// 宽高比特征
|
||||||
|
aspectRatio.value = width / height;
|
||||||
|
isUltrawide.value = 2 <= aspectRatio.value; // 21:9 ≈ 2.33
|
||||||
|
isSquare.value = 0.9 < aspectRatio.value && aspectRatio.value < 1.1;
|
||||||
|
|
||||||
|
// 布局相关
|
||||||
|
isShortScreen.value = height < 500;
|
||||||
|
isMobileLayout.value = width < Breakpoints.MD;
|
||||||
|
|
||||||
|
if (Breakpoints.XL <= width) {
|
||||||
|
currentBreakpoint.value = "xl";
|
||||||
|
} else if (Breakpoints.LG <= width) {
|
||||||
|
currentBreakpoint.value = "lg";
|
||||||
|
} else if (Breakpoints.MD <= width) {
|
||||||
|
currentBreakpoint.value = "md";
|
||||||
|
} else if (Breakpoints.SM <= width) {
|
||||||
|
currentBreakpoint.value = "sm";
|
||||||
|
} else {
|
||||||
|
currentBreakpoint.value = "xs";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const deviceStore = {
|
||||||
|
isPhone,
|
||||||
|
isTablet,
|
||||||
|
isDesktop,
|
||||||
|
isLargeScreen,
|
||||||
|
|
||||||
|
isVertical,
|
||||||
|
isHorizontal,
|
||||||
|
aspectRatio,
|
||||||
|
isUltrawide,
|
||||||
|
isSquare,
|
||||||
|
|
||||||
|
currentBreakpoint,
|
||||||
|
screenWidth,
|
||||||
|
screenHeight,
|
||||||
|
isShortScreen,
|
||||||
|
isMobileLayout
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
deviceStore
|
||||||
|
};
|
||||||
77
src/store/user.ts
Normal file
77
src/store/user.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { LoginResponse, LoginToken, LoginUser, Storage, UserAPI, UserToken } from "timi-web";
|
||||||
|
|
||||||
|
const loginUser = reactive<LoginUser>({
|
||||||
|
token: undefined,
|
||||||
|
user: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
function isLogged(): boolean {
|
||||||
|
return !!loginUser.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userStore = {
|
||||||
|
loginUser,
|
||||||
|
isLogged,
|
||||||
|
login4Token,
|
||||||
|
login4Storage,
|
||||||
|
updateToken,
|
||||||
|
logout,
|
||||||
|
reloadProfile
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
userStore
|
||||||
|
};
|
||||||
43
src/types.ts
Normal file
43
src/types.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import type { Plugin } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安装方法
|
||||||
|
*/
|
||||||
|
export type InstallRecord<T> = T & Plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认插槽参数
|
||||||
|
*/
|
||||||
|
export type DefaultSlotProp = (props: {}) => unknown
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认插槽类型
|
||||||
|
*/
|
||||||
|
export interface DefaultSlots {
|
||||||
|
default: DefaultSlotProp;
|
||||||
|
icon?: DefaultSlotProp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并类型为交叉类型
|
||||||
|
*/
|
||||||
|
export type Merge<T, R> = {
|
||||||
|
[K in keyof T]: T[K]
|
||||||
|
} & {
|
||||||
|
[K in keyof R]: R[K]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并交叉类型
|
||||||
|
*/
|
||||||
|
type MergeIntersection<T> = Pick<T, keyof T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取必传属性
|
||||||
|
*/
|
||||||
|
export type PickRequiredUnion<P, U extends keyof P> = MergeIntersection<Merge<Required<Pick<P, U>>, Omit<P, U>>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 除了提取的属性,其他都是必传属性
|
||||||
|
*/
|
||||||
|
export type PickNotRequiredUnion<P, U extends keyof P> = MergeIntersection<Merge<Pick<P, U>, Required<Omit<P, U>>>>
|
||||||
24
src/types/Attachment.ts
Normal file
24
src/types/Attachment.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Model } from "./Model";
|
||||||
|
|
||||||
|
export type Attachment = {
|
||||||
|
bizType: AttachmentBizType;
|
||||||
|
bizId: number;
|
||||||
|
attachType?: string;
|
||||||
|
mongoId: string;
|
||||||
|
lidTitle?: number;
|
||||||
|
name?: string;
|
||||||
|
size: number;
|
||||||
|
} & Model
|
||||||
|
|
||||||
|
export type AttachmentView = {
|
||||||
|
title?: string;
|
||||||
|
} & Attachment
|
||||||
|
|
||||||
|
export enum AttachmentBizType {
|
||||||
|
|
||||||
|
GIT_ISSUE,
|
||||||
|
|
||||||
|
GIT_MERGE,
|
||||||
|
|
||||||
|
GIT_RELEASE
|
||||||
|
}
|
||||||
78
src/types/Comment.ts
Normal file
78
src/types/Comment.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { Model, Page } from "./Model";
|
||||||
|
import { UserView } from "./User";
|
||||||
|
|
||||||
|
/** 评论 */
|
||||||
|
export type Comment = {
|
||||||
|
bizType: CommentBizType;
|
||||||
|
bizId: number;
|
||||||
|
userId?: number;
|
||||||
|
nick?: string;
|
||||||
|
content: string;
|
||||||
|
} & Model
|
||||||
|
|
||||||
|
/** 评论视图 */
|
||||||
|
export type CommentView = {
|
||||||
|
/** 所属用户 */
|
||||||
|
user?: UserView;
|
||||||
|
|
||||||
|
/** 回复列表 */
|
||||||
|
replies: CommentReplyView[];
|
||||||
|
|
||||||
|
/** 回复分页 */
|
||||||
|
repliesPage: CommentReplyPage;
|
||||||
|
|
||||||
|
/** 用于绑定组件当前页下标 */
|
||||||
|
repliesCurrent: number;
|
||||||
|
|
||||||
|
/** 回复数量 */
|
||||||
|
repliesLength: number;
|
||||||
|
|
||||||
|
/** 关联文章 */
|
||||||
|
article?: object;
|
||||||
|
|
||||||
|
/** 关联 Git 仓库 */
|
||||||
|
repository?: object;
|
||||||
|
} & Comment;
|
||||||
|
|
||||||
|
export type CommentReply = {
|
||||||
|
replyId?: number;
|
||||||
|
commentId: number;
|
||||||
|
senderId?: number;
|
||||||
|
senderNick?: string;
|
||||||
|
receiverId?: number;
|
||||||
|
receiverNick?: string;
|
||||||
|
content: string;
|
||||||
|
} & Model;
|
||||||
|
|
||||||
|
export type CommentReplyView = {
|
||||||
|
comment: CommentView;
|
||||||
|
sender?: UserView;
|
||||||
|
receiver?: UserView;
|
||||||
|
} & CommentReply;
|
||||||
|
|
||||||
|
export type CommentPage = {
|
||||||
|
bizType?: CommentBizType;
|
||||||
|
bizId?: number;
|
||||||
|
} & Page;
|
||||||
|
|
||||||
|
export enum CommentReplyBizType {
|
||||||
|
|
||||||
|
COMMENT = "COMMENT",
|
||||||
|
|
||||||
|
SENDER = "SENDER",
|
||||||
|
|
||||||
|
RECEIVER = "RECEIVER"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommentReplyPage = {
|
||||||
|
bizType: CommentReplyBizType
|
||||||
|
bizId?: number
|
||||||
|
} & Page;
|
||||||
|
|
||||||
|
export enum CommentBizType {
|
||||||
|
ARTICLE = "ARTICLE",
|
||||||
|
|
||||||
|
GIT_ISSUE = "GIT_ISSUE",
|
||||||
|
|
||||||
|
GIT_MERGE = "GIT_MERGE",
|
||||||
|
}
|
||||||
6
src/types/Developer.ts
Normal file
6
src/types/Developer.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/** 开发者配置 */
|
||||||
|
export type Developer = {
|
||||||
|
developerId?: number;
|
||||||
|
name?: string;
|
||||||
|
rsa?: string;
|
||||||
|
}
|
||||||
79
src/types/Model.ts
Normal file
79
src/types/Model.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
export enum RunEnv {
|
||||||
|
DEV = "DEV",
|
||||||
|
DEV_SSL = "DEV_SSL",
|
||||||
|
PROD = "PROD"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基本实体模型
|
||||||
|
export type Model = {
|
||||||
|
id?: number;
|
||||||
|
|
||||||
|
createdAt?: number;
|
||||||
|
updatedAt?: number;
|
||||||
|
deletedAt?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Response = {
|
||||||
|
code: number;
|
||||||
|
msg?: string;
|
||||||
|
data: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Page = {
|
||||||
|
index: number;
|
||||||
|
size: number;
|
||||||
|
keyword?: string;
|
||||||
|
orderMap?: { [key: string]: OrderType };
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OrderType {
|
||||||
|
ASC = "ASC",
|
||||||
|
DESC = "DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PageResult<T> = {
|
||||||
|
total: number;
|
||||||
|
list: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 携带验证码的请求体
|
||||||
|
export type CaptchaData<T> = {
|
||||||
|
from: 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 enum ImageType {
|
||||||
|
AUTO = "ir-auto",
|
||||||
|
SMOOTH = "ir-smooth",
|
||||||
|
PIXELATED = "ir-pixelated"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KeyValue<T> = {
|
||||||
|
key: string;
|
||||||
|
value: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LabelValue<T> = {
|
||||||
|
label: string;
|
||||||
|
value: T;
|
||||||
|
}
|
||||||
28
src/types/Setting.ts
Normal file
28
src/types/Setting.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export enum SettingKey {
|
||||||
|
RUN_ENV = "RUN_ENV",
|
||||||
|
PUBLIC_RESOURCES = "PUBLIC_RESOURCES",
|
||||||
|
|
||||||
|
DOMAIN_ROOT = "DOMAIN_ROOT",
|
||||||
|
DOMAIN_API = "DOMAIN_API",
|
||||||
|
DOMAIN_GIT = "DOMAIN_GIT",
|
||||||
|
DOMAIN_BLOG = "DOMAIN_BLOG",
|
||||||
|
DOMAIN_SPACE = "DOMAIN_SPACE",
|
||||||
|
DOMAIN_DOWNLOAD = "DOMAIN_DOWNLOAD",
|
||||||
|
DOMAIN_RESOURCE = "DOMAIN_RESOURCE",
|
||||||
|
|
||||||
|
ENABLE_COMMENT = "ENABLE_COMMENT",
|
||||||
|
ENABLE_DEBUG = "ENABLE_DEBUG",
|
||||||
|
ENABLE_LOGIN = "ENABLE_LOGIN",
|
||||||
|
ENABLE_REGISTER = "ENABLE_REGISTER",
|
||||||
|
ENABLE_USER_UPDATE = "ENABLE_USER_UPDATE",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PublicResources = {
|
||||||
|
wechatReceiveQRCode: string;
|
||||||
|
user: PublicResourcesUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PublicResourcesUser = {
|
||||||
|
avatar: string;
|
||||||
|
wrapper: string;
|
||||||
|
}
|
||||||
6
src/types/Template.ts
Normal file
6
src/types/Template.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export enum TemplateBizType {
|
||||||
|
|
||||||
|
GIT = "GIT",
|
||||||
|
|
||||||
|
FOREVER_MC = "FOREVER_MC"
|
||||||
|
}
|
||||||
89
src/types/User.ts
Normal file
89
src/types/User.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { ImageType, Model } from "./Model";
|
||||||
|
import { AttachmentView } from "./Attachment";
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
name: 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;
|
||||||
|
sex?: number;
|
||||||
|
birthdate?: number;
|
||||||
|
qq?: string;
|
||||||
|
description: string;
|
||||||
|
lastLoginIP?: string;
|
||||||
|
lastLoginAt?: number;
|
||||||
|
updatedAt?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserProfileView = {
|
||||||
|
attachmentList?: AttachmentView[]
|
||||||
|
} & UserProfile
|
||||||
|
|
||||||
|
export type UserToken = {
|
||||||
|
value: string;
|
||||||
|
expireAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RegisterRequest = {
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
email?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoginRequest = {
|
||||||
|
user: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录返回
|
||||||
|
export type LoginResponse = {
|
||||||
|
id: number;
|
||||||
|
token: string;
|
||||||
|
expireAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoginUser = {
|
||||||
|
token?: LoginToken;
|
||||||
|
user?: UserView
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoginToken = {
|
||||||
|
id?: number;
|
||||||
|
} & UserToken;
|
||||||
|
|
||||||
|
export enum LoginType {
|
||||||
|
ALERT,
|
||||||
|
IFRAME,
|
||||||
|
REDIRECT
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserLevelType = {
|
||||||
|
exp: number; // 经验数值,和 UserData.exp 一样
|
||||||
|
value: number; // 经验对应等级,[0, 8]
|
||||||
|
percent: number; // 该经验在该等级的百分比 [0, 1]
|
||||||
|
nextLevelUp: number; // 下一级经验值
|
||||||
|
}
|
||||||
31
src/utils/Cooker.ts
Normal file
31
src/utils/Cooker.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export default class Cooker {
|
||||||
|
|
||||||
|
static set(name: string, value: string, ttlMS: number) {
|
||||||
|
let expires = "";
|
||||||
|
if (ttlMS) {
|
||||||
|
let date = new Date();
|
||||||
|
date.setTime(date.getTime() + ttlMS);
|
||||||
|
expires = "; expires=" + date.toUTCString();
|
||||||
|
}
|
||||||
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||||
|
}
|
||||||
|
|
||||||
|
static get(name: string) {
|
||||||
|
let nameSplit = name + "=";
|
||||||
|
let ca = document.cookie.split(";");
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) == " ") {
|
||||||
|
c = c.substring(1, c.length);
|
||||||
|
}
|
||||||
|
if (c.indexOf(nameSplit) == 0) {
|
||||||
|
return c.substring(nameSplit.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static remove(name: string) {
|
||||||
|
document.cookie = name + "=; Max-Age=-99999999;";
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/utils/Events.ts
Normal file
106
src/utils/Events.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* ### 全局事件管理
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* // 注册
|
||||||
|
* Events.register("eventName", () => {
|
||||||
|
* // 触发执行
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // 触发
|
||||||
|
* Events.emit("eventName", '支持参数');
|
||||||
|
*
|
||||||
|
* // 移除
|
||||||
|
* Events.remove("eventName");
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default class Events {
|
||||||
|
|
||||||
|
// 监听数组
|
||||||
|
private static listeners = new Map<any, Observer<any>[]>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册事件(会叠加)
|
||||||
|
*
|
||||||
|
* @param key 事件名称
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
public static register<T>(key: T, callback: Function) {
|
||||||
|
const observers: Observer<T>[] | undefined = Events.listeners.get(key);
|
||||||
|
if (!observers) {
|
||||||
|
Events.listeners.set(key, []);
|
||||||
|
}
|
||||||
|
Events.listeners.get(key)?.push(new Observer<T>(key, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置并注册(不会叠加)
|
||||||
|
*
|
||||||
|
* @param key 事件名称
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
public static reset<T>(key: T, callback: Function) {
|
||||||
|
Events.listeners.set(key, []);
|
||||||
|
this.register(key, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除事件
|
||||||
|
*
|
||||||
|
* @param key 事件名称
|
||||||
|
*/
|
||||||
|
public static remove<T>(key: T) {
|
||||||
|
const observers: Observer<T>[] | undefined = Events.listeners.get(key);
|
||||||
|
if (observers) {
|
||||||
|
for (let i = 0, l = observers.length; i < l; i++) {
|
||||||
|
if (observers[i].equals(key)) {
|
||||||
|
observers.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Events.listeners.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发事件
|
||||||
|
*
|
||||||
|
* @param key 事件名称
|
||||||
|
* @param args 参数
|
||||||
|
*/
|
||||||
|
public static emit<T>(key: T, ...args: any[]) {
|
||||||
|
const observers: Observer<T>[] | undefined = Events.listeners.get(key);
|
||||||
|
if (observers) {
|
||||||
|
for (const observer of observers) {
|
||||||
|
// 通知
|
||||||
|
observer.notify(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 观察者 */
|
||||||
|
class Observer<T> {
|
||||||
|
|
||||||
|
private callback: Function = () => {}; // 回调函数
|
||||||
|
|
||||||
|
private readonly key: T;
|
||||||
|
|
||||||
|
constructor(key: T, callback: Function) {
|
||||||
|
this.key = key;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送通知
|
||||||
|
*
|
||||||
|
* @param args 不定参数
|
||||||
|
*/
|
||||||
|
notify(...args: any[]): void {
|
||||||
|
this.callback.call(this.key, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(name: any): boolean {
|
||||||
|
return name === this.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/utils/IOSize.ts
Normal file
80
src/utils/IOSize.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
export enum Unit {
|
||||||
|
|
||||||
|
/** 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 {
|
||||||
|
|
||||||
|
/** 1 字节 */
|
||||||
|
public static BYTE = 1;
|
||||||
|
|
||||||
|
/** 1 KB */
|
||||||
|
public static KB = IOSize.BYTE << 10;
|
||||||
|
|
||||||
|
/** 1 MB */
|
||||||
|
public static MB = IOSize.KB << 10;
|
||||||
|
|
||||||
|
/** 1 GB */
|
||||||
|
public static GB = IOSize.MB << 10;
|
||||||
|
|
||||||
|
/** 1 TB */
|
||||||
|
public static TB = IOSize.GB << 10;
|
||||||
|
|
||||||
|
/** 1 PB */
|
||||||
|
public static PB = IOSize.TB << 10;
|
||||||
|
|
||||||
|
/** 1 EB */
|
||||||
|
public static EB = IOSize.PB << 10;
|
||||||
|
|
||||||
|
public static Unit = Unit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>格式化一个储存容量,保留两位小数
|
||||||
|
* <pre>
|
||||||
|
* // 返回 100.01 KB
|
||||||
|
* format(102411, 2);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param size 字节大小
|
||||||
|
* @param fixed 保留小数
|
||||||
|
* @param stopUnit 停止单位
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static format(size: number, fixed = 2, stopUnit?: Unit): string {
|
||||||
|
const units = Object.keys(Unit);
|
||||||
|
if (0 < size) {
|
||||||
|
for (let i = 0; i < units.length; i++, size /= 1024) {
|
||||||
|
const unit = units[i];
|
||||||
|
if (size <= 1000 || i === units.length - 1 || unit === stopUnit) {
|
||||||
|
if (i === 0) {
|
||||||
|
// 最小单位不需要小数
|
||||||
|
return size + " B";
|
||||||
|
} else {
|
||||||
|
return `${size.toFixed(fixed)} ${unit}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "0 B";
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/utils/IconMapper.ts
Normal file
26
src/utils/IconMapper.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import iconJson from "../assets/icon.json";
|
||||||
|
|
||||||
|
export default class IconMapper {
|
||||||
|
|
||||||
|
private static instance: IconMapper;
|
||||||
|
|
||||||
|
icons = new Map<string, string>();
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
Object.entries(iconJson).forEach(([key, value]) => this.icons.set(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSVG(name: string, scale = 1) {
|
||||||
|
const svg = IconMapper.getInstance().icons.get(name.toLowerCase());
|
||||||
|
return svg?.replace(/\d+(\.\d+)?/gm, n => {
|
||||||
|
return String((Number(n) * scale));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): IconMapper {
|
||||||
|
if (!IconMapper.instance) {
|
||||||
|
IconMapper.instance = new IconMapper();
|
||||||
|
}
|
||||||
|
return IconMapper.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
209
src/utils/Markdown.ts
Normal file
209
src/utils/Markdown.ts
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
import { marked } from "marked";
|
||||||
|
import { gfmHeadingId } from "marked-gfm-heading-id";
|
||||||
|
import { markedHighlight } from "marked-highlight";
|
||||||
|
import { mangle } from "marked-mangle";
|
||||||
|
import Prism from "prismjs";
|
||||||
|
import "prismjs/themes/prism.css";
|
||||||
|
import { Toolkit } from "~/index";
|
||||||
|
|
||||||
|
export default class Markdown {
|
||||||
|
|
||||||
|
private static instance: Markdown;
|
||||||
|
|
||||||
|
renderer = new marked.Renderer();
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
marked.use(mangle());
|
||||||
|
marked.use(gfmHeadingId());
|
||||||
|
marked.use(markedHighlight({
|
||||||
|
highlight(code: string, lang: string) {
|
||||||
|
if (Prism.languages[lang]) {
|
||||||
|
return Prism.highlight(code, Prism.languages[lang], lang);
|
||||||
|
} else {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
// Markdown 解析器配置
|
||||||
|
marked.setOptions({
|
||||||
|
renderer: this.renderer,
|
||||||
|
pedantic: false,
|
||||||
|
gfm: true,
|
||||||
|
breaks: true
|
||||||
|
});
|
||||||
|
|
||||||
|
Prism.hooks.add("complete", (env: any) => {
|
||||||
|
if (!env.code) return;
|
||||||
|
|
||||||
|
// 行号渲染调整
|
||||||
|
const el = env.element;
|
||||||
|
|
||||||
|
const lineNumber = el.querySelector(".line-numbers-rows") as HTMLDivElement;
|
||||||
|
if (lineNumber) {
|
||||||
|
const clone = lineNumber.cloneNode(true);
|
||||||
|
el.removeChild(lineNumber);
|
||||||
|
// 加容器做滚动
|
||||||
|
el.innerHTML = `<span class="codes">${el.innerHTML}</span>`;
|
||||||
|
el.insertBefore(clone, el.firstChild);
|
||||||
|
|
||||||
|
if (el.parentNode) {
|
||||||
|
const markdownRoot = el.parentNode.parentNode;
|
||||||
|
if (markdownRoot) {
|
||||||
|
const maxHeight = markdownRoot.dataset.maxHeight;
|
||||||
|
if (maxHeight === "auto") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 注册双击事件
|
||||||
|
const lines = lineNumber.children.length;
|
||||||
|
if (lines < 18) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parent = el.parentNode;
|
||||||
|
parent.addEventListener("dblclick", (): void => {
|
||||||
|
const isExpand = parent.classList.contains("expand");
|
||||||
|
if (isExpand) {
|
||||||
|
parent.style.maxHeight = maxHeight;
|
||||||
|
parent.classList.remove("expand");
|
||||||
|
} else {
|
||||||
|
parent.style.maxHeight = lines * 22 + "px";
|
||||||
|
parent.classList.add("expand");
|
||||||
|
}
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection) {
|
||||||
|
selection.removeAllRanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### 超链渲染方式
|
||||||
|
*
|
||||||
|
* 1. 链接前加 ~ 符号会被渲染为新标签打开。例:`[文本](~链接)`
|
||||||
|
* 3. 没有标题的链接将使用文本作为标题
|
||||||
|
* 4. 没有链接的会被渲染为 span 标签
|
||||||
|
*/
|
||||||
|
this.renderer.link = function ({ href, title, text }) {
|
||||||
|
title = title ?? text;
|
||||||
|
|
||||||
|
if (!href) {
|
||||||
|
return `<span>${text}</span>`;
|
||||||
|
}
|
||||||
|
// 新标签打开
|
||||||
|
let target = "_self";
|
||||||
|
if (href.startsWith("~")) {
|
||||||
|
target = "_blank";
|
||||||
|
href = href.substring(1);
|
||||||
|
}
|
||||||
|
// 内部资源链接
|
||||||
|
if (href.indexOf("@") !== -1) {
|
||||||
|
href = Toolkit.toResURL(href);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// 处理嵌套 markdown,这可能不是最优解
|
||||||
|
const tokens = (marked.lexer(text, { inline: true } as any) as any)[0].tokens;
|
||||||
|
text = this.parser.parseInline(tokens);
|
||||||
|
}
|
||||||
|
return `<a href="${href}" target="${target}" title="${title}">${text}</a>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### 重点内容扩展
|
||||||
|
*
|
||||||
|
* ```md
|
||||||
|
* 默认 `文本` 表现为红色
|
||||||
|
* 使用 `[red bold]文本` 可以自定义类
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
this.renderer.codespan = ({ text }) => {
|
||||||
|
const clazz = text.match(/\[(.+?)]/);
|
||||||
|
if (clazz) {
|
||||||
|
return `<span class="${clazz[1]}">${text.substring(text.indexOf("]") + 1)}</span>`;
|
||||||
|
} else {
|
||||||
|
return `<span class="red">${text}</span>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### 组件渲染方式(原为图像渲染方式)
|
||||||
|
*
|
||||||
|
* ```md
|
||||||
|
* [] 内文本以 # 开始时,该组件带边框
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 1. 渲染为网页:``
|
||||||
|
* 2. 渲染为视频:``
|
||||||
|
* 3. 渲染为音频:``
|
||||||
|
* 4. 渲染为图片:``
|
||||||
|
* 6. 带边框图片:``
|
||||||
|
*/
|
||||||
|
this.renderer.image = ({ href, title, text }) => {
|
||||||
|
const clazz = ["media"];
|
||||||
|
|
||||||
|
const hasBorder = text[0] === "#";
|
||||||
|
if (hasBorder) {
|
||||||
|
clazz.push("border");
|
||||||
|
}
|
||||||
|
title = title ?? text;
|
||||||
|
|
||||||
|
const extendTags = ["~", "#", "$"];
|
||||||
|
let extendTag;
|
||||||
|
if (extendTags.indexOf(href.charAt(0)) !== -1) {
|
||||||
|
extendTag = href.charAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部资源链接
|
||||||
|
if (href.indexOf("@") !== -1) {
|
||||||
|
if (extendTag) {
|
||||||
|
href = href.substring(1);
|
||||||
|
}
|
||||||
|
href = Toolkit.toResURL(href);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clazzStr = clazz.join(" ");
|
||||||
|
let elStr;
|
||||||
|
switch (extendTag) {
|
||||||
|
case "~":
|
||||||
|
elStr = `<audio class="${clazzStr}" controls><source type="audio/mp3" src="${href}" title="${title}" /></audio>`;
|
||||||
|
break;
|
||||||
|
case "#":
|
||||||
|
elStr = `<video class="${clazzStr}" controls><source type="video/mp4" src="${href}" title="${title}" /></video>`;
|
||||||
|
break;
|
||||||
|
case "$":
|
||||||
|
elStr = `<iframe class="${clazzStr}" src="${href}" allowfullscreen title="${title}"></iframe>`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
elStr = `<img class="${clazzStr}" src="${href}" alt="${title}" />`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return elStr + `<span class="media-tips">${title}</span>`;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### 解析 Markdown 文本为 HTML 节点
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* const html = toHTML('# Markdown Content');
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param mkData Markdown 文本
|
||||||
|
* @returns HTML 节点
|
||||||
|
*/
|
||||||
|
public toHTML(mkData: string | undefined): string | Promise<string> {
|
||||||
|
if (mkData) {
|
||||||
|
return marked(mkData);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): Markdown {
|
||||||
|
if (!Markdown.instance) {
|
||||||
|
Markdown.instance = new Markdown();
|
||||||
|
}
|
||||||
|
return Markdown.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/utils/MethodLocker.ts
Normal file
48
src/utils/MethodLocker.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
export class MethodLocker<R> {
|
||||||
|
private isLocked: boolean = false;
|
||||||
|
private queue: Array<() => Promise<any>> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行被锁定的方法
|
||||||
|
* @param task 需要执行的任务,返回一个 Promise
|
||||||
|
*/
|
||||||
|
async execute(task: () => Promise<R>): Promise<R> {
|
||||||
|
// 如果当前没有被锁定,直接执行任务
|
||||||
|
if (!this.isLocked) {
|
||||||
|
this.isLocked = true;
|
||||||
|
try {
|
||||||
|
return await task();
|
||||||
|
} finally {
|
||||||
|
this.isLocked = false;
|
||||||
|
await this.dequeue();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果被锁定,将任务加入队列并等待
|
||||||
|
return new Promise<R>((resolve, reject) => {
|
||||||
|
this.queue.push(async () => {
|
||||||
|
try {
|
||||||
|
const result = await task();
|
||||||
|
resolve(result);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
} finally {
|
||||||
|
this.dequeue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理队列中的下一个任务
|
||||||
|
*/
|
||||||
|
private dequeue(): Promise<R> | undefined {
|
||||||
|
if (this.queue.length > 0) {
|
||||||
|
const nextTask = this.queue.shift();
|
||||||
|
if (nextTask) {
|
||||||
|
return this.execute(nextTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/utils/Network.ts
Normal file
61
src/utils/Network.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import axios, { InternalAxiosRequestConfig } from "axios";
|
||||||
|
import { Response } from "~/types/Model";
|
||||||
|
import { Cooker, Time, userStore } from "~/index";
|
||||||
|
|
||||||
|
const userTokenInterceptors = async (config: InternalAxiosRequestConfig<any>) => {
|
||||||
|
let token = userStore.loginUser.token?.value;
|
||||||
|
const cookieToken = Cooker.get("Token");
|
||||||
|
if (token && token === cookieToken) {
|
||||||
|
config.headers.set({"Token": token});
|
||||||
|
} else {
|
||||||
|
if (cookieToken) {
|
||||||
|
token = cookieToken;
|
||||||
|
userStore.loginUser.token = {
|
||||||
|
value: token,
|
||||||
|
expireAt: Time.now() + Time.D
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (token) {
|
||||||
|
await userStore.login4Token({
|
||||||
|
value: token,
|
||||||
|
expireAt: Time.now() + Time.D
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
axios.defaults.withCredentials = true;
|
||||||
|
axios.interceptors.response.use((response: any) => {
|
||||||
|
if (!response.config.responseType) {
|
||||||
|
// 服务端返回
|
||||||
|
const data = response.data as Response;
|
||||||
|
if (data.code < 40000) {
|
||||||
|
// 200 或 300 HTTP 状态段视为成功
|
||||||
|
return data.data;
|
||||||
|
} else {
|
||||||
|
// 由调用方处理
|
||||||
|
return Promise.reject(data.msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
}, (error: any) => {
|
||||||
|
// 请求错误
|
||||||
|
if (error) {
|
||||||
|
if (error.response && error.response.status) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (error.request) {
|
||||||
|
if (error.message.startsWith("timeout")) {
|
||||||
|
throw new Error("time out");
|
||||||
|
} else {
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
axios,
|
||||||
|
userTokenInterceptors
|
||||||
|
};
|
||||||
96
src/utils/Prismjs.ts
Normal file
96
src/utils/Prismjs.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
export enum PrismjsType {
|
||||||
|
PlainText = "PlainText",
|
||||||
|
Markdown = "Markdown",
|
||||||
|
JavaScript = "JavaScript",
|
||||||
|
TypeScript = "TypeScript",
|
||||||
|
Initialization = "Initialization",
|
||||||
|
PHP = "PHP",
|
||||||
|
SQL = "SQL",
|
||||||
|
XML = "XML",
|
||||||
|
CSS = "CSS",
|
||||||
|
VUE = "VUE",
|
||||||
|
LESS = "LESS",
|
||||||
|
Markup = "Markup",
|
||||||
|
YAML = "YAML",
|
||||||
|
Json = "Json",
|
||||||
|
Java = "Java",
|
||||||
|
Properties = "Properties",
|
||||||
|
NginxConf = "NginxConf",
|
||||||
|
ApacheConf = "ApacheConf"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PrismjsProperties = {
|
||||||
|
|
||||||
|
extensions: string[]
|
||||||
|
prismjs: string;
|
||||||
|
viewer: PrismjsViewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PrismjsViewer {
|
||||||
|
|
||||||
|
MARKDOWN = "MARKDOWN",
|
||||||
|
|
||||||
|
CODE = "CODE",
|
||||||
|
|
||||||
|
TEXT = "TEXT",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Prismjs {
|
||||||
|
|
||||||
|
private static instance: Prismjs;
|
||||||
|
|
||||||
|
map = new Map<PrismjsType, PrismjsProperties>();
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.map.set(PrismjsType.PlainText, {extensions: ["txt"], prismjs: "", viewer: PrismjsViewer.TEXT});
|
||||||
|
this.map.set(PrismjsType.Markdown, {extensions: ["md"], prismjs: "md", viewer: PrismjsViewer.MARKDOWN});
|
||||||
|
this.map.set(PrismjsType.JavaScript, {extensions: ["js"], prismjs: "js", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.VUE, {extensions: ["vue"], prismjs: "html", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.TypeScript, {extensions: ["ts"], prismjs: "ts", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.Initialization, {extensions: ["ini"], prismjs: "ini", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.PHP, {extensions: ["php"], prismjs: "php", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.SQL, {extensions: ["sql"], prismjs: "sql", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.XML, {extensions: ["xml", "fxml"], prismjs: "xml", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.CSS, {extensions: ["css"], prismjs: "css", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.LESS, {extensions: ["less"], prismjs: "less", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.Markup, {extensions: ["htm", "html"], prismjs: "markup", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.YAML, {extensions: ["yml", "yaml"], prismjs: "yaml", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.Json, {extensions: ["json"], prismjs: "json", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.Java, {extensions: ["java"], prismjs: "java", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.Properties, {extensions: ["properties"], prismjs: "properties", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.NginxConf, {extensions: [], prismjs: "nginx", viewer: PrismjsViewer.CODE});
|
||||||
|
this.map.set(PrismjsType.ApacheConf, {extensions: [], prismjs: "apacheconf", viewer: PrismjsViewer.CODE});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getInstance(): Prismjs {
|
||||||
|
if (!Prismjs.instance) {
|
||||||
|
Prismjs.instance = new Prismjs();
|
||||||
|
}
|
||||||
|
return Prismjs.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static typeFromFileName(fileName: string): PrismjsType | undefined {
|
||||||
|
const ext = fileName.substring(fileName.lastIndexOf(".") + 1);
|
||||||
|
if (!ext) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const map = Prismjs.getInstance().map;
|
||||||
|
for (const key of map.keys()) {
|
||||||
|
const value = map.get(key);
|
||||||
|
if (value) {
|
||||||
|
const extensions = value.extensions;
|
||||||
|
for (let i = 0; i < extensions.length; i++) {
|
||||||
|
const extension = extensions[i];
|
||||||
|
if (extension === ext) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getFileProperties(type: PrismjsType): PrismjsProperties | undefined {
|
||||||
|
return Prismjs.getInstance().map.get(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
83
src/utils/Resizer.ts
Normal file
83
src/utils/Resizer.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* ### 浏览器缩放
|
||||||
|
*
|
||||||
|
* 此类对象由 Resizer 注册、触发和销毁
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* new ResizeListener("注册名", () => console.log("回调函数"));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class ResizeListener {
|
||||||
|
|
||||||
|
/** 事件名 */
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/** 回调函数 */
|
||||||
|
listener: (width: number, height: number) => void;
|
||||||
|
|
||||||
|
constructor(name: string, listener: (width: number, height: number) => void) {
|
||||||
|
this.name = name;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### 浏览器窗体缩放监听触发器
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* Resizer.addListener("Comment", (width: number, height: number) => console.log("缩放中"));
|
||||||
|
* Resizer.removeListener("Comment");
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default class Resizer {
|
||||||
|
|
||||||
|
private static instance: Resizer;
|
||||||
|
|
||||||
|
listeners: ResizeListener[];
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.listeners = [];
|
||||||
|
|
||||||
|
window.addEventListener("resize", resizeEvent => {
|
||||||
|
const width = (resizeEvent.currentTarget as any).innerWidth;
|
||||||
|
const height = (resizeEvent.currentTarget as any).innerHeight;
|
||||||
|
for (const e of this.listeners) {
|
||||||
|
e.listener(width, height);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getInstance(): Resizer {
|
||||||
|
if (!Resizer.instance) {
|
||||||
|
Resizer.instance = new Resizer();
|
||||||
|
}
|
||||||
|
return Resizer.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加事件
|
||||||
|
public static addListener(name: string, listener: (width: number, height: number) => void) {
|
||||||
|
const instance = Resizer.getInstance();
|
||||||
|
let e = instance.listeners.find((se) => se.name === name);
|
||||||
|
if (e) {
|
||||||
|
e.listener = listener;
|
||||||
|
} else {
|
||||||
|
instance.listeners.push(e = new ResizeListener(name, listener));
|
||||||
|
}
|
||||||
|
// 默认触发一次
|
||||||
|
e.listener(window.innerWidth, window.innerHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除事件
|
||||||
|
public static removeListener(name: string) {
|
||||||
|
const instance = Resizer.getInstance();
|
||||||
|
instance.listeners.splice(instance.listeners.findIndex(e => e.name === name), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getWidth(): number {
|
||||||
|
return document.documentElement.clientWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getHeight(): number {
|
||||||
|
return document.documentElement.clientHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/utils/Scroller.ts
Normal file
74
src/utils/Scroller.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
export type ScrollListener = {
|
||||||
|
|
||||||
|
source: Event;
|
||||||
|
viewWidth: number;
|
||||||
|
viewHeight: number;
|
||||||
|
top: number;
|
||||||
|
bottom: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Scroller {
|
||||||
|
|
||||||
|
private static instance: Scroller;
|
||||||
|
|
||||||
|
listeners = new Map<string, Function>();
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
|
||||||
|
window.addEventListener("scroll", source => {
|
||||||
|
// 滚动距离
|
||||||
|
const top = document.body.scrollTop || document.documentElement.scrollTop;
|
||||||
|
// 可视高度
|
||||||
|
const viewWidth = document.documentElement.clientWidth || document.body.clientWidth;
|
||||||
|
// 可视高度
|
||||||
|
const viewHeight = document.documentElement.clientHeight || document.body.clientHeight;
|
||||||
|
// 滚动高度
|
||||||
|
const sH = document.documentElement.scrollHeight || document.body.scrollHeight;
|
||||||
|
// 触发事件
|
||||||
|
const bottom = sH - top - viewHeight;
|
||||||
|
this.listeners.forEach(listener => listener({
|
||||||
|
source,
|
||||||
|
viewWidth,
|
||||||
|
viewHeight,
|
||||||
|
top,
|
||||||
|
bottom
|
||||||
|
} as ScrollListener));
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getInstance(): Scroller {
|
||||||
|
if (!Scroller.instance) {
|
||||||
|
Scroller.instance = new Scroller();
|
||||||
|
}
|
||||||
|
return Scroller.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加监听
|
||||||
|
*
|
||||||
|
* @param name 事件名
|
||||||
|
* @param listener 监听事件
|
||||||
|
*/
|
||||||
|
public static addListener(name: string, listener: (event: ScrollListener) => void) {
|
||||||
|
Scroller.getInstance().listeners.set(name, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除监听
|
||||||
|
*
|
||||||
|
* @param name 事件名
|
||||||
|
*/
|
||||||
|
public static removeListener(name: string) {
|
||||||
|
Scroller.getInstance().listeners.delete(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 滚动至顶(平滑地) */
|
||||||
|
public static toTop() {
|
||||||
|
document.body.scrollIntoView({behavior: "smooth"});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 滚动至指定节点(平滑地) */
|
||||||
|
public static toElement(el: HTMLElement) {
|
||||||
|
el.scrollIntoView({behavior: "smooth"});
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/utils/SettingMapper.ts
Normal file
94
src/utils/SettingMapper.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { SettingKey } from "~/types/Setting";
|
||||||
|
import { readonly, Ref, ref } from "vue";
|
||||||
|
import CommonAPI from "~/api/CommonAPI";
|
||||||
|
import { RunEnv, Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export default class SettingMapper {
|
||||||
|
|
||||||
|
private static instance: SettingMapper;
|
||||||
|
|
||||||
|
private map = new Map<string, Ref<string>>();
|
||||||
|
|
||||||
|
public static async loadSetting(...settings: { key: string, args?: { [key: string]: any }}[]): Promise<void> {
|
||||||
|
const map = new Map<string, object | undefined>();
|
||||||
|
{
|
||||||
|
// 默认配置
|
||||||
|
map.set(SettingKey.RUN_ENV, undefined);
|
||||||
|
map.set(SettingKey.PUBLIC_RESOURCES, {
|
||||||
|
as: "json"
|
||||||
|
});
|
||||||
|
map.set(SettingKey.DOMAIN_ROOT, undefined);
|
||||||
|
map.set(SettingKey.DOMAIN_API, undefined);
|
||||||
|
map.set(SettingKey.DOMAIN_GIT, undefined);
|
||||||
|
map.set(SettingKey.DOMAIN_BLOG, undefined);
|
||||||
|
map.set(SettingKey.DOMAIN_SPACE, undefined);
|
||||||
|
map.set(SettingKey.DOMAIN_RESOURCE, undefined);
|
||||||
|
map.set(SettingKey.DOMAIN_DOWNLOAD, undefined);
|
||||||
|
|
||||||
|
map.set(SettingKey.ENABLE_COMMENT, undefined);
|
||||||
|
map.set(SettingKey.ENABLE_DEBUG, undefined);
|
||||||
|
map.set(SettingKey.ENABLE_LOGIN, undefined);
|
||||||
|
map.set(SettingKey.ENABLE_REGISTER, undefined);
|
||||||
|
map.set(SettingKey.ENABLE_USER_UPDATE, undefined);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// 附加配置
|
||||||
|
for (let i = 0; i < settings.length; i++) {
|
||||||
|
map.set(settings[i].key, settings[i].args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const instance = this.getInstance();
|
||||||
|
const result = await CommonAPI.listSetting(map);
|
||||||
|
for (const [key, value] of result) {
|
||||||
|
instance.map.set(key, ref(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static is(key: SettingKey | string, args?: { [key: string]: any }): boolean {
|
||||||
|
const value = this.getValueRef(key, args).value;
|
||||||
|
return !value && value === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getValue(key: SettingKey | string, args?: { [key: string]: any }): string | undefined {
|
||||||
|
return this.getValueRef(key, args).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getValueRef(key: SettingKey| string, args?: { [key: string]: any }): Ref<string | undefined> {
|
||||||
|
const instance = this.getInstance();
|
||||||
|
let result = instance.map.get(key);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
instance.map.set(key, result = ref<any>());
|
||||||
|
Toolkit.async(async () => {
|
||||||
|
const value = instance.map.get(key);
|
||||||
|
if (value) {
|
||||||
|
return value.value;
|
||||||
|
}
|
||||||
|
result.value = await CommonAPI.getSetting(key, args);
|
||||||
|
});
|
||||||
|
return readonly(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getDomainLink(domainKey: SettingKey): string | undefined {
|
||||||
|
const runEnv = <RunEnv>(this.getValue(SettingKey.RUN_ENV));
|
||||||
|
let protocol = "https";
|
||||||
|
switch (runEnv) {
|
||||||
|
case RunEnv.DEV:
|
||||||
|
protocol = "http";
|
||||||
|
break;
|
||||||
|
case RunEnv.DEV_SSL:
|
||||||
|
case RunEnv.PROD:
|
||||||
|
protocol = "https";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return `${protocol}://${this.getValue(domainKey)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): SettingMapper {
|
||||||
|
if (SettingMapper.instance) {
|
||||||
|
return SettingMapper.instance;
|
||||||
|
}
|
||||||
|
return SettingMapper.instance = new SettingMapper();
|
||||||
|
}
|
||||||
|
}
|
||||||
129
src/utils/Storage.ts
Normal file
129
src/utils/Storage.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
export default class Storage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取为布尔值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @returns 布尔值
|
||||||
|
*/
|
||||||
|
public static is(key: string): boolean {
|
||||||
|
return this.getString(key) === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取为布尔值并取反
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @returns 布尔值
|
||||||
|
*/
|
||||||
|
public static not(key: string): boolean {
|
||||||
|
return !this.is(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取为指定对象
|
||||||
|
*
|
||||||
|
* @template T 对象类型
|
||||||
|
* @param key 键
|
||||||
|
* @returns {T | undefined} 返回对象
|
||||||
|
*/
|
||||||
|
public static getObject<T>(key: string): T {
|
||||||
|
if (this.has(key)) {
|
||||||
|
return this.getJSON(key) as T;
|
||||||
|
}
|
||||||
|
throw Error(`not found ${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取值,如果没有则储存并使用默认值
|
||||||
|
*
|
||||||
|
* @template T 默认值类型
|
||||||
|
* @param key 键
|
||||||
|
* @param def 默认值
|
||||||
|
* @return {T} 对象
|
||||||
|
*/
|
||||||
|
public static getDefault<T>(key: string, def: T): T {
|
||||||
|
if (this.has(key)) {
|
||||||
|
return this.getJSON(key) as T;
|
||||||
|
}
|
||||||
|
this.setObject(key, def);
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取为 JSON
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @returns JSON 对象
|
||||||
|
*/
|
||||||
|
public static getJSON(key: string) {
|
||||||
|
return JSON.parse(this.getString(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取为字符串(其他获取方式一般经过这个方法,找不到配置或配置值无效时会抛错)
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @returns 字符串
|
||||||
|
*/
|
||||||
|
public static getString(key: string): string {
|
||||||
|
const value = localStorage.getItem(key);
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
throw new Error(`not found: ${key}, ${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否存在某配置
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @returns true 为存在
|
||||||
|
*/
|
||||||
|
public static has(key: string): boolean {
|
||||||
|
return localStorage.getItem(key) !== undefined && localStorage.getItem(key) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
public static setObject(key: string, value: any) {
|
||||||
|
if (value instanceof Object || value instanceof Array) {
|
||||||
|
this.setJSON(key, value);
|
||||||
|
} else {
|
||||||
|
this.setString(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 JSON 值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param json JSON 字符串
|
||||||
|
*/
|
||||||
|
public static setJSON(key: string, json: any) {
|
||||||
|
localStorage.setItem(key, JSON.stringify(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
public static setString(key: string, value: any) {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除属性
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
*/
|
||||||
|
public static remove(key: string) {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/utils/Time.ts
Normal file
97
src/utils/Time.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
export default class Time {
|
||||||
|
|
||||||
|
/** 1 秒时间戳 */
|
||||||
|
public static S = 1E3;
|
||||||
|
/** 1 分钟时间戳 */
|
||||||
|
public static M = Time.S * 60;
|
||||||
|
/** 1 小时时间戳 */
|
||||||
|
public static H = Time.M * 60;
|
||||||
|
/** 1 天时间戳 */
|
||||||
|
public static D = Time.H * 24;
|
||||||
|
|
||||||
|
public static now(): number {
|
||||||
|
return new Date().getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unix 时间戳转日期
|
||||||
|
*
|
||||||
|
* @param unix 时间戳
|
||||||
|
*/
|
||||||
|
public static toDate(unix?: number): string {
|
||||||
|
if (!unix) return "";
|
||||||
|
const d = new Date(unix);
|
||||||
|
return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d.getDate().toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unix 时间戳转时间
|
||||||
|
*
|
||||||
|
* @param unix 时间戳
|
||||||
|
*/
|
||||||
|
public static toTime(unix?: number): string {
|
||||||
|
if (!unix) return "";
|
||||||
|
const d = new Date(unix);
|
||||||
|
return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unix 时间戳转日期和时间
|
||||||
|
*
|
||||||
|
* @param unix 时间戳
|
||||||
|
*/
|
||||||
|
public static toDateTime(unix?: number): string {
|
||||||
|
if (!unix) return "";
|
||||||
|
return `${this.toDate(unix)} ${this.toTime(unix)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toPassedDate(unix?: number): string {
|
||||||
|
return this.toPassedDateTime(unix, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toPassedDateTime(unix?: number, withDetailTime = true): string {
|
||||||
|
if (!unix) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const now = new Date().getTime();
|
||||||
|
const between = now - unix;
|
||||||
|
|
||||||
|
if (Time.D * 4 <= between) {
|
||||||
|
return withDetailTime ? this.toDateTime(unix) : this.toDate(unix);
|
||||||
|
} else if (Time.D < between) {
|
||||||
|
return `${Math.floor(between / Time.D)} 天前`;
|
||||||
|
} else if (Time.H < between) {
|
||||||
|
return `${Math.floor(between / Time.H)} 小时前`;
|
||||||
|
} else if (Time.M < between) {
|
||||||
|
return `${Math.floor(between / Time.M)} 分钟前`;
|
||||||
|
} else {
|
||||||
|
return "刚刚";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static between(begin: Date, end?: Date) : any {
|
||||||
|
if (!end) {
|
||||||
|
end = new Date();
|
||||||
|
}
|
||||||
|
const cs = 1000, cm = 6E4, ch = 36E5, cd = 864E5, cy = 31536E6;
|
||||||
|
const l = end.getTime() - begin.getTime();
|
||||||
|
const y = Math.floor(l / cy),
|
||||||
|
d = Math.floor((l / cd) - y * 365),
|
||||||
|
h = Math.floor((l - (y * 365 + d) * cd) / ch),
|
||||||
|
m = Math.floor((l - (y * 365 + d) * cd - h * ch) / cm),
|
||||||
|
s = Math.floor((l - (y * 365 + d) * cd - h * ch - m * cm) / cs),
|
||||||
|
ms = Math.floor(((l - (y * 365 + d) * cd - h * ch - m * cm) / cs - s) * cs);
|
||||||
|
return { l, y, d, h, m, s, ms };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toMediaTime(seconds: number): string {
|
||||||
|
seconds = Math.floor(seconds);
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const second = seconds % 60;
|
||||||
|
if (0 < hours) {
|
||||||
|
return `${hours}:${minutes.toString().padStart(2, "0")}:${second.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
return `${minutes.toString().padStart(2, "0")}:${second.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
354
src/utils/Toolkit.ts
Normal file
354
src/utils/Toolkit.ts
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
import type { App } from "vue";
|
||||||
|
import type { InstallRecord } from "~/types";
|
||||||
|
import { UserLevelType } from "~/types/User";
|
||||||
|
import { CommonAPI, SettingKey, SettingMapper } from "~/index";
|
||||||
|
|
||||||
|
export default class Toolkit {
|
||||||
|
|
||||||
|
public static isFunction = (val: any) => typeof val === "function";
|
||||||
|
public static isArray = Array.isArray;
|
||||||
|
public static isString = (val: any) => typeof val === "string";
|
||||||
|
public static isSymbol = (val: any) => typeof val === "symbol";
|
||||||
|
public static isObject = (val: any) => val !== null && typeof val === "object";
|
||||||
|
public static isFile = (val: any) => val instanceof File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加安装方法
|
||||||
|
* @example
|
||||||
|
* ```JS
|
||||||
|
* import { MeDemo } from './index.vue'
|
||||||
|
*
|
||||||
|
* addInstall(MeDemo)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public static withInstall = <T>(comp: T): InstallRecord<T> => {
|
||||||
|
const _comp = comp as InstallRecord<T>;
|
||||||
|
_comp.install = (app: App) => {
|
||||||
|
app.component((comp as any).name, _comp);
|
||||||
|
};
|
||||||
|
return _comp;
|
||||||
|
};
|
||||||
|
|
||||||
|
public static guid() {
|
||||||
|
const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
|
||||||
|
return `${s4() + s4()}-${s4()}-${s4()}-${s4()}-${s4() + s4() + s4()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static className(...args: any[]) {
|
||||||
|
const classes = [];
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const value = args[i];
|
||||||
|
if (!value) continue;
|
||||||
|
if (this.isString(value)) {
|
||||||
|
classes.push(value);
|
||||||
|
} else if (this.isArray(value)) {
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const inner: any = this.className(value[i]);
|
||||||
|
if (inner) {
|
||||||
|
classes.push(inner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (this.isObject(value)) {
|
||||||
|
for (const name in value) {
|
||||||
|
if (value[name]) {
|
||||||
|
classes.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return classes.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isEmpty(obj: any): boolean {
|
||||||
|
if (this.isString(obj)) {
|
||||||
|
return (obj as string).trim().length === 0;
|
||||||
|
}
|
||||||
|
if (this.isArray(obj)) {
|
||||||
|
return (obj as []).length === 0;
|
||||||
|
}
|
||||||
|
if (this.isFunction(obj)) {
|
||||||
|
return this.isEmpty(obj());
|
||||||
|
}
|
||||||
|
if (this.isObject(obj)) {
|
||||||
|
return obj === undefined || obj === null;
|
||||||
|
}
|
||||||
|
return obj === undefined || obj === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isNotEmpty(obj: any): boolean {
|
||||||
|
return !this.isEmpty(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### 延时执行
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* await sleep(1E3)
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param ms 延时毫秒
|
||||||
|
*/
|
||||||
|
public static async sleep(ms: number): Promise<unknown> {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取节点属性
|
||||||
|
*
|
||||||
|
* @param el 节点
|
||||||
|
* @param name 属性名
|
||||||
|
* @returns 属性
|
||||||
|
*/
|
||||||
|
public static getAttribute(el: Element, name: string): string | null {
|
||||||
|
return el.hasAttribute(name) ? el.getAttribute(name) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转为数字
|
||||||
|
*
|
||||||
|
* @param string 字符串
|
||||||
|
* @param fallback 如果失败,返回该值
|
||||||
|
* @returns 转换后或转换失败数据
|
||||||
|
*/
|
||||||
|
public static toNumber(string: string, fallback?: number): number {
|
||||||
|
if (!string) return fallback as number;
|
||||||
|
const number = Number(string);
|
||||||
|
return isFinite(number) ? number : fallback as number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ### 解析字符串为 DOM 节点。此 DOM 字符串必须有且仅有一个根节点
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* toDOM(`
|
||||||
|
* <div>
|
||||||
|
* <p>imyeyu.net</p>
|
||||||
|
* </div>
|
||||||
|
* `)
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param string 字符串
|
||||||
|
* @returns DOM 节点
|
||||||
|
*/
|
||||||
|
public static toDOM(string: string): Document {
|
||||||
|
return new DOMParser().parseFromString(string, "text/xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步执行
|
||||||
|
*
|
||||||
|
* @param event 函数
|
||||||
|
*/
|
||||||
|
public static async(event: Function) {
|
||||||
|
setTimeout(event, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机数
|
||||||
|
*
|
||||||
|
* @param min 最小值
|
||||||
|
* @param max 最大值
|
||||||
|
*/
|
||||||
|
public static random(min = 0, max = 100): number {
|
||||||
|
return Math.floor(Math.random() * (max + 1 - min)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64 数据转文件
|
||||||
|
*
|
||||||
|
* __需要头部元数据__
|
||||||
|
*
|
||||||
|
* __示例:__
|
||||||
|
* data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA
|
||||||
|
*
|
||||||
|
* @param data Base64 数据
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @returns 文件对象
|
||||||
|
*/
|
||||||
|
public static base64ToFile(data: string, fileName: string): File {
|
||||||
|
const splitData = data.split(",");
|
||||||
|
const base64 = atob(splitData[1]);
|
||||||
|
let n = base64.length as number;
|
||||||
|
const u8arr = new Uint8Array(n);
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = base64.charCodeAt(n);
|
||||||
|
}
|
||||||
|
return new File([u8arr], fileName, {type: splitData[0].split(":")[1]});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深克隆对象
|
||||||
|
*
|
||||||
|
* @param origin 源对象
|
||||||
|
* @param target 递归对象
|
||||||
|
* @returns 克隆对象
|
||||||
|
*/
|
||||||
|
public static deepClone(origin: any, target = {} as any) {
|
||||||
|
const toString = Object.prototype.toString;
|
||||||
|
const arrType = "[object Array]";
|
||||||
|
for (const key in origin) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(origin, key)) {
|
||||||
|
if (typeof origin[key] === "object" && origin[key] !== null) {
|
||||||
|
target[key] = toString.call(origin[key]) === arrType ? [] : {};
|
||||||
|
this.deepClone(origin[key], target[key]);
|
||||||
|
} else {
|
||||||
|
target[key] = origin[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static keyValueString(obj: object, assign: string, split: string): string {
|
||||||
|
let result = "";
|
||||||
|
Object.entries(obj).forEach(([k, v]) => result += k + assign + v + split);
|
||||||
|
return result.substring(0, result.length - split.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toURLArgs(obj?: { [key: string]: any }): string {
|
||||||
|
if (!obj) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const args: string[] = [];
|
||||||
|
for (const key in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
|
const value = obj[key];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach((item) => {
|
||||||
|
args.push(`${encodeURIComponent(key)}=${encodeURIComponent(item)}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
args.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toObject(map: Map<any, any>): object {
|
||||||
|
return Array.from(map.entries()).reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = value ?? null;
|
||||||
|
return acc;
|
||||||
|
}, {} as { [key: string]: string | undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uuid(): string {
|
||||||
|
const char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
const length = [8, 4, 4, 4, 12];
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < length.length; i++) {
|
||||||
|
for (let j = 0; j < length[i]; j++) {
|
||||||
|
result += char[this.random(0, char.length - 1)];
|
||||||
|
}
|
||||||
|
result += "-";
|
||||||
|
}
|
||||||
|
return result.substring(0, result.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static keyFromValue(e: any, value: any): string {
|
||||||
|
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
|
||||||
|
} {
|
||||||
|
let timerId: ReturnType<typeof setTimeout> | null = null; // 存储定时器
|
||||||
|
let immediate = defaultImmediate;
|
||||||
|
// 定义一个 cancel 办法,用于勾销防抖
|
||||||
|
const cancel = (): void => {
|
||||||
|
if (timerId) {
|
||||||
|
clearTimeout(timerId);
|
||||||
|
timerId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const debounced = function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
|
||||||
|
const context = this;
|
||||||
|
if (timerId) {
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
if (immediate) {
|
||||||
|
callback.apply(context, args);
|
||||||
|
immediate = false;
|
||||||
|
timerId = setTimeout(() => {
|
||||||
|
immediate = defaultImmediate;
|
||||||
|
}, delay);
|
||||||
|
} else {
|
||||||
|
// 设置定时器,在延迟时间后执行指标函数
|
||||||
|
timerId = setTimeout(() => {
|
||||||
|
callback.apply(context, args);
|
||||||
|
immediate = defaultImmediate;
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 将 cancel 方法附加到 debounced 函数上
|
||||||
|
(debounced as any).cancel = cancel;
|
||||||
|
return debounced as T & { cancel(): void };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toUserLevel(exp?: number): UserLevelType {
|
||||||
|
exp = exp ?? 0;
|
||||||
|
const levels = [0, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096];
|
||||||
|
for (let i = 1; i < levels.length; i++) {
|
||||||
|
if (exp < levels[i]) {
|
||||||
|
return {
|
||||||
|
exp,
|
||||||
|
value: i - 1,
|
||||||
|
percent: (exp - levels[i - 1]) / (levels[i] - levels[i - 1]),
|
||||||
|
nextLevelUp: i === levels.length - 1 ? 4096 : levels[i]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { exp, value: 8, percent: 1, nextLevelUp: 4096 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toFormData(root?: object): FormData {
|
||||||
|
const form = new FormData();
|
||||||
|
if (!root) {
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
const run = (parent: string | null, obj: object) => {
|
||||||
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
if (this.isObject(value) && !this.isFile(value)) {
|
||||||
|
if (parent) {
|
||||||
|
run(`${parent}.${key}`, value);
|
||||||
|
} else {
|
||||||
|
run(key, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (parent) {
|
||||||
|
form.append(`${parent}.${key}`, value);
|
||||||
|
} else {
|
||||||
|
form.append(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
run(null, root);
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static toResURL(val: string): string {
|
||||||
|
const at = val.indexOf("@");
|
||||||
|
const start = val.substring(0, at);
|
||||||
|
const path = val.substring(at + 1);
|
||||||
|
switch (start) {
|
||||||
|
case "res": return SettingMapper.getDomainLink(SettingKey.DOMAIN_RESOURCE) + path;
|
||||||
|
case "dl": return SettingMapper.getDomainLink(SettingKey.DOMAIN_DOWNLOAD) + path;
|
||||||
|
case "attach": return CommonAPI.getAttachmentReadAPI(path);
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static format(template: string, variables: { [key: string]: any }): string {
|
||||||
|
return template.replace(/\$\{(\w+)}/g, (_, key) => variables[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static leftClickCallback(event: MouseEvent, callback: Function): void {
|
||||||
|
if (event.button === 0 && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
130
src/utils/directives/Draggable.ts
Normal file
130
src/utils/directives/Draggable.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
|
|
||||||
|
export type DraggableConfig = {
|
||||||
|
|
||||||
|
/** 点下 */
|
||||||
|
onMouseDown?: (e: MouseEvent | TouchEvent) => void;
|
||||||
|
|
||||||
|
/** 中断条件(返回 true 时不触发 onDragging 及往后事件) */
|
||||||
|
interruptWhen?: (e: MouseEvent | TouchEvent) => boolean;
|
||||||
|
|
||||||
|
/** 拖动 */
|
||||||
|
onDragging: (e: MouseEvent | TouchEvent, relX: number, relY: number, offsetX: number, offsetY: number) => void;
|
||||||
|
|
||||||
|
/** 释放 */
|
||||||
|
onDragged?: (e: MouseEvent | TouchEvent, x: number, y: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEventPosition = (e: MouseEvent | TouchEvent): { clientX: number, clientY: number, pageX: number, pageY: number } => {
|
||||||
|
if ("touches" in e && e.touches.length > 0) {
|
||||||
|
const touch = e.touches[0];
|
||||||
|
return {
|
||||||
|
clientX: touch.clientX,
|
||||||
|
clientY: touch.clientY,
|
||||||
|
pageX: touch.pageX,
|
||||||
|
pageY: touch.pageY
|
||||||
|
};
|
||||||
|
} else if (e instanceof MouseEvent) {
|
||||||
|
return {
|
||||||
|
clientX: e.clientX,
|
||||||
|
clientY: e.clientY,
|
||||||
|
pageX: e.pageX,
|
||||||
|
pageY: e.pageY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { clientX: 0, clientY: 0, pageX: 0, pageY: 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
const VDraggable: Directive = {
|
||||||
|
// 挂载
|
||||||
|
mounted(el: HTMLElement, binding: DirectiveBinding<DraggableConfig>) {
|
||||||
|
const config = binding.value as DraggableConfig;
|
||||||
|
let isClicked = false, ox = 0, oy = 0, ol = 0, ot = 0, opx = 0, opy = 0;
|
||||||
|
|
||||||
|
// 按下
|
||||||
|
const handleStart = (e: MouseEvent | TouchEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const pos = getEventPosition(e);
|
||||||
|
ox = pos.clientX;
|
||||||
|
oy = pos.clientY;
|
||||||
|
ol = el.offsetLeft;
|
||||||
|
ot = el.offsetTop;
|
||||||
|
opx = pos.pageX;
|
||||||
|
opy = pos.pageY;
|
||||||
|
|
||||||
|
config.onMouseDown?.(e);
|
||||||
|
if (config.interruptWhen?.(e)) return;
|
||||||
|
|
||||||
|
isClicked = true;
|
||||||
|
};
|
||||||
|
// 移动
|
||||||
|
const handleMove = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (!isClicked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const pos = getEventPosition(e);
|
||||||
|
const relX = pos.clientX - (ox - ol);
|
||||||
|
const relY = pos.clientY - (oy - ot);
|
||||||
|
const offsetX = pos.pageX - opx;
|
||||||
|
const offsetY = pos.pageY - opy;
|
||||||
|
|
||||||
|
config.onDragging(e, relX, relY, offsetX, offsetY);
|
||||||
|
};
|
||||||
|
// 释放
|
||||||
|
const handleEnd = (e: MouseEvent | TouchEvent) => {
|
||||||
|
if (!isClicked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pos = getEventPosition(e);
|
||||||
|
config.onDragged?.(e, pos.clientX - ox, pos.clientY - oy);
|
||||||
|
isClicked = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
(el.style as any)["user-drag"] = "none";
|
||||||
|
(el.style as any)["touch-action"] = "none";
|
||||||
|
|
||||||
|
// 鼠标
|
||||||
|
el.addEventListener("mousedown", handleStart as EventListener);
|
||||||
|
document.addEventListener("mousemove", handleMove as EventListener);
|
||||||
|
document.addEventListener("mouseup", handleEnd as EventListener);
|
||||||
|
|
||||||
|
// 触控
|
||||||
|
el.addEventListener("touchstart", handleStart as EventListener, { passive: false });
|
||||||
|
document.addEventListener("touchmove", handleMove as EventListener, { passive: false });
|
||||||
|
document.addEventListener("touchend", handleEnd as EventListener, { passive: false });
|
||||||
|
|
||||||
|
// 保存事件处理器以便卸载时使用
|
||||||
|
(el as any)._vDraggableHandlers = {
|
||||||
|
handleStart,
|
||||||
|
handleMove,
|
||||||
|
handleEnd
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// 卸载
|
||||||
|
unmounted(el: HTMLElement) {
|
||||||
|
const handlers = (el as any)._vDraggableHandlers as {
|
||||||
|
handleStart: EventListener,
|
||||||
|
handleMove: EventListener,
|
||||||
|
handleEnd: EventListener
|
||||||
|
};
|
||||||
|
if (handlers) {
|
||||||
|
// 鼠标
|
||||||
|
el.removeEventListener("mousedown", handlers.handleStart);
|
||||||
|
document.removeEventListener("mousemove", handlers.handleMove);
|
||||||
|
document.removeEventListener("mouseup", handlers.handleEnd);
|
||||||
|
|
||||||
|
// 触控
|
||||||
|
el.removeEventListener("touchstart", handlers.handleStart);
|
||||||
|
document.removeEventListener("touchmove", handlers.handleMove);
|
||||||
|
document.removeEventListener("touchend", handlers.handleEnd);
|
||||||
|
|
||||||
|
// 引用
|
||||||
|
delete (el as any)._vDraggableHandlers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VDraggable;
|
||||||
120
src/utils/directives/Popup.ts
Normal file
120
src/utils/directives/Popup.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
|
import Toolkit from "../Toolkit";
|
||||||
|
|
||||||
|
export enum PopupType {
|
||||||
|
TEXT,
|
||||||
|
IMG,
|
||||||
|
HTML,
|
||||||
|
EL
|
||||||
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
export type PopupConfig = {
|
||||||
|
|
||||||
|
type: PopupType,
|
||||||
|
value?: string | HTMLElement;
|
||||||
|
canShow?: () => boolean;
|
||||||
|
beforeShow?: (type: PopupType, value: string | HTMLElement) => Promise<void>;
|
||||||
|
afterHidden?: (type: PopupType, value: string | HTMLElement) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popup 弹出提示 DOM 节点,全局唯一
|
||||||
|
let popup: HTMLElement | null;
|
||||||
|
|
||||||
|
const VPopup: Directive = {
|
||||||
|
|
||||||
|
mounted(el: HTMLElement, binding: DirectiveBinding<PopupConfig>) {
|
||||||
|
// 转配置
|
||||||
|
let config: PopupConfig;
|
||||||
|
if (binding.arg && binding.arg === "config") {
|
||||||
|
config = binding.value as PopupConfig;
|
||||||
|
} else {
|
||||||
|
config = {
|
||||||
|
type: PopupType.TEXT,
|
||||||
|
value: binding.value as any as string,
|
||||||
|
canShow: () => true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Popup 节点
|
||||||
|
if (!popup) {
|
||||||
|
popup = document.getElementById("tui-popup");
|
||||||
|
}
|
||||||
|
let isShowing = false;
|
||||||
|
// 显示
|
||||||
|
el.addEventListener("mouseenter", async e => {
|
||||||
|
if (!config.value) {
|
||||||
|
console.warn("not found popup value", config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (config.beforeShow) {
|
||||||
|
await config.beforeShow(config.type, config.value);
|
||||||
|
}
|
||||||
|
if (config.canShow && config.canShow() && popup) {
|
||||||
|
let el: HTMLElement | null = null;
|
||||||
|
if (!config) {
|
||||||
|
el = document.createElement("div");
|
||||||
|
el.className = "text";
|
||||||
|
el.textContent = config as string;
|
||||||
|
popup.appendChild(el);
|
||||||
|
}
|
||||||
|
switch (config.type) {
|
||||||
|
case PopupType.TEXT:
|
||||||
|
// 文本
|
||||||
|
el = document.createElement("div");
|
||||||
|
el.className = "text";
|
||||||
|
el.textContent = config.value as string;
|
||||||
|
popup.appendChild(el);
|
||||||
|
break;
|
||||||
|
case PopupType.IMG:
|
||||||
|
// 图片
|
||||||
|
el = document.createElement("img");
|
||||||
|
(el as HTMLImageElement).src = config.value as string;
|
||||||
|
popup.appendChild(el);
|
||||||
|
break;
|
||||||
|
case PopupType.HTML:
|
||||||
|
// HTML 字符串
|
||||||
|
popup.appendChild(Toolkit.toDOM(config.value as string));
|
||||||
|
break;
|
||||||
|
case PopupType.EL:
|
||||||
|
// DOM 节点
|
||||||
|
if (config.value instanceof HTMLElement) {
|
||||||
|
const valueEl = config.value as HTMLElement;
|
||||||
|
valueEl.style.display = "block";
|
||||||
|
popup.appendChild(valueEl);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
console.error(config);
|
||||||
|
throw new Error("Vue 指令错误:v-popup:el 的值不是 HTML 元素");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
popup.style.left = (e.x + 20) + "px";
|
||||||
|
popup.style.top = (e.y + 14) + "px";
|
||||||
|
popup.style.visibility = "visible";
|
||||||
|
isShowing = true;
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
// 移动
|
||||||
|
el.addEventListener("mousemove", async (e) => {
|
||||||
|
if (config.canShow && config.canShow() && isShowing && popup) {
|
||||||
|
popup.style.left = (e.x + 20) + "px";
|
||||||
|
popup.style.top = (e.y + 14) + "px";
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
// 隐藏
|
||||||
|
el.addEventListener("mouseleave", async () => {
|
||||||
|
if (popup) {
|
||||||
|
popup.style.visibility = "hidden";
|
||||||
|
popup.innerText = "";
|
||||||
|
popup.style.left = "0px";
|
||||||
|
popup.style.top = "0px";
|
||||||
|
|
||||||
|
// 隐藏后事件
|
||||||
|
if (config.afterHidden && config.value) {
|
||||||
|
await config.afterHidden(config.type, config.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VPopup;
|
||||||
44
tsconfig.json
Normal file
44
tsconfig.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"strict": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"~/*": [
|
||||||
|
"./src/*"
|
||||||
|
],
|
||||||
|
"@/*": [
|
||||||
|
"./examples/*"
|
||||||
|
],
|
||||||
|
"timi-web": [
|
||||||
|
"./src/index.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"examples/**/*.ts",
|
||||||
|
"examples/**/*.d.ts",
|
||||||
|
"examples/**/*.vue"
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
tsconfig.node.json
Normal file
11
tsconfig.node.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"vite.config.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
10
tsconfig.types.json
Normal file
10
tsconfig.types.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true,
|
||||||
|
"emitDeclarationOnly": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
||||||
110
vite.config.ts
Normal file
110
vite.config.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { resolve } from "path";
|
||||||
|
import { Alias, defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import VueSetupExtend from "vite-plugin-vue-setup-extend";
|
||||||
|
import { prismjsPlugin } from "vite-plugin-prismjs";
|
||||||
|
import AutoImport from "unplugin-auto-import/vite";
|
||||||
|
import Components from "unplugin-vue-components/vite";
|
||||||
|
import dts from "vite-plugin-dts";
|
||||||
|
|
||||||
|
const alias: Alias[] = [
|
||||||
|
{
|
||||||
|
find: "@",
|
||||||
|
replacement: resolve(__dirname, "./examples")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "~",
|
||||||
|
replacement: resolve(__dirname, "./src")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "*",
|
||||||
|
replacement: resolve("")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: /^timi-web(\/(es|lib))?$/,
|
||||||
|
replacement: resolve(__dirname, "./src/index.ts")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
port: 3003,
|
||||||
|
host: true
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: "dist",
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, "./src/index.ts"),
|
||||||
|
name: "TimiWeb",
|
||||||
|
fileName: "timi-web"
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [
|
||||||
|
"vue"
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
globals: {
|
||||||
|
vue: "Vue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minify: "terser",
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
drop_console: false,
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
drop_debugger: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue({
|
||||||
|
include: [/\.vue$/, /\.md$/]
|
||||||
|
}),
|
||||||
|
VueSetupExtend(),
|
||||||
|
dts(),
|
||||||
|
prismjsPlugin({
|
||||||
|
languages: [
|
||||||
|
"ini",
|
||||||
|
"php",
|
||||||
|
"sql",
|
||||||
|
"xml",
|
||||||
|
"css",
|
||||||
|
"less",
|
||||||
|
"html",
|
||||||
|
"json",
|
||||||
|
"yaml",
|
||||||
|
"java",
|
||||||
|
"nginx",
|
||||||
|
"javascript",
|
||||||
|
"typescript",
|
||||||
|
"apacheconf",
|
||||||
|
"properties"
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
"line-numbers"
|
||||||
|
],
|
||||||
|
theme: "default",
|
||||||
|
css: true
|
||||||
|
}),
|
||||||
|
AutoImport({
|
||||||
|
imports: [
|
||||||
|
"vue"
|
||||||
|
],
|
||||||
|
dts: "examples/auto-imports.d.ts",
|
||||||
|
eslintrc: {
|
||||||
|
enabled: true,
|
||||||
|
globalsPropValue: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
dirs: [
|
||||||
|
"src/components"
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user