@@ -50,6 +50,18 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
- 测试用户名: `11` 测试密码: `111111` (连接远程服务器,具有推荐功能,优先使用这个)
|
||||
- 测试用户名: `user` 测试密码: `pass` (mock数据模拟,远程服务器获取不到数据时使用,没有推荐功能)
|
||||
|
||||
<br>
|
||||
|
||||
#### react
|
||||
|
||||
**源代码:**
|
||||
[GitHub](https://github.com/datawhalechina/fun-rec/tree/master/codes/news_recsys/news_rec_web/react-fun-rec) [Gitee](https://gitee.com/theNeverLemon/react-fun-rec)
|
||||
|
||||
**演示链接:**
|
||||
http://theneverlemon.gitee.io/react-fun-rec/
|
||||
|
||||
- 测试用户名: `user` 测试密码: `pass` (mock数据模拟,远程服务器获取不到数据时使用,没有推荐功能)
|
||||
-
|
||||
---
|
||||
|
||||
|
||||
@@ -62,7 +74,7 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/登录.jpg" width = "30%" height = "30%" alt="登录"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%99%BB%E5%BD%95.jpg" width = "30%" height = "30%" alt="登录"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -77,7 +89,7 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
- 注册成功后将跳转至主页面
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/注册.jpg" width = "30%" height = "30%" alt="注册"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%B3%A8%E5%86%8C.jpg" width = "30%" height = "30%" alt="注册"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -88,8 +100,8 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
- 点进新闻详情页后阅读次数会实时增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/推荐.jpg" width = "30%" height = "30%" alt="推荐"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/热门.jpg" width = "30%" height = "30%" alt="热门"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%8E%A8%E8%8D%90.jpg" width = "30%" height = "30%" alt="推荐"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%83%AD%E9%97%A8.jpg" width = "30%" height = "30%" alt="热门"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -100,8 +112,8 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
- 底部点击`喜欢`或者`收藏`可以记录将当前用户行为,并在列表页相应增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/新闻详情.jpg" width = "30%" height = "30%" alt="新闻详情"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/新闻详情2.jpg" width = "30%" height = "30%" alt="新闻详情2"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%85.jpg" width = "30%" height = "30%" alt="新闻详情"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%852.jpg" width = "30%" height = "30%" alt="新闻详情2"/>
|
||||
</div>
|
||||
|
||||
- [X] **个人中心** —— 记录用户的头像和用户名
|
||||
@@ -113,6 +125,6 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
- 点击`退出登录`回到登录页
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/个人中心1.jpg" width = "30%" height = "30%" alt="个人中心1"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/个人中心2.jpg" width = "30%" height = "30%" alt="个人中心2"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%831.jpg" width = "30%" height = "30%" alt="个人中心1"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%832.jpg" width = "30%" height = "30%" alt="个人中心2"/>
|
||||
</div>
|
||||
@@ -44,8 +44,11 @@
|
||||
|
||||
- `忘记密码` 暂时没有写逻辑不能使用
|
||||
|
||||
如果获取不到远程数据库 将使用mock进行数据模拟
|
||||
测试用户名`user` 密码`pass`
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/登录.jpg" width = "30%" height = "30%" alt="登录"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%99%BB%E5%BD%95.jpg" width = "30%" height = "30%" alt="登录"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -60,7 +63,7 @@
|
||||
- 注册成功后将跳转至主页面
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/注册.jpg" width = "30%" height = "30%" alt="注册"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%B3%A8%E5%86%8C.jpg" width = "30%" height = "30%" alt="注册"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -70,9 +73,9 @@
|
||||
|
||||
- 点进新闻详情页后阅读次数会实时增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/推荐.jpg" width = "30%" height = "30%" alt="推荐"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/热门.jpg" width = "30%" height = "30%" alt="热门"/>
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%8E%A8%E8%8D%90.jpg" width = "30%" height = "30%" alt="推荐"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%83%AD%E9%97%A8.jpg" width = "30%" height = "30%" alt="热门"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -83,8 +86,8 @@
|
||||
- 底部点击`喜欢`或者`收藏`可以记录将当前用户行为,并在列表页相应增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/新闻详情.jpg" width = "30%" height = "30%" alt="新闻详情"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/新闻详情2.jpg" width = "30%" height = "30%" alt="新闻详情2"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%85.jpg" width = "30%" height = "30%" alt="新闻详情"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%852.jpg" width = "30%" height = "30%" alt="新闻详情2"/>
|
||||
</div>
|
||||
|
||||
- [X] **个人中心** —— 记录用户的头像和用户名
|
||||
@@ -94,8 +97,8 @@
|
||||
- 显示DataWhale相关介绍
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/个人中心1.jpg" width = "30%" height = "30%" alt="个人中心1"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/个人中心2.jpg" width = "30%" height = "30%" alt="个人中心2"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%831.jpg" width = "30%" height = "30%" alt="个人中心1"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%832.jpg" width = "30%" height = "30%" alt="个人中心2"/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
27
codes/news_recsys/news_rec_web/react-fun-rec/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
build
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
6
codes/news_recsys/news_rec_web/react-fun-rec/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"pkief.material-icon-theme",
|
||||
"PKief.material-icon-theme"
|
||||
]
|
||||
}
|
||||
3
codes/news_recsys/news_rec_web/react-fun-rec/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
||||
31297
codes/news_recsys/news_rec_web/react-fun-rec/package-lock.json
generated
Normal file
61
codes/news_recsys/news_rec_web/react-fun-rec/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "react-fun-rec",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"homepage": ".",
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.2",
|
||||
"@testing-library/react": "^12.1.4",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/react": "^17.0.41",
|
||||
"@types/react-dom": "^17.0.14",
|
||||
"antd-mobile": "^5.7.2",
|
||||
"axios": "^0.26.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"js-pinyin": "^0.1.9",
|
||||
"mockjs": "^1.1.0",
|
||||
"node-sass": "^7.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"react-scripts": "5.0.0",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.4.1",
|
||||
"sass-loader": "^12.6.0",
|
||||
"typescript": "^4.6.2",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"predeploy": "npm run build",
|
||||
"deploy": "gh-pages -d build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"gh-pages": "^3.2.3"
|
||||
}
|
||||
}
|
||||
BIN
codes/news_recsys/news_rec_web/react-fun-rec/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
<title>fun-rec</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
627
codes/news_recsys/news_rec_web/react-fun-rec/readme.md
Normal file
@@ -0,0 +1,627 @@
|
||||
**演示链接:**
|
||||
http://theneverlemon.gitee.io/react-fun-rec
|
||||
|
||||
- 测试用户名: `user` 测试密码: `pass` (mock数据模拟,远程服务器获取不到数据时使用,没有推荐功能)
|
||||
|
||||
---
|
||||
|
||||
|
||||
# 新闻推荐系统
|
||||
|
||||
+ 基于react的框架antd-mobile UI的基本使用。
|
||||
|
||||
+ TypeScript的使用。
|
||||
|
||||
+ 基于nodejs的npm包管理工具。
|
||||
|
||||
+ react路由的相关知识,路由的配置和渲染以及路由守卫的使用。
|
||||
|
||||
+ axios的使用,项目里调用接口都是用的这个异步ajax插件。
|
||||
|
||||
+ 全局组件的使用。
|
||||
|
||||
---
|
||||
|
||||
|
||||
### 运行
|
||||
|
||||
1. 跳转到前端项目文件目录:`cd react-fun-rec`
|
||||
|
||||
2. 本地安装node环境,在项目根目录命令行输入命令`npm install`安装依赖包
|
||||
|
||||
如果因为版本或者网络问题下载失败请执行`npm install -g cnpm -registry=https://registry.npm.taobao.org/
|
||||
`和`cnpm install`
|
||||
|
||||
1. 启动前端服务:`npm start`
|
||||
|
||||
2. 本机访问地址`http://localhost:3000/#/`
|
||||
|
||||
3. 点击`F12`或者右键选择`检查`打开`开发者模式`,选中移动端浏览(点击左上角箭头右边的手机按钮)开始体验
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
### 目标功能
|
||||
- [X] **用户登录** —— 老用户登录
|
||||
|
||||
- `记住我`可以保存将登录信息保存7天
|
||||
|
||||
|
||||
测试用户名`user` 密码`pass`
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%99%BB%E5%BD%95.jpg" width = "30%" height = "30%" alt="登录"/>
|
||||
</div>
|
||||
|
||||
|
||||
- [X] **用户注册** —— 新用户注册信息
|
||||
|
||||
- 用户名可以为英文和数字
|
||||
|
||||
- 密码是大于6位的英文和数字
|
||||
|
||||
- 年龄是1-100的整数
|
||||
|
||||
- 注册成功后将跳转至主页面
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%B3%A8%E5%86%8C.jpg" width = "30%" height = "30%" alt="注册"/>
|
||||
</div>
|
||||
|
||||
|
||||
- [X] **推荐页及热门页内容显示** —— 根据不同用户个性化显示不同新闻内容
|
||||
|
||||
- 推荐页和热门页之间的切换
|
||||
|
||||
- 点进新闻详情页后阅读次数会实时增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%8E%A8%E8%8D%90.jpg" width = "30%" height = "30%" alt="推荐"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%83%AD%E9%97%A8.jpg" width = "30%" height = "30%" alt="热门"/>
|
||||
</div>
|
||||
|
||||
|
||||
- [x] **新闻详情** —— 显示当前新闻的详细信息
|
||||
|
||||
- 显示标题、内容等信息
|
||||
|
||||
- 底部点击`喜欢`或者`收藏`可以记录将当前用户行为,并在列表页相应增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%85.jpg" width = "30%" height = "30%" alt="新闻详情"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%852.jpg" width = "30%" height = "30%" alt="新闻详情2"/>
|
||||
</div>
|
||||
|
||||
- [X] **个人中心** —— 记录用户的头像和用户名
|
||||
|
||||
- 显示头像和登录名(头像暂时统一为DataWhle图标)
|
||||
|
||||
- 显示DataWhale相关介绍
|
||||
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%831.jpg" width = "30%" height = "30%" alt="个人中心1"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%832.jpg" width = "30%" height = "30%" alt="个人中心2"/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
### 项目目录
|
||||
```markdown
|
||||
├─package.json 项目配置文件
|
||||
├─README.md 项目介绍
|
||||
├─tsconfig.json ts配置文件
|
||||
├─src
|
||||
| ├─App.css 根组件样式
|
||||
| ├─App.tsx 根组件
|
||||
| ├─index.css 入口文件样式
|
||||
| ├─index.tsx 入口文件
|
||||
| ├─routes
|
||||
| | └index.tsx 配置路由控制页面跳转
|
||||
| ├─redux 状态管理
|
||||
| | ├─actions.ts 定义更改状态时action的接收和返回值
|
||||
| | ├─reducers.ts 汇总所有reducers
|
||||
| | ├─store.ts 状态管理入口
|
||||
| | ├─types
|
||||
| | | ├─constant.ts 定义常量
|
||||
| | | └─hooks.ts 重新定义useSelector
|
||||
| | ├─reducers 根据action对状态进行更改
|
||||
| | | ├─numChange.ts 阅读/喜欢/收藏次数的改变
|
||||
| | | ├─pushLists.ts 列表添加数据
|
||||
| | | └─userLog.ts 用户登录/注册/退出
|
||||
| ├─pages
|
||||
| | ├─SignUp 用户注册
|
||||
| | | ├─index.scss
|
||||
| | | └index.tsx
|
||||
| | ├─SignIn 用户登录
|
||||
| | | ├─index.scss
|
||||
| | | └index.tsx
|
||||
| | ├─RecLists 推荐页
|
||||
| | | └index.tsx
|
||||
| | ├─NewsInfo 新闻详情页
|
||||
| | | ├─index.scss
|
||||
| | | └index.tsx
|
||||
| | ├─MySelf 个人中心
|
||||
| | | ├─index.scss
|
||||
| | | └index.tsx
|
||||
| | ├─HotLists 热门页
|
||||
| | | └index.tsx
|
||||
| | ├─404 404页
|
||||
| | | └index.tsx
|
||||
| ├─mock
|
||||
| | └index.ts 数据模拟
|
||||
| ├─components
|
||||
| | ├─BottomBar 底部导航
|
||||
| | | ├─index.scss
|
||||
| | | └index.tsx
|
||||
| ├─assets
|
||||
| | ├─type
|
||||
| | | └─index.ts 定义所有数据的类型
|
||||
| | ├─js
|
||||
| | | ├─cookie.js 定义cookie的相关操作
|
||||
| | | ├─encrypt.js 密码加密
|
||||
| | ├─images
|
||||
│ │ │ ├─ collects.png 未选中收藏
|
||||
│ │ │ ├─ collects1.png 选中收藏
|
||||
│ │ │ ├─ datawhale.png DataWhale头像
|
||||
│ │ │ ├─ dw.png DataWhale二维码
|
||||
│ │ │ ├─ favicon.ico 浏览器小图标
|
||||
│ │ │ ├─ likes.png 未选中喜欢
|
||||
│ │ │ └─ likes1.png 选中喜欢
|
||||
| | ├─data
|
||||
| | | ├─city.ts 城市选择数据
|
||||
| | ├─css
|
||||
| | | └─topBar.scss 顶部导航样式
|
||||
└─public
|
||||
├─favicon.ico 浏览器小图标
|
||||
└─index.html 首页入口文件
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 数据获取
|
||||
|
||||
通过`mockjs`进行数据的模拟
|
||||
|
||||
定义获取数据的url和出现错误时的状态码
|
||||
|
||||
``` typescript
|
||||
import { NewsInfo } from '../assets/type'
|
||||
import store from '../redux/store'
|
||||
|
||||
let Mock = require('mockjs')
|
||||
|
||||
// 登录
|
||||
Mock.mock('/login', 'post', (req: { body: string }) => {
|
||||
const { username, passwd } = JSON.parse(req.body)
|
||||
// 用户名默认为user 密码默认为pass
|
||||
// 密码由前端加密后转换为h
|
||||
if (username === 'user' && passwd === 'h') {
|
||||
return {
|
||||
code:200
|
||||
}
|
||||
} else if(username !== 'user'){
|
||||
return {
|
||||
code:502
|
||||
}
|
||||
}else if(passwd !== 'h'){
|
||||
return {
|
||||
code:501
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
code:500
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export { }
|
||||
```
|
||||
在登录时使用
|
||||
|
||||
```typescript
|
||||
let successData = await axios.post("/login", res).then((res) => {
|
||||
return res.data
|
||||
})
|
||||
|
||||
if (successData.code === 200) {
|
||||
Toast.show({
|
||||
icon: 'success',
|
||||
content: '登录成功',
|
||||
})
|
||||
} else if (successData.code === 500) {
|
||||
Toast.show({
|
||||
icon: 'fail',
|
||||
content: '登陆失败',
|
||||
})
|
||||
} else if (successData.code === 501) {
|
||||
Toast.show({
|
||||
icon: 'fail',
|
||||
content: '密码输入错误',
|
||||
})
|
||||
} else if (successData.code === 502) {
|
||||
Toast.show({
|
||||
icon: 'fail',
|
||||
content: '用户名不存在',
|
||||
})
|
||||
}
|
||||
```
|
||||
### 路由守卫
|
||||
|
||||
用户为登录时通过外链访问推荐/热门/详情页会跳转至首页进行登录
|
||||
|
||||
#### App.tsx
|
||||
|
||||
在 `App` 组件中注册路由
|
||||
|
||||
在这里区分`auth`,通过`router.map`遍历所有路由
|
||||
|
||||
|
||||
当`auth`值为`false`时,也就是(`!item.auth`),即不需要身份就可以进入的页面(登录和注册页),那么就渲染当前组件`< item.component />`
|
||||
|
||||
```typescript
|
||||
if (!item.auth) {
|
||||
return (
|
||||
<Route key={i} path={item.path} element={
|
||||
<Suspense fallback={
|
||||
<>
|
||||
<Skeleton.Title animated />
|
||||
<Skeleton.Paragraph lineCount={20} animated />
|
||||
</>
|
||||
}>
|
||||
< item.component />
|
||||
</Suspense>
|
||||
} />
|
||||
)
|
||||
```
|
||||
当`auth`值为`true`时,也就是(`item.auth`),即需要身份才可以进入的页面(内容页),那么就通过`RequireAuth`函数判断是否有身份权限,如果有,渲染当前组件;如果没有,定位到登录
|
||||
|
||||
RequireAuth函数
|
||||
|
||||
通过是否有cookie判断,在登录或者注册时候会存储一个cookie,退出登录时再删掉
|
||||
|
||||
```typescript
|
||||
function RequireAuth({ children }: { children: JSX.Element }) {
|
||||
let location = useLocation()
|
||||
if (!getCookie('openId')) {
|
||||
|
||||
Toast.show({
|
||||
content: '暂未登录,请先登录',
|
||||
})
|
||||
|
||||
let second = 1;
|
||||
// 延迟一秒执行
|
||||
const timer = setInterval(() => {
|
||||
second--;
|
||||
if (!second) {
|
||||
clearInterval(timer);
|
||||
// 手动清除 Toast
|
||||
Toast.clear();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return <Navigate to="/signIn" state={{ from: location }} replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
```
|
||||
渲染的代码
|
||||
|
||||
```typescript
|
||||
else {
|
||||
return (
|
||||
<Route key={i} path={item.path} element={
|
||||
<Suspense fallback={
|
||||
<>
|
||||
<Skeleton.Title animated />
|
||||
<Skeleton.Paragraph lineCount={20} animated />
|
||||
</>
|
||||
}>
|
||||
<RequireAuth>
|
||||
<item.component />
|
||||
</RequireAuth>
|
||||
</Suspense>
|
||||
}>
|
||||
</Route>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 状态管理
|
||||
|
||||

|
||||
|
||||
- action:动作的对象,包含2个属性
|
||||
- type:标识属性, 值为字符串, 唯一, 必要属性
|
||||
- data:数据属性, 值类型任意, 可选属性
|
||||
|
||||
- reducer:用于初始化状态、加工状态
|
||||
- store:将state、action、reducer联系在一起的对象
|
||||
|
||||
|
||||
#### redux/actions.ts
|
||||
|
||||
引入常量变量
|
||||
|
||||
``` typescript
|
||||
import { COLLECT_DECREASE,COLLECT_INCREASE, CLEAR_INFO } from './types/constant'
|
||||
```
|
||||
|
||||
用户登录(注册)时的action
|
||||
|
||||
- type:`signIn` / `signUp` / `CLEAR_INFO`
|
||||
- data:登录或者注册时提交的表单数据,退出时清空数据
|
||||
|
||||
返回对应的type和data
|
||||
|
||||
``` typescript
|
||||
export const userLog = (type: string, data: object) => ({ type, data })
|
||||
```
|
||||
|
||||
#### redux/reducers/userLog.ts
|
||||
|
||||
引入常量和类型约束
|
||||
|
||||
``` typescript
|
||||
import { userLogState } from '../../assets/type'
|
||||
import { CLEAR_INFO, SIGNIN, SIGNUP } from '../types/constant'
|
||||
```
|
||||
|
||||
定义初始状态
|
||||
|
||||
``` typescript
|
||||
const userLogDefault: userLogState = {
|
||||
type: SIGNIN,
|
||||
data: {
|
||||
username: '',
|
||||
passwd: '',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过用户的action定义对应的reducer
|
||||
|
||||
通过action.type判断不同的登录状态
|
||||
|
||||
``` typescript
|
||||
export function userLog(preState = userLogDefault, action: userLogState) {
|
||||
switch(action.type) {
|
||||
case SIGNIN:
|
||||
return ({ type: action.type, data: action.data })
|
||||
case SIGNUP:
|
||||
return ({ type: action.type, data: action.data })
|
||||
case CLEAR_INFO:
|
||||
return ({ type: action.type, data: action.data })
|
||||
default:
|
||||
return preState
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### redux/redecers.ts
|
||||
|
||||
汇总所有的reducer变为一个总的reducer
|
||||
|
||||
``` typescript
|
||||
//引入combineReducers,用于汇总多个reducer
|
||||
import { combineReducers } from 'redux'
|
||||
|
||||
import {userLog} from './reducers/userLog'
|
||||
import { pushRecLists, pushHotLists } from './reducers/pushLists'
|
||||
import { readChange, likesChange, collectionsChange } from './reducers/numChange'
|
||||
|
||||
export default combineReducers({
|
||||
userLog,
|
||||
pushRecLists, pushHotLists,
|
||||
readChange, likesChange, collectionsChange
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
#### redux/store.ts
|
||||
|
||||
创建redux的store对象,只有一个store对象
|
||||
|
||||
引入
|
||||
- createStore:创建store对象
|
||||
- applyMiddleware:中间件,对redux的扩展
|
||||
- redux-thunk:支持异步action
|
||||
- rootReducer:汇总之后的所有reducer
|
||||
- redux-persist:使用sessionstorage持久化数据,解决页面刷新数据丢失的问题
|
||||
- redux-devtools-extension:在浏览器中可以使用redux扩展插件
|
||||
|
||||
``` typescript
|
||||
import {createStore,applyMiddleware} from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
import rootReducer from './reducers'
|
||||
import { composeWithDevTools } from 'redux-devtools-extension'
|
||||
import { persistStore, persistReducer } from 'redux-persist'
|
||||
import storageSession from 'redux-persist/lib/storage/session'
|
||||
|
||||
const storageConfig = {
|
||||
key: 'root', // 必须
|
||||
storage: storageSession, // 缓存机制
|
||||
}
|
||||
|
||||
const myPersistReducer = persistReducer(storageConfig, rootReducer);
|
||||
const store = createStore(myPersistReducer, composeWithDevTools(applyMiddleware(thunk)));
|
||||
|
||||
export const persistor = persistStore(store)
|
||||
export default store;
|
||||
```
|
||||
|
||||
#### 使用
|
||||
|
||||
##### 登录
|
||||
``` typescript
|
||||
let res = {
|
||||
username: values.name,
|
||||
passwd: encrypt.Decrypt(values.passwd)
|
||||
};
|
||||
|
||||
const dispatch = useDispatch();
|
||||
dispatch(userLog(SIGNIN, res))
|
||||
```
|
||||
|
||||
##### 注册
|
||||
``` typescript
|
||||
const dispatch = useDispatch();
|
||||
dispatch(userLog(SIGNUP, values))
|
||||
```
|
||||
|
||||
##### 退出
|
||||
``` typescript
|
||||
const dispatch = useDispatch();
|
||||
dispatch(userLog(CLEAR_INFO, []))
|
||||
```
|
||||
|
||||
---
|
||||
### 主要文件说明
|
||||
|
||||
#### assets/js/cookie.js
|
||||
|
||||
定义cookie的相关操作
|
||||
|
||||
定义了`setCookie`,`getCookie`,`clearCookie`三个函数
|
||||
|
||||
在用户登录注册时存入cookie
|
||||
|
||||
``` javascript
|
||||
function setCookie(json, days) {
|
||||
// 设置过期时间
|
||||
let data = new Date(
|
||||
new Date().getTime() + days * 24 * 60 * 60 * 1000
|
||||
).toUTCString();
|
||||
|
||||
for (var key in json) {
|
||||
document.cookie = key + "=" + json[key] + "; expires=" + data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
获取cookie
|
||||
|
||||
``` javascript
|
||||
function getCookie(name) {
|
||||
var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
|
||||
if (arr != null) {
|
||||
return unescape(arr[2])
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
用户退出登录时删除cookie
|
||||
|
||||
``` javascript
|
||||
function clearCookie(name) {
|
||||
let json = {};
|
||||
json[name] = '';
|
||||
setCookie(json, -1)
|
||||
}
|
||||
```
|
||||
|
||||
#### router/index.tsx
|
||||
|
||||
定义路由相关配置,控制页面跳转
|
||||
|
||||
``` typescript
|
||||
// 路由懒加载
|
||||
import { lazy } from 'react'
|
||||
// 引入路由的类型
|
||||
import { Router } from '../assets/type'
|
||||
|
||||
const router: Array<Router> = [
|
||||
{
|
||||
path: '/signIn', // 路径
|
||||
auth: false, // 是否需要身份验证
|
||||
component: lazy(() => import('../pages/SignIn')) // 组件
|
||||
},
|
||||
{
|
||||
path: '/recLists',
|
||||
auth: true, // 登录后才可以访问
|
||||
component: lazy(() => import('../pages/RecLists'))
|
||||
}
|
||||
]
|
||||
|
||||
export default router
|
||||
```
|
||||
|
||||
|
||||
#### SignIn.tsx/SignUp.tsx
|
||||
|
||||
存入cookie值
|
||||
|
||||
``` typescript
|
||||
let loginInfo = {
|
||||
LoginName: res.username,
|
||||
openId: "asfafsfsfsdfsdfsdfdsf"
|
||||
}
|
||||
|
||||
// checke:true--选中记住我 checke:false--未选中记住我
|
||||
if (values.remember) {
|
||||
// 调用setCookie方法,同时传递需要存储的数据,保存天数
|
||||
setCookie(loginInfo, 7)
|
||||
} else {
|
||||
setCookie(loginInfo, 1)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### NewsInfo.tsx
|
||||
|
||||
发送action请求
|
||||
|
||||
``` javascript
|
||||
sendInfo() {
|
||||
// 阅读
|
||||
let val = {
|
||||
user_name: user_name,
|
||||
news_id: id,
|
||||
action_time: Date.now(),
|
||||
action_type: 'read',
|
||||
}
|
||||
|
||||
// 喜欢
|
||||
let val = {
|
||||
user_name: user_name,
|
||||
news_id: id,
|
||||
action_time: Date.now(),
|
||||
action_type: `likes:${isLike}`,
|
||||
}
|
||||
|
||||
|
||||
// 收藏
|
||||
let val = {
|
||||
user_name: user_name,
|
||||
news_id: id,
|
||||
action_time: Date.now(),
|
||||
action_type: `collections::${isCollection}`,
|
||||
}
|
||||
|
||||
|
||||
// 发送对应请求
|
||||
let successData = await axios.post("/action", val).then(res => {
|
||||
return res
|
||||
})
|
||||
|
||||
if (successData.status === 200) {
|
||||
setIsCollection(!isCollection)
|
||||
// 调用store中的actionChange函数控制次数的变化
|
||||
if (isCollection === true) {
|
||||
dispatch(collectionsDecrease({ lists, id }))
|
||||
} else {
|
||||
dispatch(collectionsIncrease({ lists, id }))
|
||||
}
|
||||
} else {
|
||||
Toast.show({
|
||||
content: '加载数据失败',
|
||||
})
|
||||
}
|
||||
},
|
||||
```
|
||||
38
codes/news_recsys/news_rec_web/react-fun-rec/src/App.css
Normal file
@@ -0,0 +1,38 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
77
codes/news_recsys/news_rec_web/react-fun-rec/src/App.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import './App.css';
|
||||
|
||||
import { Suspense } from 'react'
|
||||
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Skeleton, Toast } from 'antd-mobile'
|
||||
|
||||
import router from './routes'
|
||||
import { getCookie } from './assets/js/cookie';
|
||||
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Routes>
|
||||
{
|
||||
router.map((item, i) => {
|
||||
if (!item.auth) {
|
||||
return (
|
||||
<Route key={i} path={item.path} element={
|
||||
<Suspense fallback={
|
||||
<>
|
||||
<Skeleton.Title animated />
|
||||
<Skeleton.Paragraph lineCount={20} animated />
|
||||
</>
|
||||
}>
|
||||
< item.component />
|
||||
</Suspense>
|
||||
} />
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Route key={i} path={item.path} element={
|
||||
<Suspense fallback={
|
||||
<>
|
||||
<Skeleton.Title animated />
|
||||
<Skeleton.Paragraph lineCount={20} animated />
|
||||
</>
|
||||
}>
|
||||
<RequireAuth>
|
||||
<item.component />
|
||||
</RequireAuth>
|
||||
</Suspense>
|
||||
}>
|
||||
</Route>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
function RequireAuth({ children }: { children: JSX.Element }) {
|
||||
let location = useLocation()
|
||||
if (!getCookie('openId')) {
|
||||
|
||||
Toast.show({
|
||||
content: '暂未登录,请先登录',
|
||||
})
|
||||
|
||||
let second = 1;
|
||||
// 延迟一秒执行
|
||||
const timer = setInterval(() => {
|
||||
second--;
|
||||
if (!second) {
|
||||
clearInterval(timer);
|
||||
// 手动清除 Toast
|
||||
Toast.clear();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return <Navigate to="/signIn" state={{ from: location }} replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
export default App;
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
.tabs {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: white;
|
||||
width: 70%;
|
||||
border-radius: 5px;
|
||||
width: 100vw;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
background-color: white;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.tabs input[name=tab-control] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tabs .content section h2,
|
||||
.tabs ul li label {
|
||||
font-family: "Montserrat";
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.tabs ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-end;
|
||||
}
|
||||
|
||||
.tabs ul li {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
width: 25%;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tabs ul li label {
|
||||
transition: all 0.3s ease-in-out;
|
||||
padding: 5px auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
white-space: nowrap;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tabs ul li label svg {
|
||||
height: 2em;
|
||||
vertical-align: bottom;
|
||||
margin-right: 0.2em;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
@-webkit-keyframes content {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes content {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.tabs ul li label svg {
|
||||
height: 1.5em;
|
||||
}
|
||||
.tabs ul li label br {
|
||||
display: initial;
|
||||
}
|
||||
.tabs ul li label {
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.tabs ul li label span {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.lists {
|
||||
position: relative;
|
||||
top: 70px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.2rem;
|
||||
color: black;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.cate {
|
||||
font-size: 1rem;
|
||||
color: #00F;
|
||||
margin-right: .5rem;
|
||||
padding: .08rem;
|
||||
border: 1px solid #00F;
|
||||
}
|
||||
|
||||
.discribe {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 1rem;
|
||||
color: #918d8d;
|
||||
padding-top: 12px;
|
||||
}
|
||||
1
codes/news_recsys/news_rec_web/react-fun-rec/src/assets/css/topBar.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.tabs{left:50%;transform:translateX(-50%);background:white;width:70%;border-radius:5px;width:100vw;position:fixed;top:0;background-color:white;z-index:9}.tabs input[name=tab-control]{display:none}.tabs .content section h2,.tabs ul li label{font-family:"Montserrat";font-weight:bold;font-size:18px}.tabs ul{list-style-type:none;padding-left:0;display:flex;flex-direction:row;justify-content:space-between;align-items:center;flex-wrap:wrap;align-content:flex-end}.tabs ul li{box-sizing:border-box;flex:1;width:25%;padding:8px;text-align:center}.tabs ul li label{transition:all 0.3s ease-in-out;padding:5px auto;overflow:hidden;text-overflow:ellipsis;display:block;cursor:pointer;transition:all 0.2s ease-in-out;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tabs ul li label svg{height:2em;vertical-align:bottom;margin-right:0.2em;transition:all 0.2s ease-in-out}@-webkit-keyframes content{from{opacity:0;transform:translateY(5%)}to{opacity:1;transform:translateY(0%)}}@keyframes content{from{opacity:0;transform:translateY(5%)}to{opacity:1;transform:translateY(0%)}}@media (max-width: 600px){.tabs ul li label svg{height:1.5em}.tabs ul li label br{display:initial}.tabs ul li label{padding:5px;border-radius:5px}.tabs ul li label span{display:initial}}.lists{position:relative;top:70px;padding:0 10px}.title{font-size:1.2rem;color:black;font-weight:bolder}.cate{font-size:1rem;color:#00F;margin-right:.5rem;padding:.08rem;border:1px solid #00F}.discribe{display:flex;justify-content:space-between;font-size:1rem;color:#918d8d;padding-top:12px}
|
||||
@@ -0,0 +1,138 @@
|
||||
.tabs {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: white;
|
||||
width: 70%;
|
||||
border-radius: 5px;
|
||||
width: 100vw;
|
||||
position: fixed;
|
||||
top:0;
|
||||
background-color: white;
|
||||
z-index: 9
|
||||
}
|
||||
|
||||
// 隐藏input默认样式
|
||||
.tabs input[name=tab-control] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tabs .content section h2,
|
||||
.tabs ul li label {
|
||||
font-family: "Montserrat";
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.tabs ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-end;
|
||||
}
|
||||
|
||||
.tabs ul li {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
width: 25%;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tabs ul li label {
|
||||
transition: all 0.3s ease-in-out;
|
||||
padding: 5px auto;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
white-space: nowrap;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tabs ul li label svg {
|
||||
height: 2em;
|
||||
vertical-align: bottom;
|
||||
margin-right: 0.2em;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
@-webkit-keyframes content {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5%);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes content {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5%);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.tabs ul li label svg {
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.tabs ul li label br {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.tabs ul li label {
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.tabs ul li label span {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.lists {
|
||||
position: relative;
|
||||
top: 70px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.2rem;
|
||||
color: black;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.cate {
|
||||
font-size: 1rem;
|
||||
color: #00F;
|
||||
margin-right: .5rem;
|
||||
padding: .08rem;
|
||||
border: 1px solid #00F
|
||||
}
|
||||
|
||||
.discribe {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 1rem;
|
||||
color: #918d8d;
|
||||
padding-top: 12px;
|
||||
}
|
||||
1519
codes/news_recsys/news_rec_web/react-fun-rec/src/assets/data/city.ts
Normal file
|
After Width: | Height: | Size: 323 B |
|
After Width: | Height: | Size: 433 B |
|
After Width: | Height: | Size: 681 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 313 B |
|
After Width: | Height: | Size: 393 B |
35
codes/news_recsys/news_rec_web/react-fun-rec/src/assets/js/cookie.js
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
// 保存cookie
|
||||
// json 需要存储cookie的对象
|
||||
// days 默认存储多少天
|
||||
export function setCookie(json, days) {
|
||||
// 设置过期时间
|
||||
let data = new Date(
|
||||
new Date().getTime() + days * 24 * 60 * 60 * 1000
|
||||
).toUTCString();
|
||||
|
||||
for (var key in json) {
|
||||
document.cookie = key + "=" + json[key] + "; expires=" + data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 获取cookie
|
||||
// name 需要获取cookie的key
|
||||
export function getCookie(name) {
|
||||
var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
|
||||
if (arr != null) {
|
||||
return unescape(arr[2])
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 删除cookie
|
||||
// name 需要删除cookie的key
|
||||
export function clearCookie(name) {
|
||||
let json = {};
|
||||
json[name] = '';
|
||||
setCookie(json, -1)
|
||||
}
|
||||
27
codes/news_recsys/news_rec_web/react-fun-rec/src/assets/js/encrypt.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
//引用AES源码js
|
||||
const CryptoJS = require('crypto-js');
|
||||
//十六位十六进制数作为密钥
|
||||
const key = CryptoJS.enc.Utf8.parse("1234123412ABCDEF");
|
||||
//十六位十六进制数作为密钥偏移量
|
||||
const iv = CryptoJS.enc.Utf8.parse('ABCDEF1234123412');
|
||||
|
||||
//解密方法
|
||||
function Decrypt(word) {
|
||||
let encryptedHexStr = CryptoJS.enc.Hex.parse(word);
|
||||
let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
|
||||
let decrypt = CryptoJS.AES.decrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
|
||||
let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
|
||||
return decryptedStr.toString();
|
||||
}
|
||||
|
||||
//加密方法
|
||||
function Encrypt(word) {
|
||||
let srcs = CryptoJS.enc.Utf8.parse(word);
|
||||
let encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
|
||||
return encrypted.ciphertext.toString().toUpperCase();
|
||||
}
|
||||
|
||||
export default {
|
||||
Decrypt ,
|
||||
Encrypt
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// redux存的用户信息
|
||||
export interface userLogState {
|
||||
type: string,
|
||||
data: {
|
||||
username: string,
|
||||
passwd: string,
|
||||
passwd2?: string,
|
||||
age?: string,
|
||||
area?: string,
|
||||
gender?: string,
|
||||
remember?: boolean
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 用户登录的信息
|
||||
export interface UserSignIn {
|
||||
name: string
|
||||
passwd: string
|
||||
remember: boolean
|
||||
}
|
||||
|
||||
// 用户注册的信息
|
||||
export interface UserSignUp {
|
||||
name: string
|
||||
passwd: string
|
||||
passwd2: string
|
||||
age: string
|
||||
gender: string
|
||||
area: string
|
||||
}
|
||||
|
||||
// 从列表页进入内容页传入的params类型
|
||||
export interface ToListInfo {
|
||||
id: number
|
||||
cate: string
|
||||
}
|
||||
|
||||
// 列表页的展示数据
|
||||
export interface Lists {
|
||||
news_id: number
|
||||
cate: string
|
||||
title: string
|
||||
ctime: string
|
||||
read_num: number
|
||||
likes: number
|
||||
collections: number
|
||||
}
|
||||
|
||||
// 新闻详情页的数据
|
||||
export interface NewsInfo {
|
||||
cate: string
|
||||
news_id: number
|
||||
title: string
|
||||
content: string
|
||||
ctime: string
|
||||
read_num: number
|
||||
likes: number
|
||||
collections: number
|
||||
}
|
||||
|
||||
// 路由
|
||||
export interface Router {
|
||||
name ?: string,
|
||||
path: string,
|
||||
auth: boolean,
|
||||
children ?: Array < Router >,
|
||||
component: any
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.adm-tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
1
codes/news_recsys/news_rec_web/react-fun-rec/src/components/BottomBar/index.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.adm-tab-bar{position:fixed;bottom:0;left:0;width:100%;background-color:white}
|
||||
@@ -0,0 +1,8 @@
|
||||
// tabBar固定在底部
|
||||
.adm-tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
useNavigate,
|
||||
useLocation,
|
||||
} from 'react-router-dom'
|
||||
import { NavBar, TabBar } from 'antd-mobile'
|
||||
|
||||
import {
|
||||
AppOutline,
|
||||
UserOutline,
|
||||
} from 'antd-mobile-icons'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const BottomBar = () => {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { pathname } = location
|
||||
|
||||
const setRouteActive = (value: string) => {
|
||||
navigate(value, { replace: true })
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
key: '/recLists',
|
||||
title: '首页',
|
||||
icon: <AppOutline />,
|
||||
},
|
||||
{
|
||||
key: '/mySelf',
|
||||
title: '我的',
|
||||
icon: <UserOutline />
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<TabBar activeKey={pathname} onChange={value => setRouteActive(value)} safeArea>
|
||||
{tabs.map(item => (
|
||||
<TabBar.Item key={item.key} icon={item.icon} title={item.title} />
|
||||
))}
|
||||
</TabBar>
|
||||
)
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div className='bottom'>
|
||||
<BottomBar />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*{
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
22
codes/news_recsys/news_rec_web/react-fun-rec/src/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
import store, { persistor } from './redux/store'
|
||||
import { Provider } from 'react-redux'
|
||||
|
||||
import './mock'
|
||||
import { PersistGate } from 'redux-persist/lib/integration/react';
|
||||
|
||||
ReactDOM.render(
|
||||
/* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<BrowserRouter>
|
||||
<App/>
|
||||
</BrowserRouter>
|
||||
</PersistGate>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
106
codes/news_recsys/news_rec_web/react-fun-rec/src/mock/index.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { NewsInfo } from '../assets/type'
|
||||
import store from '../redux/store'
|
||||
|
||||
let Mock = require('mockjs')
|
||||
|
||||
// 登录
|
||||
Mock.mock('/login', 'post', (req: { body: string }) => {
|
||||
const { username, passwd } = JSON.parse(req.body)
|
||||
// 用户名默认为user 密码默认为pass
|
||||
// 密码由前端加密后转换为h
|
||||
if (username === 'user' && passwd === 'h') {
|
||||
return {
|
||||
code:200
|
||||
}
|
||||
} else if(username !== 'user'){
|
||||
return {
|
||||
code:502
|
||||
}
|
||||
}else if(passwd !== 'h'){
|
||||
return {
|
||||
code:501
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
code:500
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 注册
|
||||
Mock.mock('/register', 'post', (req: { body: string }) => {
|
||||
const { username } = JSON.parse(req.body)
|
||||
if (username === 'user') {
|
||||
return {
|
||||
code:500
|
||||
}
|
||||
} else if(username !== ''){
|
||||
return {
|
||||
code:200
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
let Data: NewsInfo[] = []
|
||||
for(let i = 0; i < 10; i ++){
|
||||
Data.push({
|
||||
cate:Mock.Random.cword(2),
|
||||
// news_id:Mock.Random.string('number', 9),
|
||||
news_id: Number(Mock.mock('@id')),
|
||||
title:Mock.Random.ctitle(5,12),
|
||||
content:Mock.Random.cparagraph(3)+'责任编辑'+Mock.Random.cname(),
|
||||
ctime:Mock.Random.date('yyyy-MM-dd'),
|
||||
read_num:Mock.Random.natural(100, 1000),
|
||||
likes:Mock.Random.natural(1, 100),
|
||||
collections:Mock.Random.natural(1, 100),
|
||||
})
|
||||
}
|
||||
Mock.mock('/recList', 'get', () => {
|
||||
return {
|
||||
code:200,
|
||||
data:Data
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Mock.mock('/hotList', 'get', () => {
|
||||
return {
|
||||
code:200,
|
||||
data:Data
|
||||
}
|
||||
})
|
||||
|
||||
Mock.mock(RegExp("/newsInfo" + ".*"), 'get', (option: { url: string }) => {
|
||||
var res,
|
||||
id = Number(option.url.split(/news_id?=/)[1].split(/&user_name=/)[0]),
|
||||
// data = (store.state.recList.length > store.state.hotList.length) ? store.state.recList : store.state.hotList
|
||||
data = store.getState().pushRecLists
|
||||
for(let i=0; i< data.length; i++){
|
||||
if (id === data[i].news_id) {
|
||||
res = data[i]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status:200,
|
||||
data:{
|
||||
cate:res.cate,
|
||||
news_id:res.news_id,
|
||||
title:res.title,
|
||||
content:res.content,
|
||||
ctime:res.ctime,
|
||||
read_num:res.read_num,
|
||||
likes:res.likes,
|
||||
collections:res.collections,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Mock.mock('/action', 'post', () => {
|
||||
return {
|
||||
status:200,
|
||||
}
|
||||
})
|
||||
|
||||
export { }
|
||||
@@ -0,0 +1,81 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Noto+Sans:700");
|
||||
@import url("https://fonts.googleapis.com/css?family=Lato");
|
||||
#main {
|
||||
transition: all 0.6s;
|
||||
font-family: 'Lato', sans-serif;
|
||||
background: #D4DFE6;
|
||||
color: rgba(88, 85, 85, 0.805);
|
||||
margin: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fof h1 {
|
||||
font-size: 50px;
|
||||
display: inline-block;
|
||||
padding-right: 12px;
|
||||
animation: type .5s alternate infinite;
|
||||
}
|
||||
|
||||
.MainDescription {
|
||||
font-size: 1.2rem;
|
||||
font-weight: lighter;
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
@keyframes type {
|
||||
from {
|
||||
box-shadow: inset -3px 0px 0px #888;
|
||||
}
|
||||
to {
|
||||
box-shadow: inset -3px 0px 0px transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.MainGraphic {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Cog {
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
fill: #6AAFE6;
|
||||
transition: easeInOutQuint();
|
||||
animation: CogAnimation 5s infinite;
|
||||
}
|
||||
|
||||
.Hummingbird {
|
||||
position: absolute;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
fill: #30A9DE;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.err_btn {
|
||||
background: #fff;
|
||||
color: rgba(88, 85, 85, 0.805);
|
||||
border: 2px solid #6AAFE6;
|
||||
padding: 15px 30px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
font-size: 13pt;
|
||||
transition: background 0.5s;
|
||||
}
|
||||
|
||||
@keyframes CogAnimation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
1
codes/news_recsys/news_rec_web/react-fun-rec/src/pages/404/index.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Noto+Sans:700");@import url("https://fonts.googleapis.com/css?family=Lato");#main{transition:all 0.6s;font-family:'Lato', sans-serif;background:#D4DFE6;color:rgba(88,85,85,0.805);margin:0;display:flex;width:100%;height:100vh;text-align:center;flex-direction:column;justify-content:center;align-items:center}.fof h1{font-size:50px;display:inline-block;padding-right:12px;animation:type .5s alternate infinite}.MainDescription{font-size:1.2rem;font-weight:lighter;padding:3rem}@keyframes type{from{box-shadow:inset -3px 0px 0px #888}to{box-shadow:inset -3px 0px 0px transparent}}.MainGraphic{position:relative}.Cog{width:10rem;height:10rem;fill:#6AAFE6;transition:easeInOutQuint();animation:CogAnimation 5s infinite}.Hummingbird{position:absolute;width:3rem;height:3rem;fill:#30A9DE;left:50%;top:50%;transform:translate(-50%, -50%)}.err_btn{background:#fff;color:rgba(88,85,85,0.805);border:2px solid #6AAFE6;padding:15px 30px;border-radius:5px;box-shadow:0px 8px 15px rgba(0,0,0,0.1);cursor:pointer;font-size:13pt;transition:background 0.5s}@keyframes CogAnimation{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
|
||||
@@ -0,0 +1,73 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Noto+Sans:700');
|
||||
@import url('https://fonts.googleapis.com/css?family=Lato');
|
||||
|
||||
#main{
|
||||
transition: all 0.6s;
|
||||
font-family: 'Lato', sans-serif;
|
||||
background: #D4DFE6;
|
||||
color: rgba(88, 85, 85, 0.805);
|
||||
margin: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fof h1{
|
||||
font-size: 50px;
|
||||
display: inline-block;
|
||||
padding-right: 12px;
|
||||
animation: type .5s alternate infinite;
|
||||
}
|
||||
|
||||
.MainDescription {
|
||||
font-size: 1.2rem;
|
||||
font-weight: lighter;
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
@keyframes type{
|
||||
from{box-shadow: inset -3px 0px 0px #888;}
|
||||
to{box-shadow: inset -3px 0px 0px transparent;}
|
||||
}
|
||||
|
||||
.MainGraphic {
|
||||
position: relative;
|
||||
}
|
||||
.Cog {
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
fill: #6AAFE6;
|
||||
transition: easeInOutQuint();
|
||||
animation: CogAnimation 5s infinite;
|
||||
}
|
||||
|
||||
.Hummingbird{
|
||||
position: absolute;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
fill: #30A9DE;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
}
|
||||
|
||||
.err_btn{
|
||||
background:#fff;
|
||||
color: rgba(88, 85, 85, 0.805);
|
||||
border:2px solid #6AAFE6;
|
||||
padding:15px 30px;
|
||||
border-radius:5px;
|
||||
box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.1);
|
||||
cursor:pointer;
|
||||
font-size:13pt;
|
||||
transition:background 0.5s;
|
||||
}
|
||||
|
||||
@keyframes CogAnimation {
|
||||
0% {transform: rotate(0deg);}
|
||||
100% {transform: rotate(360deg);}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
export default function NotFound() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<div id="main">
|
||||
<div className="MainGraphic">
|
||||
<svg className="Hummingbird" viewBox="0 0 55 41" xmlns="http://www.w3.org/2000/svg"><path d="M35.5 5L54.7.6H32.3L35.5 5zM12.4 40.8l10.3-10.1-6.2-6.7-4.1 16.8zM33.8 5.3L30.5.8l-5.4 4 8.7.5zM20.8 4.6L8.8 0l1.9 4.1 10.1.5zM0 5l15.2 15.4 7.5-14.2L0 5zM34.2 6.8l-9.9-.5-8 15.2 7.4 8.1 8-7.9 2.5-14.9z"/></svg>
|
||||
|
||||
<svg className="Cog" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M29.18 19.07c-1.678-2.908-.668-6.634 2.256-8.328L28.29 5.295c-.897.527-1.942.83-3.057.83-3.36 0-6.085-2.743-6.085-6.126h-6.29c.01 1.043-.25 2.102-.81 3.07-1.68 2.907-5.41 3.896-8.34 2.21L.566 10.727c.905.515 1.69 1.268 2.246 2.234 1.677 2.904.673 6.624-2.24 8.32l3.145 5.447c.895-.522 1.935-.82 3.044-.82 3.35 0 6.066 2.725 6.083 6.092h6.29c-.004-1.035.258-2.08.81-3.04 1.676-2.902 5.4-3.893 8.325-2.218l3.145-5.447c-.9-.515-1.678-1.266-2.232-2.226zM16 22.48c-3.578 0-6.48-2.902-6.48-6.48S12.423 9.52 16 9.52c3.578 0 6.48 2.902 6.48 6.48s-2.902 6.48-6.48 6.48z"/></svg>
|
||||
</div>
|
||||
|
||||
<div className="fof">
|
||||
<h1>Error 404</h1>
|
||||
</div>
|
||||
|
||||
<p className="MainDescription">
|
||||
OOPS. Looks like the page you're looking for no longer exists
|
||||
</p>
|
||||
|
||||
<button className="err_btn" onClick={() => { navigate('/signIn') }}>返回登录</button>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { InfiniteScroll, List } from 'antd-mobile'
|
||||
|
||||
import { useSelector } from '../../redux/types/hooks'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import axios from 'axios'
|
||||
|
||||
import BottomBar from '../../components/BottomBar'
|
||||
|
||||
import '../../assets/css/topBar.scss'
|
||||
|
||||
//引入action
|
||||
import { pushHotLists } from '../../redux/actions'
|
||||
import { Lists } from '../../assets/type'
|
||||
|
||||
export default function HotLists(){
|
||||
|
||||
const navigate = useNavigate()
|
||||
const dispatch = useDispatch();
|
||||
const formData = useSelector(state => {
|
||||
return state.pushHotLists
|
||||
})
|
||||
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
|
||||
async function loadMore() {
|
||||
let url = '/recList'
|
||||
|
||||
let successData = await axios.get(url).then((res) => {
|
||||
return res.data
|
||||
})
|
||||
dispatch(pushHotLists([...formData, ...successData.data]))
|
||||
|
||||
setHasMore(formData.length < 40)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tabs">
|
||||
<input type="radio" id="tab1" name="tab-control" />
|
||||
<input type="radio" id="tab2" name="tab-control" />
|
||||
<ul>
|
||||
<li title="Features" style={{borderBottom: '2px solid #bec5cf'}} onClick={() => {navigate('/recLists')}}>
|
||||
<label htmlFor="tab1" role="button" style={{cursor: 'default',color: '#bec5cf'}}>
|
||||
<svg viewBox="0 0 24 24" style={{fill: '#bec5cf'}}>
|
||||
<path d="M14,2A8,8 0 0,0 6,10A8,8 0 0,0 14,18A8,8 0 0,0 22,10H20C20,13.32 17.32,16 14,16A6,6 0 0,1 8,10A6,6 0 0,1 14,4C14.43,4 14.86,4.05 15.27,4.14L16.88,2.54C15.96,2.18 15,2 14,2M20.59,3.58L14,10.17L11.62,7.79L10.21,9.21L14,13L22,5M4.93,5.82C3.08,7.34 2,9.61 2,12A8,8 0 0,0 10,20C10.64,20 11.27,19.92 11.88,19.77C10.12,19.38 8.5,18.5 7.17,17.29C5.22,16.25 4,14.21 4,12C4,11.7 4.03,11.41 4.07,11.11C4.03,10.74 4,10.37 4,10C4,8.56 4.32,7.13 4.93,5.82Z" />
|
||||
</svg>
|
||||
<span>推荐</span>
|
||||
</label>
|
||||
</li>
|
||||
<li title="Delivery Contents" style={{borderBottom: '2px solid #428bff'}}>
|
||||
<label htmlFor="tab2" role="button" style={{cursor: 'default',color: '#428bff'}}>
|
||||
<svg viewBox="0 0 24 24" style={{fill: '#428bff'}}>
|
||||
<path d="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" />
|
||||
</svg>
|
||||
<span>热门</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<List>
|
||||
{formData.map((item: Lists, index: number) => (
|
||||
<Link
|
||||
style={{ textDecoration:'none'}}
|
||||
key={index}
|
||||
to={`/newsInfo/${item.news_id}`}
|
||||
state={{
|
||||
id: item.news_id,
|
||||
likes: item.likes,
|
||||
collections: item.collections,
|
||||
cate: item.cate,
|
||||
read_num:item.read_num,
|
||||
}}>
|
||||
<List.Item key={index}>
|
||||
<p>
|
||||
<span className="cate">{item.cate}</span>
|
||||
<span className='title'>{ item.title }</span>
|
||||
</p>
|
||||
|
||||
<p className="discribe">
|
||||
<span className="ctime">{ item.ctime} </span>
|
||||
<span className="read_num">阅读:{item.read_num}</span>
|
||||
<span className="likes">喜欢:{item.likes}</span>
|
||||
<span className="collections">收藏:{item.collections}</span>
|
||||
</p>
|
||||
</List.Item>
|
||||
</Link>
|
||||
))}
|
||||
</List>
|
||||
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
|
||||
|
||||
<BottomBar />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
.profile {
|
||||
margin: auto;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-color {
|
||||
padding-bottom: 150px;
|
||||
width: 100vh;
|
||||
background: #4f759b;
|
||||
}
|
||||
|
||||
.profile-pic img {
|
||||
height: 40vw;
|
||||
width: 40vw;
|
||||
border-radius: 50%;
|
||||
border: 10px solid #ffffff;
|
||||
margin-top: -100px;
|
||||
}
|
||||
|
||||
.myself-title {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #131b23;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 25px;
|
||||
color: #131b23;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.description p:not(:last-child) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
color: #ffffff;
|
||||
background: #4f759b;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.025em;
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
min-height: 35px;
|
||||
width: 100px;
|
||||
margin-bottom: 25px;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
width: 115px;
|
||||
background: #4f759b;
|
||||
}
|
||||
|
||||
.about {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.datawhale {
|
||||
font-size: 1.3rem;
|
||||
padding: 0 20px 20px 20px;
|
||||
text-indent: 2em;
|
||||
line-height: 2.4rem;
|
||||
}
|
||||
|
||||
.dwimg {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
display: block;
|
||||
margin: 15px auto 100px auto;
|
||||
}
|
||||
1
codes/news_recsys/news_rec_web/react-fun-rec/src/pages/MySelf/index.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.profile{margin:auto;background:#ffffff;display:flex;flex-direction:column;align-items:center;text-align:center;box-shadow:rgba(0,0,0,0.19) 0px 10px 20px,rgba(0,0,0,0.23) 0px 6px 6px;-ms-overflow-style:none;scrollbar-width:none;overflow-y:scroll}::-webkit-scrollbar{display:none}.header-color{padding-bottom:150px;width:100vh;background:#4f759b}.profile-pic img{height:40vw;width:40vw;border-radius:50%;border:10px solid #ffffff;margin-top:-100px}.myself-title{margin-bottom:25px}h1{font-size:32px;font-weight:700;color:#131b23;margin-bottom:10px;letter-spacing:0.025em}.description{margin-bottom:25px;color:#131b23;letter-spacing:0.01em}.description p:not(:last-child){margin-bottom:5px}button{font-family:"Open Sans", sans-serif;color:#ffffff;background:#4f759b;font-size:18px;font-weight:600;letter-spacing:0.025em;border:none;border-radius:15px;min-height:35px;width:100px;margin-bottom:25px;transition:all 0.2s ease;cursor:pointer}button:hover{width:115px;background:#4f759b}.about{padding-top:20px;padding-bottom:20px;text-align:center;font-weight:bold;font-size:1.5rem}.datawhale{font-size:1.3rem;padding:0 20px 20px 20px;text-indent:2em;line-height:2.4rem}.dwimg{width:150px;height:150px;display:block;margin:15px auto 100px auto}
|
||||
@@ -0,0 +1,95 @@
|
||||
.profile {
|
||||
margin: auto;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-color {
|
||||
padding-bottom: 150px;
|
||||
width: 100vh;
|
||||
background: #4f759b;
|
||||
}
|
||||
|
||||
.profile-pic img {
|
||||
height: 40vw;
|
||||
width: 40vw;
|
||||
border-radius: 50%;
|
||||
border: 10px solid #ffffff;
|
||||
margin-top: -100px;
|
||||
}
|
||||
|
||||
.myself-title {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #131b23;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 25px;
|
||||
color: #131b23;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.description p:not(:last-child) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
color: #ffffff;
|
||||
background: #4f759b;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.025em;
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
min-height: 35px;
|
||||
width: 100px;
|
||||
margin-bottom: 25px;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
width: 115px;
|
||||
background: #4f759b;
|
||||
}
|
||||
|
||||
.about {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.datawhale {
|
||||
font-size: 1.3rem;
|
||||
padding: 0 20px 20px 20px;
|
||||
text-indent: 2em;
|
||||
line-height: 2.4rem;
|
||||
}
|
||||
|
||||
.dwimg {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
display: block;
|
||||
margin: 15px auto 100px auto;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import BottomBar from '../../components/BottomBar'
|
||||
|
||||
import './index.scss'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Toast } from 'antd-mobile'
|
||||
import { useSelector } from '../../redux/types/hooks'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { pushHotLists, pushRecLists, userLog } from '../../redux/actions'
|
||||
import { CLEAR_INFO } from '../../redux/types/constant'
|
||||
import { clearCookie } from '../../assets/js/cookie'
|
||||
|
||||
export default function MySelf() {
|
||||
|
||||
const navigate = useNavigate()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const quit = () => {
|
||||
dispatch(pushRecLists([]))
|
||||
dispatch(pushHotLists([]))
|
||||
dispatch(userLog(CLEAR_INFO, []))
|
||||
|
||||
clearCookie('LoginName')
|
||||
clearCookie('openId')
|
||||
|
||||
Toast.show({
|
||||
icon: 'success',
|
||||
content: '退出成功',
|
||||
})
|
||||
|
||||
let second = 1;
|
||||
// 延迟一秒执行
|
||||
const timer = setInterval(() => {
|
||||
second--;
|
||||
if (!second) {
|
||||
clearInterval(timer);
|
||||
// 手动清除 Toast
|
||||
Toast.clear();
|
||||
navigate('/signIn')
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const username = useSelector(state => state.userLog.data.username)
|
||||
|
||||
return (
|
||||
<div className="my-info">
|
||||
<div className="profile">
|
||||
<div className="profile-pic">
|
||||
<div className="header-color"></div>
|
||||
<img className="tx" src={require('../../assets/images/datawhale.png')} alt="头像" />
|
||||
</div>
|
||||
<div className="myself-title">
|
||||
<h1>{username}</h1>
|
||||
</div>
|
||||
<button className="follow" onClick={quit}>退出登录</button>
|
||||
<div className="description">
|
||||
<h4 className="about">DataWhale 新闻推荐开源项目</h4>
|
||||
<p className="datawhale">Datawhale是一个专注于数据科学与AI领域的开源组织,汇集了众多领域院校和知名企业的优秀学习者,
|
||||
聚合了一群有开源精神和探索精神的团队成员。Datawhale 以“for the learner,和学习者一起成长”为愿景,
|
||||
鼓励真实地展现自我、开放包容、互信互助、敢于试错和勇于担当。同时 Datawhale 用开源的理念去探
|
||||
索开源内容、开源学习和开源方案,赋能人才培养,助力人才成长,建立起人与人,人与知识,人与企业和
|
||||
人与未来的联结。</p>
|
||||
<img src={require('../../assets/images/dw.png')} alt="二维码" className="dwimg" />
|
||||
</div>
|
||||
|
||||
<BottomBar></BottomBar>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
@charset "UTF-8";
|
||||
/* 标题 */
|
||||
.newsTitle {
|
||||
padding: 3.5rem 1.5rem 0 1.5rem;
|
||||
}
|
||||
|
||||
/* 大标题 */
|
||||
.newsTitle h1 {
|
||||
text-align: center;
|
||||
color: #4d4f53;
|
||||
font-weight: 600;
|
||||
padding: 40px 0 20px 0;
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* 副标题 */
|
||||
.newsTitle p {
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
color: #4679e3;
|
||||
}
|
||||
|
||||
/* 内容 距离底部padding 遮挡action */
|
||||
.newsinfo-continer {
|
||||
padding-bottom: 5rem;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* 具体内容 */
|
||||
.content {
|
||||
font-size: 1rem;
|
||||
padding: 0 20px 20px 20px;
|
||||
text-indent: 2em;
|
||||
line-height: 2.2rem;
|
||||
/* 首行文本缩进 */
|
||||
color: #1c1b1d;
|
||||
}
|
||||
|
||||
/* 责任编辑 */
|
||||
.editor {
|
||||
font-size: 1rem;
|
||||
padding: 0 20px 0 20px;
|
||||
line-height: 2.2rem;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
#action {
|
||||
/* margin: 20px 0 20px 0; */
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
#action > span {
|
||||
display: flex;
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.blank {
|
||||
width: 100vw;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.block-style {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hide-style {
|
||||
display: none;
|
||||
}
|
||||
1
codes/news_recsys/news_rec_web/react-fun-rec/src/pages/NewsInfo/index.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.newsTitle{padding:3.5rem 1.5rem 0 1.5rem}.newsTitle h1{text-align:center;color:#4d4f53;font-weight:600;padding:40px 0 20px 0;margin:0;font-size:1.5rem}.newsTitle p{font-size:12px;display:flex;justify-content:space-between;padding-top:1rem;padding-bottom:1rem;color:#4679e3}.newsinfo-continer{padding-bottom:5rem;background-color:white}.content{font-size:1rem;padding:0 20px 20px 20px;text-indent:2em;line-height:2.2rem;color:#1c1b1d}.editor{font-size:1rem;padding:0 20px 0 20px;line-height:2.2rem;text-align:end}#action{display:flex;justify-content:space-evenly}#action>span{display:flex;padding-top:2rem;padding-bottom:2rem;font-size:1rem}.blank{width:100vw;height:50px;position:fixed;bottom:0;background-color:white}.block-style{display:block}.hide-style{display:none}
|
||||
@@ -0,0 +1,76 @@
|
||||
/* 标题 */
|
||||
.newsTitle {
|
||||
padding: 3.5rem 1.5rem 0 1.5rem;
|
||||
}
|
||||
|
||||
/* 大标题 */
|
||||
.newsTitle h1 {
|
||||
text-align: center;
|
||||
color: rgb(77,79,83);
|
||||
font-weight: 600;
|
||||
padding: 40px 0 20px 0;
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
/* 副标题 */
|
||||
.newsTitle p {
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
color: #4679e3;
|
||||
}
|
||||
|
||||
/* 内容 距离底部padding 遮挡action */
|
||||
.newsinfo-continer{
|
||||
padding-bottom: 5rem;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* 具体内容 */
|
||||
.content {
|
||||
font-size: 1rem;
|
||||
padding: 0 20px 20px 20px;
|
||||
text-indent: 2em;
|
||||
line-height: 2.2rem;
|
||||
/* 首行文本缩进 */
|
||||
color: rgb(28,27,29);
|
||||
}
|
||||
|
||||
/* 责任编辑 */
|
||||
.editor {
|
||||
font-size: 1rem;
|
||||
padding: 0 20px 0 20px;
|
||||
line-height: 2.2rem;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
#action {
|
||||
/* margin: 20px 0 20px 0; */
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
#action>span {
|
||||
display: flex;
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.blank {
|
||||
width: 100vw;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.block-style{
|
||||
display: block
|
||||
}
|
||||
.hide-style{
|
||||
display: none
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import { NavBar, Toast } from 'antd-mobile';
|
||||
import axios from 'axios';
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { ToListInfo } from '../../assets/type';
|
||||
import { collectionsDecrease, collectionsIncrease, likesDecrease, likesIncrease, readChange } from '../../redux/actions';
|
||||
import { useSelector } from '../../redux/types/hooks';
|
||||
|
||||
import './index.scss'
|
||||
|
||||
export default function NewsInfo(){
|
||||
const navigate = useNavigate()
|
||||
const back = () => {
|
||||
navigate(-1)
|
||||
}
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const location = useLocation()
|
||||
const { id,cate } = location.state as ToListInfo;
|
||||
|
||||
const [news_content, setNewsContent] = useState<any>({})
|
||||
const [content, setContent] = useState('')
|
||||
const [editor, setEditor] = useState('')
|
||||
const [isLike, setIsLike] = useState(false)
|
||||
const [isCollection, setIsCollection] = useState(false)
|
||||
// let isFixed: true,
|
||||
|
||||
let reg = /责任编辑/
|
||||
let user_name = useSelector(state => {
|
||||
return state.userLog.data.username
|
||||
});
|
||||
|
||||
let lists = useSelector(state => state.pushRecLists)
|
||||
|
||||
// 点击喜欢时发送请求
|
||||
async function ifLike() {
|
||||
let val = {
|
||||
user_name: user_name,
|
||||
news_id: id,
|
||||
action_time: Date.now(),
|
||||
action_type: `likes:${isLike}`,
|
||||
}
|
||||
|
||||
let successData = await axios.post("/action", val).then(res => {
|
||||
return res
|
||||
})
|
||||
|
||||
if (successData.status === 200) {
|
||||
setIsLike(!isLike)
|
||||
// 调用store中的actionChange函数控制次数的变化
|
||||
if (isLike === true) {
|
||||
dispatch(likesDecrease({ lists, id }))
|
||||
} else {
|
||||
dispatch(likesIncrease({ lists, id }))
|
||||
}
|
||||
} else {
|
||||
Toast.show({
|
||||
content: '加载数据失败',
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// 点击收藏时发送请求
|
||||
async function ifCollection() {
|
||||
let val = {
|
||||
user_name: user_name,
|
||||
news_id: id,
|
||||
action_time: Date.now(),
|
||||
action_type: `collections::${isCollection}`,
|
||||
}
|
||||
|
||||
let successData = await axios.post("/action", val).then(res => {
|
||||
return res
|
||||
})
|
||||
|
||||
if (successData.status === 200) {
|
||||
setIsCollection(!isCollection)
|
||||
// 调用store中的actionChange函数控制次数的变化
|
||||
if (isCollection === true) {
|
||||
dispatch(collectionsDecrease({ lists, id }))
|
||||
} else {
|
||||
dispatch(collectionsIncrease({ lists, id }))
|
||||
}
|
||||
} else {
|
||||
Toast.show({
|
||||
content: '加载数据失败',
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
// 获取新闻详情
|
||||
(async function getNewsInfo () {
|
||||
let successData = await axios.get("/newsInfo?news_id=" + id + '&user_name=' + user_name).then(res => {
|
||||
return res
|
||||
})
|
||||
|
||||
if (successData.status === 200) {
|
||||
setNewsContent(successData.data.data)
|
||||
setContent(successData.data.data.content.split(reg)[0])
|
||||
setEditor('责任编辑:' + successData.data.data.content.split(reg)[1])
|
||||
|
||||
if (successData.data.data.likes === true) {
|
||||
setIsLike(true)
|
||||
} else {
|
||||
setIsLike(false)
|
||||
}
|
||||
if (successData.data.data.collections === true) {
|
||||
setIsCollection(true)
|
||||
} else {
|
||||
setIsCollection(false)
|
||||
}
|
||||
} else {
|
||||
Toast.show({
|
||||
content: '加载数据失败',
|
||||
})
|
||||
}
|
||||
})();
|
||||
|
||||
// 发送action为read的请求
|
||||
(async function sendInfo() {
|
||||
let val = {
|
||||
user_name: user_name,
|
||||
news_id: id,
|
||||
action_time: Date.now(),
|
||||
action_type: 'read',
|
||||
}
|
||||
|
||||
let successData = await axios.post("/action", val).then(res => {
|
||||
return res
|
||||
})
|
||||
|
||||
if (successData.status === 200) {
|
||||
dispatch(readChange({ lists, id }))
|
||||
} else {
|
||||
Toast.show({
|
||||
content: '加载数据失败',
|
||||
})
|
||||
}
|
||||
})();
|
||||
},[])
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavBar back='返回' onBack={back}></NavBar>
|
||||
<div className="newsinfo-continer">
|
||||
<div className="newsTitle">
|
||||
{/* 大标题 */}
|
||||
<h1>{ news_content.title }</h1>
|
||||
{/* 子标题 */}
|
||||
<p>
|
||||
<span>发布时间:{ news_content.ctime}</span>
|
||||
<span>标签:{cate}</span>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div className="content" dangerouslySetInnerHTML = {{ __html : content }}></div>
|
||||
<div className="editor" dangerouslySetInnerHTML = {{ __html : editor }}></div>
|
||||
|
||||
<div id="action">
|
||||
<span>喜欢:
|
||||
<img src={require("../../assets/images/likes.png")} alt='likes' className={!isLike ? 'block-style' : 'hide-style'} onClick={ifLike} />
|
||||
<img src={require("../../assets/images/likes1.png")} alt='likes1' className={isLike?'block-style':'hide-style'} onClick={ifLike} />
|
||||
</span>
|
||||
<span>收藏:
|
||||
<img src={require("../../assets/images/collects.png")} alt='collects' className={!isCollection ? 'block-style' : 'hide-style'} onClick={ifCollection} />
|
||||
<img src={require("../../assets/images/collects1.png")} alt='collects1' className={isCollection ? 'block-style' : 'hide-style'} onClick={ifCollection} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="blank"></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { InfiniteScroll, List } from 'antd-mobile'
|
||||
|
||||
import { useSelector } from '../../redux/types/hooks'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import axios from 'axios'
|
||||
|
||||
import BottomBar from '../../components/BottomBar'
|
||||
|
||||
import '../../assets/css/topBar.scss'
|
||||
|
||||
//引入action
|
||||
import { pushRecLists } from '../../redux/actions'
|
||||
import { Lists } from '../../assets/type'
|
||||
|
||||
export default function RecLists(){
|
||||
|
||||
const navigate = useNavigate()
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const formData = useSelector(state => state.pushRecLists)
|
||||
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
|
||||
async function loadMore() {
|
||||
let url = '/recList'
|
||||
|
||||
let successData = await axios.get(url).then((res) => {
|
||||
return res.data
|
||||
})
|
||||
|
||||
dispatch(pushRecLists([...formData, ...successData.data]))
|
||||
|
||||
setHasMore(formData.length < 40)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="tabs">
|
||||
<input type="radio" id="tab1" name="tab-control" />
|
||||
<input type="radio" id="tab2" name="tab-control" />
|
||||
<ul>
|
||||
<li title="Features" style={{borderBottom: '2px solid #428bff'}}>
|
||||
<label htmlFor="tab1" role="button" style={{cursor: 'default',color: '#428bff'}}>
|
||||
<svg viewBox="0 0 24 24" style={{fill: '#428bff'}}>
|
||||
<path d="M14,2A8,8 0 0,0 6,10A8,8 0 0,0 14,18A8,8 0 0,0 22,10H20C20,13.32 17.32,16 14,16A6,6 0 0,1 8,10A6,6 0 0,1 14,4C14.43,4 14.86,4.05 15.27,4.14L16.88,2.54C15.96,2.18 15,2 14,2M20.59,3.58L14,10.17L11.62,7.79L10.21,9.21L14,13L22,5M4.93,5.82C3.08,7.34 2,9.61 2,12A8,8 0 0,0 10,20C10.64,20 11.27,19.92 11.88,19.77C10.12,19.38 8.5,18.5 7.17,17.29C5.22,16.25 4,14.21 4,12C4,11.7 4.03,11.41 4.07,11.11C4.03,10.74 4,10.37 4,10C4,8.56 4.32,7.13 4.93,5.82Z" />
|
||||
</svg>
|
||||
<span>推荐</span>
|
||||
</label>
|
||||
</li>
|
||||
<li title="Delivery Contents" style={{borderBottom: '2px solid #bec5cf'}} onClick={() => { navigate('/hotLists') }}>
|
||||
<label htmlFor="tab2" role="button" style={{cursor: 'default',color: '#bec5cf'}}>
|
||||
<svg viewBox="0 0 24 24" style={{fill: '#bec5cf'}}>
|
||||
<path d="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" />
|
||||
</svg>
|
||||
<span>热门</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<List>
|
||||
{formData.map((item: Lists, index: number) => (
|
||||
<Link
|
||||
style={{ textDecoration:'none'}}
|
||||
key={index}
|
||||
to={`/newsInfo/${item.news_id}`}
|
||||
state={{
|
||||
id: item.news_id,
|
||||
likes: item.likes,
|
||||
collections: item.collections,
|
||||
cate: item.cate,
|
||||
read_num:item.read_num,
|
||||
}}>
|
||||
<List.Item key={index}>
|
||||
<p>
|
||||
<span className="cate">{item.cate}</span>
|
||||
<span className='title'>{ item.title }</span>
|
||||
</p>
|
||||
|
||||
<p className="discribe">
|
||||
<span className="ctime">{ item.ctime} </span>
|
||||
<span className="read_num">阅读:{item.read_num}</span>
|
||||
<span className="likes">喜欢:{item.likes}</span>
|
||||
<span className="collections">收藏:{item.collections}</span>
|
||||
</p>
|
||||
</List.Item>
|
||||
</Link>
|
||||
))}
|
||||
</List>
|
||||
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
|
||||
|
||||
<BottomBar />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
@charset "UTF-8";
|
||||
.login-nav {
|
||||
margin: 0 0 6em 1rem;
|
||||
}
|
||||
|
||||
/* container */
|
||||
.login-container {
|
||||
height: 100vh;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
letter-spacing: 1px;
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
background-image: url(https://img0.baidu.com/it/u=3564438015,1736378667&fm=26&fmt=auto);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: 100% 100%;
|
||||
background-color: black;
|
||||
padding: 4rem 4rem 4rem 4rem;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 登录和注册的切换 */
|
||||
.login-nav {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.login-nav__item {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.login-nav__item + .login-nav__item {
|
||||
margin-left: 2.25rem;
|
||||
}
|
||||
|
||||
.login-nav__item a {
|
||||
position: relative;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
padding-bottom: .5rem;
|
||||
transition: .20s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item.active a,
|
||||
.login-nav__item a:hover {
|
||||
color: #ffffff;
|
||||
transition: .15s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item a:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
border-radius: 50%;
|
||||
transition: .15s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item a:hover:after,
|
||||
.login-nav__item.active a:after {
|
||||
background-color: #1161ed;
|
||||
height: 2px;
|
||||
right: 0;
|
||||
bottom: 2px;
|
||||
border-radius: 0;
|
||||
transition: .20s all ease;
|
||||
}
|
||||
|
||||
/* label标签 */
|
||||
.login__label {
|
||||
display: block;
|
||||
padding-left: 1rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-transform: uppercase;
|
||||
font-size: .75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.login__label {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.login__submit {
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 2rem;
|
||||
padding: .75rem;
|
||||
border-radius: 2rem;
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: rgba(17, 97, 237, 0.75);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login__submit:hover {
|
||||
background-color: #1161ed;
|
||||
}
|
||||
|
||||
.login__label {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-transform: uppercase;
|
||||
font-size: .75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.login__label--checkbox {
|
||||
font-size: .75rem;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.login__input--checkbox {
|
||||
position: absolute;
|
||||
top: .1rem;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login__forgot {
|
||||
display: block;
|
||||
margin-top: 3rem;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
font-size: .75rem;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.login__forgot:hover {
|
||||
color: #1161ed;
|
||||
}
|
||||
|
||||
.adm-list-body {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.adm-form {
|
||||
--border-inner: none;
|
||||
--border-top: none;
|
||||
--border-bottom: none;
|
||||
}
|
||||
|
||||
.adm-input {
|
||||
--color: rgba(255, 255, 255, 0.5);
|
||||
--placeholder-color: rgba(255, 255, 255, 0.5);
|
||||
--font-size: 1.225rem;
|
||||
}
|
||||
|
||||
.adm-checkbox-content {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
1
codes/news_recsys/news_rec_web/react-fun-rec/src/pages/SignIn/index.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.login-nav{margin:0 0 6em 1rem}.login-container{height:100vh;font-family:'Montserrat', sans-serif;font-size:16px;line-height:1;letter-spacing:1px;display:block;position:relative;z-index:0;background-image:url(https://img0.baidu.com/it/u=3564438015,1736378667&fm=26&fmt=auto);background-repeat:no-repeat;background-attachment:fixed;background-size:100% 100%;background-color:black;padding:4rem 4rem 4rem 4rem;overflow:hidden;height:100vh;box-sizing:border-box}.login-nav{position:relative;padding:0}.login-nav__item{list-style:none;display:inline-block}.login-nav__item+.login-nav__item{margin-left:2.25rem}.login-nav__item a{position:relative;color:rgba(255,255,255,0.5);text-decoration:none;text-transform:uppercase;font-weight:500;font-size:1.5rem;padding-bottom:.5rem;transition:.20s all ease}.login-nav__item.active a,.login-nav__item a:hover{color:#ffffff;transition:.15s all ease}.login-nav__item a:after{content:'';display:inline-block;height:10px;background-color:#fff;position:absolute;right:100%;bottom:-1px;left:0;border-radius:50%;transition:.15s all ease}.login-nav__item a:hover:after,.login-nav__item.active a:after{background-color:#1161ed;height:2px;right:0;bottom:2px;border-radius:0;transition:.20s all ease}.login__label{display:block;padding-left:1rem;color:rgba(255,255,255,0.5);text-transform:uppercase;font-size:.75rem;margin-bottom:1rem}.login__label{margin-top:1.5rem}.login__submit{color:#ffffff;font-size:1rem;font-family:'Montserrat', sans-serif;text-transform:uppercase;letter-spacing:1px;margin-top:2rem;padding:.75rem;border-radius:2rem;display:block;width:100%;background-color:rgba(17,97,237,0.75);border:none;cursor:pointer}.login__submit:hover{background-color:#1161ed}.login__label{color:rgba(255,255,255,0.5);text-transform:uppercase;font-size:.75rem;margin-bottom:1rem}.login__label--checkbox{font-size:.75rem;margin-bottom:1rem;display:flex;position:relative;margin-top:2rem}.login__input--checkbox{position:absolute;top:.1rem;left:0;margin:0}.login__forgot{display:block;margin-top:3rem;text-align:center;color:rgba(255,255,255,0.75);font-size:.75rem;text-decoration:none;position:relative;z-index:1}.login__forgot:hover{color:#1161ed}.adm-list-body{background-color:transparent}.adm-form{--border-inner:none;--border-top:none;--border-bottom:none}.adm-input{--color:rgba(255,255,255,0.5);--placeholder-color:rgba(255,255,255,0.5);--font-size:1.225rem}.adm-checkbox-content{color:rgba(255,255,255,0.5)}
|
||||
@@ -0,0 +1,172 @@
|
||||
.login-nav {
|
||||
margin: 0 0 6em 1rem;
|
||||
}
|
||||
/* container */
|
||||
.login-container {
|
||||
height: 100vh;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
letter-spacing: 1px;
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
background-image: url(https://img0.baidu.com/it/u=3564438015,1736378667&fm=26&fmt=auto);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: 100% 100%;
|
||||
background-color: black;
|
||||
padding: 4rem 4rem 4rem 4rem;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 登录和注册的切换 */
|
||||
.login-nav {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.login-nav__item {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.login-nav__item+.login-nav__item {
|
||||
margin-left: 2.25rem;
|
||||
}
|
||||
|
||||
.login-nav__item a {
|
||||
position: relative;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
padding-bottom: .5rem;
|
||||
transition: .20s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item.active a,
|
||||
.login-nav__item a:hover {
|
||||
color: #ffffff;
|
||||
transition: .15s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item a:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
border-radius: 50%;
|
||||
transition: .15s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item a:hover:after,
|
||||
.login-nav__item.active a:after {
|
||||
background-color: rgb(17, 97, 237);
|
||||
height: 2px;
|
||||
right: 0;
|
||||
bottom: 2px;
|
||||
border-radius: 0;
|
||||
transition: .20s all ease;
|
||||
}
|
||||
|
||||
/* label标签 */
|
||||
.login__label {
|
||||
display: block;
|
||||
padding-left: 1rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-transform: uppercase;
|
||||
font-size: .75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.login__label {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.login__submit {
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 2rem;
|
||||
padding: .75rem;
|
||||
border-radius: 2rem;
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: rgba(17, 97, 237, .75);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login__submit:hover {
|
||||
background-color: rgba(17, 97, 237, 1);
|
||||
}
|
||||
|
||||
.login__label{
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-transform: uppercase;
|
||||
font-size: .75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.login__label--checkbox {
|
||||
font-size: .75rem;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.login__input--checkbox {
|
||||
position: absolute;
|
||||
top: .1rem;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login__forgot {
|
||||
display: block;
|
||||
margin-top: 3rem;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
font-size: .75rem;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.login__forgot:hover {
|
||||
color: rgb(17, 97, 237);
|
||||
}
|
||||
|
||||
// 去除antd默认表单背景色
|
||||
.adm-list-body{
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
// 去除antd默认表单边框
|
||||
.adm-form{
|
||||
--border-inner: none;
|
||||
--border-top: none;
|
||||
--border-bottom: none;
|
||||
}
|
||||
// antd input框字体
|
||||
.adm-input{
|
||||
--color:rgba(255, 255, 255, 0.5);
|
||||
--placeholder-color:rgba(255, 255, 255, 0.5);
|
||||
--font-size: 1.225rem;
|
||||
}
|
||||
// antd 单选框字体
|
||||
.adm-checkbox-content {
|
||||
color:rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { NavLink, useNavigate } from 'react-router-dom'
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import { Form, Input, Checkbox, Toast } from 'antd-mobile';
|
||||
|
||||
import './index.scss'
|
||||
|
||||
//引入action
|
||||
import { userLog } from '../../redux/actions'
|
||||
import { SIGNIN } from '../../redux/types/constant';
|
||||
|
||||
import encrypt from '../../assets/js/encrypt'
|
||||
import axios from 'axios';
|
||||
import {setCookie} from '../../assets/js/cookie'
|
||||
import { UserSignIn } from '../../assets/type';
|
||||
|
||||
export const SignIn = () => {
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate()
|
||||
|
||||
async function onFinish(values: UserSignIn) {
|
||||
|
||||
//密码解密
|
||||
let res = { username: values.name, passwd: encrypt.Decrypt(values.passwd) };
|
||||
let successData = await axios.post("/login", res).then((res) => {
|
||||
return res.data
|
||||
})
|
||||
|
||||
if (successData.code === 200) {
|
||||
let loginInfo = {
|
||||
LoginName: res.username,
|
||||
openId: "asfafsfsfsdfsdfsdfdsf"
|
||||
}
|
||||
|
||||
// checke:true--选中记住我 checke:false--未选中记住我
|
||||
if (values.remember) {
|
||||
// 调用setCookie方法,同时传递需要存储的数据,保存天数
|
||||
setCookie(loginInfo, 7)
|
||||
} else {
|
||||
setCookie(loginInfo, 1)
|
||||
}
|
||||
|
||||
Toast.show({
|
||||
icon: 'success',
|
||||
content: '登录成功',
|
||||
})
|
||||
|
||||
let second = 1;
|
||||
// 延迟一秒执行
|
||||
const timer = setInterval(() => {
|
||||
second--;
|
||||
if (!second) {
|
||||
clearInterval(timer);
|
||||
// 手动清除 Toast
|
||||
Toast.clear();
|
||||
dispatch(userLog(SIGNIN, res))
|
||||
navigate('/recLists', { replace: true })
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
} else if (successData.code === 500) {
|
||||
Toast.show({
|
||||
icon: 'fail',
|
||||
content: '登陆失败',
|
||||
})
|
||||
} else if (successData.code === 501) {
|
||||
Toast.show({
|
||||
icon: 'fail',
|
||||
content: '密码输入错误',
|
||||
})
|
||||
} else if (successData.code === 502) {
|
||||
Toast.show({
|
||||
icon: 'fail',
|
||||
content: '用户名不存在',
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login-container">
|
||||
<ul className="login-nav">
|
||||
<li className="login-nav__item active">
|
||||
<NavLink to="/signIn">登录</NavLink>
|
||||
</li>
|
||||
<li className="login-nav__item">
|
||||
<NavLink to='/signUp'>注册</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
<Form
|
||||
onFinish={onFinish}
|
||||
initialValues={{remember:true}}
|
||||
footer={<button className="login__submit">登录</button>}
|
||||
>
|
||||
|
||||
<label className="login__label">用户名</label>
|
||||
<Form.Item name='name'
|
||||
rules={[
|
||||
{ required: true, message: '请输入用户名' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder='请输入用户名' />
|
||||
</Form.Item>
|
||||
|
||||
<label className="login__label">密码</label>
|
||||
<Form.Item
|
||||
name='passwd'
|
||||
rules={[
|
||||
{ required: true, message: '请输入密码' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder='请输入密码' type='password' />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name='remember'>
|
||||
<Checkbox className="login__label--checkbox" defaultChecked
|
||||
style={{
|
||||
'--icon-size': '18px',
|
||||
'--font-size': '14px',
|
||||
'--gap': '6px',
|
||||
}}>记住我</Checkbox>
|
||||
</Form.Item>
|
||||
|
||||
</Form>
|
||||
|
||||
{/*<a href="#" className="login__forgot">忘记密码?</a>*/}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignIn
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
@charset "UTF-8";
|
||||
.errorMessage {
|
||||
color: red;
|
||||
font-size: .8rem;
|
||||
display: block;
|
||||
padding: 1rem 0 0 1rem;
|
||||
}
|
||||
|
||||
/* container */
|
||||
.login-container {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
letter-spacing: 1px;
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
background-image: url(https://img0.baidu.com/it/u=3564438015,1736378667&fm=26&fmt=auto);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: 100% 100%;
|
||||
background-color: black;
|
||||
padding: 4rem 4rem 4rem 4rem;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 登录和注册的切换 */
|
||||
.login-nav {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0 0 3em 1rem;
|
||||
}
|
||||
|
||||
.login-nav__item {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.login-nav__item + .login-nav__item {
|
||||
margin-left: 2.25rem;
|
||||
}
|
||||
|
||||
.login-nav__item a {
|
||||
position: relative;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
padding-bottom: .5rem;
|
||||
transition: .20s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item.active a,
|
||||
.login-nav__item a:hover {
|
||||
color: #ffffff;
|
||||
transition: .15s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item a:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
border-radius: 50%;
|
||||
transition: .15s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item a:hover:after,
|
||||
.login-nav__item.active a:after {
|
||||
background-color: #1161ed;
|
||||
height: 2px;
|
||||
right: 0;
|
||||
bottom: 2px;
|
||||
border-radius: 0;
|
||||
transition: .20s all ease;
|
||||
}
|
||||
|
||||
/* label标签 */
|
||||
.login__label {
|
||||
display: block;
|
||||
padding-left: 1rem;
|
||||
color: #cccccc;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.login__label {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.login__submit {
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 2rem;
|
||||
padding: .75rem;
|
||||
border-radius: 2rem;
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: rgba(17, 97, 237, 0.75);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login__submit:hover {
|
||||
background-color: #1161ed;
|
||||
}
|
||||
|
||||
.adm-list-body {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.adm-form {
|
||||
--border-inner: none;
|
||||
--border-top: none;
|
||||
--border-bottom: none;
|
||||
}
|
||||
|
||||
.adm-input {
|
||||
--color: rgba(255, 255, 255, 0.5);
|
||||
--placeholder-color: rgba(255, 255, 255, 0.5);
|
||||
--font-size: 1.225rem;
|
||||
}
|
||||
|
||||
.adm-list {
|
||||
--active-background-color: transparent;
|
||||
}
|
||||
|
||||
.adm-plain-anchor {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.adm-radio-content {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.adm-radio {
|
||||
padding: 10px;
|
||||
}
|
||||
1
codes/news_recsys/news_rec_web/react-fun-rec/src/pages/SignUp/index.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.errorMessage{color:red;font-size:.8rem;display:block;padding:1rem 0 0 1rem}.login-container{font-family:'Montserrat', sans-serif;font-size:16px;line-height:1;letter-spacing:1px;display:block;position:relative;z-index:0;background-image:url(https://img0.baidu.com/it/u=3564438015,1736378667&fm=26&fmt=auto);background-repeat:no-repeat;background-attachment:fixed;background-size:100% 100%;background-color:black;padding:4rem 4rem 4rem 4rem;overflow:hidden;height:100vh;box-sizing:border-box}.login-nav{position:relative;padding:0;margin:0 0 3em 1rem}.login-nav__item{list-style:none;display:inline-block}.login-nav__item+.login-nav__item{margin-left:2.25rem}.login-nav__item a{position:relative;color:rgba(255,255,255,0.5);text-decoration:none;text-transform:uppercase;font-weight:500;font-size:1.5rem;padding-bottom:.5rem;transition:.20s all ease}.login-nav__item.active a,.login-nav__item a:hover{color:#ffffff;transition:.15s all ease}.login-nav__item a:after{content:'';display:inline-block;height:10px;background-color:#fff;position:absolute;right:100%;bottom:-1px;left:0;border-radius:50%;transition:.15s all ease}.login-nav__item a:hover:after,.login-nav__item.active a:after{background-color:#1161ed;height:2px;right:0;bottom:2px;border-radius:0;transition:.20s all ease}.login__label{display:block;padding-left:1rem;color:#cccccc;text-transform:uppercase;font-size:1.25rem;margin-bottom:1rem}.login__label{margin-top:1.5rem}.login__submit{color:#ffffff;font-size:1rem;font-family:'Montserrat', sans-serif;text-transform:uppercase;letter-spacing:1px;margin-top:2rem;padding:.75rem;border-radius:2rem;display:block;width:100%;background-color:rgba(17,97,237,0.75);border:none;cursor:pointer}.login__submit:hover{background-color:#1161ed}.adm-list-body{background-color:transparent}.adm-form{--border-inner:none;--border-top:none;--border-bottom:none}.adm-input{--color:rgba(255,255,255,0.5);--placeholder-color:rgba(255,255,255,0.5);--font-size:1.225rem}.adm-list{--active-background-color:transparent}.adm-plain-anchor{color:rgba(255,255,255,0.5)}.adm-radio-content{color:rgba(255,255,255,0.5)}.adm-radio{padding:10px}
|
||||
@@ -0,0 +1,153 @@
|
||||
.errorMessage {
|
||||
color: red;
|
||||
font-size: .8rem;
|
||||
display: block;
|
||||
padding: 1rem 0 0 1rem;
|
||||
}
|
||||
|
||||
/* container */
|
||||
.login-container {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
letter-spacing: 1px;
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
background-image: url(https://img0.baidu.com/it/u=3564438015,1736378667&fm=26&fmt=auto);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: 100% 100%;
|
||||
background-color: black;
|
||||
padding: 4rem 4rem 4rem 4rem;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 登录和注册的切换 */
|
||||
.login-nav {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0 0 3em 1rem;
|
||||
}
|
||||
|
||||
.login-nav__item {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.login-nav__item+.login-nav__item {
|
||||
margin-left: 2.25rem;
|
||||
}
|
||||
|
||||
.login-nav__item a {
|
||||
position: relative;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
padding-bottom: .5rem;
|
||||
transition: .20s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item.active a,
|
||||
.login-nav__item a:hover {
|
||||
color: #ffffff;
|
||||
transition: .15s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item a:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
border-radius: 50%;
|
||||
transition: .15s all ease;
|
||||
}
|
||||
|
||||
.login-nav__item a:hover:after,
|
||||
.login-nav__item.active a:after {
|
||||
background-color: rgb(17, 97, 237);
|
||||
height: 2px;
|
||||
right: 0;
|
||||
bottom: 2px;
|
||||
border-radius: 0;
|
||||
transition: .20s all ease;
|
||||
}
|
||||
|
||||
/* label标签 */
|
||||
.login__label {
|
||||
display: block;
|
||||
padding-left: 1rem;
|
||||
color: #cccccc;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.login__label {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.login__submit {
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 2rem;
|
||||
padding: .75rem;
|
||||
border-radius: 2rem;
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: rgba(17, 97, 237, .75);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login__submit:hover {
|
||||
background-color: rgba(17, 97, 237, 1);
|
||||
}
|
||||
|
||||
// 去除antd默认表单背景色
|
||||
.adm-list-body{
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
// 去除antd默认表单边框
|
||||
.adm-form{
|
||||
--border-inner: none;
|
||||
--border-top: none;
|
||||
--border-bottom: none;
|
||||
}
|
||||
// antd input框字体
|
||||
.adm-input{
|
||||
--color:rgba(255, 255, 255, 0.5);
|
||||
--placeholder-color:rgba(255, 255, 255, 0.5);
|
||||
--font-size: 1.225rem;
|
||||
}
|
||||
// 去除antd级联选择背景颜色
|
||||
.adm-list{
|
||||
--active-background-color:transparent
|
||||
}
|
||||
// antd级联选择字体颜色
|
||||
.adm-plain-anchor{
|
||||
color:rgba(255, 255, 255, 0.5)
|
||||
}
|
||||
|
||||
// antd单选框字体颜色
|
||||
.adm-radio-content{
|
||||
color:rgba(255, 255, 255, 0.5)
|
||||
}
|
||||
|
||||
// antd单选框边距
|
||||
.adm-radio{
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import {useState} from 'react'
|
||||
import { NavLink, useNavigate } from 'react-router-dom'
|
||||
import { Form, Input, Radio, CascadePicker, Toast } from 'antd-mobile'
|
||||
|
||||
import city from '../../assets/data/city'
|
||||
import './index.scss'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { userLog } from '../../redux/actions'
|
||||
import { SIGNUP } from '../../redux/types/constant'
|
||||
import encrypt from '../../assets/js/encrypt'
|
||||
import axios from 'axios'
|
||||
import { setCookie } from '../../assets/js/cookie'
|
||||
import { UserSignUp } from '../../assets/type'
|
||||
|
||||
let pinyin = require('js-pinyin')
|
||||
|
||||
|
||||
export default function SignUp(){
|
||||
|
||||
// 控制级联选择是否显示
|
||||
const [visible, setVisible] = useState(false)
|
||||
// 获取选择的城市数据
|
||||
const [cityValue, setCityValue] = useState<any>('')
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate()
|
||||
|
||||
async function onFinish(values: UserSignUp){
|
||||
values.area = cityValue
|
||||
//密码加密
|
||||
values.passwd = encrypt.Encrypt(values.passwd)
|
||||
let successData = await axios.post("/register", values).then((res)=>{
|
||||
return res.data
|
||||
})
|
||||
|
||||
if (successData.code === 200) {
|
||||
let loginInfo = {
|
||||
LoginName: values.name,
|
||||
openId: "asdasdadasdasdadad"
|
||||
}
|
||||
|
||||
// 调用setCookie方法,同时传递需要存储的数据,保存天数
|
||||
setCookie(loginInfo, 7)
|
||||
Toast.show({
|
||||
icon: 'success',
|
||||
content: '注册成功',
|
||||
})
|
||||
|
||||
let second = 1;
|
||||
// 延迟一秒执行
|
||||
const timer = setInterval(() => {
|
||||
second--;
|
||||
if (!second) {
|
||||
clearInterval(timer);
|
||||
// 手动清除 Toast
|
||||
Toast.clear();
|
||||
dispatch(userLog(SIGNUP, values));
|
||||
navigate('/recLists', { replace: true })
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
}else if(successData.code === 500) {
|
||||
Toast.show({
|
||||
icon: 'fail',
|
||||
content: '用户名已存在',
|
||||
})
|
||||
}else {
|
||||
Toast.show({
|
||||
icon: 'fail',
|
||||
content: '请完整填写注册信息',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const checkName = (_: any, value: string) => {
|
||||
let nameReg = /^[A-Za-z0-9]+$/
|
||||
if (!nameReg.test(value) && value) {
|
||||
return Promise.reject(new Error('用户名格式为字母和数字'))
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
const checkPasswd = (_: any, value: string) => {
|
||||
let passwdReg = /^[A-Za-z0-9]{6,}$/
|
||||
if (!passwdReg.test(value) && value) {
|
||||
return Promise.reject(new Error('密码长度至少6位,仅包括为字母和数字'))
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
const checkAge = (_: any, value: string) => {
|
||||
var ageReg = /^([1-9][0-9]{0,1}|100)$/
|
||||
if (!ageReg.test(value) && value) {
|
||||
return Promise.reject(new Error('请输入1-100的整数'))
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login-container">
|
||||
<ul className="login-nav">
|
||||
<li className="login-nav__item">
|
||||
<NavLink to="/signIn">登录</NavLink>
|
||||
</li>
|
||||
<li className="login-nav__item active">
|
||||
<NavLink to='/signUp'>注册</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
<Form
|
||||
onFinish={onFinish}
|
||||
initialValues={{gender:'male'}}
|
||||
footer={<button className="login__submit">注册</button>}
|
||||
>
|
||||
|
||||
<label className="login__label">用户名</label>
|
||||
<Form.Item name='name'
|
||||
rules={[
|
||||
{ required: true, message: '请输入用户名' },
|
||||
{ validator: checkName}
|
||||
]}
|
||||
>
|
||||
<Input placeholder='请输入用户名' />
|
||||
</Form.Item>
|
||||
|
||||
<label className="login__label">密码</label>
|
||||
<Form.Item
|
||||
name='passwd'
|
||||
rules={[
|
||||
{ required: true, message: '请输入密码' },
|
||||
{ validator: checkPasswd }]
|
||||
}
|
||||
>
|
||||
<Input placeholder='请输入密码' type='password' />
|
||||
</Form.Item>
|
||||
|
||||
<label className="login__label">验证密码</label>
|
||||
<Form.Item
|
||||
name='passwd2'
|
||||
dependencies={['passwd']}
|
||||
rules={[
|
||||
{ required: true, message: '请再次输入密码' },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (getFieldValue('passwd') === value && value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('两次输入的密码不一致'));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input placeholder='再次输入密码' type='password' />
|
||||
</Form.Item>
|
||||
|
||||
<label className="login__label">年龄</label>
|
||||
<Form.Item name='age'
|
||||
rules={[
|
||||
{ required: true, message: '请输入年龄' },
|
||||
{ validator: checkAge }
|
||||
]}
|
||||
>
|
||||
<Input placeholder='请输入年龄' type='number'/>
|
||||
</Form.Item>
|
||||
|
||||
<label className="login__label">城市</label>
|
||||
<Form.Item
|
||||
name='area'
|
||||
trigger='onConfirm'
|
||||
onClick={() => { setVisible(true) }}
|
||||
rules={[
|
||||
{ required: true, message: '请选择城市' }
|
||||
]}
|
||||
>
|
||||
<CascadePicker
|
||||
title='选择城市'
|
||||
options={city}
|
||||
visible={visible}
|
||||
onClose={() => { setVisible(false) }}
|
||||
onConfirm={(val, _) => {
|
||||
setCityValue(pinyin.getFullChars(val[0]))
|
||||
}}
|
||||
>
|
||||
{items => {
|
||||
if (items.every(item => item === null)) {
|
||||
return '请选择城市'
|
||||
} else {
|
||||
return items.map(item => item?.label ?? '').join('-')
|
||||
}
|
||||
}}
|
||||
</CascadePicker>
|
||||
</Form.Item>
|
||||
|
||||
<label className="login__label">性别</label>
|
||||
<Form.Item name='gender'>
|
||||
<Radio.Group >
|
||||
<Radio value='male' style={{
|
||||
'--icon-size': '18px',
|
||||
'--font-size': '14px',
|
||||
}}>男</Radio>
|
||||
<Radio value='female' style={{
|
||||
'--icon-size': '18px',
|
||||
'--font-size': '14px',
|
||||
}}>女</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { REC_LIST, HOT_LIST, READ, LIKE_DECREASE,LIKE_INCREASE,COLLECT_DECREASE,COLLECT_INCREASE, CLEAR_INFO} from './types/constant'
|
||||
|
||||
// 用户登录(注册)时传入信息
|
||||
export const userLog = (type: string, data: object) => ({ type, data })
|
||||
|
||||
// 给列表赋值
|
||||
export const pushRecLists = (data: any) => ({ type:REC_LIST, data })
|
||||
export const pushHotLists = (data: any) => ({ type: HOT_LIST, data })
|
||||
|
||||
// 清空列表 在刷新时调用 重新给列表赋值
|
||||
export const clearRecLists = () => ({ type: REC_LIST })
|
||||
export const clearHotLists = () => ({ type: HOT_LIST })
|
||||
|
||||
// 退出登录时清空用户相关数据
|
||||
export const clearUser = (data: Array<object>) => ({ type: CLEAR_INFO, data })
|
||||
|
||||
//点进新闻详情页时触发,让阅读次数增加
|
||||
export const readChange = (data:{lists:Array<object>,id:number}) => ({ type: READ, data })
|
||||
|
||||
//点击喜欢或者收藏时触发,让相应次数增加或者减少
|
||||
export const likesDecrease = (data: { lists: Array<object>, id: number }) => ({ type: LIKE_DECREASE, data })
|
||||
export const likesIncrease = (data: { lists: Array<object>, id: number }) => ({ type: LIKE_INCREASE, data })
|
||||
|
||||
export const collectionsDecrease = (data: { lists: Array<object>, id: number }) => ({ type: COLLECT_DECREASE, data })
|
||||
export const collectionsIncrease = (data: { lists: Array<object>, id: number }) => ({ type: COLLECT_INCREASE, data })
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//引入combineReducers,用于汇总多个reducer
|
||||
import { combineReducers } from 'redux'
|
||||
|
||||
import {userLog} from './reducers/userLog'
|
||||
import { pushRecLists, pushHotLists } from './reducers/pushLists'
|
||||
import { readChange, likesChange, collectionsChange } from './reducers/numChange'
|
||||
|
||||
//汇总所有的reducer变为一个总的reducer
|
||||
export default combineReducers({
|
||||
userLog,
|
||||
pushRecLists, pushHotLists,
|
||||
readChange, likesChange, collectionsChange
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Lists } from '../../assets/type'
|
||||
import { READ, LIKE_DECREASE, LIKE_INCREASE, COLLECT_DECREASE, COLLECT_INCREASE } from '../types/constant'
|
||||
|
||||
const readDefault = 0
|
||||
export function readChange(preState = readDefault, action: { type: string, data: {lists:Array<Lists>,id:number} }) {
|
||||
switch (action.type){
|
||||
case READ:
|
||||
return action.data.lists.map((item) => {
|
||||
if (item.news_id !== action.data.id) {
|
||||
return action.data.lists
|
||||
}
|
||||
|
||||
item.read_num++
|
||||
return {
|
||||
...item,
|
||||
...action.data.lists
|
||||
}
|
||||
})
|
||||
default:
|
||||
return preState
|
||||
}
|
||||
}
|
||||
|
||||
const likesDefault = 0
|
||||
export function likesChange(preState = likesDefault, action: { type: string; data: { lists: Array<Lists>, id: number } }) {
|
||||
switch (action.type) {
|
||||
case LIKE_DECREASE:
|
||||
return action.data.lists.map((item) => {
|
||||
if (item.news_id !== action.data.id) {
|
||||
return action.data.lists
|
||||
} else {
|
||||
item.likes--
|
||||
return {
|
||||
...item,
|
||||
...action.data.lists
|
||||
}
|
||||
}
|
||||
})
|
||||
case LIKE_INCREASE:
|
||||
return action.data.lists.map((item) => {
|
||||
if (item.news_id !== action.data.id) {
|
||||
return action.data.lists
|
||||
} else {
|
||||
item.likes ++
|
||||
return {
|
||||
...item,
|
||||
...action.data.lists
|
||||
}
|
||||
}
|
||||
})
|
||||
default:
|
||||
return preState
|
||||
}
|
||||
}
|
||||
|
||||
const colllectionsDefault = 0
|
||||
export function collectionsChange(preState = colllectionsDefault, action: { type: string; data: { lists: Array<Lists>, id: number } }) {
|
||||
switch (action.type) {
|
||||
case COLLECT_DECREASE:
|
||||
return action.data.lists.map((item) => {
|
||||
if (item.news_id !== action.data.id) {
|
||||
return action.data.lists
|
||||
} else {
|
||||
item.collections--
|
||||
return {
|
||||
...item,
|
||||
...action.data.lists
|
||||
}
|
||||
}
|
||||
})
|
||||
case COLLECT_INCREASE:
|
||||
return action.data.lists.map((item) => {
|
||||
if (item.news_id !== action.data.id) {
|
||||
return action.data.lists
|
||||
} else {
|
||||
item.collections++
|
||||
return {
|
||||
...item,
|
||||
...action.data.lists
|
||||
}
|
||||
}
|
||||
})
|
||||
default:
|
||||
return preState
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { HOT_LIST, REC_LIST } from '../types/constant'
|
||||
|
||||
export function pushRecLists(preState = [], action: { type: string; data: any }) {
|
||||
switch (action.type){
|
||||
case REC_LIST:
|
||||
return action.data
|
||||
default:
|
||||
return preState
|
||||
}
|
||||
}
|
||||
|
||||
export function pushHotLists(preState = [], action: { type: string; data: any }) {
|
||||
switch (action.type){
|
||||
case HOT_LIST:
|
||||
return action.data
|
||||
default:
|
||||
return preState
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { userLogState } from '../../assets/type'
|
||||
import { CLEAR_INFO, SIGNIN, SIGNUP } from '../types/constant'
|
||||
|
||||
|
||||
const userLogDefault: userLogState = {
|
||||
type: SIGNIN,
|
||||
data: {
|
||||
username: '',
|
||||
passwd: '',
|
||||
}
|
||||
}
|
||||
export function userLog(preState = userLogDefault, action: userLogState) {
|
||||
switch(action.type) {
|
||||
case SIGNIN:
|
||||
return ({ type: action.type, data: action.data })
|
||||
case SIGNUP:
|
||||
return ({ type: action.type, data: action.data })
|
||||
case CLEAR_INFO:
|
||||
return ({ type: action.type, data: action.data })
|
||||
default:
|
||||
return preState
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//引入createStore,专门用于创建redux中最为核心的store对象
|
||||
import {createStore,applyMiddleware} from 'redux'
|
||||
//引入汇总之后的reducer
|
||||
import rootReducer from './reducers'
|
||||
//引入redux-thunk,用于支持异步action
|
||||
import thunk from 'redux-thunk'
|
||||
//引入redux-devtools-extension
|
||||
import { composeWithDevTools } from 'redux-devtools-extension'
|
||||
// 引入redux-persist插件,使用sessionstorage持久化数据,解决页面刷新数据丢失的问题
|
||||
import { persistStore, persistReducer } from 'redux-persist';
|
||||
// 存储机制,当前使用sessionStorage
|
||||
import storageSession from 'redux-persist/lib/storage/session'
|
||||
|
||||
const storageConfig = {
|
||||
key: 'root', // 必须
|
||||
storage: storageSession, // 缓存机制
|
||||
}
|
||||
|
||||
const myPersistReducer = persistReducer(storageConfig, rootReducer);
|
||||
const store = createStore(myPersistReducer, composeWithDevTools(applyMiddleware(thunk)));
|
||||
|
||||
// 使用ts的条件类型 ReturnType<T>,T:函数类型。 获取函数返回值的类型
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
|
||||
export const persistor = persistStore(store)
|
||||
export default store;
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
定义action对象中type类型的常量值
|
||||
*/
|
||||
|
||||
export const SIGNIN = 'signIn'
|
||||
export const SIGNUP = 'signUp'
|
||||
|
||||
export const REC_LIST = 'recList'
|
||||
export const HOT_LIST = 'hotList'
|
||||
|
||||
export const SEARCH_ID = 'searchId'
|
||||
|
||||
export const READ = 'read'
|
||||
|
||||
export const LIKE_DECREASE = 'likeDecrease'
|
||||
export const LIKE_INCREASE = 'likeIncrease'
|
||||
|
||||
export const COLLECT_DECREASE = 'collectDecrease'
|
||||
export const COLLECT_INCREASE = 'collectIncrease'
|
||||
|
||||
export const LIKES = 'likes'
|
||||
export const COLLECTIONS = 'collections'
|
||||
|
||||
export const CLEAR_INFO = 'clearInfo'
|
||||
@@ -0,0 +1,7 @@
|
||||
// 使用TypeUseSelectorHook这个interface 来重新定义 useSelector这个hook
|
||||
import { useSelector as useReduxSelector, TypedUseSelectorHook } from 'react-redux'
|
||||
import { RootState } from '../store'
|
||||
|
||||
export const useSelector:TypedUseSelectorHook<RootState> = useReduxSelector
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { lazy } from 'react'
|
||||
import { Router } from '../assets/type'
|
||||
import SignIn from '../pages/SignIn'
|
||||
|
||||
const router: Array<Router> = [
|
||||
{
|
||||
path: '/',
|
||||
component: SignIn,
|
||||
auth: false,
|
||||
},
|
||||
{
|
||||
path: '/signIn',
|
||||
auth: false,
|
||||
component: lazy(() => import('../pages/SignIn'))
|
||||
},
|
||||
{
|
||||
path: '/signUp',
|
||||
auth: false,
|
||||
component: lazy(() => import('../pages/SignUp'))
|
||||
},
|
||||
{
|
||||
path: '/recLists',
|
||||
auth: true,
|
||||
component: lazy(() => import('../pages/RecLists'))
|
||||
},
|
||||
{
|
||||
path: '/hotLists',
|
||||
auth: true,
|
||||
component: lazy(() => import('../pages/HotLists'))
|
||||
},
|
||||
{
|
||||
path: "/newsInfo/:id",
|
||||
name: "newsInfo",
|
||||
auth: true,
|
||||
component: lazy(() => import('../pages/NewsInfo'))
|
||||
},
|
||||
{
|
||||
path: '/mySelf',
|
||||
auth: true,
|
||||
component: lazy(() => import('../pages/MySelf'))
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
auth: false,
|
||||
component: lazy(() => import('../pages/404'))
|
||||
},
|
||||
]
|
||||
|
||||
export default router
|
||||
|
||||
|
||||
37
codes/news_recsys/news_rec_web/react-fun-rec/tsconfig.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
// 编译选项
|
||||
"compilerOptions": {
|
||||
// 生成代码的语言版本
|
||||
"target": "es5",
|
||||
// 指定要包含在编译中的 library
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
// 允许 ts 编译器编译 js 文件
|
||||
"allowJs": true,
|
||||
// 跳过声明文件的类型检查
|
||||
"skipLibCheck": true,
|
||||
// es 模块 互操作,屏蔽 ESModule 和 CommonJS 之间的差异
|
||||
"esModuleInterop": true,
|
||||
// 允许通过 import x from 'y' 即使模块没有显式指定 default 导出
|
||||
"allowSyntheticDefaultImports": true,
|
||||
// 开启严格模式
|
||||
"strict": true,
|
||||
// 对文件名称强制区分大小写
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
// 为 switch 语句启用错误报告
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
// 生成代码的模块化标准
|
||||
"module": "esnext",
|
||||
// 模块解析(查找)策略
|
||||
"moduleResolution": "node",
|
||||
// 允许导入扩展名为.json的模块
|
||||
"resolveJsonModule": true,
|
||||
// 是否将没有 import/export 的文件视为旧(全局而非模块化)脚本文件。
|
||||
"isolatedModules": true,
|
||||
// 编译时不生成任何文件(只进行类型检查)
|
||||
"noEmit": true,
|
||||
// 指定将 JSX 编译成什么形式
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
// 指定允许 ts 处理的目录
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -52,7 +52,7 @@ http://theneverlemon.gitee.io/vue2-fun-rec-project/#/
|
||||
测试用户名`user` 密码`pass`
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/登录.jpg" width = "30%" height = "30%" alt="登录"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%99%BB%E5%BD%95.jpg" width = "30%" height = "30%" alt="登录"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ http://theneverlemon.gitee.io/vue2-fun-rec-project/#/
|
||||
- 注册成功后将跳转至主页面
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/注册.jpg" width = "30%" height = "30%" alt="注册"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%B3%A8%E5%86%8C.jpg" width = "30%" height = "30%" alt="注册"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -77,9 +77,9 @@ http://theneverlemon.gitee.io/vue2-fun-rec-project/#/
|
||||
|
||||
- 点进新闻详情页后阅读次数会实时增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/推荐.jpg" width = "30%" height = "30%" alt="推荐"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/热门.jpg" width = "30%" height = "30%" alt="热门"/>
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%8E%A8%E8%8D%90.jpg" width = "30%" height = "30%" alt="推荐"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%83%AD%E9%97%A8.jpg" width = "30%" height = "30%" alt="热门"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -90,8 +90,8 @@ http://theneverlemon.gitee.io/vue2-fun-rec-project/#/
|
||||
- 底部点击`喜欢`或者`收藏`可以记录将当前用户行为,并在列表页相应增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/新闻详情.jpg" width = "30%" height = "30%" alt="新闻详情"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/新闻详情2.jpg" width = "30%" height = "30%" alt="新闻详情2"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%85.jpg" width = "30%" height = "30%" alt="新闻详情"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%852.jpg" width = "30%" height = "30%" alt="新闻详情2"/>
|
||||
</div>
|
||||
|
||||
- [X] **个人中心** —— 记录用户的头像和用户名
|
||||
@@ -101,8 +101,8 @@ http://theneverlemon.gitee.io/vue2-fun-rec-project/#/
|
||||
- 显示DataWhale相关介绍
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/个人中心1.jpg" width = "30%" height = "30%" alt="个人中心1"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/个人中心2.jpg" width = "30%" height = "30%" alt="个人中心2"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%831.jpg" width = "30%" height = "30%" alt="个人中心1"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%832.jpg" width = "30%" height = "30%" alt="个人中心2"/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
测试用户名`user` 密码`pass`
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/登录.jpg" width = "30%" height = "30%" alt="登录"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%99%BB%E5%BD%95.jpg" width = "30%" height = "30%" alt="登录"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
- 注册成功后将跳转至主页面
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/注册.jpg" width = "30%" height = "30%" alt="注册"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%B3%A8%E5%86%8C.jpg" width = "30%" height = "30%" alt="注册"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -77,9 +77,9 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
|
||||
- 点进新闻详情页后阅读次数会实时增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/推荐.jpg" width = "30%" height = "30%" alt="推荐"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/热门.jpg" width = "30%" height = "30%" alt="热门"/>
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%8E%A8%E8%8D%90.jpg" width = "30%" height = "30%" alt="推荐"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E7%83%AD%E9%97%A8.jpg" width = "30%" height = "30%" alt="热门"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -90,8 +90,8 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
- 底部点击`喜欢`或者`收藏`可以记录将当前用户行为,并在列表页相应增加
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/新闻详情.jpg" width = "30%" height = "30%" alt="新闻详情"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/新闻详情2.jpg" width = "30%" height = "30%" alt="新闻详情2"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%85.jpg" width = "30%" height = "30%" alt="新闻详情"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E6%96%B0%E9%97%BB%E8%AF%A6%E6%83%852.jpg" width = "30%" height = "30%" alt="新闻详情2"/>
|
||||
</div>
|
||||
|
||||
- [X] **个人中心** —— 记录用户的头像和用户名
|
||||
@@ -101,8 +101,8 @@ http://theneverlemon.gitee.io/vue3-fun-rec-project/#/
|
||||
- 显示DataWhale相关介绍
|
||||
|
||||
<div align="center">
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/个人中心1.jpg" width = "30%" height = "30%" alt="个人中心1"/>
|
||||
<img src="https://gitee.com/theNeverLemon/news-img/raw/master/img/个人中心2.jpg" width = "30%" height = "30%" alt="个人中心2"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%831.jpg" width = "30%" height = "30%" alt="个人中心1"/>
|
||||
<img src="https://raw.githubusercontent.com/kenken-xr/image/main/fun-rec-img%E4%B8%AA%E4%BA%BA%E4%B8%AD%E5%BF%832.jpg" width = "30%" height = "30%" alt="个人中心2"/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "vue3-fun-rec2",
|
||||
"name": "vue3-fun-rec",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<!-- 在路由节点内配置name属性,且保证为唯一标识 -->
|
||||
<keep-alive>
|
||||
<component :is="Component" />
|
||||
<component :is="Component" v-if="$route.meta.keepAlive" :key="$route.name"/>
|
||||
</keep-alive>
|
||||
<component :is="Component" v-if="!$route.meta.keepAlive" :key="$route.name"/>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useStore } from "vuex";
|
||||
const store = useStore();
|
||||
// 在页面加载时读取sessionStorage里的状态信息
|
||||
if (sessionStorage.getItem('store')) {
|
||||
// 存储状态
|
||||
store.replaceState(Object.assign({}, store.state, JSON.parse(sessionStorage.getItem('store'))))
|
||||
}
|
||||
|
||||
// 在页面刷新时将vuex里的信息保存到sessionStorage里
|
||||
window.addEventListener('beforeunload', () => {
|
||||
sessionStorage.setItem('store', JSON.stringify(store.state))
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
* {
|
||||
padding: 0;
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
// const CryptoJS = require('crypto-js'); //引用AES源码js
|
||||
import CryptoJS from 'crypto-js'
|
||||
|
||||
// 解析
|
||||
const key = CryptoJS.enc.Utf8.parse("1234123412ABCDEF"); //十六位十六进制数作为密钥
|
||||
const iv = CryptoJS.enc.Utf8.parse('ABCDEF1234123412'); //十六位十六进制数作为密钥偏移量
|
||||
|
||||
//解密方法
|
||||
function Decrypt(word) {
|
||||
let encryptedHexStr = CryptoJS.enc.Hex.parse(word);
|
||||
let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
|
||||
let decrypt = CryptoJS.AES.decrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
|
||||
let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
|
||||
return decryptedStr.toString();
|
||||
let encryptedHexStr = CryptoJS.enc.Hex.parse(word); // 16进制
|
||||
let srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr); // 解密的密文必须是base64
|
||||
let decrypt = CryptoJS.AES.decrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
|
||||
let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8); // 以utf8方法转换
|
||||
return decryptedStr.toString();
|
||||
}
|
||||
|
||||
//加密方法
|
||||
function Encrypt(word) {
|
||||
let srcs = CryptoJS.enc.Utf8.parse(word);
|
||||
let encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
|
||||
return encrypted.ciphertext.toString().toUpperCase();
|
||||
let encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); // 偏移向量 加密模式 填充方式
|
||||
return encrypted.ciphertext.toString().toUpperCase(); // 加密后的是对象 需要转到文本
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export const sessionStorage = {
|
||||
|
||||
//存储
|
||||
set(key, value) {
|
||||
window.sessionStorage.setItem(key, JSON.stringify(value))
|
||||
},
|
||||
|
||||
//取出数据
|
||||
get(key) {
|
||||
const value = window.sessionStorage.getItem(key)
|
||||
if (value && value != "undefined" && value != "null") {
|
||||
return JSON.parse(value)
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
// 删除数据
|
||||
remove(key) {
|
||||
window.sessionStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ axios.post('http://47.108.56.188:3000/recsys/login?username=11&passwd=111111').t
|
||||
store.state.flag = true
|
||||
}).catch((e)=>{
|
||||
store.state.flag = false
|
||||
console.log(e)
|
||||
})
|
||||
!store.state.flag && require("./mock/index.js")
|
||||
|
||||
|
||||
@@ -54,18 +54,20 @@ for(let i = 0; i < 10; i ++){
|
||||
})
|
||||
}
|
||||
Mock.mock('/recList', 'get', () => {
|
||||
return {
|
||||
code:200,
|
||||
data:Data
|
||||
}
|
||||
})
|
||||
console.log('recListMock',Data)
|
||||
return {
|
||||
code:200,
|
||||
data:Data
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Mock.mock('/hotList', 'get', () => {
|
||||
return {
|
||||
code:200,
|
||||
data:Data
|
||||
}
|
||||
console.log('hotListMock',Data)
|
||||
return {
|
||||
code:200,
|
||||
data:Data
|
||||
}
|
||||
})
|
||||
|
||||
Mock.mock(RegExp("/newsInfo" + ".*"), 'get', (option) => {
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<van-pull-refresh v-model="data.isLoading" @refresh="data.onRefresh">
|
||||
<van-pull-refresh v-model="data.isLoading" @refresh="onRefresh">
|
||||
<div class="lists">
|
||||
<van-list v-model="data.vanListLoading" :finished="data.finished" :finished-text="data.finishedText" @load="onLoad" :offset=300>
|
||||
<!-- 循环store.state.hotList内的每一个item并显示 -->
|
||||
<van-cell v-for="(item,i) in store.state.hotList" :key="i">
|
||||
<van-cell v-for="(item) in store.state.hotList" :key="item.news_id">
|
||||
<!-- 路由地址传参,需要前面加:号,表示这个参数不是字符串 -->
|
||||
<router-link :to="{name:'NewsInfo' ,params:{id:item.news_id,likes:item.likes,collections:item.collections,cate:item.cate}}">
|
||||
<div>
|
||||
@@ -51,8 +51,6 @@
|
||||
</div>
|
||||
</van-pull-refresh>
|
||||
|
||||
|
||||
|
||||
<!-- 底部导航栏,多个组件都会用到,需要时直接引入 -->
|
||||
<bottomBar></bottomBar>
|
||||
|
||||
@@ -61,10 +59,10 @@
|
||||
|
||||
<script setup>
|
||||
import bottomBar from "@/components/bottomBar.vue";
|
||||
import { reactive, onMounted, onActivated, getCurrentInstance } from "vue";
|
||||
import { reactive, onActivated, getCurrentInstance } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
const store = useStore();
|
||||
import { useRouter, onBeforeRouteLeave } from "vue-router";
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
@@ -123,7 +121,6 @@
|
||||
document.documentElement.scrollTop = data.scrollTop
|
||||
})
|
||||
|
||||
|
||||
//在离开该组件时执行,执行完后跳转
|
||||
// to:要去到的组件 from:离开的组件(本组件) next():执行的函数,下一步
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
@@ -181,4 +178,7 @@
|
||||
.hotPath {
|
||||
fill: #428bff;
|
||||
}
|
||||
/* .van-pull-refresh {
|
||||
overflow: auto;
|
||||
} */
|
||||
</style>
|
||||
@@ -31,11 +31,11 @@
|
||||
|
||||
<script setup>
|
||||
import bottomBarVue from "@/components/bottomBar.vue"
|
||||
import { routerKey, useRouter } from "vue-router";
|
||||
import { Toast } from 'vant'
|
||||
import { reactive, getCurrentInstance } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
const store = useStore();
|
||||
import { routerKey, useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
|
||||
<script setup>
|
||||
import { Toast } from 'vant'
|
||||
import { reactive, getCurrentInstance } from "vue";
|
||||
import { reactive, getCurrentInstance, onMounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
const store = useStore();
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { proxy } = getCurrentInstance();
|
||||
@@ -77,6 +77,8 @@
|
||||
})
|
||||
}
|
||||
|
||||
console.log(successData);
|
||||
|
||||
if (successData.status === 200) {
|
||||
data.news_content = successData.data.data
|
||||
data.content = data.news_content.content.split(reg)[0]
|
||||
@@ -185,9 +187,9 @@
|
||||
}
|
||||
|
||||
|
||||
// 创建页面时调用函数
|
||||
getNewsInfo()
|
||||
sendInfo()
|
||||
// 创建页面时调用函数
|
||||
getNewsInfo()
|
||||
sendInfo()
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div ref="content">
|
||||
<div class="tabs" ref="tabs">
|
||||
<div>
|
||||
<div class="tabs">
|
||||
<input type="radio" id="tab1" name="tab-control" checked>
|
||||
<input type="radio" id="tab2" name="tab-control">
|
||||
<ul>
|
||||
<li title="Features" class="rec" @click="toRec">
|
||||
<li title="Features" class="rec">
|
||||
<label for="tab1" role="button" class="recCont">
|
||||
<svg viewBox="0 0 24 24" class="recPath">
|
||||
<path d="M14,2A8,8 0 0,0 6,10A8,8 0 0,0 14,18A8,8 0 0,0 22,10H20C20,13.32 17.32,16 14,16A6,6 0 0,1 8,10A6,6 0 0,1 14,4C14.43,4 14.86,4.05 15.27,4.14L16.88,2.54C15.96,2.18 15,2 14,2M20.59,3.58L14,10.17L11.62,7.79L10.21,9.21L14,13L22,5M4.93,5.82C3.08,7.34 2,9.61 2,12A8,8 0 0,0 10,20C10.64,20 11.27,19.92 11.88,19.77C10.12,19.38 8.5,18.5 7.17,17.29C5.22,16.25 4,14.21 4,12C4,11.7 4.03,11.41 4.07,11.11C4.03,10.74 4,10.37 4,10C4,8.56 4.32,7.13 4.93,5.82Z" />
|
||||
@@ -26,10 +26,10 @@
|
||||
</div>
|
||||
|
||||
<van-pull-refresh v-model="data.isLoading" @refresh="onRefresh">
|
||||
<div class="lists" ref="lists">
|
||||
<van-list v-model="data.vanListLoading" :finished="data.finished" :finished-text="data.finishedText" @load="onLoad" :offset=300>
|
||||
<!-- 循环$store.state.hotList内的每一个item并显示 -->
|
||||
<van-cell v-for="(item,i) in store.state.recList" :key="i">
|
||||
<div class="lists">
|
||||
<van-list v-model:loading="data.vanListLoading" :finished="data.finished" :finished-text="data.finishedText" @load="onLoad" :offset=300>
|
||||
<!-- 循环store.state.recList内的每一个item并显示 -->
|
||||
<van-cell v-for="(item) in store.state.recList" :key="item.news_id">
|
||||
<!-- 路由地址传参,需要前面加:号,表示这个参数不是字符串 -->
|
||||
<router-link :to="{name:'NewsInfo' ,params:{id:item.news_id,likes:item.likes,collections:item.collections,cate:item.cate}}">
|
||||
<div>
|
||||
@@ -61,14 +61,13 @@
|
||||
import bottomBar from "@/components/bottomBar.vue";
|
||||
import { reactive, onActivated, getCurrentInstance } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
const store = useStore();
|
||||
import { useRouter, onBeforeRouteLeave } from "vue-router";
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const data = reactive({
|
||||
isActive: true,
|
||||
vanListLoading: false, // 加载状态
|
||||
finished: false, // 是否加载
|
||||
finishedText: '', // 加载完成后的提示文案
|
||||
@@ -77,15 +76,22 @@
|
||||
});
|
||||
|
||||
function onRefresh() {
|
||||
setTimeout(() => {
|
||||
// data.isLoading = false;
|
||||
// store.commit('clearList', "recList")
|
||||
// getList()
|
||||
// data.isLoading = true;
|
||||
// data.finished = false;
|
||||
// data.isLoading = true;
|
||||
// getList()
|
||||
setTimeout(() => {
|
||||
data.isLoading = false;
|
||||
store.commit('clearList', "hotList")
|
||||
this.getList()
|
||||
getList()
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function getList() {
|
||||
var url;
|
||||
let url;
|
||||
if(store.state.type == 'signIn'){
|
||||
url = '/recsys/rec_list?' + 'user_id=' + store.state.user.username
|
||||
}else if(store.state.type == 'signUp'){
|
||||
@@ -98,7 +104,8 @@
|
||||
return res
|
||||
})
|
||||
}else {
|
||||
successData = await proxy.axios.get("/recList").then((res)=>{
|
||||
successData = await proxy.axios.get("/recList").then(res => {
|
||||
console.log(store.state.recList,'recList')
|
||||
return res
|
||||
})
|
||||
}
|
||||
@@ -184,4 +191,8 @@
|
||||
.recPath {
|
||||
fill: #428bff;
|
||||
}
|
||||
|
||||
/* .van-pull-refresh {
|
||||
overflow: auto;
|
||||
} */
|
||||
</style>
|
||||
@@ -93,7 +93,7 @@ async function login() {
|
||||
const timer = setInterval(() => {
|
||||
second--;
|
||||
if (!second) {
|
||||
clearInterval(timer);
|
||||
clearInterval(timer);
|
||||
// 手动清除 Toast
|
||||
Toast.clear();
|
||||
router.push({name:'recLists' ,params:{type:'signIn',username:data.model.username}})
|
||||
|
||||