添加文件
This commit is contained in:
commit
c7df07d1c5
|
@ -0,0 +1,28 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
# YiiReader
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](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.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
|
@ -0,0 +1,230 @@
|
||||||
|
[toc]
|
||||||
|
|
||||||
|
### 请求地址
|
||||||
|
|
||||||
|
## https://fe2kao.tiaozhan.com/api/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 用户模块
|
||||||
|
|
||||||
|
### 用户注册 `POST ${base}/user/registry`
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Luthics",
|
||||||
|
"password": "wenwen"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"name": "string",
|
||||||
|
"id": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"name": "Luthics",
|
||||||
|
"id": 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 用户登陆 `POST ${base}/user`
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"name": "string",
|
||||||
|
"id": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取用户登录信息 `GET ${base}/user/state`
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
无
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"id": "number",
|
||||||
|
"name": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 用户修改信息 `PUT ${base}/user`
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"password": "string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 用户登出 `DELETE ${base}/user`
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
无
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文章模块
|
||||||
|
|
||||||
|
### 获取文章列表 `GET ${base}/passagelist`
|
||||||
|
**注意这里使用的参数传递方式为query传参,而非请求体!**
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type":"string", // 可选"ch"||"en",参数为可选,若传递该参数则获取全部文章列表
|
||||||
|
"page": "number",
|
||||||
|
"limit": "number",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"total": "number",//实际符合条件总数
|
||||||
|
"passages": [
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"title": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 获取文章内容 `GET ${base}/passage/:id`
|
||||||
|
**注意这里使用的参数传递方式为params传参,而非请求体!**
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id":"number"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success":true,
|
||||||
|
"data": {
|
||||||
|
"id":"number",
|
||||||
|
"title":"string",
|
||||||
|
"content":["string", ...], //按照段落分成数组
|
||||||
|
"comments":[
|
||||||
|
{
|
||||||
|
"id":"number",
|
||||||
|
"paragraph":"number", //评论的段落
|
||||||
|
"user":{
|
||||||
|
"name":"string" //评论者用户名
|
||||||
|
},
|
||||||
|
"marked":"string", //标记的文字
|
||||||
|
"comment":"string", //用户的评论
|
||||||
|
"createAt":"string", //创建时间
|
||||||
|
"updatedAt":"string" //修改时间
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 发布一条评论 `POST ${base}/comment`
|
||||||
|
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"passageId":"number",
|
||||||
|
"paragraph":"number",
|
||||||
|
"marked":"string",
|
||||||
|
"comment":"string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success":true,
|
||||||
|
"data":{
|
||||||
|
"id":"number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 修改一条评论 `PUT ${base}/comment`
|
||||||
|
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id":"number",
|
||||||
|
"comment":"string"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success":true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 删除一条评论 `DELETE ${base}/comment`
|
||||||
|
**注意这里使用的参数传递方式为params传参,而非请求体!**
|
||||||
|
#### request
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id":"number"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success":true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误请求
|
||||||
|
### 所有类型的错误请求将返回一个包含错误信息的返回体
|
||||||
|
#### response
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error":"string"
|
||||||
|
}
|
||||||
|
```
|
Binary file not shown.
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"name": "yiireader",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check build-only",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.2.1",
|
||||||
|
"vue": "^3.2.45",
|
||||||
|
"vue-axios": "^3.5.2",
|
||||||
|
"vue-cli": "^2.9.6",
|
||||||
|
"vue-cookies": "^1.8.2",
|
||||||
|
"vue-router": "^4.1.6",
|
||||||
|
"vuex": "^4.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18.11.12",
|
||||||
|
"@vicons/antd": "^0.12.0",
|
||||||
|
"@vicons/carbon": "^0.12.0",
|
||||||
|
"@vicons/fa": "^0.12.0",
|
||||||
|
"@vicons/fluent": "^0.12.0",
|
||||||
|
"@vicons/ionicons4": "^0.12.0",
|
||||||
|
"@vicons/ionicons5": "^0.12.0",
|
||||||
|
"@vicons/material": "^0.12.0",
|
||||||
|
"@vicons/tabler": "^0.12.0",
|
||||||
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"naive-ui": "^2.34.2",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"typescript": "~4.7.4",
|
||||||
|
"vfonts": "^0.0.3",
|
||||||
|
"vite": "^4.0.0",
|
||||||
|
"vue-tsc": "^1.0.12"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,48 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterLink, RouterView } from 'vue-router'
|
||||||
|
import { zhCN, dateZhCN } from 'naive-ui'
|
||||||
|
import { NConfigProvider, darkTheme, NMessageProvider,NLoadingBarProvider } from 'naive-ui'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
import HeaderView from './views/HeaderView.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-config-provider :locale="zhCN" :date-locale="dateZhCN" :theme="darkTheme" :class='{
|
||||||
|
top: router.currentRoute.value.path != "/login" && router.currentRoute.value.path != "/edit"
|
||||||
|
}'>
|
||||||
|
<n-message-provider>
|
||||||
|
<n-loading-bar-provider>
|
||||||
|
<HeaderView></HeaderView>
|
||||||
|
<RouterView></RouterView>
|
||||||
|
</n-loading-bar-provider>
|
||||||
|
</n-message-provider>
|
||||||
|
</n-config-provider>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <div class="left">
|
||||||
|
<router-view name="left"></router-view>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CenterCol />
|
||||||
|
<div class="right">
|
||||||
|
<router-view name="right"></router-view>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.top {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-left: calc(var(--section-gap) / 2);
|
||||||
|
} */
|
||||||
|
</style>
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
|
:root {
|
||||||
|
--vt-c-white: #ffffff;
|
||||||
|
--vt-c-white-soft: #f8f8f8;
|
||||||
|
--vt-c-white-mute: #f2f2f2;
|
||||||
|
|
||||||
|
--vt-c-black: #181818;
|
||||||
|
--vt-c-black-soft: #222222;
|
||||||
|
--vt-c-black-mute: #282828;
|
||||||
|
|
||||||
|
--vt-c-indigo: #2c3e50;
|
||||||
|
|
||||||
|
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||||
|
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||||
|
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||||
|
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||||
|
|
||||||
|
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||||
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* semantic color variables for this project */
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-white);
|
||||||
|
--color-background-soft: var(--vt-c-white-soft);
|
||||||
|
--color-background-mute: var(--vt-c-white-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-light-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-light-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-light-1);
|
||||||
|
--color-text: var(--vt-c-text-light-1);
|
||||||
|
|
||||||
|
--section-gap: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-black);
|
||||||
|
--color-background-soft: var(--vt-c-black-soft);
|
||||||
|
--color-background-mute: var(--vt-c-black-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-dark-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-dark-1);
|
||||||
|
--color-text: var(--vt-c-text-dark-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background);
|
||||||
|
transition: color 0.5s, background-color 0.5s;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||||
|
Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
After Width: | Height: | Size: 308 B |
|
@ -0,0 +1,38 @@
|
||||||
|
@import './base.css';
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
.green {
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsla(160, 100%, 37%, 1);
|
||||||
|
transition: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
a:hover {
|
||||||
|
background-color: hsla(160, 100%, 37%, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 2rem;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CommunityIcon from './icons/IconCommunity.vue'
|
||||||
|
import SupportIcon from './icons/IconSupport.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="col">
|
||||||
|
<div class="item">
|
||||||
|
<i>
|
||||||
|
<CommunityIcon />
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<i>
|
||||||
|
<SupportIcon />
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 0vw;
|
||||||
|
height: 100vh;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
justify-content:space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
place-content: center;
|
||||||
|
|
||||||
|
top: calc(50% - 25px);
|
||||||
|
left: -26px;
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.item:before {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:after {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:first-of-type:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:last-of-type:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createStore } from 'vuex'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import VueCookies from 'vue-cookies'
|
||||||
|
import axios from 'axios'
|
||||||
|
import VueAxios from 'vue-axios'
|
||||||
|
|
||||||
|
import './assets/main.css'
|
||||||
|
import 'vfonts/Lato.css'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
axios.defaults.baseURL = "/api"
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
id: null,
|
||||||
|
name: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
set(state, userInfo) {
|
||||||
|
state.id = userInfo.id
|
||||||
|
state.name = userInfo.name
|
||||||
|
},
|
||||||
|
clear(state) {
|
||||||
|
state.id = null
|
||||||
|
state.name = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
app.use(VueCookies)
|
||||||
|
app.use(VueAxios, axios)
|
||||||
|
app.use(store)
|
||||||
|
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import VueCookies from 'vue-cookies'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const cookies = VueCookies
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: () => import('../views/ListView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: () => import('../views/LoginView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/edit',
|
||||||
|
name: 'edit',
|
||||||
|
component: () => import('../views/EditView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
name: '404',
|
||||||
|
component: () => import('../views/404.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:lang([ch|en|all]+)/:page(\\d+)',
|
||||||
|
name: 'list',
|
||||||
|
component: () => import('../views/ListView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/page/:id(\\d+)',
|
||||||
|
name: 'page',
|
||||||
|
component: () => import('../views/ContentView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/page/:id(\\d+)/:para(\\d+)',
|
||||||
|
name: 'comment',
|
||||||
|
component: () => import('../views/ContentView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
redirect: '/404'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
function iscookieExist() {
|
||||||
|
if (!cookies.isKey('TENZOR_AUTH')) {
|
||||||
|
cookies.remove('TENZOR_AUTH');
|
||||||
|
}
|
||||||
|
|
||||||
|
cookies.set('TENZOR_AUTH', 'TEST');
|
||||||
|
if (!cookies.isKey('TENZOR_AUTH')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cookies.remove('TENZOR_AUTH')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function issessionExist() {
|
||||||
|
if (sessionStorage.getItem('user_info') !== null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cookies.remove('TENZOR_AUTH');
|
||||||
|
// // cookies.set('TENZOR_AUTH', "o29P-qxkMkWoDCSC4cqiDQjxTV0_7u527PDckR-mWNFL6BHVM6KyJepcMjmiwjG-ieL44wWJdo2oKHSlFgJwwR1ij1LaYajAMs4Pp-fRaZb8=")
|
||||||
|
// console.log(cookies.get('TENZOR_AUTH'))
|
||||||
|
|
||||||
|
router.beforeEach(async (to, from) => {
|
||||||
|
// console.log(cookie)
|
||||||
|
// console.log(iscookieExist(),issessionExist())
|
||||||
|
if (iscookieExist()) { //有 cookie
|
||||||
|
if (to.name === 'login') {
|
||||||
|
return { name: 'home' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (issessionExist()) {
|
||||||
|
sessionStorage.removeItem('user_info')
|
||||||
|
}
|
||||||
|
if (to.name !== 'login') {
|
||||||
|
return { name: 'login' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NGradientText } from 'naive-ui';
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="s404">
|
||||||
|
<n-gradient-text type="success">
|
||||||
|
404 NOT FOUND
|
||||||
|
</n-gradient-text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.n-gradient-text {
|
||||||
|
display: flex;
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.s404{
|
||||||
|
margin-top: calc(50vh - 1.5em);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,517 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { NGradientText, NIcon, NButton, NTime, useMessage, useLoadingBar, NPopover, NModal, NCard, NInput } from 'naive-ui'
|
||||||
|
import { Comment28Regular as TrainIcon } from '@vicons/fluent'
|
||||||
|
import { Send as SendIcon } from '@vicons/carbon'
|
||||||
|
import { UpdateRound as UpdateIcon } from '@vicons/material'
|
||||||
|
import { ArrowBack as BackIcon } from '@vicons/ionicons5'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import { unset } from 'lodash'
|
||||||
|
|
||||||
|
const loadingbar = useLoadingBar()
|
||||||
|
const store = useStore()
|
||||||
|
const message = useMessage()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
|
||||||
|
interface Page {
|
||||||
|
page: number | null
|
||||||
|
para: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageInfo = reactive({
|
||||||
|
page: null,
|
||||||
|
para: null,
|
||||||
|
} as Page)
|
||||||
|
|
||||||
|
const contentInfo = reactive({
|
||||||
|
title: null,
|
||||||
|
paras: [] as string[],
|
||||||
|
comments: [] as comment[],
|
||||||
|
commentCount: [] as number[],
|
||||||
|
commentpara: [] ,
|
||||||
|
editing: null
|
||||||
|
} as ContentInfo)
|
||||||
|
|
||||||
|
interface ContentInfo {
|
||||||
|
title: string | null
|
||||||
|
paras: string[]
|
||||||
|
comments: comment[]
|
||||||
|
commentCount: number[]
|
||||||
|
commentpara: comment[]
|
||||||
|
editing: null
|
||||||
|
}
|
||||||
|
|
||||||
|
const JSONHeader = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface comment {
|
||||||
|
id: number,
|
||||||
|
paragraph: number,
|
||||||
|
marked: string,
|
||||||
|
comment: string,
|
||||||
|
createdAt: string,
|
||||||
|
updatedAt: string,
|
||||||
|
user: {
|
||||||
|
name: string
|
||||||
|
},
|
||||||
|
// push: Function
|
||||||
|
length: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Tool {
|
||||||
|
show: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
text: string | null,
|
||||||
|
para: number | null,
|
||||||
|
modal: boolean,
|
||||||
|
comment: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
var tool = reactive({
|
||||||
|
show: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
text: null,
|
||||||
|
para: null,
|
||||||
|
modal: false,
|
||||||
|
comment: null
|
||||||
|
} as Tool)
|
||||||
|
|
||||||
|
let canSelect = reactive([] as boolean[])
|
||||||
|
|
||||||
|
function canSel(index: number) {
|
||||||
|
for (let i = 0; i < canSelect.length; i++) canSelect[i] = false;
|
||||||
|
canSelect[index] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateContent(id: number) {
|
||||||
|
loadingbar.start()
|
||||||
|
await axios({
|
||||||
|
method: 'get',
|
||||||
|
url: 'passage/' + id,
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200) {
|
||||||
|
if (res.data.success) {
|
||||||
|
loadingbar.finish()
|
||||||
|
contentInfo.title = res.data.data.title
|
||||||
|
contentInfo.paras = res.data.data.content
|
||||||
|
contentInfo.paras.forEach(() => {
|
||||||
|
contentInfo.commentCount.push(0)
|
||||||
|
contentInfo.commentpara.push([] as unknown as comment)
|
||||||
|
});
|
||||||
|
contentInfo.commentCount.push(0)
|
||||||
|
contentInfo.commentpara.push([] as unknown as comment)
|
||||||
|
contentInfo.comments = res.data.data.comments
|
||||||
|
contentInfo.comments.forEach(element => {
|
||||||
|
contentInfo.commentCount[0]++
|
||||||
|
contentInfo.commentCount[element.paragraph]++
|
||||||
|
contentInfo.commentpara[0].push(element)
|
||||||
|
contentInfo.commentpara[element.paragraph].push(element)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadingbar.error()
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loadingbar.error()
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.params.id !== undefined) {
|
||||||
|
pageInfo.page = parseInt(route.params.id as string)
|
||||||
|
updateContent(pageInfo.page)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.params.para !== undefined) {
|
||||||
|
pageInfo.para = parseInt(route.params.para as string)
|
||||||
|
if (contentInfo.commentCount[pageInfo.para] == 0 || contentInfo.commentCount[pageInfo.para] == null) {
|
||||||
|
pageInfo.para = null
|
||||||
|
router.push('/page/' + pageInfo.page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouse(event: object) {
|
||||||
|
var sele = window.getSelection()
|
||||||
|
if (sele === null || sele.anchorNode === null || sele.anchorNode.parentElement === null) return
|
||||||
|
var p = sele.anchorNode.parentElement
|
||||||
|
if (sele.type != 'Range' || p.localName != 'p') {
|
||||||
|
tool.show = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var text = sele.toString().split('\n')[0]
|
||||||
|
if (text.length != 0) {
|
||||||
|
tool.show = true
|
||||||
|
tool.x = p.getBoundingClientRect().left
|
||||||
|
tool.y = p.getBoundingClientRect().top
|
||||||
|
tool.para = parseInt(p.id.split('_')[1])
|
||||||
|
tool.text = text.trim()
|
||||||
|
tool.comment = null
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tool.show = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewComment(para: number) {
|
||||||
|
pageInfo.para = para
|
||||||
|
router.push('/page/' + pageInfo.page + '/' + para)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Back() {
|
||||||
|
if (pageInfo.para !== null) {
|
||||||
|
pageInfo.para = null
|
||||||
|
router.push('/page/' + pageInfo.page)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editComment(id: number) {
|
||||||
|
var text = document.getElementById(id.toString())?.innerText;
|
||||||
|
loadingbar.start()
|
||||||
|
await axios({
|
||||||
|
method: 'put',
|
||||||
|
url: 'comment',
|
||||||
|
data: {
|
||||||
|
id: id,
|
||||||
|
comment: text
|
||||||
|
},
|
||||||
|
headers: JSONHeader
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200 && res.data.success) {
|
||||||
|
loadingbar.finish()
|
||||||
|
message.success("修改成功")
|
||||||
|
contentInfo.editing = null
|
||||||
|
} else {
|
||||||
|
loadingbar.error()
|
||||||
|
message.error("出错了,再试一次吧")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function delComment(id: number, para: number) {
|
||||||
|
loadingbar.start()
|
||||||
|
await axios({
|
||||||
|
method: 'delete',
|
||||||
|
url: 'comment',
|
||||||
|
params: {
|
||||||
|
id: id,
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200 && res.data.success) {
|
||||||
|
loadingbar.finish()
|
||||||
|
message.success("删除成功")
|
||||||
|
contentInfo.editing = null
|
||||||
|
contentInfo.commentCount[0]--
|
||||||
|
contentInfo.commentCount[para]--
|
||||||
|
var index_0, index_1
|
||||||
|
for (var i = 0; i < contentInfo.commentpara[0].length; i++) {
|
||||||
|
if (contentInfo.commentpara[0][i].id == id) {
|
||||||
|
index_0 = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < contentInfo.commentpara[para].length; i++) {
|
||||||
|
if (contentInfo.commentpara[para][i].id == id) {
|
||||||
|
index_1 = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentInfo.commentpara[0].splice(index_0, 1)
|
||||||
|
contentInfo.commentpara[para].splice(index_1, 1)
|
||||||
|
if (contentInfo.commentCount[para] == 0) {
|
||||||
|
pageInfo.para = null
|
||||||
|
router.push('/page/' + pageInfo.page)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loadingbar.error()
|
||||||
|
message.error("出错了,再试一次吧")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdateShow(show: boolean) {
|
||||||
|
console.log(show)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCommentbar() {
|
||||||
|
tool.modal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendComment() {
|
||||||
|
loadingbar.start()
|
||||||
|
if (tool.comment === null || tool.comment.length == 0) {
|
||||||
|
loadingbar.error()
|
||||||
|
message.error("请输入内容")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
axios({
|
||||||
|
method: 'post',
|
||||||
|
url: 'comment',
|
||||||
|
headers: JSONHeader,
|
||||||
|
data: {
|
||||||
|
passageId: pageInfo.page,
|
||||||
|
paragraph: tool.para,
|
||||||
|
marked: tool.text,
|
||||||
|
comment: tool.comment
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200 && res.data.success) {
|
||||||
|
contentInfo.commentCount[0]++
|
||||||
|
contentInfo.commentCount[tool.para]++
|
||||||
|
var d = new Date()
|
||||||
|
var commentElement = {
|
||||||
|
"id": res.data.data.id,
|
||||||
|
"paragraph": tool.para,
|
||||||
|
"marked": tool.text,
|
||||||
|
"comment": tool.comment,
|
||||||
|
"createdAt": d.toJSON(),
|
||||||
|
"updatedAt": d.toJSON(),
|
||||||
|
"user": {
|
||||||
|
"name": store.state.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentInfo.commentpara[0].push(commentElement)
|
||||||
|
contentInfo.commentpara[tool.para].push(commentElement)
|
||||||
|
loadingbar.finish()
|
||||||
|
tool.modal = false
|
||||||
|
tool.comment = null
|
||||||
|
console.log(contentInfo)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadingbar.error()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="main">
|
||||||
|
<div class="titlediv">
|
||||||
|
<n-gradient-text type="success" class="title">
|
||||||
|
{{ contentInfo.title }}
|
||||||
|
</n-gradient-text>
|
||||||
|
<n-gradient-text type="warning" class="subtitle" v-if="pageInfo.para === 0">
|
||||||
|
全部评论
|
||||||
|
</n-gradient-text>
|
||||||
|
<n-gradient-text type="warning" class="subtitle" v-else-if="pageInfo.para !== null">
|
||||||
|
第 {{ pageInfo.para }} 段评论
|
||||||
|
</n-gradient-text>
|
||||||
|
<div>
|
||||||
|
<n-button @click="viewComment(0)" v-if="pageInfo.para !== 0">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<train-icon />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
全部评论
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="Back" style="margin-left:10px">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<BackIcon />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
返回
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content" v-if="pageInfo.para === null">
|
||||||
|
<n-popover placement="left-start" :show="tool.show" :x="tool.x" :y="tool.y" @update:show="handleUpdateShow"
|
||||||
|
trigger="manual">
|
||||||
|
<n-button type="primary" text @click="showCommentbar">评论</n-button>
|
||||||
|
</n-popover>
|
||||||
|
<n-modal v-model:show="tool.modal">
|
||||||
|
<n-card style="width: 600px" title="发表评论" :bordered="false" size="huge" role="dialog" aria-modal="true">
|
||||||
|
<div class="marked">{{ tool.text }}</div>
|
||||||
|
<n-input v-model:value="tool.comment" type="textarea" style="margin-top:10px" placeholder="输入你的评论..." />
|
||||||
|
<template #footer>
|
||||||
|
<div class="buts">
|
||||||
|
<n-button @click="tool.modal = false" secondary>取消</n-button>
|
||||||
|
<n-button @click="sendComment()" style="margin-left: 10px" secondary>发表</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
<p v-for="(item, index) in contentInfo.paras" @mousedown="canSel(index)" @mouseup="onMouse"
|
||||||
|
:class="{ canselect: canSelect[index] }" :id="'t_' + (index + 1)">
|
||||||
|
{{ item }}
|
||||||
|
<n-button text v-if="contentInfo.commentCount[index + 1]" @click="viewComment(index + 1)"
|
||||||
|
style="user-select: none;">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<train-icon />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
查看 {{ contentInfo.commentCount[index + 1] }} 条评论
|
||||||
|
</n-button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="comment" v-else>
|
||||||
|
<div v-for="(item, index) in contentInfo.commentpara[pageInfo.para]" class="card">
|
||||||
|
<div class="cleft">
|
||||||
|
<n-gradient-text type="info" class="uname">
|
||||||
|
{{ item.user.name }}
|
||||||
|
</n-gradient-text>
|
||||||
|
<div class="row">
|
||||||
|
<n-icon>
|
||||||
|
<SendIcon />
|
||||||
|
</n-icon>
|
||||||
|
<n-time :time="Date.parse(item.createdAt)" type="relative" />
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<n-icon>
|
||||||
|
<UpdateIcon />
|
||||||
|
</n-icon>
|
||||||
|
<n-time :time="Date.parse(item.updatedAt)" type="relative" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cright">
|
||||||
|
<div class="marked">
|
||||||
|
{{ item.marked != ' ' ? item.marked : "TA 只选中了一个空格" }}
|
||||||
|
</div>
|
||||||
|
<div class="commentr" :id="item.id"
|
||||||
|
:contenteditable="item.user.name == store.state.name && contentInfo.editing == item.id ? true : false">
|
||||||
|
{{ item.comment }}
|
||||||
|
</div>
|
||||||
|
<div class="buts" v-if="item.user.name == store.state.name">
|
||||||
|
<n-button @click="editComment(item.id)" size="small" secondary
|
||||||
|
v-if="contentInfo.editing == item.id">确定</n-button>
|
||||||
|
<n-button @click="contentInfo.editing = item.id, message.info('点击原评论即可编辑')" size="small" secondary
|
||||||
|
v-else>修改</n-button>
|
||||||
|
<n-button @click="delComment(item.id, item.paragraph)" style="margin-left: 10px" size="small"
|
||||||
|
secondary>删除</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.float {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buts {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlediv {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentr {
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-size: large;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marked {
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
border: 1px dotted gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uname {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cright {
|
||||||
|
padding: 10px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 50vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cleft {
|
||||||
|
width: 10vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
/* border-bottom: 1px solid hsla(160, 100%, 37%, 1); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
display: flex;
|
||||||
|
height: 80vh;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 2.6rem;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
user-select: none;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canselect {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
/* display: none; */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 60vw;
|
||||||
|
height: 95vh;
|
||||||
|
padding: 7vh 0 3vh 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,137 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import axios from 'axios'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import { NGradientText, NSpace, NInput, NButton, NAlert, useLoadingBar } from 'naive-ui'
|
||||||
|
import { ro } from 'date-fns/locale'
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const loadingbar = useLoadingBar()
|
||||||
|
|
||||||
|
var name = store.state.name
|
||||||
|
|
||||||
|
const JSONHeader = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo_input = reactive({
|
||||||
|
name: store.state.name,
|
||||||
|
password: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
var errmsg = reactive({
|
||||||
|
success: true,
|
||||||
|
error: "你输错啦"
|
||||||
|
})
|
||||||
|
|
||||||
|
function editProfile() {
|
||||||
|
loadingbar.start()
|
||||||
|
if (userInfo_input.name === undefined || userInfo_input.name.length == 0) {
|
||||||
|
loadingbar.error()
|
||||||
|
errmsg.success = false
|
||||||
|
errmsg.error = "请填写用户名"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (userInfo_input.password === undefined || userInfo_input.password.length == 0) {
|
||||||
|
loadingbar.error()
|
||||||
|
errmsg.success = false
|
||||||
|
errmsg.error = "请填写密码"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
axios({
|
||||||
|
method: 'put',
|
||||||
|
url: 'user',
|
||||||
|
data: userInfo_input,
|
||||||
|
headers: JSONHeader
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200) {
|
||||||
|
errmsg.success = res.data.success
|
||||||
|
if (!res.data.success) {
|
||||||
|
loadingbar.error()
|
||||||
|
errmsg.error = res.data.error
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadingbar.finish()
|
||||||
|
var userInfo = JSON.parse(sessionStorage.getItem('user_info'))
|
||||||
|
var newuserInfo = {
|
||||||
|
id: userInfo.id,
|
||||||
|
name: userInfo_input.name
|
||||||
|
}
|
||||||
|
store.commit('set', newuserInfo)
|
||||||
|
sessionStorage.setItem('user_info', JSON.stringify(newuserInfo))
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-space class="flexdiv" vertical>
|
||||||
|
<n-gradient-text type="success">
|
||||||
|
修改信息 / {{ name }}
|
||||||
|
</n-gradient-text>
|
||||||
|
<n-alert v-if="!errmsg.success" type="error" class="erralert">
|
||||||
|
{{ errmsg.error }}
|
||||||
|
</n-alert>
|
||||||
|
<div class="row flexdiv">
|
||||||
|
<div class="r_label">用户名</div>
|
||||||
|
<n-input v-model:value="userInfo_input.name" type="text" placeholder="请输入你的用户名" />
|
||||||
|
</div>
|
||||||
|
<div class="row flexdiv">
|
||||||
|
<div class="r_label">新密码</div>
|
||||||
|
<n-input v-model:value="userInfo_input.password" type="password" show-password-on="mousedown"
|
||||||
|
placeholder="请输入你的密码" />
|
||||||
|
</div>
|
||||||
|
<div class="row buts flexdiv">
|
||||||
|
<n-button @click="editProfile">修改</n-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.flexdiv {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.erralert {
|
||||||
|
width: 30vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-alert-body {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-gradient-text {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 2.6rem;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-button {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
width: 30vw;
|
||||||
|
margin-top: 10px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r_label {
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buts {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,189 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { NDropdown, NButton, useMessage, NInput, NGradientText } from 'naive-ui';
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const store = useStore()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
var edit = reactive({
|
||||||
|
state: false,
|
||||||
|
userInfo: {
|
||||||
|
name: null,
|
||||||
|
password: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (sessionStorage.getItem('user_info') === null) {
|
||||||
|
axios({
|
||||||
|
method: 'get',
|
||||||
|
url: 'user/state'
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200 && res.data.success) {
|
||||||
|
var userInfo = {
|
||||||
|
id: res.data.data.id,
|
||||||
|
name: res.data.data.name
|
||||||
|
}
|
||||||
|
store.commit('set', userInfo)
|
||||||
|
sessionStorage.setItem('user_info', JSON.stringify(userInfo))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
if (store.state.id === null) {
|
||||||
|
var userInfo = JSON.parse(sessionStorage.getItem('user_info'))
|
||||||
|
store.commit('set', userInfo)
|
||||||
|
edit.userInfo = userInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = ref([
|
||||||
|
{
|
||||||
|
label: '查看个人资料',
|
||||||
|
key: 'profile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '编辑用户资料',
|
||||||
|
key: 'editProfile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '退出登录',
|
||||||
|
key: 'logout',
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const JSONHeader = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelect(key: string | number) {
|
||||||
|
switch (key) {
|
||||||
|
case 'profile': {
|
||||||
|
message.info(
|
||||||
|
"ID: " + store.state.id + " 丨 姓名:" + store.state.name,
|
||||||
|
{
|
||||||
|
keepAliveOnHover: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'editProfile': {
|
||||||
|
router.push('/edit')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'logout': {
|
||||||
|
axios({
|
||||||
|
method: 'delete',
|
||||||
|
url: 'user'
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200 && res.data.success) {
|
||||||
|
sessionStorage.removeItem('user_info')
|
||||||
|
store.commit('clear')
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="header" v-if='router.currentRoute.value.path != "/login"
|
||||||
|
&& router.currentRoute.value.path != "/edit"
|
||||||
|
&& router.currentRoute.value.path != "/404"'>
|
||||||
|
<n-dropdown :options="options" @select="handleSelect">
|
||||||
|
<n-button quaternary class="header_but" size="large">{{ store.state.name }}</n-button>
|
||||||
|
</n-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="editarea" v-if="edit.state">
|
||||||
|
<div class="buts flexdiv">
|
||||||
|
<n-gradient-text type="success">
|
||||||
|
修改信息
|
||||||
|
</n-gradient-text>
|
||||||
|
<div class="butts">
|
||||||
|
<n-button @click="edit.state = false">取消</n-button>
|
||||||
|
<n-button @click="editProfile">修改</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row flexdiv">
|
||||||
|
<div class="r_label">用户名</div>
|
||||||
|
<n-input v-model:value="edit.userInfo.name" type="text" placeholder="请输入你的用户名" />
|
||||||
|
</div>
|
||||||
|
<div class="row flexdiv">
|
||||||
|
<div class="r_label">密码</div>
|
||||||
|
<n-input v-model:value="edit.userInfo.password" type="password" show-password-on="mousedown"
|
||||||
|
placeholder="请输入你的密码" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
width: 100%;
|
||||||
|
height: 5vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header_but {
|
||||||
|
justify-self: flex-end;
|
||||||
|
display: flex;
|
||||||
|
max-width: 100px;
|
||||||
|
margin-top: 10px;
|
||||||
|
border-bottom: 1px solid hsla(160, 100%, 37%, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editarea {
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 30px;
|
||||||
|
border: 1px solid hsla(160, 100%, 37%, 1);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexdiv {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.erralert {
|
||||||
|
width: 30vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-alert-body {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-gradient-text {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-button {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin-top: 10px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r_label {
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buts {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,208 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import { NGradientText, NPagination, NButton, useLoadingBar } from 'naive-ui'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const loadingbar = useLoadingBar()
|
||||||
|
|
||||||
|
var contents: object = reactive([])
|
||||||
|
|
||||||
|
var pagedata = reactive({
|
||||||
|
page: 1,
|
||||||
|
total: 10,
|
||||||
|
lang: 'ch'
|
||||||
|
})
|
||||||
|
|
||||||
|
async function updateContent() {
|
||||||
|
var type = pagedata.lang == 'all' ? null : pagedata.lang;
|
||||||
|
loadingbar.start()
|
||||||
|
await axios({
|
||||||
|
method: 'get',
|
||||||
|
url: 'passagelist',
|
||||||
|
params: {
|
||||||
|
type: type,
|
||||||
|
page: pagedata.page,
|
||||||
|
limit: 9,
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200 && res.data.success) {
|
||||||
|
loadingbar.finish()
|
||||||
|
contents.length = 0
|
||||||
|
res.data.data.passages.forEach(element => {
|
||||||
|
contents.push(element)
|
||||||
|
});
|
||||||
|
pagedata.total = Math.ceil(res.data.data.total / 9)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadingbar.error()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.params.page !== undefined) {
|
||||||
|
pagedata.lang = route.params.lang
|
||||||
|
pagedata.page = parseInt(route.params.page)
|
||||||
|
}
|
||||||
|
updateContent()
|
||||||
|
|
||||||
|
function changeCH() {
|
||||||
|
var page = {
|
||||||
|
lang: pagedata.lang,
|
||||||
|
page: pagedata.page,
|
||||||
|
}
|
||||||
|
switch (pagedata.lang) {
|
||||||
|
case 'ch': {
|
||||||
|
page.lang = 'en'
|
||||||
|
pagedata.lang = 'en'
|
||||||
|
router.push({ name: 'list', params: page })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'en': {
|
||||||
|
page.lang = 'all'
|
||||||
|
pagedata.lang = 'all'
|
||||||
|
router.push({ name: 'list', params: page })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'all': {
|
||||||
|
page.lang = 'en'
|
||||||
|
pagedata.lang = 'en'
|
||||||
|
router.push({ name: 'list', params: page })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeEN() {
|
||||||
|
var page = {
|
||||||
|
lang: pagedata.lang,
|
||||||
|
page: pagedata.page,
|
||||||
|
}
|
||||||
|
switch (pagedata.lang) {
|
||||||
|
case 'ch': {
|
||||||
|
page.lang = 'all'
|
||||||
|
pagedata.lang = 'all'
|
||||||
|
router.push({ name: 'list', params: page })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'en': {
|
||||||
|
page.lang = 'ch'
|
||||||
|
pagedata.lang = 'ch'
|
||||||
|
router.push({ name: 'list', params: page })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'all': {
|
||||||
|
page.lang = 'ch'
|
||||||
|
pagedata.lang = 'ch'
|
||||||
|
router.push({ name: 'list', params: page })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="cards_container flexdiv">
|
||||||
|
<div class="title">
|
||||||
|
<n-gradient-text type="success">
|
||||||
|
文章列表
|
||||||
|
</n-gradient-text>
|
||||||
|
<n-button class="langbut" :type="pagedata.lang == 'ch' || pagedata.lang == 'all' ? 'primary' : 'default'"
|
||||||
|
@click="changeCH">
|
||||||
|
中</n-button>
|
||||||
|
<n-button class="langbut" :type="pagedata.lang == 'en' || pagedata.lang == 'all' ? 'primary' : 'default'"
|
||||||
|
@click="changeEN">
|
||||||
|
英</n-button>
|
||||||
|
</div>
|
||||||
|
<div class="cards">
|
||||||
|
<router-link class="card" v-for="(item, index) in contents" :to="{ path: '/page/' + item.id }">
|
||||||
|
{{ item.title }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<!-- pagedata.total -->
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<n-pagination v-model:page="pagedata.page" :page-count="pagedata.total"
|
||||||
|
@update-page="updateContent(pagedata.lang, pagedata.page)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.langbut {
|
||||||
|
margin-left: 30px;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
margin: 0 auto;
|
||||||
|
top: 95vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-pagination {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-gradient-text {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 2.6rem;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
border: 1px solid hsla(160, 100%, 37%, 1);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 50vw;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards {
|
||||||
|
max-height: 75vh;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 51vw;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.cards::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards_container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 7vh 0 3vh 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards_container h1,
|
||||||
|
.cards_container h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
|
||||||
|
.cards_container h1,
|
||||||
|
.cards_container h3 {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,177 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { NGradientText, NSpace, NInput, NButton, NAlert, useLoadingBar } from 'naive-ui'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const loadingbar = useLoadingBar()
|
||||||
|
|
||||||
|
var userInfo_input = reactive({
|
||||||
|
name: undefined,
|
||||||
|
password: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
var errmsg = reactive({
|
||||||
|
success: true,
|
||||||
|
error: "你输错啦"
|
||||||
|
})
|
||||||
|
|
||||||
|
const JSONHeader = {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
async function register() {
|
||||||
|
loadingbar.start()
|
||||||
|
if (userInfo_input.name === undefined || userInfo_input.name.length == 0) {
|
||||||
|
loadingbar.error()
|
||||||
|
errmsg.success = false
|
||||||
|
errmsg.error = "请填写用户名"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (userInfo_input.password === undefined || userInfo_input.password.length == 0) {
|
||||||
|
loadingbar.error()
|
||||||
|
errmsg.success = false
|
||||||
|
errmsg.error = "请填写密码"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await axios({
|
||||||
|
method: 'post',
|
||||||
|
url: 'user/registry',
|
||||||
|
data: userInfo_input,
|
||||||
|
headers: JSONHeader
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200) {
|
||||||
|
errmsg.success = res.data.success
|
||||||
|
if (!res.data.success) {
|
||||||
|
loadingbar.error()
|
||||||
|
errmsg.error = res.data.error
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadingbar.finish()
|
||||||
|
var userInfo = {
|
||||||
|
id: res.data.data.id,
|
||||||
|
name: res.data.data.name
|
||||||
|
}
|
||||||
|
store.commit('set', userInfo)
|
||||||
|
sessionStorage.setItem('user_info', JSON.stringify(userInfo))
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function login() {
|
||||||
|
loadingbar.start()
|
||||||
|
if (userInfo_input.name === undefined || userInfo_input.name.length == 0) {
|
||||||
|
loadingbar.error()
|
||||||
|
errmsg.success = false
|
||||||
|
errmsg.error = "请填写用户名"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (userInfo_input.password === undefined || userInfo_input.password.length == 0) {
|
||||||
|
loadingbar.error()
|
||||||
|
errmsg.success = false
|
||||||
|
errmsg.error = "请填写密码"
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
axios({
|
||||||
|
method: 'post',
|
||||||
|
url: 'user',
|
||||||
|
data: userInfo_input,
|
||||||
|
headers: JSONHeader
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status == 200) {
|
||||||
|
errmsg.success = res.data.success
|
||||||
|
if (!res.data.success) {
|
||||||
|
loadingbar.error()
|
||||||
|
errmsg.error = res.data.error
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loadingbar.finish()
|
||||||
|
var userInfo = {
|
||||||
|
id: res.data.data.id,
|
||||||
|
name: res.data.data.name
|
||||||
|
}
|
||||||
|
store.commit('set', userInfo)
|
||||||
|
sessionStorage.setItem('user_info', JSON.stringify(userInfo))
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-space class="flexdiv" vertical>
|
||||||
|
<n-gradient-text type="success">
|
||||||
|
登录账户 / Login
|
||||||
|
</n-gradient-text>
|
||||||
|
<n-alert v-if="!errmsg.success" type="error" class="erralert">
|
||||||
|
{{ errmsg.error }}
|
||||||
|
</n-alert>
|
||||||
|
<div class="row flexdiv">
|
||||||
|
<div class="r_label">用户名</div>
|
||||||
|
<n-input v-model:value="userInfo_input.name" type="text" placeholder="请输入你的用户名" />
|
||||||
|
</div>
|
||||||
|
<div class="row flexdiv">
|
||||||
|
<div class="r_label">密码</div>
|
||||||
|
<n-input v-model:value="userInfo_input.password" type="password" show-password-on="mousedown"
|
||||||
|
placeholder="请输入你的密码" />
|
||||||
|
</div>
|
||||||
|
<div class="row buts flexdiv">
|
||||||
|
<n-button @click="register">注册</n-button>
|
||||||
|
<n-button @click="login">登录</n-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.flexdiv {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.erralert {
|
||||||
|
width: 30vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-alert-body {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-gradient-text {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 2.6rem;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-button {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
width: 30vw;
|
||||||
|
margin-top: 10px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r_label {
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buts {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||||
|
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.config.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'https://fe2kao.tiaozhan.com/api',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue