Initial project
This commit is contained in:
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
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
# timi-tdesign-pc
|
# Vue 3 + TypeScript + Vite
|
||||||
|
|
||||||
Timi PC 前端组件库
|
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.
|
||||||
|
|
||||||
|
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||||
|
|||||||
29
examples/Root.vue
Normal file
29
examples/Root.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<root-layout v-if="isReady" icp="test icp" author="test cr name">
|
||||||
|
<authorize-form class="test" />
|
||||||
|
<textarea v-text="JSON.stringify(userStore.loginUser)"></textarea>
|
||||||
|
<comment :bizType="CommentBizType.ARTICLE" :bizId="1" />
|
||||||
|
</root-layout>
|
||||||
|
<popup />
|
||||||
|
<user-profile-popup />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { CommentBizType, Popup, SettingMapper, userStore } from "timi-web";
|
||||||
|
import { AuthorizeForm, Comment, RootLayout, UserProfilePopup } from "~/components";
|
||||||
|
|
||||||
|
const isReady = ref(false);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await SettingMapper.loadSetting();
|
||||||
|
await userStore.login4Storage();
|
||||||
|
isReady.value = true;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.test {
|
||||||
|
width: 20rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
15
examples/main.ts
Normal file
15
examples/main.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import Root from "./Root.vue";
|
||||||
|
import TimiWebUI, { axios, Network, VPopup } from "timi-web"; // 本地开发
|
||||||
|
|
||||||
|
import "timi-web/style.css";
|
||||||
|
import "~/assets/style/tencent-cloud.less";
|
||||||
|
import "~/assets/style/tencent-cloud-custom.less";
|
||||||
|
|
||||||
|
axios.defaults.baseURL = "http://localhost:8091";
|
||||||
|
axios.interceptors.request.use(Network.userTokenInterceptors);
|
||||||
|
|
||||||
|
const app = createApp(Root);
|
||||||
|
app.use(TimiWebUI);
|
||||||
|
app.directive("popup", VPopup);
|
||||||
|
app.mount("#root");
|
||||||
5
examples/vite-env.d.ts
vendored
Normal file
5
examples/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
declare module "*.vue" {
|
||||||
|
import type { DefineComponent } from "vue";
|
||||||
|
const component: DefineComponent<{}, {}, any>;
|
||||||
|
export default component;
|
||||||
|
}
|
||||||
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>
|
||||||
64
package.json
Normal file
64
package.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"name": "timi-tdesign-pc",
|
||||||
|
"main": "./dist/timi-tdesign-pc.umd.js",
|
||||||
|
"types": "./dist/src/index.d.ts",
|
||||||
|
"module": "./dist/timi-tdesign-pc.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-tdesign-pc.mjs",
|
||||||
|
"require": "./dist/timi-tdesign-pc.umd.js"
|
||||||
|
},
|
||||||
|
"./style.css": "./dist/timi-tdesign-pc.css"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.5.13"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.15.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.31.0",
|
||||||
|
"@typescript-eslint/parser": "^8.31.0",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
|
"@vue/cli-plugin-babel": "^5.0.8",
|
||||||
|
"@vue/compiler-sfc": "^3.5.13",
|
||||||
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
|
"@vue/eslint-config-typescript": "^14.5.0",
|
||||||
|
"eslint": "^9.25.1",
|
||||||
|
"eslint-config-prettier": "^10.1.2",
|
||||||
|
"eslint-plugin-prettier": "^5.2.6",
|
||||||
|
"eslint-plugin-promise": "^7.2.1",
|
||||||
|
"eslint-plugin-vue": "^10.0.0",
|
||||||
|
"less": "^4.3.0",
|
||||||
|
"prettier": "^3.5.2",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"unplugin-auto-import": "^19.1.2",
|
||||||
|
"unplugin-vue-components": "^28.5.0",
|
||||||
|
"vite-plugin-dts": "^4.5.0",
|
||||||
|
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||||
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"vite": "6.3.4",
|
||||||
|
"vue-tsc": "^2.2.10",
|
||||||
|
"tdesign-vue-next": "^1.14.2",
|
||||||
|
"timi-web": "link:..\\timi-web"
|
||||||
|
}
|
||||||
|
}
|
||||||
9632
pnpm-lock.yaml
generated
Normal file
9632
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
228
src/assets/style/tencent-cloud-custom.less
Normal file
228
src/assets/style/tencent-cloud-custom.less
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
/* TDesign 腾讯云样式附加修改 */
|
||||||
|
@popupShadowColor: rgba(0, 0, 0, .1);
|
||||||
|
@popupShadow: 3px 3px 0 @popupShadowColor;
|
||||||
|
|
||||||
|
.t-button {
|
||||||
|
cursor: var(--tui-cur-pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-card {
|
||||||
|
box-shadow: var(--tui-shadow);
|
||||||
|
|
||||||
|
.t-card__title {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-card__body,
|
||||||
|
.t-card__header {
|
||||||
|
margin: 14px 0;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-link {
|
||||||
|
cursor: var(--tui-cur-pointer) !important;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-input .t-input__inner {
|
||||||
|
cursor: var(--tui-cur-text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-select .t-input__inner {
|
||||||
|
cursor: var(--tui-cur-default) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-textarea__inner {
|
||||||
|
cursor: var(--tui-cur-text) !important;
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-checkbox {
|
||||||
|
cursor: var(--tui-cur-pointer) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-tabs {
|
||||||
|
|
||||||
|
.t-tabs__nav-item {
|
||||||
|
cursor: var(--tui-cur-pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-pagination {
|
||||||
|
|
||||||
|
.t-pagination__number {
|
||||||
|
cursor: var(--tui-cur-pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-breadcrumb {
|
||||||
|
|
||||||
|
.t-breadcrumb__item {
|
||||||
|
cursor: var(--tui--pointer);
|
||||||
|
|
||||||
|
.t-breadcrumb__inner-text {
|
||||||
|
cursor: var(--tui--pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
|
||||||
|
.t-breadcrumb__inner {
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
color: var(--td-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-breadcrumb__inner-text {
|
||||||
|
cursor: var(--tui--default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-list {
|
||||||
|
|
||||||
|
.t-list-item {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
&:last-child::after {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-tree {
|
||||||
|
|
||||||
|
.t-tree__item,
|
||||||
|
.t-tree__icon,
|
||||||
|
.t-tree__label {
|
||||||
|
cursor: var(--tui--pointer) !important;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
.t-tree__label {
|
||||||
|
padding: 3px var(--td-comp-paddingLR-xs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-timeline-item__wrapper {
|
||||||
|
|
||||||
|
.t-timeline-item__dot {
|
||||||
|
border: none;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
margin: 2px 0 0 1px;
|
||||||
|
background: var(--td-brand-color);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-timeline-item__tail {
|
||||||
|
border-left: 2px solid var(--td-component-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-layout {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.t-layout__sider,
|
||||||
|
.t-layout__header,
|
||||||
|
.t-layout__content,
|
||||||
|
.t-layout__footer {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.narrow-scrollbar {
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
cursor: var(--tui--default) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
border: none;
|
||||||
|
cursor: var(--tui--default);
|
||||||
|
background: #525870;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-popup {
|
||||||
|
|
||||||
|
.t-popup__content {
|
||||||
|
outline: 1px solid var(--td-component-border);
|
||||||
|
box-shadow: @popupShadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-select-option {
|
||||||
|
cursor: var(--tui--pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.t-popup[data-popper-placement^="left"] {
|
||||||
|
|
||||||
|
.t-popup__arrow {
|
||||||
|
right: calc(-9px / 2);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
box-shadow: inset -1px 0 0 var(--td-component-border), inset 0 1px 0 var(--td-component-border), 2px -2px 0 rgba(0, 0, 0, .08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.t-popup[data-popper-placement^="right"] {
|
||||||
|
|
||||||
|
.t-popup__arrow {
|
||||||
|
left: calc(-10px / 2);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
box-shadow: inset 0 -1px 0 var(--td-component-border), inset 1px 0 0 var(--td-component-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.t-popup[data-popper-placement^="bottom"] {
|
||||||
|
|
||||||
|
&.t-select__dropdown .t-popup__content {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-dialog__ctx {
|
||||||
|
--td-mask-active: rgba(0, 0, 0, .2) !important;
|
||||||
|
|
||||||
|
.t-dialog {
|
||||||
|
background: rgba(255, 255, 255, .8);
|
||||||
|
box-shadow: 2px 2px 0 rgba(0, 0, 0, .3);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
animation-duration: .3s !important;
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
animation-timing-function: cubic-bezier(.23, 1, .32, 1) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-message {
|
||||||
|
outline: 1px solid var(--td-component-border);
|
||||||
|
box-shadow: @popupShadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-dropdown {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-avatar {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t-image-viewer {
|
||||||
|
|
||||||
|
.t-swiper-item {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
1202
src/assets/style/tencent-cloud.less
Normal file
1202
src/assets/style/tencent-cloud.less
Normal file
File diff suppressed because it is too large
Load Diff
18
src/components/authorize-form/emits.ts
Normal file
18
src/components/authorize-form/emits.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* emits
|
||||||
|
*/
|
||||||
|
export interface Emits {
|
||||||
|
(event: "loginSuccess"): Promise<void>;
|
||||||
|
(event: "registerSuccess"): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHandler = (emit: Emits) => {
|
||||||
|
|
||||||
|
const onLoginSuccess = async () => await emit("loginSuccess");
|
||||||
|
const onRegisterSuccess = async () => await emit("registerSuccess");
|
||||||
|
|
||||||
|
return {
|
||||||
|
onLoginSuccess,
|
||||||
|
onRegisterSuccess
|
||||||
|
};
|
||||||
|
};
|
||||||
5
src/components/authorize-form/index.ts
Normal file
5
src/components/authorize-form/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import { Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export const AuthorizeForm = Toolkit.withInstall(view);
|
||||||
|
export default AuthorizeForm;
|
||||||
64
src/components/authorize-form/index.vue
Normal file
64
src/components/authorize-form/index.vue
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tui-authorize-form">
|
||||||
|
<t-tabs class="tab" v-model="tabValue">
|
||||||
|
<t-tab-panel :value="Tab.LOGIN" label="登录" />
|
||||||
|
<t-tab-panel :value="Tab.REGISTER" label="注册" />
|
||||||
|
</t-tabs>
|
||||||
|
<div class="content">
|
||||||
|
<login v-if="tabValue == Tab.LOGIN" @login-success="onLoginSuccess" />
|
||||||
|
<register v-if="tabValue == Tab.REGISTER" @register-success="onRegisterSuccess" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Login from "./login.vue";
|
||||||
|
import Register from "./register.vue";
|
||||||
|
import { Emits, useHandler } from "~/components/authorize-form/emits";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "AuthorizeForm"
|
||||||
|
});
|
||||||
|
|
||||||
|
enum Tab {
|
||||||
|
LOGIN = "LOGIN",
|
||||||
|
REGISTER = "REGISTER"
|
||||||
|
}
|
||||||
|
|
||||||
|
const emits = defineEmits<Emits>();
|
||||||
|
const { onLoginSuccess, onRegisterSuccess } = useHandler(emits);
|
||||||
|
|
||||||
|
const tabValue = ref(Tab.LOGIN);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-authorize-form {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
width: 16rem;
|
||||||
|
background: transparent;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
:deep(.t-tabs__nav-wrap) {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.t-tabs__nav-item {
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
.t-tabs__nav-item-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 20rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
106
src/components/authorize-form/login.vue
Normal file
106
src/components/authorize-form/login.vue
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<template>
|
||||||
|
<t-form class="tui-login-form" :data="loginRequest" :rules="rules" @submit="doSubmit">
|
||||||
|
<t-form-item label="用户" name="user">
|
||||||
|
<t-input class="user" v-model="loginRequest.data.user" placeholder="UID、邮箱或用户名" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="密码" name="password">
|
||||||
|
<t-input type="password" v-model="loginRequest.data.password" placeholder="" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="验证码" name="captcha">
|
||||||
|
<div class="captcha-box">
|
||||||
|
<t-input class="captcha" v-model="loginRequest.captcha" placeholder="" />
|
||||||
|
<captcha :api="CommonAPI.getCaptchaAPI()" :width="90" :height="28" :from="CaptchaFrom.LOGIN" />
|
||||||
|
</div>
|
||||||
|
</t-form-item>
|
||||||
|
<div class="exec">
|
||||||
|
<t-button
|
||||||
|
class="button"
|
||||||
|
type="submit"
|
||||||
|
:loading="doSubmitting"
|
||||||
|
:disabled="doSubmitting"
|
||||||
|
>登录</t-button>
|
||||||
|
</div>
|
||||||
|
</t-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Captcha, CaptchaData, CaptchaFrom, CommonAPI, LoginRequest, UserAPI, userStore } from "timi-web";
|
||||||
|
import { Emits, useHandler } from "~/components/authorize-form/emits";
|
||||||
|
import { MessagePlugin } from "tdesign-vue-next";
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
user: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入用户名"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入密码"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
captcha: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入验证码"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const emits = defineEmits<Emits>();
|
||||||
|
const { onLoginSuccess } = useHandler(emits);
|
||||||
|
|
||||||
|
const doSubmitting = ref<boolean>(false);
|
||||||
|
|
||||||
|
const loginRequest = reactive<CaptchaData<LoginRequest>>({
|
||||||
|
from: CaptchaFrom.LOGIN,
|
||||||
|
captcha: "",
|
||||||
|
data: {
|
||||||
|
user: "",
|
||||||
|
password: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const doSubmit = async () => {
|
||||||
|
doSubmitting.value = true;
|
||||||
|
UserAPI.login(loginRequest).then(async (response) => {
|
||||||
|
await userStore.updateToken(response);
|
||||||
|
await onLoginSuccess();
|
||||||
|
doSubmitting.value = false;
|
||||||
|
}).catch(msg => {
|
||||||
|
MessagePlugin.error(msg, 5E3);
|
||||||
|
doSubmitting.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-login-form {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-left: -32px;
|
||||||
|
|
||||||
|
.captcha-box {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.captcha {
|
||||||
|
width: 7rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.exec {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 2rem;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 8rem;
|
||||||
|
margin-left: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
142
src/components/authorize-form/register.vue
Normal file
142
src/components/authorize-form/register.vue
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<template>
|
||||||
|
<t-form class="tui-register-form" :data="formData.data" :rules="rules" @submit="doSubmit">
|
||||||
|
<t-form-item label="昵称" name="name">
|
||||||
|
<t-input class="name" v-model="formData.data.name" placeholder="" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="密码" name="password">
|
||||||
|
<t-input type="password" v-model="formData.data.password" placeholder="" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="确认密码" name="rePassword">
|
||||||
|
<t-input type="password" v-model="formData.data.rePassword" placeholder="" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="邮箱" name="email">
|
||||||
|
<t-input type="text" v-model="formData.data.email" placeholder="选填" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="验证码" name="captcha">
|
||||||
|
<div class="captcha-box">
|
||||||
|
<t-input class="captcha" v-model="formData.captcha" placeholder="" />
|
||||||
|
<captcha :api="CommonAPI.getCaptchaAPI()" :width="90" :height="28" :from="CaptchaFrom.REGISTER" />
|
||||||
|
</div>
|
||||||
|
</t-form-item>
|
||||||
|
<div class="exec">
|
||||||
|
<t-button
|
||||||
|
class="button"
|
||||||
|
type="submit"
|
||||||
|
:loading="doSubmitting"
|
||||||
|
:disabled="doSubmitting"
|
||||||
|
>注册</t-button>
|
||||||
|
</div>
|
||||||
|
</t-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Captcha, CaptchaData, CaptchaFrom, CommonAPI, RegisterRequest, UserAPI, userStore } from "timi-web";
|
||||||
|
import { FormRules, MessagePlugin, SubmitContext } from "tdesign-vue-next";
|
||||||
|
import { Emits, useHandler } from "~/components/authorize-form/emits";
|
||||||
|
|
||||||
|
type RegisterForm = {
|
||||||
|
rePassword: string;
|
||||||
|
} & RegisterRequest
|
||||||
|
|
||||||
|
const emits = defineEmits<Emits>();
|
||||||
|
const { onRegisterSuccess } = useHandler(emits);
|
||||||
|
|
||||||
|
const doSubmitting = ref<boolean>(false);
|
||||||
|
|
||||||
|
const formData = reactive<CaptchaData<RegisterForm>>({
|
||||||
|
from: CaptchaFrom.LOGIN,
|
||||||
|
captcha: "",
|
||||||
|
data: {
|
||||||
|
name: "",
|
||||||
|
password: "",
|
||||||
|
rePassword: "",
|
||||||
|
email: undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入昵称",
|
||||||
|
type: "error"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入密码",
|
||||||
|
type: "error"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
rePassword: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入确认密码",
|
||||||
|
type: "error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validator: (val: string) => new Promise((resolve) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
resolve(formData.data.password === val);
|
||||||
|
clearTimeout(timer);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
message: "两次密码不一致"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
captcha: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入验证码",
|
||||||
|
type: "error"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as FormRules;
|
||||||
|
|
||||||
|
const doSubmit = ({validateResult, e}: SubmitContext) => {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (validateResult === true) {
|
||||||
|
doSubmitting.value = true;
|
||||||
|
UserAPI.register({...formData}).then(async (response) => {
|
||||||
|
await userStore.updateToken(response);
|
||||||
|
await onRegisterSuccess();
|
||||||
|
doSubmitting.value = false;
|
||||||
|
}).catch(msg => {
|
||||||
|
MessagePlugin.error(msg, 5E3);
|
||||||
|
doSubmitting.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-register-form {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-left: -32px;
|
||||||
|
|
||||||
|
.captcha-box {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.captcha {
|
||||||
|
width: 7rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.exec {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 2rem;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 8rem;
|
||||||
|
margin-left: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
17
src/components/comment/form/emits.ts
Normal file
17
src/components/comment/form/emits.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Comment } from "timi-web";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* emits
|
||||||
|
*/
|
||||||
|
export interface Emits {
|
||||||
|
(event: "submit"): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHandler = (emit: Emits) => {
|
||||||
|
|
||||||
|
const onSubmit = async (comment: Comment) => await emit("submit");
|
||||||
|
|
||||||
|
return {
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
};
|
||||||
5
src/components/comment/form/index.ts
Normal file
5
src/components/comment/form/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import { Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export const CommentForm = Toolkit.withInstall(view);
|
||||||
|
export default CommentForm;
|
||||||
114
src/components/comment/form/index.vue
Normal file
114
src/components/comment/form/index.vue
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<t-form class="tui-comment-form">
|
||||||
|
<t-form-item label="昵称" name="nick">
|
||||||
|
<t-input
|
||||||
|
class="nick"
|
||||||
|
v-model="formData.data.nick"
|
||||||
|
:disabled="userStore.isLogged()"
|
||||||
|
placeholder="昵称"
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="评论" name="content">
|
||||||
|
<markdown-editor v-model:data="formData.data.content" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="验证码" name="captcha">
|
||||||
|
<t-input class="captcha" v-model="formData.captcha" placeholder="" />
|
||||||
|
<captcha
|
||||||
|
ref="captchaRef"
|
||||||
|
class="captcha-img"
|
||||||
|
:api="CommonAPI.getCaptchaAPI()"
|
||||||
|
:width="90"
|
||||||
|
:height="28"
|
||||||
|
:from="CaptchaFrom.COMMENT"
|
||||||
|
/>
|
||||||
|
<t-button
|
||||||
|
type="submit"
|
||||||
|
:loading="doSubmitting"
|
||||||
|
:disabled="doSubmitting"
|
||||||
|
@click="doSubmit"
|
||||||
|
>提交</t-button>
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
Captcha,
|
||||||
|
CaptchaData,
|
||||||
|
CaptchaFrom,
|
||||||
|
Comment,
|
||||||
|
CommentAPI,
|
||||||
|
CommentBizType,
|
||||||
|
CommonAPI,
|
||||||
|
MarkdownEditor,
|
||||||
|
userStore
|
||||||
|
} from "timi-web";
|
||||||
|
import { Emits, useHandler } from "~/components/comment/form/emits";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "CommentForm"
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits<Emits>();
|
||||||
|
const { onSubmit } = useHandler(emits);
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
bizType: CommentBizType,
|
||||||
|
bizId: number,
|
||||||
|
}>(), {});
|
||||||
|
const { bizType, bizId } = toRefs(props);
|
||||||
|
|
||||||
|
const captchaRef = ref<{ update: () => void}>();
|
||||||
|
const formData = reactive<CaptchaData<Comment>>({
|
||||||
|
from: CaptchaFrom.COMMENT,
|
||||||
|
captcha: "",
|
||||||
|
data: {
|
||||||
|
bizType: bizType.value,
|
||||||
|
bizId: bizId.value,
|
||||||
|
nick: "",
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const doSubmitting = ref(false);
|
||||||
|
|
||||||
|
async function doSubmit() {
|
||||||
|
doSubmitting.value = true;
|
||||||
|
|
||||||
|
const loginUser = userStore.loginUser;
|
||||||
|
if (userStore.isLogged() && loginUser.user) {
|
||||||
|
formData.data.nick = loginUser.user.name;
|
||||||
|
formData.data.userId = loginUser.user.id;
|
||||||
|
}
|
||||||
|
await CommentAPI.create(formData);
|
||||||
|
formData.captcha = "";
|
||||||
|
formData.data.content = "";
|
||||||
|
captchaRef.value?.update();
|
||||||
|
|
||||||
|
doSubmitting.value = false;
|
||||||
|
|
||||||
|
await onSubmit(formData.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录用户
|
||||||
|
const updateNick = () => formData.data.nick = userStore.loginUser?.user?.name || "";
|
||||||
|
watch(userStore.loginUser, updateNick);
|
||||||
|
onMounted(updateNick);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-comment-form {
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
.nick {
|
||||||
|
width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha {
|
||||||
|
width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-img {
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
src/components/comment/form/reply/emits.ts
Normal file
20
src/components/comment/form/reply/emits.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { CommentReply } from "timi-web";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* emits
|
||||||
|
*/
|
||||||
|
export interface Emits {
|
||||||
|
(event: "cancel"): Promise<void>;
|
||||||
|
(event: "submit", reply: CommentReply): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHandler = (emit: Emits) => {
|
||||||
|
|
||||||
|
const onCancel = async () => await emit("cancel");
|
||||||
|
const onSubmit = async (reply: CommentReply) => await emit("submit", reply);
|
||||||
|
|
||||||
|
return {
|
||||||
|
onCancel,
|
||||||
|
onSubmit
|
||||||
|
};
|
||||||
|
};
|
||||||
5
src/components/comment/form/reply/index.ts
Normal file
5
src/components/comment/form/reply/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import { Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export const CommentReplyForm = Toolkit.withInstall(view);
|
||||||
|
export default CommentReplyForm;
|
||||||
168
src/components/comment/form/reply/index.vue
Normal file
168
src/components/comment/form/reply/index.vue
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<t-form class="tui-comment-reply-form">
|
||||||
|
<t-form-item label="昵称" name="nick">
|
||||||
|
<t-input
|
||||||
|
ref="nickRef"
|
||||||
|
class="nick"
|
||||||
|
v-model="formData.data.senderNick"
|
||||||
|
placeholder="昵称"
|
||||||
|
:disabled="userStore.isLogged()"
|
||||||
|
/>
|
||||||
|
<span v-if="replyToNick" class="gray selectable clip-text" v-text="` 回复 ${replyToNick}`"></span>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="评论" name="content">
|
||||||
|
<markdown-editor ref="contentRef" v-model:data="formData.data.content" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="验证码" name="captcha">
|
||||||
|
<div class="captcha-box">
|
||||||
|
<div>
|
||||||
|
<t-input class="captcha" v-model="formData.captcha" placeholder="" />
|
||||||
|
<captcha
|
||||||
|
ref="captchaRef"
|
||||||
|
class="captcha-img"
|
||||||
|
:api="CommonAPI.getCaptchaAPI()"
|
||||||
|
:width="90"
|
||||||
|
:height="28"
|
||||||
|
:from="CaptchaFrom.COMMENT_REPLY"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<t-button
|
||||||
|
class="submit"
|
||||||
|
type="submit"
|
||||||
|
:loading="doSubmitting"
|
||||||
|
:disabled="doSubmitting"
|
||||||
|
@click="doSubmit"
|
||||||
|
>提交</t-button>
|
||||||
|
<t-button theme="default" @click="onCancel">取消</t-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t-form-item>
|
||||||
|
</t-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
Captcha,
|
||||||
|
CaptchaData,
|
||||||
|
CaptchaFrom,
|
||||||
|
CommentAPI,
|
||||||
|
CommentReply,
|
||||||
|
CommonAPI,
|
||||||
|
MarkdownEditor,
|
||||||
|
Scroller,
|
||||||
|
userStore
|
||||||
|
} from "timi-web";
|
||||||
|
import { Emits, useHandler } from "~/components/comment/form/reply/emits";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "CommentReplyForm"
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits<Emits>();
|
||||||
|
const { onCancel, onSubmit } = useHandler(emits);
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
commentId: number,
|
||||||
|
replyId?: number,
|
||||||
|
replyToNick?: string,
|
||||||
|
}>(), {});
|
||||||
|
const { commentId, replyId, replyToNick } = toRefs(props);
|
||||||
|
|
||||||
|
const doSubmitting = ref(false);
|
||||||
|
const nickRef = ref();
|
||||||
|
const contentRef = ref();
|
||||||
|
|
||||||
|
const formData = reactive<CaptchaData<CommentReply>>({
|
||||||
|
from: CaptchaFrom.COMMENT_REPLY,
|
||||||
|
captcha: "",
|
||||||
|
data: {
|
||||||
|
commentId: commentId.value,
|
||||||
|
replyId: replyId.value,
|
||||||
|
senderNick: "",
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 提交回复 */
|
||||||
|
async function doSubmit() {
|
||||||
|
doSubmitting.value = true;
|
||||||
|
|
||||||
|
formData.data.commentId = commentId.value;
|
||||||
|
formData.data.replyId = replyId.value;
|
||||||
|
const loginUser = userStore.loginUser;
|
||||||
|
if (userStore.isLogged() && loginUser.user) {
|
||||||
|
formData.data.senderNick = loginUser.user.name;
|
||||||
|
formData.data.senderId = loginUser.user.id;
|
||||||
|
}
|
||||||
|
await CommentAPI.createReply(formData);
|
||||||
|
formData.captcha = "";
|
||||||
|
await onSubmit(formData.data);
|
||||||
|
|
||||||
|
doSubmitting.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function focus() {
|
||||||
|
await nextTick();
|
||||||
|
let focusEl: HTMLElement;
|
||||||
|
if (userStore.isLogged()) {
|
||||||
|
focusEl = nickRef.value[0].textArea;
|
||||||
|
} else {
|
||||||
|
focusEl = contentRef.value[0].inputRef;
|
||||||
|
}
|
||||||
|
focusEl.focus();
|
||||||
|
Scroller.toElement(focusEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录用户
|
||||||
|
const updateNick = () => formData.data.senderNick = userStore.loginUser?.user?.name || "";
|
||||||
|
watch(userStore.loginUser, updateNick);
|
||||||
|
onMounted(updateNick);
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focus
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-comment-reply-form {
|
||||||
|
padding: 1rem 1rem 1rem 0;
|
||||||
|
|
||||||
|
.nick {
|
||||||
|
width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-box {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha {
|
||||||
|
width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-img {
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 650px) {
|
||||||
|
.tui-comment-reply-form {
|
||||||
|
|
||||||
|
.captcha-box {
|
||||||
|
|
||||||
|
> div:first-child {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
5
src/components/comment/index.ts
Normal file
5
src/components/comment/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import { Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export const Comment = Toolkit.withInstall(view);
|
||||||
|
export default Comment;
|
||||||
153
src/components/comment/index.vue
Normal file
153
src/components/comment/index.vue
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="canComment || (pageResult && 0 < pageResult.total)"
|
||||||
|
class="tui-comment"
|
||||||
|
:class="{ 'can-comment': canComment }"
|
||||||
|
>
|
||||||
|
<span ref="titleAnchorRef"></span>
|
||||||
|
<h4
|
||||||
|
class="title cur-pointer"
|
||||||
|
@click="Scroller.toElement(titleAnchorRef!)"
|
||||||
|
>评论</h4>
|
||||||
|
<comment-form
|
||||||
|
ref="commentFormRef"
|
||||||
|
v-if="canComment"
|
||||||
|
:bizType="bizType" :bizId="bizId"
|
||||||
|
@submit="onSubmit"
|
||||||
|
/>
|
||||||
|
<t-pagination
|
||||||
|
v-if="pageResult && 16 < pageResult.total"
|
||||||
|
class="pages top"
|
||||||
|
:total="pageResult.total"
|
||||||
|
:pageSize="16"
|
||||||
|
:showPageSize="false"
|
||||||
|
v-model:current="pageCurrent"
|
||||||
|
:onCurrentChange="doFetch"
|
||||||
|
>
|
||||||
|
<template #total-content>
|
||||||
|
<div class="total" v-text="`共 ${pageResult.total} 条评论`"></div>
|
||||||
|
</template>
|
||||||
|
</t-pagination>
|
||||||
|
<comment-list class="list" v-if="pageResult" :items="pageResult.list" />
|
||||||
|
<t-pagination
|
||||||
|
v-if="pageResult && 16 < pageResult.total"
|
||||||
|
class="pages bottom"
|
||||||
|
:total="pageResult.total"
|
||||||
|
:pageSize="16"
|
||||||
|
:showPageSize="false"
|
||||||
|
v-model:current="pageCurrent"
|
||||||
|
:onCurrentChange="doFetch"
|
||||||
|
>
|
||||||
|
<template #total-content>
|
||||||
|
<div class="total" v-text="`共 ${pageResult.total} 条评论`"></div>
|
||||||
|
</template>
|
||||||
|
</t-pagination>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
CommentAPI,
|
||||||
|
CommentBizType,
|
||||||
|
CommentPage,
|
||||||
|
CommentReplyBizType,
|
||||||
|
CommentView,
|
||||||
|
PageResult,
|
||||||
|
Scroller
|
||||||
|
} from "timi-web";
|
||||||
|
import { CommentForm, CommentList } from "~/components";
|
||||||
|
|
||||||
|
// 评论总成
|
||||||
|
defineOptions({
|
||||||
|
name: "Comment"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
bizType: CommentBizType,
|
||||||
|
bizId: number,
|
||||||
|
canComment?: boolean,
|
||||||
|
titleStickyOffset?: number,
|
||||||
|
}>(), {
|
||||||
|
canComment: true,
|
||||||
|
titleStickyOffset: 0
|
||||||
|
});
|
||||||
|
const { bizType, bizId, canComment } = toRefs(props);
|
||||||
|
|
||||||
|
// 数据列表
|
||||||
|
const page = reactive<CommentPage>({
|
||||||
|
bizType: bizType.value,
|
||||||
|
bizId: bizId.value,
|
||||||
|
index: 0,
|
||||||
|
size: 16
|
||||||
|
});
|
||||||
|
// 当前页
|
||||||
|
const pageCurrent = ref(1);
|
||||||
|
const pageResult = ref<PageResult<CommentView>>();
|
||||||
|
async function doFetch() {
|
||||||
|
page.index = pageCurrent.value - 1;
|
||||||
|
pageResult.value = await CommentAPI.page(page);
|
||||||
|
for (let i = 0; i < pageResult.value.list.length; i++) {
|
||||||
|
const item = pageResult.value.list[i];
|
||||||
|
item.repliesCurrent = 1;
|
||||||
|
item.repliesPage = {
|
||||||
|
bizId: item.id!,
|
||||||
|
bizType: CommentReplyBizType.COMMENT,
|
||||||
|
index: 0,
|
||||||
|
size: 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(doFetch);
|
||||||
|
|
||||||
|
const titleAnchorRef = ref<HTMLSpanElement>();
|
||||||
|
|
||||||
|
/** 提交评论 */
|
||||||
|
async function onSubmit() {
|
||||||
|
page.index = 0;
|
||||||
|
await doFetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-comment {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
top: v-bind("titleStickyOffset + 'px'");
|
||||||
|
margin: 0;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
z-index: 2;
|
||||||
|
position: sticky;
|
||||||
|
background: #E4EFFA;
|
||||||
|
box-shadow: 0 -1px 0 var(--tui-light-gray);
|
||||||
|
border-bottom: var(--tui-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.can-comment {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
border-top: 2px solid #CDDEF0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages {
|
||||||
|
padding: 4px 4px 4px 1rem;
|
||||||
|
|
||||||
|
&.top {
|
||||||
|
border-top: 1px solid #E4EFFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bottom {
|
||||||
|
border-bottom: 1px solid #E4EFFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
11
src/components/comment/list/emits.ts
Normal file
11
src/components/comment/list/emits.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* emits
|
||||||
|
*/
|
||||||
|
export interface Emits {
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHandler = (emit: Emits) => {
|
||||||
|
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
};
|
||||||
5
src/components/comment/list/index.ts
Normal file
5
src/components/comment/list/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import { Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export const CommentList = Toolkit.withInstall(view);
|
||||||
|
export default CommentList;
|
||||||
241
src/components/comment/list/index.vue
Normal file
241
src/components/comment/list/index.vue
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="items" class="tui-comment-list">
|
||||||
|
<div class="comment" v-for="comment in items" :key="comment.id">
|
||||||
|
<!-- 主评论 -->
|
||||||
|
<div class="user">
|
||||||
|
<!-- 主评论头像 -->
|
||||||
|
<template v-if="comment.user">
|
||||||
|
<a
|
||||||
|
:href="`/user/space/${comment.userId}`"
|
||||||
|
target="_blank"
|
||||||
|
v-popup:config="popupUserStore.config"
|
||||||
|
@mouseenter="popupUserStore.user.value = comment.user"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="avatar"
|
||||||
|
:class="(<any>ImageType)[comment.user.profile.avatarType]"
|
||||||
|
:src="UserAPI.getAvatarURL(comment.user.profile)"
|
||||||
|
alt="主评论头像"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="name pink selectable"
|
||||||
|
:href="`/user/space/${comment.userId}`"
|
||||||
|
v-text="comment.user.name"
|
||||||
|
v-popup:config="popupUserStore.config"
|
||||||
|
@mouseenter="popupUserStore.user.value = comment.user"
|
||||||
|
></a>
|
||||||
|
</template>
|
||||||
|
<!-- 游客的 -->
|
||||||
|
<template v-else>
|
||||||
|
<img class="avatar ir-pixelated" :src="defAvatarURL" alt="头像" />
|
||||||
|
<div class="name gray selectable clip-text" v-text="comment.nick"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<!-- 主评论内容 -->
|
||||||
|
<markdown-view class="value" :content="comment.content" />
|
||||||
|
<div class="reply-to">
|
||||||
|
<a class="button" href="javascript:" @click="replyTo(comment)">回复</a>
|
||||||
|
<span
|
||||||
|
class="light-gray"
|
||||||
|
v-text="Time.toPassedDate(comment.createdAt)"
|
||||||
|
v-popup:text="Time.toDateTime(comment.createdAt)"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<!-- 子评论 -->
|
||||||
|
<comment-reply-list :comment="comment" @reply="replyTo" />
|
||||||
|
<t-pagination
|
||||||
|
class="pages"
|
||||||
|
v-if="6 < comment.repliesLength"
|
||||||
|
:total="comment.repliesLength"
|
||||||
|
:pageSize="6"
|
||||||
|
:showPageSize="false"
|
||||||
|
v-model:current="comment.repliesCurrent"
|
||||||
|
@change="() => doFetchReply(comment)"
|
||||||
|
>
|
||||||
|
<template #total-content>
|
||||||
|
<div class="total" v-text="`共 ${comment.repliesLength} 条回复`"></div>
|
||||||
|
</template>
|
||||||
|
</t-pagination>
|
||||||
|
<!-- 回复表单,此表单跟随(页面上)激活的回复对象 -->
|
||||||
|
<comment-reply-form
|
||||||
|
v-if="canReply && comment.id && activeReply === comment.id"
|
||||||
|
:commentId="comment.id"
|
||||||
|
:replyId="replyId"
|
||||||
|
:replyToNick="replyToNick"
|
||||||
|
@submit="onSubmitReply"
|
||||||
|
@cancel="activeReply = -1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
CommentAPI,
|
||||||
|
CommentReply,
|
||||||
|
CommentReplyView,
|
||||||
|
CommentView,
|
||||||
|
CommonAPI,
|
||||||
|
ImageType,
|
||||||
|
MarkdownView,
|
||||||
|
SettingKey,
|
||||||
|
SettingMapper,
|
||||||
|
Time,
|
||||||
|
UserAPI
|
||||||
|
} from "timi-web";
|
||||||
|
import { CommentReplyForm, CommentReplyList } from "~/components";
|
||||||
|
import { popupUserStore } from "timi-tdesign-pc";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "CommentList"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
items: CommentView[],
|
||||||
|
canReply?: boolean,
|
||||||
|
}>(), {
|
||||||
|
canReply: true
|
||||||
|
});
|
||||||
|
const { items, canReply } = toRefs(props);
|
||||||
|
|
||||||
|
// 默认头像
|
||||||
|
const defAvatarURL = ref();
|
||||||
|
onMounted(async () => {
|
||||||
|
const res = JSON.parse(SettingMapper.getValue(SettingKey.PUBLIC_RESOURCES) as string);
|
||||||
|
defAvatarURL.value = CommonAPI.getAttachmentReadAPI(res.user.avatar);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 激活的回复评论下标
|
||||||
|
const activeReply = ref();
|
||||||
|
const replyId = ref();
|
||||||
|
// 被回复昵称
|
||||||
|
const replyToNick = ref();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取回复
|
||||||
|
*
|
||||||
|
* @param comment 所属评论
|
||||||
|
*/
|
||||||
|
async function doFetchReply(comment: CommentView) {
|
||||||
|
// 组件下标以 1 开始,分页参数以 0 开始
|
||||||
|
comment.repliesPage.index = comment.repliesCurrent - 1;
|
||||||
|
const result = await CommentAPI.pageReply(comment.repliesPage);
|
||||||
|
comment.replies.length = 0;
|
||||||
|
comment.replies.push(...result.list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发回复表单(表单会跟随评论)
|
||||||
|
*
|
||||||
|
* @param comment 跟随的评论
|
||||||
|
* @param replyTo 被回复对象
|
||||||
|
*/
|
||||||
|
async function replyTo(comment: CommentView, replyTo?: CommentReplyView) {
|
||||||
|
activeReply.value = comment.id;
|
||||||
|
if (replyTo) {
|
||||||
|
// 被回复对象
|
||||||
|
replyToNick.value = replyTo.sender?.name || replyTo.senderNick;
|
||||||
|
replyId.value = replyTo.id;
|
||||||
|
} else {
|
||||||
|
replyToNick.value = comment.user?.name || comment.nick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSubmitReply(reply: CommentReply) {
|
||||||
|
activeReply.value = -1;
|
||||||
|
|
||||||
|
const comment = items.value.find(comment => comment.id === reply.commentId)!;
|
||||||
|
comment.repliesLength++;
|
||||||
|
comment.repliesCurrent = Math.ceil(comment.repliesLength / 6); // 上取整最后一页
|
||||||
|
doFetchReply(comment);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-comment-list {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
display: flex;
|
||||||
|
line-height: 1.5;
|
||||||
|
border-bottom: 2px solid #CDDEF0;
|
||||||
|
|
||||||
|
.user {
|
||||||
|
width: 120px;
|
||||||
|
padding: 16px 0;
|
||||||
|
text-align: center;
|
||||||
|
background: #E4EFFA;
|
||||||
|
transition: .5s var(--tui-bezier);
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 64px;
|
||||||
|
border: 1px solid #FBC7D4;
|
||||||
|
margin: 0 auto 10px auto;
|
||||||
|
padding: 3px;
|
||||||
|
background: #FFF;
|
||||||
|
transition: .5s var(--tui-bezier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: calc(100% - 120px);
|
||||||
|
transition: .5s var(--tui-bezier);
|
||||||
|
|
||||||
|
.value {
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
padding: .5rem;
|
||||||
|
min-height: 92px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages {
|
||||||
|
padding: 4px 4px 4px 1rem;
|
||||||
|
border-top: 1px solid #E4EFFA;
|
||||||
|
|
||||||
|
.total {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-to {
|
||||||
|
display: flex;
|
||||||
|
padding-right: .5rem;
|
||||||
|
justify-content: end;
|
||||||
|
|
||||||
|
> a {
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 650px) {
|
||||||
|
.tui-comment-list {
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
|
||||||
|
.user {
|
||||||
|
width: 60px;
|
||||||
|
padding: 8px 0;
|
||||||
|
transition: .5s var(--tui-bezier);
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 32px;
|
||||||
|
margin: 0 auto 4px auto;
|
||||||
|
padding: 2px;
|
||||||
|
transition: .5s var(--tui-bezier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
transition: .5s var(--tui-bezier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
17
src/components/comment/list/reply/emits.ts
Normal file
17
src/components/comment/list/reply/emits.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { CommentReplyView, CommentView } from "timi-web";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* emits
|
||||||
|
*/
|
||||||
|
export interface Emits {
|
||||||
|
(event: "reply", comment: CommentView, replyTo?: CommentReplyView): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHandler = (emit: Emits) => {
|
||||||
|
|
||||||
|
const onReply = async (comment: CommentView, replyTo?: CommentReplyView) => await emit("reply", comment, replyTo);
|
||||||
|
|
||||||
|
return {
|
||||||
|
onReply,
|
||||||
|
};
|
||||||
|
};
|
||||||
5
src/components/comment/list/reply/index.ts
Normal file
5
src/components/comment/list/reply/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import { Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export const CommentReplyList = Toolkit.withInstall(view);
|
||||||
|
export default CommentReplyList;
|
||||||
179
src/components/comment/list/reply/index.vue
Normal file
179
src/components/comment/list/reply/index.vue
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tui-comment-reply-list">
|
||||||
|
<div v-if="comment && comment.replies" class="reply" v-for="reply in comment.replies" :key="reply.id">
|
||||||
|
<div class="header">
|
||||||
|
<!-- 发送者头像 -->
|
||||||
|
<div class="sender-avatar">
|
||||||
|
<a
|
||||||
|
v-if="reply.sender"
|
||||||
|
:href="`/user/space/${reply.senderId}`"
|
||||||
|
v-popup:config="popupUserStore.config"
|
||||||
|
@mouseenter="popupUserStore.user.value = reply.sender"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:class="(<any>ImageType)[reply.sender.profile.avatarType]"
|
||||||
|
:src="UserAPI.getAvatarURL(reply.sender.profile)"
|
||||||
|
alt="回复发送者头像"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<img v-else class="ir-pixelated" :src="defAvatarURL" alt="回复发送者头像" />
|
||||||
|
</div>
|
||||||
|
<!-- 被回复用户 -->
|
||||||
|
<a
|
||||||
|
v-if="reply.sender"
|
||||||
|
class="pink bold selectable"
|
||||||
|
:href="`/user/space/${reply.senderId}`"
|
||||||
|
target="_blank"
|
||||||
|
v-text="reply.sender.name"
|
||||||
|
v-popup:config="popupUserStore.config"
|
||||||
|
@mouseenter="popupUserStore.user.value = reply.sender"
|
||||||
|
></a>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="gray selectable"
|
||||||
|
v-text="reply.senderNick"
|
||||||
|
></span>
|
||||||
|
<!-- 非回复层主时才显示被回复用户 -->
|
||||||
|
<template v-if="!!reply.replyId">
|
||||||
|
<span> 回复 </span>
|
||||||
|
<!-- 被回复用户 -->
|
||||||
|
<a
|
||||||
|
v-if="reply.receiver"
|
||||||
|
class="pink bold selectable"
|
||||||
|
:href="`/user/space/${reply.receiverId}`"
|
||||||
|
target="_blank"
|
||||||
|
v-text="reply.receiver.name"
|
||||||
|
v-popup:config="popupUserStore.config"
|
||||||
|
@mouseenter="popupUserStore.user.value = reply.receiver"
|
||||||
|
></a>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="gray selectable"
|
||||||
|
v-text="reply.receiverNick"
|
||||||
|
></span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<!-- 回复内容 -->
|
||||||
|
<markdown-view class="content" :content="reply.content" />
|
||||||
|
<!-- 其他回复信息 -->
|
||||||
|
<div class="reply-to">
|
||||||
|
<a href="javascript:" @click="onReply(comment, reply)">回复</a>
|
||||||
|
<span
|
||||||
|
class="light-gray"
|
||||||
|
v-text="Time.toPassedDate(reply.createdAt)"
|
||||||
|
v-popup:text="Time.toDateTime(reply.createdAt)"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
CommentView,
|
||||||
|
CommonAPI,
|
||||||
|
ImageType,
|
||||||
|
MarkdownView,
|
||||||
|
PublicResources,
|
||||||
|
SettingKey,
|
||||||
|
SettingMapper,
|
||||||
|
Time,
|
||||||
|
UserAPI
|
||||||
|
} from "timi-web";
|
||||||
|
import { Emits, useHandler } from "~/components/comment/list/reply/emits";
|
||||||
|
import { popupUserStore } from "~/store/popupUser";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "CommentReplyList"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
comment: CommentView,
|
||||||
|
}>(), {});
|
||||||
|
const { comment } = toRefs(props);
|
||||||
|
|
||||||
|
const emits = defineEmits<Emits>();
|
||||||
|
const { onReply } = useHandler(emits);
|
||||||
|
|
||||||
|
// 默认头像
|
||||||
|
const defAvatarURL = ref();
|
||||||
|
onMounted(async () => {
|
||||||
|
const res = JSON.parse(SettingMapper.getValue(SettingKey.PUBLIC_RESOURCES) as string) as PublicResources;
|
||||||
|
defAvatarURL.value = CommonAPI.getAttachmentReadAPI(res.user.avatar);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-comment-reply-list {
|
||||||
|
|
||||||
|
.reply {
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
&:nth-child(n + 2) {
|
||||||
|
border-top: 1px #d7d7d7 dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top: 2px solid #E4EFFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.sender-avatar {
|
||||||
|
border: 1px solid #FBC7D4;
|
||||||
|
padding: 1px;
|
||||||
|
margin-right: .5rem;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
transition: .5s var(--tui-bezier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-to {
|
||||||
|
display: flex;
|
||||||
|
padding-right: 0;
|
||||||
|
justify-content: end;
|
||||||
|
|
||||||
|
> a {
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: 650px) {
|
||||||
|
.tui-comment-reply-list {
|
||||||
|
|
||||||
|
.reply {
|
||||||
|
padding: 3px;
|
||||||
|
transition: .5s var(--tui-bezier);
|
||||||
|
|
||||||
|
.header {
|
||||||
|
|
||||||
|
.sender-avatar {
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
transition: .5s var(--tui-bezier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
34
src/components/index.ts
Normal file
34
src/components/index.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/** 导出所有组件 */
|
||||||
|
import AuthorizeForm from "~/components/authorize-form";
|
||||||
|
import RootLayout from "~/components/root-layout";
|
||||||
|
import Comment from "~/components/comment";
|
||||||
|
import CommentForm from "~/components/comment/form";
|
||||||
|
import CommentList from "~/components/comment/list";
|
||||||
|
import CommentReplyList from "~/components/comment/list/reply";
|
||||||
|
import CommentReplyForm from "~/components/comment/form/reply";
|
||||||
|
import UserProfile from "~/components/user-profile";
|
||||||
|
import UserProfilePopup from "~/components/user-profile/popup";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
RootLayout,
|
||||||
|
AuthorizeForm,
|
||||||
|
Comment,
|
||||||
|
CommentForm,
|
||||||
|
CommentReplyForm,
|
||||||
|
CommentList,
|
||||||
|
CommentReplyList,
|
||||||
|
UserProfile,
|
||||||
|
UserProfilePopup
|
||||||
|
];
|
||||||
|
|
||||||
|
export {
|
||||||
|
RootLayout,
|
||||||
|
AuthorizeForm,
|
||||||
|
Comment,
|
||||||
|
CommentForm,
|
||||||
|
CommentReplyForm,
|
||||||
|
CommentList,
|
||||||
|
CommentReplyList,
|
||||||
|
UserProfile,
|
||||||
|
UserProfilePopup
|
||||||
|
};
|
||||||
5
src/components/root-layout/index.ts
Normal file
5
src/components/root-layout/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import { Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export const RootLayout = Toolkit.withInstall(view);
|
||||||
|
export default RootLayout;
|
||||||
59
src/components/root-layout/index.vue
Normal file
59
src/components/root-layout/index.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tui-root-layout">
|
||||||
|
<slot></slot>
|
||||||
|
<copyright :icp="icp" :domain="domain" :author="author" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Copyright } from "timi-web";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "RootLayout"
|
||||||
|
});
|
||||||
|
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
icp?: string;
|
||||||
|
domain?: string;
|
||||||
|
author?: string;
|
||||||
|
}>(), {
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-root-layout {
|
||||||
|
border: var(--tui-border);
|
||||||
|
margin: 128px 80px 128px 0;
|
||||||
|
display: flex;
|
||||||
|
min-height: 520px;
|
||||||
|
background: WHITE;
|
||||||
|
transition: width 500ms var(--tui-bezier), margin 500ms var(--tui-bezier), min-height 500ms var(--tui-bezier);
|
||||||
|
box-shadow: var(--tui-shadow);
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自适应 */
|
||||||
|
@media screen and (max-width: 2560px) {
|
||||||
|
.tui-root-layout {
|
||||||
|
width: 1200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1920px) {
|
||||||
|
.tui-root-layout {
|
||||||
|
width: 900px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1200px) {
|
||||||
|
.tui-root-layout {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
5
src/components/user-profile/index.ts
Normal file
5
src/components/user-profile/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import { Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export const UserProfile = Toolkit.withInstall(view);
|
||||||
|
export default UserProfile;
|
||||||
96
src/components/user-profile/index.vue
Normal file
96
src/components/user-profile/index.vue
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tui-user-profile">
|
||||||
|
<template v-if="user">
|
||||||
|
<img
|
||||||
|
:class="(<any>ImageType)[user.profile.wrapperType]"
|
||||||
|
:src="UserAPI.getWrapperURL(user.profile)"
|
||||||
|
width="320"
|
||||||
|
alt="背景"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="avatar"
|
||||||
|
:class="(<any>ImageType)[user.profile.avatarType]"
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
:src="UserAPI.getAvatarURL(user.profile)"
|
||||||
|
alt="头像"
|
||||||
|
/>
|
||||||
|
<div class="info">
|
||||||
|
<div class="name">
|
||||||
|
<h3 class="value pink" v-text="user.name"></h3>
|
||||||
|
<user-level :value="Toolkit.toUserLevel(user.profile.exp).value" />
|
||||||
|
</div>
|
||||||
|
<template v-if="user.profile.sex">
|
||||||
|
<icon
|
||||||
|
v-if="user.profile.sex === 1"
|
||||||
|
class="sex boy"
|
||||||
|
name="BOY"
|
||||||
|
fill="var(--tui-blue)"
|
||||||
|
v-popup="`男生`"
|
||||||
|
/>
|
||||||
|
<icon
|
||||||
|
v-else
|
||||||
|
class="sex girl"
|
||||||
|
name="GIRL"
|
||||||
|
fill="var(--tui-pink)"
|
||||||
|
v-popup="`女生`"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<p class="description" v-text="user.profile.description"></p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Icon, ImageType, Toolkit, UserAPI, UserLevel, UserView } from "timi-web";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "UserProfile"
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
user?: UserView,
|
||||||
|
}>(), {
|
||||||
|
});
|
||||||
|
const { user } = toRefs(props);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-user-profile {
|
||||||
|
width: 320px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
margin: -32px 0 0 16px;
|
||||||
|
border: 1px solid #CDDEF0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
margin-top: 4px;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin: 0 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 22px;
|
||||||
|
padding-left: 84px;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
margin: 0 .5rem 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sex {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
padding: 0 16px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
5
src/components/user-profile/popup/index.ts
Normal file
5
src/components/user-profile/popup/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import view from "./index.vue";
|
||||||
|
import { Toolkit } from "timi-web";
|
||||||
|
|
||||||
|
export const UserProfilePopup = Toolkit.withInstall(view);
|
||||||
|
export default UserProfilePopup;
|
||||||
24
src/components/user-profile/popup/index.vue
Normal file
24
src/components/user-profile/popup/index.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="rootEl" class="tui-user-profile-popup">
|
||||||
|
<user-profile :user="popupUserStore.user.value" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import UserProfile from "~/components/user-profile";
|
||||||
|
import { popupUserStore } from "~/store/popupUser";
|
||||||
|
|
||||||
|
const rootEl = ref<HTMLDivElement>();
|
||||||
|
onMounted(() => {
|
||||||
|
if (rootEl.value) {
|
||||||
|
popupUserStore.config.value = rootEl.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tui-user-profile-popup {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
9
src/index.ts
Normal file
9
src/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import "./assets/style/tencent-cloud.less";
|
||||||
|
import "./assets/style/tencent-cloud-custom.less";
|
||||||
|
import { popupUserStore } from "./store/popupUser";
|
||||||
|
|
||||||
|
export * from "./components";
|
||||||
|
|
||||||
|
export {
|
||||||
|
popupUserStore
|
||||||
|
};
|
||||||
21
src/store/popupUser.ts
Normal file
21
src/store/popupUser.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { PopupConfig, PopupType, UserView } from "timi-web";
|
||||||
|
|
||||||
|
const user = ref<UserView>();
|
||||||
|
|
||||||
|
const config = reactive<PopupConfig>({
|
||||||
|
type: PopupType.EL,
|
||||||
|
value: undefined,
|
||||||
|
canShow: () => true,
|
||||||
|
afterHidden: async () => {
|
||||||
|
user.value = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const popupUserStore = {
|
||||||
|
user,
|
||||||
|
config
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
popupUserStore
|
||||||
|
};
|
||||||
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-tdesign-pc": [
|
||||||
|
"./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"
|
||||||
|
]
|
||||||
|
}
|
||||||
98
vite.config.ts
Normal file
98
vite.config.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { resolve } from "path";
|
||||||
|
import { Alias, defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import VueSetupExtend from "vite-plugin-vue-setup-extend";
|
||||||
|
import AutoImport from "unplugin-auto-import/vite";
|
||||||
|
import Components from "unplugin-vue-components/vite";
|
||||||
|
import { TDesignResolver } from "unplugin-vue-components/resolvers";
|
||||||
|
import dts from "vite-plugin-dts";
|
||||||
|
|
||||||
|
const alias: Alias[] = [
|
||||||
|
{
|
||||||
|
find: "@",
|
||||||
|
replacement: resolve(__dirname, "./examples")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "~",
|
||||||
|
replacement: resolve(__dirname, "./src")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "*",
|
||||||
|
replacement: resolve("")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: /^timi-tdesign-pc(\/(es|lib))?$/,
|
||||||
|
replacement: resolve(__dirname, "./src/index.ts")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
port: 3004,
|
||||||
|
host: true
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: "dist",
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, "./src/index.ts"),
|
||||||
|
name: "TimiTDesignPC",
|
||||||
|
fileName: "timi-tdesign-pc"
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [
|
||||||
|
"vue",
|
||||||
|
"timi-web",
|
||||||
|
"tdesign-vue-next"
|
||||||
|
],
|
||||||
|
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(),
|
||||||
|
AutoImport({
|
||||||
|
imports: [
|
||||||
|
"vue"
|
||||||
|
],
|
||||||
|
dts: "examples/auto-imports.d.ts",
|
||||||
|
eslintrc: {
|
||||||
|
enabled: true,
|
||||||
|
globalsPropValue: true
|
||||||
|
},
|
||||||
|
resolvers: [
|
||||||
|
TDesignResolver({
|
||||||
|
library: "vue-next"
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
dirs: [
|
||||||
|
"src/components"
|
||||||
|
],
|
||||||
|
resolvers: [
|
||||||
|
TDesignResolver({
|
||||||
|
library: "vue-next"
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user