4 Commits
v1 ... v1.1

Author SHA1 Message Date
zyronon
a8f0f64868 save 2024-04-01 03:19:49 +08:00
zyronon
e53c2db532 save 2024-03-30 16:05:49 +08:00
zyronon
62cc947be5 save 2024-03-30 15:57:45 +08:00
zyronon
415d5952c2 update README.md 2024-03-29 18:15:19 +08:00
66 changed files with 1797 additions and 1410 deletions

View File

@@ -1,83 +1,66 @@
### English | [简体中文](README.md)
<h1 align="center">
Douyin
</h1>
## Preview Demo
[Online Demo](http://dy.ttentau.top/)
<p align="center">
<a href="README.md">简体中文</a> |
<a href="README-en-US.md">English</a>
</p>
### Note: Please use mobile mode of Chrome browser to access the PC side. To switch Chrome to mobile mode, press F12 to bring up the console, then Ctrl+Shift+M
### Note: For Android phones please use [Via mobile browser](https://viayoo.com/zh-cn/) or Chrome mobile version preview. When other browsers detect a video on the page, they force the video to be in full screen and display the control button. As a result, both css and js are invalid
<div style="text-align:center">
<img width="180px" src='http://www.ttentau.top/dy/imgs/0.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/1.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/6.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/3.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/2.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/8.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/9.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/11.png' />
<p align="center">
<b>douyin</b>It is a mobile short video project imitating Douyin.
It is based on <a href="https://v3.cn.vuejs.org">Vue</a>, <a href="https://cn.vitejs.dev/">Vite</a> implementation.
The latest Vue family bucket technology stack is used, and the background data is built by <a href="https://github.com/ctimmerm/axios-mock-adapter">axios-mock-adapter</a>.
</p>
## 📸 在线访问
Netlify: <https://douyins.netlify.app> (If you are in China, you need Vpn)
Vercel: <http://dy.ttentau.top> (This is recommended in China.)
**Note**: PC browsers should be accessed in mobile mode.
Press F12 to bring up the console, and then press Ctrl+Shift+M to switch to mobile mode.
**Note**: for mobile phones, please use [Via browser] (https://viayoo.com/zh-cn/) or Chrome browser preview).
Other browsers will force the video to be full-screen, invalidating the style.
## 🖼️ Effect screenshot
<div>
<img width="150px" src='./public/docs/1.gif' />
<img width="150px" src='./public/docs/2.gif' />
<img width="150px" src='./public/docs/3.gif' />
<img width="150px" src='./public/docs/4.gif' />
<img width="150px" src='./public/docs/5.gif' />
<img width="150px" src='./public/docs/img-1.jpg' />
<img width="150px" src='./public/docs/img-2.jpg' />
<img width="150px" src='./public/docs/img-3.jpg' />
<img width="150px" src='./public/docs/img-4.jpg' />
<img width="150px" src='./public/docs/img-5.jpg' />
</div>
## Introduction
## Run the project
**Douyin** is a mobile short video project imitating TiktokIt is based on [vue 3](https://v3.cn.vuejs.org/),
[vite 2](https://cn.vitejs.dev/)
implementation。
The latest Vue family bucket technology stack is used, and the background data is built through [mock-js](http://mockjs.com).
This project is based on `Vue` and needs node environment to run.
## Function point
1. To install NodeJS, refer to [official documentation] (https://nodejs.org/en/download))
2. Use `git clone https://github.com/zyronon/douyin.git` to download the project locally.
3. Enter the project directory `npm install` and run `project install` under the project root directory to download the dependency.
4. Execute `project dev` to start the project. The default address of the project is [`http://localhost:3000`](http://localhost:3000)].
5. Open [`http://localhost:3000`](http://localhost:3000)] in the browser to access the project.
Chrome switch to mobile phone mode shortcut key, press F12 to bring up the console, and then press Ctrl+Shift+M
Page | Progress
--------------|---------
Home | 50%
-- Music | &#9745;
-- 抖音音乐榜 | &#9745;
-- Search | 50%
-- Live | 50%
Friend | 0%
Message | &#9745;
-- Chat | &#9745;
-- 各种通知 | &#9745;
Me | 90%
-- 求更新 | &#9745;
-- 关注和粉丝 | &#9745;
-- 编辑资料 | &#9745;
-- Add friend | &#9745;
-- My music | &#9745;
-- Shop | 0
-- 收藏视频\音乐 | &#9745;
-- 右侧菜单子页面 | 10%
-- Setting | &#9745;
-- -- 具体设置页面 | 0%
Sign in\Sign up | &#9745;
## 🎙 Functions and suggestions
## How to start
At present, the project is in the early stage of development, and new features are being added continuously. If you have any functions and suggestions for the software, you are welcome to put forward them in Issues.
If you also like the design idea of this software, welcome to submit pr, thank you very much for your support!
```bash
# Clone the project to local
git clone https://github.com/zyronon/douyin.git
## Contact me
# Enter the project directory
cd douyin
You can contact my email address < a href= "mailto:zyronon@163.com" > zyronon@163.com < / a >
# Install dependencies
npm install
# Start the service
npm run dev
# Visit
Chrome browser access http://localhost:5173
Chrome switches to mobile mode by pressing F12 to bring up the console, then Ctrl+Shift+M
```
## Contribution
Feel free to contribute by opening issues with any questions, bug reports or feature requests.
## Get in touch
You can reach us at <a href="mailto:zyronon@163.com">zyronon@163.com</a>
## License
## MIT license Agreement
[MIT](LICENSE)

115
README.md
View File

@@ -1,83 +1,60 @@
### 简体中文 | [English](README-en-US.md)
<h1 align="center">
Douyin
</h1>
## 预览
<p align="center">
<a href="README.md">简体中文</a> |
<a href="README-en-US.md">English</a>
</p>
[在线预览DEMO](http://dy.ttentau.top/)
#### 注意电脑端请用Chrome浏览器的手机模式访问。Chrome切换成手机模式快捷键先按F12调出控制台再按Ctrl+Shift+M
<p align="center">
<b>douyin</b>是一个模仿抖音的移动端短视频项目,它基于 <a href="https://v3.cn.vuejs.org">Vue</a>,
<a href="https://cn.vitejs.dev/">Vite</a>实现。使用了最新的Vue全家桶技术栈接口数据通过
<a href="https://github.com/ctimmerm/axios-mock-adapter">axios-mock-adapter</a>模拟
</p>
#### 注意:安卓手机请用[Via手机浏览器](https://viayoo.com/zh-cn/)或者Chrome浏览器手机版本预览。其他浏览器检测到页面内有视频会强制将视频全屏并显示控制按钮导致css和js都失效
## 📸 在线访问
<div style="text-align:center">
<img width="180px" src='http://www.ttentau.top/dy/imgs/0.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/1.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/6.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/3.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/2.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/8.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/9.png' />
<img width="180px" src='http://www.ttentau.top/dy/imgs/11.png' />
Vercel: <http://dy.ttentau.top> (国内推荐访问这个)
Netlify: <https://douyins.netlify.app> (需要翻墙)
**注意**PC浏览器请用手机模式访问。先按F12调出控制台再按Ctrl+Shift+M切换到手机模式
**注意**:手机请用[Via浏览器](https://viayoo.com/zh-cn/)或者Chrome浏览器预览。其他浏览器会强制将视频全屏导致样式都失效
## 🖼️ 效果截图
<div>
<img width="150px" src='./public/docs/1.gif' />
<img width="150px" src='./public/docs/2.gif' />
<img width="150px" src='./public/docs/3.gif' />
<img width="150px" src='./public/docs/4.gif' />
<img width="150px" src='./public/docs/5.gif' />
<img width="150px" src='./public/docs/img-1.jpg' />
<img width="150px" src='./public/docs/img-2.jpg' />
<img width="150px" src='./public/docs/img-3.jpg' />
<img width="150px" src='./public/docs/img-4.jpg' />
<img width="150px" src='./public/docs/img-5.jpg' />
</div>
## 简介
## 运行项目
**douyin** 是一个模仿抖音的移动端短视频项目,它基于 [Vue 3](https://v3.cn.vuejs.org/),
[Vite](https://cn.vitejs.dev/)
实现。使用了最新的Vue全家桶技术栈后台数据通过[mock-js](http://mockjs.com)搭建
本项目是基于`Vue`开发的,需要 node 环境来运行。
## 效果截图
1. 安装 NodeJS参考[官方文档](https://nodejs.org/en/download)
2. 使用 `git clone https://github.com/zyronon/douyin.git` 下载项目到本地
3. 进入项目目录` cd douyin `,在项目根目录下,运行`npm install`来下载依赖。
4. 执行`npm run dev`来启动项目,项目默认地址为[`http://localhost:3000`](http://localhost:3000)
5. 在浏览器中打开[`http://localhost:3000`](http://localhost:3000) 来访问项目。
Chrome切换成手机模式快捷键先按F12调出控制台再按Ctrl+Shift+M
## 功能点
页面 | 进度
--------------|---------
首页 | 50%
-- 音乐 | &#9745;
-- 抖音音乐榜 | &#9745;
-- 搜索 | 50%
-- 直播 | 50%
朋友 | 0%
消息 | &#9745;
-- 聊天 | &#9745;
-- 各种通知 | &#9745;
我 | 90%
-- 求更新 | &#9745;
-- 关注和粉丝 | &#9745;
-- 编辑资料 | &#9745;
-- 添加朋友 | &#9745;
-- 我的音乐 | &#9745;
-- 抖音商城 | 0
-- 收藏视频\音乐 | &#9745;
-- 右侧菜单子页面 | 10%
-- 设置 | &#9745;
-- -- 具体设置页面 | 0%
登录\注册 | &#9745;
## 运行
```bash
# 克隆项目到本地
git clone https://github.com/zyronon/douyin.git
# 进入项目目录
cd douyin
# 安装依赖
npm install
# 启动服务
npm run dev
# 访问
Chrome浏览器访问 http://localhost:5173
Chrome切换成手机模式快捷键先按F12调出控制台再按Ctrl+Shift+M
```
## 问题反馈
如果有任何问题、错误报告或功能请求请随时提出Issues。
## 🎙 功能与建议
目前项目处于开发初期,新功能正在持续添加中,如果你对软件有任何功能与建议,欢迎在 Issues 中提出
如果你也喜欢本软件的设计思想,欢迎提交 pr非常感谢你对我们的支持
## 联系我
您可以联系我的邮箱 <a href="mailto:zyronon@163.com">zyronon@163.com</a>

653
public/data/goods.json Normal file
View File

@@ -0,0 +1,653 @@
[
{
"name": "小米电视6 65\" OLED 65英寸",
"cover": "g6-0.jpg",
"imgs": [
"g6-0.jpg",
"g6-1.jpg",
"g6-2.jpg",
"g6-3.jpg",
"g6-4.jpg"
],
"price": 6699,
"real_price": 399,
"isLowPrice": false,
"discount": "",
"sold": 863
},
{
"name": "红白撞色条纹软糯针织上衣女2022年秋季新款甜美减龄短款毛衣开衫",
"cover": "g1-0.jpg",
"imgs": [
"g1-0.jpg",
"g1-1.jpg",
"g1-2.jpg",
"g1-3.jpg"
],
"isLowPrice": true,
"discount": "满4减3",
"sold": 134,
"price": 39.9,
"real_price": 9.9
},
{
"name": "森马t恤男2023男士纯棉上衣白色情侣装凉感短袖打底衫纯色体恤潮",
"imgs": [
"g2-0.webp",
"g2-1.webp",
"g2-2.webp",
"g2-3.webp"
],
"cover": "g2-0.webp",
"real_price": 9.9,
"price": 49.99,
"isLowPrice": false,
"discount": "满20减5",
"sold": 3314
},
{
"name": "ins潮牌长袖t恤男宽松纯色内搭上衣潮牌百搭秋冬季潮流帅气打底衫",
"cover": "g3-0.jpg",
"imgs": [
"g3-0.jpg",
"g3-1.jpg",
"g3-2.jpg",
"g3-3.jpg",
"g3-4.jpg"
],
"real_price": 9.9,
"price": 22.90,
"isLowPrice": true,
"discount": "满10减3",
"sold": 129
},
{
"name": "Redmi K60狠快狠强狠旗舰2023第一台梦幻手机",
"cover": "g4-0.png",
"imgs": [
"g4-0.png",
"g4-1.png",
"g4-2.png",
"g4-3.png"
],
"real_price": 199.9,
"price": 2999,
"isLowPrice": true,
"discount": "",
"sold": 967
},
{
"name": "小米笔记本Pro X 14",
"imgs": [
"g5-0.jpg",
"g5-1.jpg",
"g5-2.jpg",
"g5-3.jpg",
"g5-4.jpg"
],
"cover": "g5-0.jpg",
"real_price": 1559.9,
"price": 6999,
"isLowPrice": false,
"discount": "",
"sold": 473
},
{
"name": "小米电视6 65\" OLED 65英寸",
"cover": "g6-0.jpg",
"imgs": [
"g6-0.jpg",
"g6-1.jpg",
"g6-2.jpg",
"g6-3.jpg",
"g6-4.jpg"
],
"price": 6699,
"real_price": 399,
"isLowPrice": false,
"discount": "",
"sold": 863
},
{
"name": "红白撞色条纹软糯针织上衣女2022年秋季新款甜美减龄短款毛衣开衫",
"cover": "g1-0.jpg",
"imgs": [
"g1-0.jpg",
"g1-1.jpg",
"g1-2.jpg",
"g1-3.jpg"
],
"isLowPrice": true,
"discount": "满4减3",
"sold": 134,
"price": 39.9,
"real_price": 9.9
},
{
"name": "森马t恤男2023男士纯棉上衣白色情侣装凉感短袖打底衫纯色体恤潮",
"imgs": [
"g2-0.webp",
"g2-1.webp",
"g2-2.webp",
"g2-3.webp"
],
"cover": "g2-0.webp",
"real_price": 9.9,
"price": 49.99,
"isLowPrice": false,
"discount": "满20减5",
"sold": 3314
},
{
"name": "ins潮牌长袖t恤男宽松纯色内搭上衣潮牌百搭秋冬季潮流帅气打底衫",
"cover": "g3-0.jpg",
"imgs": [
"g3-0.jpg",
"g3-1.jpg",
"g3-2.jpg",
"g3-3.jpg",
"g3-4.jpg"
],
"real_price": 9.9,
"price": 22.90,
"isLowPrice": true,
"discount": "满10减3",
"sold": 129
},
{
"name": "Redmi K60狠快狠强狠旗舰2023第一台梦幻手机",
"cover": "g4-0.png",
"imgs": [
"g4-0.png",
"g4-1.png",
"g4-2.png",
"g4-3.png"
],
"real_price": 199.9,
"price": 2999,
"isLowPrice": true,
"discount": "",
"sold": 967
},
{
"name": "小米笔记本Pro X 14",
"imgs": [
"g5-0.jpg",
"g5-1.jpg",
"g5-2.jpg",
"g5-3.jpg",
"g5-4.jpg"
],
"cover": "g5-0.jpg",
"real_price": 1559.9,
"price": 6999,
"isLowPrice": false,
"discount": "",
"sold": 473
},
{
"name": "小米电视6 65\" OLED 65英寸",
"cover": "g6-0.jpg",
"imgs": [
"g6-0.jpg",
"g6-1.jpg",
"g6-2.jpg",
"g6-3.jpg",
"g6-4.jpg"
],
"price": 6699,
"real_price": 399,
"isLowPrice": false,
"discount": "",
"sold": 863
},
{
"name": "红白撞色条纹软糯针织上衣女2022年秋季新款甜美减龄短款毛衣开衫",
"cover": "g1-0.jpg",
"imgs": [
"g1-0.jpg",
"g1-1.jpg",
"g1-2.jpg",
"g1-3.jpg"
],
"isLowPrice": true,
"discount": "满4减3",
"sold": 134,
"price": 39.9,
"real_price": 9.9
},
{
"name": "森马t恤男2023男士纯棉上衣白色情侣装凉感短袖打底衫纯色体恤潮",
"imgs": [
"g2-0.webp",
"g2-1.webp",
"g2-2.webp",
"g2-3.webp"
],
"cover": "g2-0.webp",
"real_price": 9.9,
"price": 49.99,
"isLowPrice": false,
"discount": "满20减5",
"sold": 3314
},
{
"name": "ins潮牌长袖t恤男宽松纯色内搭上衣潮牌百搭秋冬季潮流帅气打底衫",
"cover": "g3-0.jpg",
"imgs": [
"g3-0.jpg",
"g3-1.jpg",
"g3-2.jpg",
"g3-3.jpg",
"g3-4.jpg"
],
"real_price": 9.9,
"price": 22.90,
"isLowPrice": true,
"discount": "满10减3",
"sold": 129
},
{
"name": "Redmi K60狠快狠强狠旗舰2023第一台梦幻手机",
"cover": "g4-0.png",
"imgs": [
"g4-0.png",
"g4-1.png",
"g4-2.png",
"g4-3.png"
],
"real_price": 199.9,
"price": 2999,
"isLowPrice": true,
"discount": "",
"sold": 967
},
{
"name": "小米笔记本Pro X 14",
"imgs": [
"g5-0.jpg",
"g5-1.jpg",
"g5-2.jpg",
"g5-3.jpg",
"g5-4.jpg"
],
"cover": "g5-0.jpg",
"real_price": 1559.9,
"price": 6999,
"isLowPrice": false,
"discount": "",
"sold": 473
},
{
"name": "小米电视6 65\" OLED 65英寸",
"cover": "g6-0.jpg",
"imgs": [
"g6-0.jpg",
"g6-1.jpg",
"g6-2.jpg",
"g6-3.jpg",
"g6-4.jpg"
],
"price": 6699,
"real_price": 399,
"isLowPrice": false,
"discount": "",
"sold": 863
},
{
"name": "红白撞色条纹软糯针织上衣女2022年秋季新款甜美减龄短款毛衣开衫",
"cover": "g1-0.jpg",
"imgs": [
"g1-0.jpg",
"g1-1.jpg",
"g1-2.jpg",
"g1-3.jpg"
],
"isLowPrice": true,
"discount": "满4减3",
"sold": 134,
"price": 39.9,
"real_price": 9.9
},
{
"name": "森马t恤男2023男士纯棉上衣白色情侣装凉感短袖打底衫纯色体恤潮",
"imgs": [
"g2-0.webp",
"g2-1.webp",
"g2-2.webp",
"g2-3.webp"
],
"cover": "g2-0.webp",
"real_price": 9.9,
"price": 49.99,
"isLowPrice": false,
"discount": "满20减5",
"sold": 3314
},
{
"name": "ins潮牌长袖t恤男宽松纯色内搭上衣潮牌百搭秋冬季潮流帅气打底衫",
"cover": "g3-0.jpg",
"imgs": [
"g3-0.jpg",
"g3-1.jpg",
"g3-2.jpg",
"g3-3.jpg",
"g3-4.jpg"
],
"real_price": 9.9,
"price": 22.90,
"isLowPrice": true,
"discount": "满10减3",
"sold": 129
},
{
"name": "Redmi K60狠快狠强狠旗舰2023第一台梦幻手机",
"cover": "g4-0.png",
"imgs": [
"g4-0.png",
"g4-1.png",
"g4-2.png",
"g4-3.png"
],
"real_price": 199.9,
"price": 2999,
"isLowPrice": true,
"discount": "",
"sold": 967
},
{
"name": "小米笔记本Pro X 14",
"imgs": [
"g5-0.jpg",
"g5-1.jpg",
"g5-2.jpg",
"g5-3.jpg",
"g5-4.jpg"
],
"cover": "g5-0.jpg",
"real_price": 1559.9,
"price": 6999,
"isLowPrice": false,
"discount": "",
"sold": 473
},
{
"name": "小米电视6 65\" OLED 65英寸",
"cover": "g6-0.jpg",
"imgs": [
"g6-0.jpg",
"g6-1.jpg",
"g6-2.jpg",
"g6-3.jpg",
"g6-4.jpg"
],
"price": 6699,
"real_price": 399,
"isLowPrice": false,
"discount": "",
"sold": 863
},
{
"name": "红白撞色条纹软糯针织上衣女2022年秋季新款甜美减龄短款毛衣开衫",
"cover": "g1-0.jpg",
"imgs": [
"g1-0.jpg",
"g1-1.jpg",
"g1-2.jpg",
"g1-3.jpg"
],
"isLowPrice": true,
"discount": "满4减3",
"sold": 134,
"price": 39.9,
"real_price": 9.9
},
{
"name": "森马t恤男2023男士纯棉上衣白色情侣装凉感短袖打底衫纯色体恤潮",
"imgs": [
"g2-0.webp",
"g2-1.webp",
"g2-2.webp",
"g2-3.webp"
],
"cover": "g2-0.webp",
"real_price": 9.9,
"price": 49.99,
"isLowPrice": false,
"discount": "满20减5",
"sold": 3314
},
{
"name": "ins潮牌长袖t恤男宽松纯色内搭上衣潮牌百搭秋冬季潮流帅气打底衫",
"cover": "g3-0.jpg",
"imgs": [
"g3-0.jpg",
"g3-1.jpg",
"g3-2.jpg",
"g3-3.jpg",
"g3-4.jpg"
],
"real_price": 9.9,
"price": 22.90,
"isLowPrice": true,
"discount": "满10减3",
"sold": 129
},
{
"name": "Redmi K60狠快狠强狠旗舰2023第一台梦幻手机",
"cover": "g4-0.png",
"imgs": [
"g4-0.png",
"g4-1.png",
"g4-2.png",
"g4-3.png"
],
"real_price": 199.9,
"price": 2999,
"isLowPrice": true,
"discount": "",
"sold": 967
},
{
"name": "小米笔记本Pro X 14",
"imgs": [
"g5-0.jpg",
"g5-1.jpg",
"g5-2.jpg",
"g5-3.jpg",
"g5-4.jpg"
],
"cover": "g5-0.jpg",
"real_price": 1559.9,
"price": 6999,
"isLowPrice": false,
"discount": "",
"sold": 473
},
{
"name": "小米电视6 65\" OLED 65英寸",
"cover": "g6-0.jpg",
"imgs": [
"g6-0.jpg",
"g6-1.jpg",
"g6-2.jpg",
"g6-3.jpg",
"g6-4.jpg"
],
"price": 6699,
"real_price": 399,
"isLowPrice": false,
"discount": "",
"sold": 863
},
{
"name": "红白撞色条纹软糯针织上衣女2022年秋季新款甜美减龄短款毛衣开衫",
"cover": "g1-0.jpg",
"imgs": [
"g1-0.jpg",
"g1-1.jpg",
"g1-2.jpg",
"g1-3.jpg"
],
"isLowPrice": true,
"discount": "满4减3",
"sold": 134,
"price": 39.9,
"real_price": 9.9
},
{
"name": "森马t恤男2023男士纯棉上衣白色情侣装凉感短袖打底衫纯色体恤潮",
"imgs": [
"g2-0.webp",
"g2-1.webp",
"g2-2.webp",
"g2-3.webp"
],
"cover": "g2-0.webp",
"real_price": 9.9,
"price": 49.99,
"isLowPrice": false,
"discount": "满20减5",
"sold": 3314
},
{
"name": "ins潮牌长袖t恤男宽松纯色内搭上衣潮牌百搭秋冬季潮流帅气打底衫",
"cover": "g3-0.jpg",
"imgs": [
"g3-0.jpg",
"g3-1.jpg",
"g3-2.jpg",
"g3-3.jpg",
"g3-4.jpg"
],
"real_price": 9.9,
"price": 22.90,
"isLowPrice": true,
"discount": "满10减3",
"sold": 129
},
{
"name": "Redmi K60狠快狠强狠旗舰2023第一台梦幻手机",
"cover": "g4-0.png",
"imgs": [
"g4-0.png",
"g4-1.png",
"g4-2.png",
"g4-3.png"
],
"real_price": 199.9,
"price": 2999,
"isLowPrice": true,
"discount": "",
"sold": 967
},
{
"name": "小米笔记本Pro X 14",
"imgs": [
"g5-0.jpg",
"g5-1.jpg",
"g5-2.jpg",
"g5-3.jpg",
"g5-4.jpg"
],
"cover": "g5-0.jpg",
"real_price": 1559.9,
"price": 6999,
"isLowPrice": false,
"discount": "",
"sold": 473
},
{
"name": "小米电视6 65\" OLED 65英寸",
"cover": "g6-0.jpg",
"imgs": [
"g6-0.jpg",
"g6-1.jpg",
"g6-2.jpg",
"g6-3.jpg",
"g6-4.jpg"
],
"price": 6699,
"real_price": 399,
"isLowPrice": false,
"discount": "",
"sold": 863
},
{
"name": "红白撞色条纹软糯针织上衣女2022年秋季新款甜美减龄短款毛衣开衫",
"cover": "g1-0.jpg",
"imgs": [
"g1-0.jpg",
"g1-1.jpg",
"g1-2.jpg",
"g1-3.jpg"
],
"isLowPrice": true,
"discount": "满4减3",
"sold": 134,
"price": 39.9,
"real_price": 9.9
},
{
"name": "森马t恤男2023男士纯棉上衣白色情侣装凉感短袖打底衫纯色体恤潮",
"imgs": [
"g2-0.webp",
"g2-1.webp",
"g2-2.webp",
"g2-3.webp"
],
"cover": "g2-0.webp",
"real_price": 9.9,
"price": 49.99,
"isLowPrice": false,
"discount": "满20减5",
"sold": 3314
},
{
"name": "ins潮牌长袖t恤男宽松纯色内搭上衣潮牌百搭秋冬季潮流帅气打底衫",
"cover": "g3-0.jpg",
"imgs": [
"g3-0.jpg",
"g3-1.jpg",
"g3-2.jpg",
"g3-3.jpg",
"g3-4.jpg"
],
"real_price": 9.9,
"price": 22.90,
"isLowPrice": true,
"discount": "满10减3",
"sold": 129
},
{
"name": "Redmi K60狠快狠强狠旗舰2023第一台梦幻手机",
"cover": "g4-0.png",
"imgs": [
"g4-0.png",
"g4-1.png",
"g4-2.png",
"g4-3.png"
],
"real_price": 199.9,
"price": 2999,
"isLowPrice": true,
"discount": "",
"sold": 967
},
{
"name": "小米笔记本Pro X 14",
"imgs": [
"g5-0.jpg",
"g5-1.jpg",
"g5-2.jpg",
"g5-3.jpg",
"g5-4.jpg"
],
"cover": "g5-0.jpg",
"real_price": 1559.9,
"price": 6999,
"isLowPrice": false,
"discount": "",
"sold": 473
}
]

BIN
public/docs/1.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
public/docs/2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
public/docs/3.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

BIN
public/docs/4.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
public/docs/5.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

BIN
public/docs/img-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
public/docs/img-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

BIN
public/docs/img-3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
public/docs/img-4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
public/docs/img-5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

View File

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 231 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -61,12 +61,10 @@ export default {
this.setVh()
// 监听resize事件 视图大小发生变化就重新计算1vh的值
window.addEventListener('resize', () => {
// location.reload()
location.reload()
this.setVh()
})
// window.onresize = () => {
//
// }
try {
navigator.control.gesture(false);
} catch (e) {

View File

@@ -3,6 +3,7 @@ import request from "../utils/request";
export function userinfo(params, data) {
return request({url: '/user/userinfo', method: 'get', params, data})
}
export function userVideoList(params, data) {
return request({url: '/user/video_list', method: 'get', params, data})
}
@@ -21,4 +22,8 @@ export function userCollect(params, data) {
export function recommendedPost(params, data) {
return request({url: '/post/recommended', method: 'get', params, data})
}
export function recommendedShop(params, data) {
return request({url: '/shop/recommended', method: 'get', params, data})
}

View File

@@ -1,65 +0,0 @@
export default {
list: [
{
"name": "小米电视6 65\" OLED 65英寸",
"cover": new URL('@/assets/img/goods/g6-0.jpg', import.meta.url).href,
imgs:[
new URL('../img/goods/g6-0.jpg', import.meta.url).href,
new URL('../img/goods/g6-1.jpg', import.meta.url).href,
new URL('../img/goods/g6-2.jpg', import.meta.url).href,
new URL('../img/goods/g6-3.jpg', import.meta.url).href,
new URL('../img/goods/g6-4.jpg', import.meta.url).href,
],
price: 6699,
isLowPrice: false,
discount: '',
sold: 863,
},
{
"name": "红白撞色条纹软糯针织上衣女2022年秋季新款甜美减龄短款毛衣开衫",
"cover": new URL('../img/goods/g1-0.jpg', import.meta.url).href,
imgs:[
new URL('../img/goods/g1-0.jpg', import.meta.url).href,
new URL('../img/goods/g1-1.jpg', import.meta.url).href,
new URL('../img/goods/g1-2.jpg', import.meta.url).href,
new URL('../img/goods/g1-3.jpg', import.meta.url).href,
],
isLowPrice: true,
discount: '满4减3',
sold: 134,
price: 39.9,
},
{
"name": "森马t恤男2023男士纯棉上衣白色情侣装凉感短袖打底衫纯色体恤潮",
"cover": new URL('../img/goods/g2-0.webp', import.meta.url).href,
price: 49.99,
isLowPrice: false,
discount: '满20减5',
sold: 3314,
},
{
"name": "ins潮牌长袖t恤男宽松纯色内搭上衣潮牌百搭秋冬季潮流帅气打底衫",
"cover": new URL('../img/goods/g3-0.jpg', import.meta.url).href,
price: 22.90,
isLowPrice: true,
discount: '满10减3',
sold: 129,
},
{
"name": "Redmi K60狠快狠强狠旗舰2023第一台梦幻手机",
"cover": new URL('../img/goods/g4-0.png', import.meta.url).href,
price: 2999,
isLowPrice: true,
discount: '',
sold: 967,
},
{
"name": "小米笔记本Pro X 14",
"cover": new URL('../img/goods/g5-0.jpg', import.meta.url).href,
price: 6999,
isLowPrice: false,
discount: '',
sold: 473,
},
]
}

View File

@@ -404,6 +404,9 @@ export default {
span {
margin-right: 5rem;
}
svg{
font-size: 10rem;
}
}
}

View File

@@ -3,12 +3,12 @@
<div class="DouyinCode" v-if="modelValue">
<div class="content">
<div class="video-poster">
<img src="../assets/img/poster/1.jpg" class="poster">
<img :src="_checkImgUrl(item.video.cover.url_list[0])" class="poster">
</div>
<div class="desc">
<div class="left">
<div class="user">@名字</div>
<div class="title">#窃书不能算偷窃书读书人的事能算偷么</div>
<div class="user">@{{item.author.nickname}}</div>
<div class="title">{{item.desc}}</div>
</div>
<img class="code" src="../assets/img/icon/components/video/douyin-code.jpg" alt="">
</div>
@@ -43,9 +43,12 @@
</transition>
</template>
<script>
import {_checkImgUrl} from "@/utils";
export default {
name: "DouyinCode",
props: {
item:{},
modelValue: false
},
data() {
@@ -55,6 +58,7 @@ export default {
created() {
},
methods: {
_checkImgUrl,
cancel() {
this.$emit('update:modelValue', false)
}
@@ -87,16 +91,22 @@ export default {
overflow: hidden;
.desc {
margin-bottom: 20rem;
display: flex;
gap: 20rem;
padding: 10rem;
.left {
font-size: 18rem;
.title {
margin-top: 10rem;
font-size: 14rem;
color: var(--second-text-color);
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box; //作为弹性伸缩盒子模型显示。
-webkit-box-orient: vertical; //设置伸缩盒子的子元素排列方式--从上到下垂直排列
-webkit-line-clamp: 1; //显示的行
}
}
.code {
@@ -134,11 +144,11 @@ export default {
.shares {
display: flex;
padding-right: @space-width * 2;
margin-bottom: @space-width;
gap: 20rem;
padding-left: 20rem;
.share-to {
margin-left: 28rem;
margin-bottom: @space-width;
img {
width: @icon-width;
@@ -159,6 +169,7 @@ export default {
.cancel {
font-size: 16rem;
background: rgb(38, 38, 38);
color: rgba(white,.8);
padding: 15rem;
text-align: center;
}

View File

@@ -27,7 +27,7 @@
</div>
<div class="shares list">
<template v-if="mode === 'video'">
<div class="option" @click.stop="$no">
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
<img class="avatar" src="../assets/img/icon/components/video/torichang.png" alt="">
<span>转发</span>
</div>
@@ -51,7 +51,7 @@
<img class="small" src="../assets/img/icon/components/video/warring.png" alt="">
<span>举报</span>
</div>
<div class="option" @click.stop="$nav('/home/report',{mode:this.mode})">
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
<Icon icon="ion:paper-plane"/>
<span>私信朋友</span>
</div>
@@ -73,7 +73,7 @@
<span>不感兴趣</span>
</div>
<div class="option" @click.stop="closeShare($emit('showDouyinCode'))">
<img class="small" src="../assets/img/icon/components/video/dislike.png" alt="">
<Icon icon="tabler:photo"/>
<span>生成图片</span>
</div>
<div class="option" @click.stop="$no">
@@ -133,7 +133,7 @@ import Check from "./Check";
import FromBottomDialog from "./dialog/FromBottomDialog";
import DouyinCode from "./DouyinCode";
import {useBaseStore} from "@/store/pinia";
import {$no} from "@/utils";
import {$no, _copy} from "@/utils";
export default {
name: "Share",
@@ -144,6 +144,7 @@ export default {
},
props: {
modelValue: false,
item: {},
videoId: {
type: String,
default: null
@@ -170,8 +171,7 @@ export default {
}
},
data() {
return {
}
return {}
},
methods: {
$no,
@@ -180,6 +180,7 @@ export default {
this.$showLoading()
await this.$sleep(500)
this.$hideLoading()
_copy(this.item.share_info.share_link_desc + this.item.share_info.share_url)
//TODO 抖音样式改了
this.$notice('复制成功')
},

View File

@@ -7,7 +7,7 @@
<Icon @click="$emit('back')" class="icon" icon="eva:arrow-ios-back-fill"/>
<transition name="fade">
<div class="float-user" v-if="state.floatFixed">
<img v-lazy="Utils.$imgPreview(props.currentItem.author.avatar_168x168.url_list[0])" class="avatar"/>
<img v-lazy="_checkImgUrl(props.currentItem.author.avatar_168x168.url_list[0])" class="avatar"/>
<img v-if="!props.currentItem.author.follow_status" src="@/assets/img/icon/add-light.png" alt=""
class="add">
<span @click="followButton">{{ props.currentItem.author.follow_status ? '私信' : '关注' }}</span>
@@ -99,7 +99,7 @@
<div class="other">
<div class="scroll-x" @touchmove="stop">
<div class="item" v-for="item in props.currentItem.author.card_entries">
<img :src="item.icon_dark.url_list[0]" alt="">
<img :src="_checkImgUrl(item.icon_dark.url_list[0])" alt="">
<div class="right">
<div class="top">{{ item.title }}</div>
<div class="bottom">{{ item.sub_title }}</div>

View File

@@ -62,7 +62,7 @@ export default {
},
borderRadius: {
type: String,
default: '5rem 5rem 0 0'
default: '15rem 15rem 0 0'
},
tag: {
type: String,
@@ -218,11 +218,11 @@ export default {
}
.heng-gang {
border-radius: 5rem 5rem 0 0;
border-radius: 15rem 15rem 0 0;
z-index: 3;
width: 100%;
position: fixed;
height: 20rem;
height: 30rem;
display: flex;
transform: translateY(-24rem);
justify-content: center;

View File

@@ -20,57 +20,67 @@
<p> 您的浏览器不支持 video 标签</p>
</video>
<Icon icon="fluent:play-28-filled" class="pause-icon" v-if="!isPlaying"/>
<div class="float" :style="{opacity: isUp?0:1}">
<div :style="{opacity:isMove ? 0:1}" class="normal">
<template v-if="!commentVisible">
<ItemToolbar v-model:item="localItem"
:position="position"
v-bind="$attrs"
/>
<ItemDesc
v-model:item="localItem"
:position="position"
/>
</template>
<div v-if="isMy" class="comment-status">
<div class="comment">
<div class="type-comment">
<img src="../../assets/img/icon/head-image.jpeg" alt="" class="avatar">
<div class="right">
<p>
<span class="name">zzzzz</span>
<span class="time">2020-01-20</span>
</p>
<p class="text">北京</p>
</div>
</div>
<transition-group name="comment-status" tag="div" class="loveds">
<div class="type-loved" :key="i" v-for="i in test">
<div class="float">
<template v-if="isLive">
<div class="living">点击进入直播间</div>
<ItemDesc
:is-live="true"
v-model:item="localItem"
:position="position"
/>
</template>
<template v-else>
<div :style="{opacity:isMove ? 0:1}" class="normal">
<template v-if="!commentVisible">
<ItemToolbar v-model:item="localItem"
:position="position"
v-bind="$attrs"
/>
<ItemDesc
v-model:item="localItem"
:position="position"
/>
</template>
<div v-if="isMy" class="comment-status">
<div class="comment">
<div class="type-comment">
<img src="../../assets/img/icon/head-image.jpeg" alt="" class="avatar">
<img src="../../assets/img/icon/love.svg" alt="" class="loved">
<div class="right">
<p>
<span class="name">zzzzz</span>
<span class="time">2020-01-20</span>
</p>
<p class="text">北京</p>
</div>
</div>
</transition-group>
<transition-group name="comment-status" tag="div" class="loveds">
<div class="type-loved" :key="i" v-for="i in test">
<img src="../../assets/img/icon/head-image.jpeg" alt="" class="avatar">
<img src="../../assets/img/icon/love.svg" alt="" class="loved">
</div>
</transition-group>
</div>
</div>
</div>
</div>
<div class="progress"
:class="progressClass"
ref="progress"
@click="null"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend"
>
<div class="time" v-if="isMove">
<span class="currentTime">{{ LUtils.$duration(currentTime) }}</span>
<span class="duration"> / {{ LUtils.$duration(duration) }}</span>
<div class="progress"
:class="progressClass"
ref="progress"
@click="null"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend"
>
<div class="time" v-if="isMove">
<span class="currentTime">{{ LUtils.$duration(currentTime) }}</span>
<span class="duration"> / {{ LUtils.$duration(duration) }}</span>
</div>
<template v-if="duration > 15 || isMove || !isPlaying">
<div class="bg"></div>
<div class="progress-line" :style="durationStyle"></div>
<div class="point"></div>
</template>
</div>
<template v-if="duration > 15 || isMove || !isPlaying">
<div class="bg"></div>
<div class="progress-line" :style="durationStyle"></div>
<div class="point"></div>
</template>
</div>
</template>
</div>
</div>
</template>
@@ -125,7 +135,7 @@ export default {
return false
}
},
isUp: {
isLive: {
type: Boolean,
default: () => {
return false
@@ -290,29 +300,34 @@ export default {
}
},
click({uniqueId, index, type}) {
// console.log(this.position)
if (
this.position.uniqueId === uniqueId &&
this.position.index === index
) {
if (type === EVENT_KEY.ITEM_TOGGLE) {
if (this.status === SlideItemPlayStatus.Play) {
this.pause()
} else {
this.play()
if (this.isLive) {
if (type === EVENT_KEY.ITEM_TOGGLE) {
bus.emit(EVENT_KEY.NAV, {path: '/home/live', query: {id: this.item.id}})
}
} else {
if (type === EVENT_KEY.ITEM_TOGGLE) {
if (this.status === SlideItemPlayStatus.Play) {
this.pause()
} else {
this.play()
}
}
if (type === EVENT_KEY.ITEM_STOP) {
this.$refs.video.currentTime = 0
this.ignoreWaiting = true
this.pause()
setTimeout(() => this.ignoreWaiting = false, 300)
}
if (type === EVENT_KEY.ITEM_PLAY) {
this.$refs.video.currentTime = 0
this.ignoreWaiting = true
this.play()
setTimeout(() => this.ignoreWaiting = false, 300)
}
}
if (type === EVENT_KEY.ITEM_STOP) {
this.$refs.video.currentTime = 0
this.ignoreWaiting = true
this.pause()
setTimeout(() => this.ignoreWaiting = false, 300)
}
if (type === EVENT_KEY.ITEM_PLAY) {
this.$refs.video.currentTime = 0
this.ignoreWaiting = true
this.play()
setTimeout(() => this.ignoreWaiting = false, 300)
}
}
},
@@ -713,4 +728,17 @@ export default {
}
}
.living {
position: absolute;
left: 50%;
font-size: 18rem;
border-radius: 50rem;
border: 1px solid #e0e0e0;
padding: 15rem 20rem;
line-height: 1;
color: white;
top: 70%;
transform: translate(-50%, -50%);
}
</style>

View File

@@ -22,6 +22,12 @@ const props = defineProps({
return false
}
},
isLive: {
type: Boolean,
default: () => {
return false
}
},
})
const state = reactive({
@@ -43,14 +49,15 @@ const state = reactive({
<span>{{ props.item.address }}</span>
</div>
</div>
<div class="live" v-if="props.isLive">直播中</div>
<div class="name mb1r f18 fb" @click.stop="$emit('goUserInfo')">@{{ props.item.author.nickname }}</div>
<div class="description mb1r">
<div class="description">
{{ props.item.desc }}
</div>
<div class="music" @click.stop="bus.emit('nav','/home/music')">
<img src="../../assets/img/icon/music.svg" alt="" class="music-image">
<span>{{ props.item.music.title }}</span>
</div>
<!-- <div class="music" @click.stop="bus.emit('nav','/home/music')">-->
<!-- <img src="../../assets/img/icon/music.svg" alt="" class="music-image">-->
<!-- <span>{{ props.item.music.title }}</span>-->
<!-- </div>-->
</div>
<div v-else class="comment-status">
<div class="comment">
@@ -113,6 +120,16 @@ const state = reactive({
}
}
.live{
border-radius: 3rem;
margin-bottom: 10rem;
padding: 3rem 6rem;
font-size: 11rem;
display: inline-flex;
background: var(--primary-btn-color);
color: white;
}
.music {
position: relative;
display: flex;

View File

@@ -203,15 +203,16 @@ function getInsEl(item, index, play = false) {
}
})
return parent
} else {
const app = createApp({
render() {
return <SlideItem data-index={index}>{slideVNode}</SlideItem>
}
})
const ins = app.mount(parent)
appInsMap.set(index, app)
return ins.$el
}
const app = createApp({
render() {
return <SlideItem data-index={index}>{slideVNode}</SlideItem>
}
})
const ins = app.mount(parent)
appInsMap.set(index, app)
return ins.$el
}
function touchStart(e) {

View File

@@ -78,7 +78,7 @@ async function fetchData() {
export async function startMock() {
mock.onGet(/video\/recommended/).reply(async (config) => {
let page = getPage2(config.params)
console.log('allRecommendVideos', cloneDeep(allRecommendVideos.length),page)
console.log('allRecommendVideos', cloneDeep(allRecommendVideos.length), page)
return [200, {
data: {
total: 844,
@@ -143,7 +143,7 @@ export async function startMock() {
if (!userVideos.length) {
// let r = await fetch(BASE_URL + '/data/user-71158770.json')
// let r = await fetch(BASE_URL + '/data/user-8357999.json')
let r = await fetch(BASE_URL + '/data/user-12345xiaolaohu.json')
let r = await fetch(BASE_URL + '/data/user_video_list/user-12345xiaolaohu.json')
let list = await r.json()
const baseStore = useBaseStore()
let userList = cloneDeep(baseStore.users)
@@ -243,5 +243,18 @@ export async function startMock() {
}]
})
mock.onGet(/shop\/recommended/).reply(async (config) => {
let page = getPage2(config.params)
let r2 = await fetch(BASE_URL + '/data/goods.json')
let v = await r2.json()
return [200, {
data: {
total: v.length,
list: v.slice(page.offset, page.limit)
}, code: 200
}]
})
setTimeout(fetchData, 1000)
}

View File

@@ -1,16 +1,30 @@
<template>
<div class="LivePage" ref="page">
<div class="live-wrapper">
<img src="../../assets/img/poster/1.jpg" alt="">
<video
src="https://www.douyin.com/aweme/v1/play/?video_id=v0d00fg10000cj1lq4jc77u0ng6s1gt0&amp;line=0&amp;file_id=bed51c00899b458cbc5d8280147c22a1&amp;sign=7749aec7bd62a3760065f60e40fc1867&amp;is_play_url=1&amp;source=PackSourceEnum_PUBLISH"
poster="/images/jwWCPZVTIA4IKM-8WipLF.png"
preload=""
loop=""
muted
x5-video-player-type="h5-page"
x5-video-player-fullscreen="false"
webkit-playsinline="true"
x5-playsinline="true"
playsinline="true"
fullscreen="false"
autoplay="">
<p> 您的浏览器不支持 video 标签</p>
</video>
</div>
<div class="float">
<div class="top">
<div class="left">
<div class="liver">
<img class="avatar" src="../../assets/img/icon/avatar/10.png" alt="">
<img class="avatar" :src="_checkImgUrl(userinfo.avatar_168x168.url_list[0])" alt="">
<div class="desc">
<div class="desc-wrapper">
<div class="name">大司马大司马大司马</div>
<div class="name">{{ userinfo.nickname }}</div>
<div class="count">2万本场点赞</div>
</div>
<div class="follow-btn">关注</div>
@@ -58,8 +72,8 @@
<span>30</span>
</div>
</div>
<span class="name">嘻嘻哈哈</span>
<span class="text">{{ i }}</span>
<span class="name">{{ i.name}}</span>
<span class="text">{{ i.text }}</span>
</div>
</div>
</div>
@@ -85,8 +99,6 @@
</div>
</div>
</div>
<base-button @click="sendComment">点击</base-button>
</div>
</template>
@@ -94,6 +106,10 @@
import BaseButton from "../../components/BaseButton";
import Dom from "../../utils/dom";
import {nextTick} from "vue";
import {mapState} from "pinia";
import {useBaseStore} from "@/store/pinia";
import {_checkImgUrl, random} from "@/utils";
import Mock from "mockjs";
export default {
name: "LivePage",
@@ -101,23 +117,25 @@ export default {
props: {},
data() {
return {
timer1: -1,
timer2: -1,
timer3: -1,
isFollowed: false,
list: [
'asdfasdf',
'asdfasdf',
'asdfasdf',
],
list: [],
barrage: [],
barrageTemplate: () => {
let name = Mock.mock('@cname')
let a = Mock.mock('@csentence')
return `
<div class="barrage">
<div class="type">管理</div>
<div class="text">感谢老铁送的火箭</div>
<div class="type">${name}</div>
<div class="text">${a}</div>
</div>
`
},
userJoinedTemplate: () => {
let src = new URL('../../assets/img/icon/home/level.webp')
let src = '/images/icon/love.webp'
let name = Mock.mock('@cname')
return `
<div class="user-joined">
<div class="level">
@@ -126,23 +144,36 @@ export default {
<span>30</span>
</div>
</div>
<span class="name">嘻嘻哈哈</span>
<span class="name">${name}</span>
<span class="text">加入了直播间</span>
</div>
`
},
sendGiftTemplate: () => {
let avatar = new URL('../../assets/img/icon/avatar/3.png')
let gift = new URL('../../assets/img/icon/home/love.webp')
let avatarList = [
'/images/EPsQ7u4sNnrHC-ix-a9yQ.png',
'/images/Xex2IhY-Zm338cNlcGuNW.png',
'/images/gddHyRZrdk0Em3RRgVa9g.png',
'/images/LJ-8p2jF3HydBD5j28PgQ.png',
'/images/KwJ9N7yFjYylfwYeThWjx.png',
'/images/EKkC06GI4yXC2mNHMrm46.png',
'/images/rlkpmpGPdhYZRJl3J4Xl7.png',
'/images/Ge4mMWQoICdpyTyixk3Sf.png',
]
let avatar = avatarList[random(0, avatarList.length - 1)]
let gift = '/images/icon/love.webp'
let name = Mock.mock('@cname')
let name2 = Mock.mock('@cname')
let num = Mock.mock('@integer(60,400)')
return `
<div class="send-gift">
<div class="left">
<img src="${avatar}" alt="" class="avatar">
<div class="desc">
<div class="name">哈哈哈哈哈哈哈哈哈</div>
<div class="name">${name}</div>
<div class="sendto">
<span class="send">送</span>
<span class="to">嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻</span>
<span class="to">${name2}</span>
</div>
</div>
<div class="gift-wrapper">
@@ -150,7 +181,7 @@ export default {
</div>
</div>
<div class="right">
x339
x${num}
</div>
</div>
`
@@ -158,25 +189,33 @@ export default {
page: null,
}
},
computed: {},
computed: {
...mapState(useBaseStore, ['friends', 'userinfo']),
},
created() {
},
mounted() {
this.page = this.$refs.page
// setInterval(async () => {
// this.sendGift()
// await this.$sleep(300)
// this.sendGift()
// this.joinUser()
// }, 3000)
// setInterval(async () => {
// this.sendBarrage()
// }, 5100)
// setInterval(async () => {
// this.sendComment()
// }, 500)
this.timer1 = setInterval(async () => {
this.sendGift()
await this.$sleep(300)
this.sendGift()
this.joinUser()
}, 1000)
this.timer2 = setInterval(async () => {
this.sendBarrage()
}, 1500)
this.timer3 = setInterval(async () => {
this.sendComment()
}, 700)
},
unmounted() {
clearInterval(this.timer1)
clearInterval(this.timer2)
clearInterval(this.timer3)
},
methods: {
_checkImgUrl,
sendGift() {
let page = new Dom(this.page)
let sendGift = new Dom().create(this.sendGiftTemplate())
@@ -188,6 +227,10 @@ export default {
if (oldSendGift.els.length !== 0) {
top = sendGift.removePx(oldSendGift.css('top')) - 70
}
if (top < 100) {
top = document.body.clientHeight * .6
}
console.log('top', top)
sendGift.css('top', top)
page.append(sendGift)
},
@@ -210,11 +253,17 @@ export default {
if (oldBarrages.els.length !== 0) {
top = barrage.removePx(oldBarrages.css('top')) + 20
}
if (top > document.body.clientHeight * .5) {
top = document.body.clientHeight * .35
}
barrage.css('top', top)
page.append(barrage)
},
sendComment() {
this.list.push('评论评论评论评论评论评论评论评论评论评论' + this.list.length)
this.list.push({
name: Mock.mock('@cname'),
text: Mock.mock('@csentence')
})
nextTick(() => {
let comments = this.$refs['comments']
comments.scrollTo({top: comments.scrollHeight - comments.clientHeight, behavior: 'smooth'})
@@ -272,6 +321,8 @@ export default {
.avatar {
margin-right: 5rem;
width: 40rem;
height: 40rem;
object-fit: cover;
border-radius: 50%;
}
@@ -417,6 +468,14 @@ export default {
width: 100vw;
height: calc(var(--vh, 1vh) * 100);
background: black;
display: flex;
align-items: center;
justify-content: center;
video {
width: 100%;
object-fit: cover;
}
img {
width: 100vw;

View File

@@ -146,6 +146,7 @@
ref="share"
page-id="home-index"
@dislike="dislike"
:item="state.currentItem"
:videoId="state.recommendList[state.itemIndex]?.id"
:canDownload="state.recommendList[state.itemIndex]?.canDownload"
@play-feedback="state.showPlayFeedback = true"
@@ -156,7 +157,9 @@
<PlayFeedback v-model="state.showPlayFeedback"/>
<DouyinCode v-model="state.showDouyinCode"/>
<DouyinCode
:item="state.currentItem"
v-model="state.showDouyinCode"/>
<ShareTo v-model:type="state.shareType"
:videoId="state.recommendList[state.itemIndex]?.id"
@@ -195,7 +198,7 @@ import SlideItem from '@/components/slide/SlideItem.vue'
import Comment from "../../components/Comment.vue";
import Share from "../../components/Share.vue";
import IndicatorHome from "./components/IndicatorHome.vue";
import {onMounted, onUnmounted, reactive} from "vue";
import {onActivated, onDeactivated, onMounted, onUnmounted, reactive} from "vue";
import bus, {EVENT_KEY} from "../../utils/bus";
import {useNav} from "@/utils/hooks/useNav";
import PlayFeedback from "@/pages/home/components/PlayFeedback.vue";
@@ -221,6 +224,7 @@ const nav = useNav()
const baseStore = useBaseStore()
const state = reactive({
active: true,
baseIndex: 1,
navIndex: 4,
test: '',
@@ -253,6 +257,7 @@ function delayShowDialog(cb) {
}
function setCurrentItem(item) {
if (!state.active) return
// console.log('sss',item,state.baseIndex)
if (state.baseIndex !== 1) return
if (state.currentItem.author.uid !== item.author.uid) {
@@ -266,21 +271,34 @@ function setCurrentItem(item) {
}
onMounted(() => {
bus.on(EVENT_KEY.ENTER_FULLSCREEN, (e) => state.fullScreen = true)
bus.on(EVENT_KEY.EXIT_FULLSCREEN, (e) => state.fullScreen = false)
bus.on(EVENT_KEY.ENTER_FULLSCREEN, (e) => {
if (!state.active) return
state.fullScreen = true
})
bus.on(EVENT_KEY.EXIT_FULLSCREEN, (e) => {
if (!state.active) return
state.fullScreen = false
})
bus.on(EVENT_KEY.OPEN_COMMENTS, (e) => {
if (!state.active) return
bus.emit(EVENT_KEY.ENTER_FULLSCREEN)
state.commentVisible = true
})
bus.on(EVENT_KEY.CLOSE_COMMENTS, (e) => {
if (!state.active) return
bus.emit(EVENT_KEY.EXIT_FULLSCREEN)
state.commentVisible = false
})
bus.on(EVENT_KEY.SHOW_SHARE, (e) => {
if (!state.active) return
state.isSharing = true
})
bus.on(EVENT_KEY.NAV, ({path, query}) => nav(path, query))
bus.on(EVENT_KEY.NAV, ({path, query}) => {
if (!state.active) return
nav(path, query)
})
bus.on(EVENT_KEY.GO_USERINFO, () => {
if (!state.active) return
state.baseIndex = 2
})
bus.on(EVENT_KEY.CURRENT_ITEM, setCurrentItem)
@@ -290,6 +308,15 @@ onUnmounted(() => {
bus.offAll()
})
onActivated(() => {
state.active = true
})
onDeactivated(() => {
state.active = false
})
function closeComments() {
bus.emit(EVENT_KEY.CLOSE_COMMENTS)
}

View File

@@ -1,228 +0,0 @@
<script setup>
import {onMounted, onUnmounted, reactive, watch} from "vue";
import {_checkImgUrl, _duration, _formatNumber} from "@/utils";
import {recommendedVideo} from "@/api/videos";
import {useBaseStore} from "@/store/pinia";
const baseStore = useBaseStore()
const props = defineProps({
active: Boolean
})
const p = {
onShowComments() {
console.log('onShowComments')
}
}
const state = reactive({
index: 0,
list: [],
totalSize: 0,
pageSize: 10,
pageNo: 0,
})
function loadMore() {
if (!baseStore.loading) {
state.pageNo++
getData()
}
}
async function getData(refresh = false) {
if (baseStore.loading) return
baseStore.loading = true
let res = await recommendedVideo({pageNo: refresh ? 0 : state.pageNo, pageSize: state.pageSize})
console.log('getSlide4Data-', 'refresh', refresh, res)
baseStore.loading = false
if (res.code === 200) {
state.totalSize = res.data.total
if (refresh) {
state.list = []
}
state.list = state.list.concat(res.data.list)
} else {
state.pageNo--
}
}
watch(() => props.active, n => {
if (!state.list.length && n) {
baseStore.loading = false
getData()
}
})
onMounted(() => {
})
onUnmounted(() => {
})
</script>
<template>
<div class="page">
<div class="item"
:class="[
i % 5 === 0 && 'big',
i % 5 === 0 ? '' : (i % 2 === 1 && 'l'),
i % 5 === 0 ? '' : (i % 2 === 0 && 'r'),
]"
v-for="(item,i) in state.list">
<video
controls
:poster="_checkImgUrl(item.video.cover.url_list[0])"
:src="item.video.play_addr.url_list[0]"
></video>
<img v-lazy="_checkImgUrl(item.video.cover.url_list[0])" alt="" class="poster">
<div class="duration">{{ _duration(item.duration / 1000) }}</div>
<div class="title">
{{ item.desc }}
</div>
<div class="bottom">
<div class="l">
<img v-lazy="_checkImgUrl(item.author.avatar_168x168.url_list[0])" alt="" class="avatar">
<div class="name">{{ item.author.nickname }}</div>
</div>
<div class="r">
<Icon icon="icon-park-outline:like"/>
<div class="num">{{ _formatNumber(item.statistics.digg_count) }}</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="less">
.page {
display: grid;
grid-template-columns: repeat(2, 1fr);
row-gap: 15rem;
height: calc(var(--vh, 1vh) * 100 - var(--common-header-height) - var(--footer-height));
margin-top: var(--common-header-height);
overflow: auto;
box-sizing: border-box;
.item {
margin: 0 10rem;
display: flex;
flex-direction: column;
gap: 8rem;
position: relative;
.poster {
border-radius: 12rem;
width: 100%;
height: 140rem;
object-fit: cover;
}
video {
display: none;
height: 220rem;
object-fit: cover;
}
.title {
height: 36rem;
color: white;
font-size: 14rem;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box; //作为弹性伸缩盒子模型显示。
-webkit-box-orient: vertical; //设置伸缩盒子的子元素排列方式--从上到下垂直排列
-webkit-line-clamp: 2; //显示的行
}
.f {
display: flex;
align-items: center;
justify-content: space-between;
gap: 5rem;
}
.duration {
color: white;
position: absolute;
bottom: 80rem;
right: 10rem;
font-size: 13rem;
}
.bottom {
color: gray;
.f;
font-size: 13rem;
.l {
.f;
justify-content: flex-start;
.name {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box; //作为弹性伸缩盒子模型显示。
-webkit-box-orient: vertical; //设置伸缩盒子的子元素排列方式--从上到下垂直排列
-webkit-line-clamp: 1; //显示的行
}
.avatar {
@w: 20rem;
width: @w;
height: @w;
object-fit: cover;
border-radius: 50%;
}
}
.r {
word-break: keep-all;
.f;
svg {
font-size: 16rem;
}
}
}
&.big {
grid-column-start: 1;
grid-column-end: 3;
margin: 0;
.duration {
display: none;
}
.poster {
display: none;
}
video {
display: block;
}
.title {
height: unset;
-webkit-line-clamp: 1;
}
.title, .bottom {
padding: 0 10rem;
}
}
&.l {
margin-right: 5rem;
}
&.r {
margin-left: 5rem;
}
}
}
</style>

View File

@@ -4,17 +4,18 @@
:class="state.subTypeIsTop?'top':''"
ref="subTypeRef">
<div class="card" @touchmove.capture="stop">
<div class="nav-item" v-for="i in 2">
<img src="@/assets/img/icon/msg-icon9.webp" alt="">
<span>美食美食美食美食美食美食美食</span>
<div class="nav-item" v-for="i in store.users">
<img :src="_checkImgUrl(i.avatar_168x168.url_list[0])" alt="">
<span>{{ i.nickname }}</span>
</div>
</div>
</div>
<div class="sub-type-notice"
v-if="state.subType===-1 && !state.subTypeVisible"
@click="showSubType">1个直播
@click.stop="showSubType">{{store.users.length}}个直播
</div>
<SlideList
:cbs="{isLive:true}"
:active="props.active"
:style="{background: 'black',marginTop:state.subTypeVisible?state.subTypeHeight:0}"
:api="recommendedVideo"
@@ -27,10 +28,12 @@
import SlideItem from '@/components/slide/SlideItem.vue'
import {onMounted, onUnmounted, reactive, ref} from "vue";
import bus, {EVENT_KEY} from "@/utils/bus";
import Utils from "@/utils";
import Utils, {_checkImgUrl} from "@/utils";
import SlideList from './SlideList.vue';
import {recommendedVideo} from "@/api/videos";
import {useBaseStore} from "@/store/pinia";
const store = useBaseStore()
const props = defineProps({
active: {
type: Boolean,
@@ -104,6 +107,8 @@ onUnmounted(() => {
box-sizing: border-box;
display: flex;
overflow: auto;
gap: 10rem;
padding-left: 20rem;
}
.nav-item {
@@ -126,7 +131,7 @@ onUnmounted(() => {
img {
width: @width;
height: @width;
margin-bottom: 5rem;
border-radius: 50%;
}
}
}

View File

@@ -62,6 +62,30 @@
</div>
</div>
</div>
<!-- 消息-->
<div class="message" @click="$nav('/message/chat')">
<div class="avatar on-line">
<img src="../../assets/img/icon/avatar/2.png" alt="" class="head-image">
</div>
<div class="content">
<div class="left">
<div class="name">
<span>{{ userinfo.nickname }}</span>
</div>
<div class="detail">
哈哈哈哈哈哈
<div class="point"></div>
10-10
</div>
</div>
<div class="right">
<!-- <div class="not-read"></div>-->
<!-- <img class="camera" src="../../assets/img/icon/close-white.png" alt="">-->
<!-- <img class="arrow" src="../../assets/img/icon/close-white.png" alt="">-->
<div class="badge">12</div>
</div>
</div>
</div>
<!-- 抖音小助手-->
<div class="message" @click="$nav('/message/douyin-helper')">
<div class="avatar">
@@ -195,30 +219,6 @@
</div>
</div>
<!-- 消息-->
<div class="message" @click="$nav('/message/chat')">
<div class="avatar on-line">
<img src="../../assets/img/icon/avatar/2.png" alt="" class="head-image">
</div>
<div class="content">
<div class="left">
<div class="name">
<span>{{ userinfo.nickname }}</span>
</div>
<div class="detail">
哈哈哈哈哈哈
<div class="point"></div>
10-10
</div>
</div>
<div class="right">
<!-- <div class="not-read"></div>-->
<!-- <img class="camera" src="../../assets/img/icon/close-white.png" alt="">-->
<!-- <img class="arrow" src="../../assets/img/icon/close-white.png" alt="">-->
<div class="badge">12</div>
</div>
</div>
</div>
<NoMore/>

View File

@@ -67,6 +67,7 @@ export default {
.avatar {
margin-top: 55rem;
width: 55rem;
height: 55rem;
border-radius: 50%;
margin-bottom: 20rem;
}

View File

@@ -219,36 +219,7 @@ export default {
avatar: '../../assets/img/icon/head-image.jpg'
}
},
{
type: MESSAGE_TYPE.RED_PACKET,
state: AUDIO_STATE.NORMAL,
mode: RED_PACKET_MODE.MULTIPLE,
data: {
money: 5.11,
title: '大吉大利',
state: '未领取'
},
time: '2021-01-02 21:21',
user: {
id: '2739632844317827',
avatar: '../../assets/img/icon/head-image.jpg'
}
},
{
type: MESSAGE_TYPE.RED_PACKET,
state: AUDIO_STATE.NORMAL,
mode: RED_PACKET_MODE.SINGLE,
data: {
money: 5.11,
title: '大吉大利',
state: '已过期'
},
time: '2021-01-02 21:21',
user: {
id: 1,
avatar: '../../assets/img/icon/head-image.jpg'
}
},
{
type: MESSAGE_TYPE.MEME,
state: AUDIO_STATE.NORMAL,
@@ -441,6 +412,36 @@ export default {
avatar: '../../../assets/img/icon/head-image.jpg'
}
},
{
type: MESSAGE_TYPE.RED_PACKET,
state: AUDIO_STATE.NORMAL,
mode: RED_PACKET_MODE.MULTIPLE,
data: {
money: 5.11,
title: '大吉大利',
state: '未领取'
},
time: '2021-01-02 21:21',
user: {
id: '2739632844317827',
avatar: '../../assets/img/icon/head-image.jpg'
}
},
{
type: MESSAGE_TYPE.RED_PACKET,
state: AUDIO_STATE.NORMAL,
mode: RED_PACKET_MODE.SINGLE,
data: {
money: 5.11,
title: '大吉大利',
state: '已过期'
},
time: '2021-01-02 21:21',
user: {
id: 1,
avatar: '../../assets/img/icon/head-image.jpg'
}
},
],
typing: false,
loading: false,
@@ -466,15 +467,19 @@ export default {
created() {
},
mounted() {
$('img').on('load', this.scrollBottom)
this.scrollBottom()
},
unmounted() {
$('img').off('load', this.scrollBottom)
},
methods: {
_checkImgUrl,
scrollBottom() {
nextTick(() => {
let wrapper = this.$refs.msgWrapper
console.log('wrapper.clientHeight', wrapper.clientHeight)
console.log('wrapper.scrollHeight', wrapper.scrollHeight)
// console.log('wrapper.clientHeight', wrapper.clientHeight)
// console.log('wrapper.scrollHeight', wrapper.scrollHeight)
wrapper.scrollTo({top: wrapper.scrollHeight - wrapper.clientHeight})
})
@@ -804,6 +809,7 @@ export default {
.avatar {
margin-top: 60rem;
width: 55rem;
height: 55rem;
border-radius: 50%;
margin-bottom: 20rem;
}

View File

@@ -1,360 +0,0 @@
<template>
<div class="goods-detail base-page1">
<header>
<Icon
@click="$back()"
icon="material-symbols-light:arrow-back-ios-new"/>
<div class="option" @click="nav('/home/search')">
<Icon icon="jam:search"/>
</div>
</header>
<div class="slide-imgs">
<SlideHorizontal v-model:index="state.index">
<SlideItem v-for="item in state.detail.note_card?.image_list">
<img :src="_checkImgUrl(item.info_list?.[0]?.url)" alt="">
</SlideItem>
</SlideHorizontal>
<div class="indicator-bar" v-if="state.detail.note_card?.image_list?.length > 1">
<div class="indicator"
:class="[i <= state.index+1 && 'active']"
v-for="i in state.detail.note_card?.image_list?.length"></div>
</div>
</div>
<div class="content">
<div class="shop">
<header>
<img class="avatar" :src="_checkImgUrl(state.detail.note_card?.user?.avatar)"/>
<div class="right">
<div class="name">{{ state.detail.note_card.user.nick_name }}</div>
<div class="r">关注</div>
</div>
</header>
<div class="desc">
{{ state.detail.note_card?.display_title }}
</div>
<div class="date">{{ state.detail.note_card.createTime }}</div>
</div>
<div class="card comments">
<header>
<span class="l">评论 {{ state.detail.note_card.comment_list.length }}</span>
<div class="r">
<span>查看全部</span>
<Icon class="arrow" icon="mingcute:right-line"/>
</div>
</header>
<div class="comment" v-for="i in state.detail.note_card.comment_list.slice(0,2)">
<img src="https://cdn.seovx.com/?mom=302" alt="" class="avatar">
<span>
{{ i.name }}{{ i.text }}
</span>
</div>
</div>
</div>
<div class="toolbar">
<div class="input-wrap">
说点什么...
</div>
<div class="options">
<div class="option">
<Icon icon="solar:heart-linear"/>
<div class="text">{{ state.detail.note_card?.interact_info?.liked_count }}</div>
</div>
<div class="option">
<Icon icon="mage:message-dots-round" class="icon"/>
<div class="text">{{ state.detail.note_card.comment_list.length }}</div>
</div>
<div class="option">
<Icon icon="mage:star"/>
<div class="text">{{ state.detail.note_card?.interact_info?.collect_count }}</div>
</div>
<div class="option">
<Icon icon="ph:share-fat-light"/>
<div class="text">{{ state.detail.note_card?.interact_info?.share_count }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import SlideHorizontal from "@/components/slide/SlideHorizontal.vue";
import SlideItem from "@/components/slide/SlideItem.vue";
import {onMounted, reactive} from "vue";
import {useNav} from "@/utils/hooks/useNav";
import {Icon} from "@iconify/vue";
import {useBaseStore} from "@/store/pinia";
import {_checkImgUrl, cloneDeep} from "@/utils";
import Mock from 'mockjs'
const nav = useNav()
const store = useBaseStore()
defineOptions({
name: 'Album-Detail'
})
const state = reactive({
detail: {
"id": "",
"note_card": {
"interact_info": {},
"cover": {},
"image_list": [],
"display_title": "",
"user": {},
comment_list: [],
createTime: ''
}
},
index: 0,
})
onMounted(() => {
state.detail = cloneDeep(store.routeData)
let data = Mock.mock({
'comment_list|3-50': [{
name: '@cname',
text: '@cparagraph(3)'
}]
})
state.detail.note_card.comment_list = data.comment_list
state.detail.note_card.createTime = Mock.Random.date('MM-dd')
state.detail.note_card.interact_info.collect_count = Mock.Random.integer(60, 3000)
state.detail.note_card.interact_info.share_count = Mock.Random.integer(60, 3000)
console.log('sta', state.detail)
})
</script>
<style scoped lang="less">
@import "@/assets/less/index.less";
.goods-detail {
background: var(--color-message);
color: white;
font-size: 14rem;
@c: #a2a2a2;
@c2: #c0c0c0;
@red: rgb(248, 38, 74);
& > header {
position: fixed;
left: 0;
top: 0;
width: 100vw;
z-index: 9;
display: flex;
justify-content: space-between;
padding: 15rem;
box-sizing: border-box;
svg {
font-size: 20rem;
background: rgba(176, 176, 176, 0.4);
padding: 5rem;
color: white;
border-radius: 50%;
}
}
.slide-imgs {
position: relative;
height: 55vh;
img {
height: 100%;
width: 100%;
object-fit: cover;
touch-action: none;
}
.indicator-bar {
position: absolute;
bottom: 5rem;
left: 3vw;
width: 94vw;
display: flex;
gap: 5rem;
.indicator {
background: rgba(162, 160, 160, 0.5);
height: 3rem;
flex: 1;
border-radius: 2rem;
}
.active {
background: rgba(250, 246, 246, 0.58);
}
}
.index {
font-size: 12rem;
position: absolute;
padding: 3rem 10rem;
border-radius: 15rem;
background: rgba(91, 89, 89, 0.5);
right: 10rem;
bottom: 30rem;
color: white;
}
}
.card {
margin-top: 15rem;
border-radius: 10rem;
padding: 10rem 15rem;
background: black;
}
.arrow {
font-size: 16rem;
}
.content {
padding: 15rem;
padding-bottom: 10vh;
border-radius: 16rem 16rem 0 0;
.comments {
& > header {
margin-bottom: 16rem;
display: flex;
justify-content: space-between;
align-items: center;
.l {
font-size: 15rem;
}
.r {
color: gray;
font-size: 12rem;
display: flex;
align-items: center;
}
}
.comment {
margin-bottom: 16rem;
display: flex;
align-items: center;
gap: 5rem;
span {
display: inline-block;
white-space: nowrap;
flex: 1;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
}
img {
border-radius: 50%;
width: 20rem;
height: 20rem;
}
&:last-child {
margin-bottom: 0;
}
}
}
.shop {
& > header {
display: flex;
align-items: center;
gap: 10rem;
img {
width: 36rem;
height: 36rem;
border-radius: 50%;
}
.right {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
.name {
font-size: 16rem;
}
.r {
border-radius: 4rem;
padding: 6rem 16rem;
background: var(--primary-btn-color);
font-size: 12rem;
color: white;
}
}
}
.desc {
margin-top: 10rem;
}
.date {
font-size: 12rem;
margin-top: 10rem;
color: gray;
}
}
}
.toolbar {
position: fixed;
bottom: 0;
width: 100vw;
left: 0;
background: var(--color-message);
border-top: 1px solid rgba(white, .1);
display: flex;
align-items: center;
padding: 8rem 10rem;
padding-right: 0;
box-sizing: border-box;
gap: 6rem;
.input-wrap {
width: 110rem;
padding-left: 15rem;
height: 34rem;
border-radius: 30rem;
background: var(--second-btn-color-tran);
color: gray;
display: flex;
align-items: center;
}
.options {
flex: 1;
display: flex;
.option {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-size: 13rem;
color: white;
svg {
font-size: 24rem;
}
}
}
}
}
</style>

View File

@@ -1,8 +1,17 @@
<template>
<div id="video-detail">
<div class="search-wrapper">
<dy-back class="back" @click="$back"/>
<Search></Search>
<Icon class="back" icon="icon-park-outline:left" @click="$back"/>
<div class="search" @click="nav('/home/search')">
<div class="left">
<Icon class="icon" icon="ion:search"/>
<span>搜你想看的</span>
</div>
<div class="right">
<span class="gang">|</span>
<span class="txt">搜索</span>
</div>
</div>
</div>
<div class="content">
<SlideVerticalInfinite
@@ -19,116 +28,139 @@
/>
</div>
<div class="footer">
<div class="share-to-friend" v-if="!data.isMy">
<span>留下你的精彩评论吧</span>
<div class="share-btn" @click="data.dialog.shareToFriend = true">分享给朋友</div>
</div>
<div class="permission-setting" v-if="data.isMy">
<div class="right">
<img src="../../assets/img/icon/play-white.png" alt="">
<span>3030浏览</span>
<div class="comment">
<div class="left">
<img :src="_checkImgUrl(baseStore.userinfo.avatar_168x168.url_list[0])" class="avatar" alt=""/>
<span>善语结善缘恶言伤人心</span>
</div>
<div class="right">
<Icon icon="tabler:photo"/>
<Icon icon="ion:at-sharp"/>
<Icon icon="fa-regular:laugh"/>
</div>
<div class="share-btn" @click="data.dialog.permissionDialog = true">权限设置</div>
</div>
</div>
<from-bottom-dialog
page-id="video-detail"
v-model="data.dialog.shareToFriend"
height="50vh"
mode="light"
mask-mode="light"
<Comment page-id="video-detail"
:video-id="state.currentItem.aweme_id"
v-model="state.commentVisible"
@close="closeComments"
/>
<Share v-model="state.isSharing"
ref="share"
page-id="video-detail"
@dislike="dislike"
:item="state.currentItem"
:videoId="state.recommendList[state.itemIndex]?.id"
:canDownload="state.recommendList[state.itemIndex]?.canDownload"
@play-feedback="state.showPlayFeedback = true"
@shareToFriend="delayShowDialog(e => state.shareToFriend = true)"
@showDouyinCode="state.showDouyinCode = true"
@download="state.shareType = 9"
/>
<PlayFeedback v-model="state.showPlayFeedback"/>
<DouyinCode
:item="state.currentItem"
v-model="state.showDouyinCode"/>
<ShareTo v-model:type="state.shareType"
:videoId="state.recommendList[state.itemIndex]?.id"
:canDownload="state.recommendList[state.itemIndex]?.canDownload"
/>
<FollowSetting
v-model:currentItem="state.currentItem"
@showChangeNote="delayShowDialog( e => state.showChangeNote = true)"
@showBlockDialog="delayShowDialog(e => state.showBlockDialog = true)"
@showShare="delayShowDialog( e => state.isSharing = true)"
v-model="state.showFollowSetting"/>
<FollowSetting2
v-model:currentItem="state.currentItem"
@cancelFollow="$refs.uploader.cancelFollow()"
v-model="state.showFollowSetting2"/>
<BlockDialog v-model="state.showBlockDialog"/>
<ConfirmDialog
title="设置备注名"
ok-text="确认"
v-model:visible="state.showChangeNote"
>
<div class="share-dialog">
<div class="collection" @click="data.dialog.shareToFriend = false">
<img src="../../assets/img/icon/me/collection-black.png" alt="">
收藏
</div>
<div class="friends">
<div class="friend" v-for="i in 6">
<img src="../../assets/img/icon/head-image.jpeg" alt="">
<div class="right">
<span>三分钟情</span>
<div class="share-btn">分享</div>
</div>
</div>
<div class="friend">
<img src="../../assets/img/icon/head-image.jpeg" alt="">
<div class="right">
<span>更多好友</span>
</div>
</div>
</div>
</div>
</from-bottom-dialog>
<from-bottom-dialog
page-id="video-detail"
v-model="data.dialog.permissionDialog"
height="40vh"
mode="white"
mask-mode="white"
>
<div class="permission-dialog">
<div class="setting" @click="data.dialog.permissionDialog = false">
<img src="../../assets/img/icon/head-image.jpeg" alt="">
<div class="right">
<span>公开 所有人可见</span>
<img src="../../assets/img/icon/head-image.jpeg" alt="">
</div>
</div>
<div class="setting">
<img src="../../assets/img/icon/head-image.jpeg" alt="">
<div class="right">
<span>朋友 互关朋友可见</span>
<img src="../../assets/img/icon/head-image.jpeg" alt="">
</div>
</div>
<div class="setting">
<img src="../../assets/img/icon/head-image.jpeg" alt="">
<div class="right">
<span>私密 仅自己见</span>
<img src="../../assets/img/icon/head-image.jpeg" alt="">
</div>
</div>
<div class="setting" style="border-bottom: none;">
<img src="../../assets/img/icon/head-image.jpeg" alt="">
<div class="right">
<span>不给谁看</span>
<img src="../../assets/img/icon/head-image.jpeg" alt="">
</div>
</div>
<div class="space"></div>
<div class="setting pb4r">
<img src="../../assets/img/icon/head-image.jpeg" alt="">
<div class="right">
<span>更多好友</span>
</div>
</div>
</div>
</from-bottom-dialog>
<Search mode="light" v-model="state.test" :isShowSearchIcon="false"/>
</ConfirmDialog>
<ShareToFriend v-model="state.shareToFriend"/>
</div>
</template>
<script setup>
<script setup lang="jsx">
import Comment from "../../components/Comment.vue";
import Share from "../../components/Share.vue";
import {onMounted, onUnmounted, reactive} from "vue";
import bus, {EVENT_KEY} from "../../utils/bus";
import {useNav} from "@/utils/hooks/useNav";
import PlayFeedback from "@/pages/home/components/PlayFeedback.vue";
import ShareTo from "@/pages/home/components/ShareTo.vue";
import DouyinCode from "../../components/DouyinCode.vue";
import FollowSetting from "@/pages/home/components/FollowSetting.vue";
import BlockDialog from "../message/components/BlockDialog.vue";
import Search from "../../components/Search.vue";
import FromBottomDialog from "../../components/dialog/FromBottomDialog.vue";
import {onMounted, reactive} from "vue";
import ConfirmDialog from "../../components/dialog/ConfirmDialog.vue";
import FollowSetting2 from "@/pages/home/components/FollowSetting2.vue";
import ShareToFriend from "@/pages/home/components/ShareToFriend.vue";
import {DefaultUser} from "@/utils/const_var";
import {_checkImgUrl} from "@/utils";
import {useBaseStore} from "@/store/pinia";
import SlideVerticalInfinite from "@/components/slide/SlideVerticalInfinite.vue";
import {useSlideListItemRender} from "@/utils/hooks/useSlideListItemRender";
import {useRouter} from "vue-router";
defineOptions({
name: 'VideoDetail'
})
const store = useBaseStore()
const nav = useNav()
const router = useRouter()
const baseStore = useBaseStore()
const data = reactive({
dialog: {
shareToFriend: false,
permissionDialog: false,
test: false,
},
isMy: true
isMy: false
})
const state = reactive({
baseIndex: 1,
navIndex: 4,
test: '',
recommendList: [],
isSharing: false,
canMove: true,
shareType: -1,
showPlayFeedback: false,
showShareDuoshan: false,
showShareDialog: false,
showShare2WeChatZone: false,
showDouyinCode: false,
showFollowSetting: false,
showFollowSetting2: false,
showBlockDialog: false,
showChangeNote: false,
shareToFriend: false,
commentVisible: false,
fullScreen: false,
currentItem: {
author: DefaultUser,
isRequest: false,
aweme_list: [],
},
index: 0,
list: [],
uniqueId: 'uniqueId_2',
@@ -136,47 +168,137 @@ const state = reactive({
pageSize: 10,
pageNo: 0,
})
const render = useSlideListItemRender()
onMounted(() => {
console.log('s', store.routeData)
state.index = store.routeData.index
state.list = store.routeData.list
console.log('sss', state.list[state.index])
// console.log('s', store.routeData)
state.index = baseStore.routeData.index
state.list = baseStore.routeData.list
// console.log('sss', state.list[state.index])
})
function delayShowDialog(cb) {
setTimeout(cb, 400)
}
function setCurrentItem(item) {
// console.log('sss',item,state.baseIndex)
if (state.baseIndex !== 1) return
if (state.currentItem.author.uid !== item.author.uid) {
state.currentItem = {
...item,
isRequest: false,
aweme_list: [],
}
}
// console.log('item', item)
}
onMounted(() => {
bus.on(EVENT_KEY.ENTER_FULLSCREEN, (e) => state.fullScreen = true)
bus.on(EVENT_KEY.EXIT_FULLSCREEN, (e) => state.fullScreen = false)
bus.on(EVENT_KEY.OPEN_COMMENTS, (e) => {
bus.emit(EVENT_KEY.ENTER_FULLSCREEN)
state.commentVisible = true
})
bus.on(EVENT_KEY.CLOSE_COMMENTS, (e) => {
bus.emit(EVENT_KEY.EXIT_FULLSCREEN)
state.commentVisible = false
})
bus.on(EVENT_KEY.SHOW_SHARE, (e) => {
state.isSharing = true
})
bus.on(EVENT_KEY.NAV, ({path, query}) => nav(path, query))
bus.on(EVENT_KEY.GO_USERINFO, () => {
router.back()
})
bus.on(EVENT_KEY.CURRENT_ITEM, setCurrentItem)
})
onUnmounted(() => {
bus.offAll()
})
function closeComments() {
bus.emit(EVENT_KEY.CLOSE_COMMENTS)
}
function dislike() {
// listRef.value.dislike(state.list[1])
// state.list[state.index] = state.list[1]
// Utils.$notice('操作成功,将减少此类视频的推荐')
}
</script>
<style scoped lang="less">
@import "../../assets/less/index";
#video-detail {
position: fixed;
font-size: 14rem;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%;
width: 100%;
background: black;
.search-wrapper {
height: var(--common-header-height);
z-index: 9;
position: fixed;
top: 10rem;
left: 15rem;
right: 15rem;
top: 8rem;
left: 0;
width: 100vw;
padding: 0 15rem;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 15rem;
.back {
width: 20rem;
height: 20rem;
margin-right: 10rem;
color: white;
font-size: 30rem;
}
.search-ctn {
width: 100%;
.search {
color: var(--second-btn-color);
display: flex;
background: rgba(171, 169, 169, 0.4);
border-radius: 8rem;
flex: 1;
padding: 8rem;
justify-content: space-between;
.left {
font-size: 15rem;
display: flex;
align-items: center;
color: gainsboro;
gap: 5rem;
line-height: 1;
svg {
font-size: 14rem;
}
}
.right {
display: flex;
align-items: center;
gap: 10rem;
font-size: 16rem;
.gang {
color: dimgrey;
}
.txt {
color: white;
}
}
}
}
@@ -186,167 +308,45 @@ onMounted(() => {
.footer {
height: var(--footer-height);
}
.share-to-friend {
color: var(--second-text-color);
height: var(--footer-height);
z-index: 9;
position: fixed;
bottom: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.comment {
color: var(--second-text-color);
z-index: 9;
width: 95%;
height: 75%;
box-sizing: border-box;
padding: 0 20px;
padding: 0 10px;
display: flex;
justify-content: space-between;
align-items: center;
background: rgb(37, 37, 37);
border-radius: 50rem;
.share-btn {
color: darkgray;
padding: 6px 14px;
background: rgb(44, 44, 44);
border-radius: 50px;
.avatar {
height: 70%;
border-radius: 50%;
}
}
.permission-setting {
color: var(--second-text-color);
height: var(--footer-height);
z-index: 9;
position: fixed;
bottom: 0;
width: 100%;
box-sizing: border-box;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
.left {
height: 100%;
display: flex;
align-items: center;
gap: 10rem;
}
.right {
display: flex;
align-items: center;
font-size: 14rem;
img {
margin-right: 10px;
width: 15px;
height: 15px;
}
}
.share-btn {
color: darkgray;
padding: 6px 14px;
background: rgb(44, 44, 44);
border-radius: 50px;
}
}
.share-dialog {
.collection {
background: white;
margin: 0 10rem 10rem 10rem;
width: calc(100% - 20rem);
border-radius: 6px;
display: flex;
align-items: center;
font-size: 16rem;
font-weight: bold;
padding: 10px;
box-sizing: border-box;
img {
background: white;
width: 35px;
height: 35px;
margin-right: 10px;
}
}
.friends {
margin: 10rem 10rem 0 10rem;
width: calc(100% - 20rem);
background: white;
border-radius: 6px 6px 0 0;
.friend {
box-sizing: border-box;
padding: 10px;
width: 100%;
display: flex;
align-items: center;
//border-bottom: 1px solid ghostwhite;
border-bottom: 1px solid gainsboro;
img {
border-radius: 50%;
width: 40px;
height: 40px;
}
.right {
margin: 0 5px 0 15px;
font-size: 16rem;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.share-btn {
font-size: 14rem;
color: white;
padding: 5px 20px;
background: var(--primary-btn-color);
border-radius: 2px;
}
}
}
}
}
.permission-dialog {
.space {
height: 10rem;
background: whitesmoke;
}
.setting {
background: white;
box-sizing: border-box;
padding: 10rem 20rem;
width: 100%;
display: flex;
align-items: center;
//border-bottom: 1px solid ghostwhite;
border-bottom: 1px solid gainsboro;
img {
border-radius: 50%;
width: 30rem;
height: 30rem;
}
.right {
margin: 0 5rem 0 15rem;
font-size: 14rem;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.share-btn {
font-size: 14rem;
color: white;
padding: 5rem 20rem;
background: var(--primary-btn-color);
border-radius: 2rem;
}
}
.left;
gap: 15rem;
font-size: 24rem;
}
}
}
</style>

View File

@@ -0,0 +1,201 @@
<template>
<div id="video-detail">
<div class="search-wrapper">
<Icon class="back" icon="icon-park-outline:left" @click="$back"/>
<div class="search" @click="nav('/home/search')">
<div class="left">
<Icon class="icon" icon="ion:search"/>
<span>搜你想看的</span>
</div>
<div class="right">
<span class="gang">|</span>
<span class="txt">搜索</span>
</div>
</div>
</div>
<div class="content">
<SlideVerticalInfinite
ref="listRef"
v-love="state.uniqueId"
:id="state.uniqueId"
:uniqueId="state.uniqueId"
name="main"
:active="true"
:loading="false"
v-model:index="state.index"
:render="render"
:list="state.list"
/>
</div>
<div class="footer">
<div class="comment">
<div class="left">
<img :src="_checkImgUrl(store.userinfo.avatar_168x168.url_list[0])" class="avatar" alt=""/>
<span>善语结善缘恶言伤人心</span>
</div>
<div class="right">
<Icon icon="tabler:photo"/>
<Icon icon="ion:at-sharp"/>
<Icon icon="fa-regular:laugh"/>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {onMounted, reactive} from "vue";
import {useBaseStore} from "@/store/pinia";
import SlideVerticalInfinite from "@/components/slide/SlideVerticalInfinite.vue";
import {useSlideListItemRender} from "@/utils/hooks/useSlideListItemRender";
import {_checkImgUrl} from "@/utils";
import {useNav} from "@/utils/hooks/useNav";
defineOptions({
name: 'VideoDetail'
})
const nav = useNav()
const store = useBaseStore()
const data = reactive({
dialog: {
shareToFriend: false,
permissionDialog: false,
test: false,
},
isMy: false
})
const state = reactive({
index: 0,
list: [],
uniqueId: 'uniqueId_2',
totalSize: 0,
pageSize: 10,
pageNo: 0,
})
const render = useSlideListItemRender()
onMounted(() => {
// console.log('s', store.routeData)
state.index = store.routeData.index
state.list = store.routeData.list
// console.log('sss', state.list[state.index])
})
</script>
<style scoped lang="less">
@import "../../assets/less/index";
#video-detail {
position: fixed;
font-size: 14rem;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%;
width: 100%;
background: black;
.search-wrapper {
z-index: 9;
position: fixed;
top: 8rem;
left: 0;
width: 100vw;
padding: 0 15rem;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 15rem;
.back {
color: white;
font-size: 30rem;
}
.search {
color: var(--second-btn-color);
display: flex;
background: rgba(171, 169, 169, 0.4);
border-radius: 8rem;
flex: 1;
padding: 8rem;
justify-content: space-between;
.left {
font-size: 15rem;
display: flex;
align-items: center;
color: gainsboro;
gap: 5rem;
line-height: 1;
svg {
font-size: 14rem;
}
}
.right {
display: flex;
align-items: center;
gap: 10rem;
font-size: 16rem;
.gang {
color: dimgrey;
}
.txt {
color: white;
}
}
}
}
.content {
height: calc(var(--vh, 1vh) * 100 - var(--footer-height));
}
.footer {
height: var(--footer-height);
position: fixed;
bottom: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.comment {
color: var(--second-text-color);
z-index: 9;
width: 95%;
height: 75%;
box-sizing: border-box;
padding: 0 10px;
display: flex;
justify-content: space-between;
align-items: center;
background: rgb(37, 37, 37);
border-radius: 50rem;
.avatar {
height: 70%;
border-radius: 50%;
}
.left {
height: 100%;
display: flex;
align-items: center;
gap: 10rem;
}
.right {
.left;
gap: 15rem;
font-size: 24rem;
}
}
}
</style>

View File

@@ -56,7 +56,7 @@
<div class="slide-imgs">
<SlideHorizontal v-model:index="state.index">
<SlideItem v-for="item in state.detail.imgs">
<img :src="item" alt="">
<img v-lazy="_checkImgUrl('goods/'+item)" alt=""/>
</SlideItem>
</SlideHorizontal>
<div class="index">{{ state.index + 1 }}/{{ state.detail.imgs.length }}</div>
@@ -66,20 +66,20 @@
<div class="price-wrap">
<div class="price">
<span class="symbol"></span>
<span class="int">8</span>
<span class="decimal">.8</span>
<span class="int">{{ state.detail.price }}</span>
<!-- <span class="decimal">.8</span>-->
</div>
<div class="discount">
<span class="text">热销款券后</span>
<div class="price">
<span class="symbol"></span>
<span class="int">5</span>
<span class="decimal">.9</span>
<span class="int">{{ state.detail.real_price }}</span>
<!-- <span class="decimal">.9</span>-->
</div>
</div>
</div>
<div class="name">{{ state.detail.name }}</div>
<div class="num">已售20/100</div>
<div class="num">已售{{ state.detail.sold }}</div>
</div>
<div class="card desc-wrapper">
@@ -145,9 +145,9 @@
<div class="tag">推荐 <span class="gray">18</span></div>
<div class="tag">商用服务好 <span class="gray">15</span></div>
</div>
<div class="comment" v-for="i in 2">
<div class="comment">
<header>
<img src="https://cdn.seovx.com/?mom=302" alt="" class="avatar">
<img :src="_checkImgUrl('2S9bbgb-Sf2kIdSTxoeTw.png')" alt="" class="avatar">
<span class="gray">***</span>
</header>
<div class="w">
@@ -159,14 +159,31 @@
china款/超值买る双+送2双共5双
</div>
</div>
<img src="https://cdn.seovx.com/?mom=302" alt="" class="avatar">
<img :src="_checkImgUrl('NYEfuYS-LVZ620UYgQNAM.png')" alt="" class="avatar">
</div>
</div>
<div class="comment">
<header>
<img :src="_checkImgUrl('9Tx6cZkUOoHqPkbETUZ5Y.png')" alt="" class="avatar">
<span class="gray">***</span>
</header>
<div class="w">
<div class="left">
<div class="d">
东西不错质量也很好 性价比很高 良心商家就冲这图必须给好评
</div>
<div class="c2">
china款/超值买る双+送2双共5双
</div>
</div>
<img :src="_checkImgUrl('2b2rpive_RVzDrYgo-F9K.png')" alt="" class="avatar">
</div>
</div>
</div>
<div class="card shop">
<header>
<img src="https://cdn.seovx.com/?mom=302" alt="" class="avatar">
<img :src="_checkImgUrl('LJ-8p2jF3HydBD5j28PgQ.png')" alt="" class="avatar">
<div class="right">
<div class="l">
<div class="name">店铺名</div>
@@ -207,9 +224,36 @@
</div>
</header>
<div class="wrap">
<div class="item" v-for="i in 4">
<img src="https://cdn.seovx.com/?mom=302" alt="" class="avatar">
<div class="name">热销中袜子男潮流百搭中筒袜子袜子男潮流百搭中筒袜子</div>
<div class="item">
<img :src="_checkImgUrl('/goods/g6-0.jpg')" alt="" class="avatar">
<div class="name">小米电视6 65" OLED 65英寸</div>
<div class="price">
<span class="symbol">¥</span>
<span class="int">8</span>
<span class="decimal">.8</span>
</div>
</div>
<div class="item">
<img :src="_checkImgUrl('/goods/g1-0.jpg')" alt="" class="avatar">
<div class="name">红白撞色条纹软糯针织上衣女2022年秋季新款甜美减龄短款毛衣开衫</div>
<div class="price">
<span class="symbol">¥</span>
<span class="int">8</span>
<span class="decimal">.8</span>
</div>
</div>
<div class="item">
<img :src="_checkImgUrl('/goods/g2-0.webp')" alt="" class="avatar">
<div class="name">森马t恤男2023男士纯棉上衣白色情侣装凉感短袖打底衫纯色体恤潮</div>
<div class="price">
<span class="symbol">¥</span>
<span class="int">8</span>
<span class="decimal">.8</span>
</div>
</div>
<div class="item">
<img :src="_checkImgUrl('/goods/g3-0.jpg')" alt="" class="avatar">
<div class="name">ins潮牌长袖t恤男宽松纯色内搭上衣潮牌百搭秋冬季潮流帅气打底衫</div>
<div class="price">
<span class="symbol">¥</span>
<span class="int">8</span>
@@ -221,7 +265,7 @@
</div>
</div>
<div class="img-list" v-if="true">
<div class="img-list">
<header>
<div class="l"></div>
<span class="gray">商品详情</span>
@@ -229,7 +273,7 @@
</header>
<div class="imgs">
<img v-lazy="`https://cdn.seovx.com/?mom=302&d=${i}`" alt="" class="avatar" v-for="i in 5">
<img v-lazy="_checkImgUrl('goods/'+i)" alt="" class="avatar" v-for="i in state.detail.imgs">
</div>
</div>
@@ -254,34 +298,39 @@
你可以还会喜欢
</header>
<div v-masonry class="goods-list"
:class="{fixed:state.fixed}"
transition-duration="0s"
item-selector=".goods">
<div v-masonry-tile class="goods"
@click="nav('/shop/detail')"
v-for="(item, index) in state.list">
<div class="item">
<img class="poster" v-lazy="Utils.$imgPreview(item.cover)"/>
<div class="bottom">
<div class="desc">
{{ item.name }}
</div>
<div class="discounts" v-if="item.discount">{{ item.discount }}</div>
<div class="info">
<div class="price">
<div class="big">{{ item.price }}</div>
<ScrollList
class="Scroll"
:api="recommendedShop"
>
<template v-slot="{list}">
<WaterfallList :list="list">
<template v-slot="{item}">
<div class="goods"
@click="nav('/shop/detail',{},item)">
<div class="item">
<img class="poster" v-lazy="_checkImgUrl('goods/'+item.cover)"/>
<div class="bottom">
<div class="desc">
{{ item.name }}
</div>
<div class="discounts" v-if="item.discount">{{ item.discount }}</div>
<div class="info">
<div class="price">
<div class="big">{{ item.price }}</div>
</div>
<div class="num">已售{{ item.sold }}件</div>
</div>
<div class="low" v-if="item.isLowPrice">
近30天低价
</div>
</div>
</div>
<div class="num">已售{{ item.sold }}</div>
</div>
<div class="low" v-if="item.isLowPrice">
近30天低价
</div>
</div>
</div>
</div>
</div>
</template>
</WaterfallList>
</template>
</ScrollList>
</div>
</div>
@@ -312,19 +361,21 @@
<script setup>
import SlideHorizontal from "@/components/slide/SlideHorizontal.vue";
import SlideItem from "@/components/slide/SlideItem.vue";
import {reactive, ref} from "vue";
import goods from "@/assets/data/goods";
import {onMounted, onUnmounted, reactive, ref} from "vue";
import {useNav} from "@/utils/hooks/useNav";
import Utils from "@/utils";
import {_checkImgUrl} from "@/utils";
import {useBaseStore} from "@/store/pinia";
import {recommendedShop} from "@/api/user";
import WaterfallList from "@/components/WaterfallList.vue";
import ScrollList from "@/components/ScrollList.vue";
defineOptions({
name: 'GoodsDetail'
})
let activeIndexs = ref([])
const nav = useNav()
const props = defineProps({
id: {
type: String,
default: () => ''
}
})
const store = useBaseStore()
let page = ref()
let header = ref()
let headerShadow = ref()
@@ -341,9 +392,10 @@ function scroll() {
}
const state = reactive({
detail: goods.list[1],
index: 2,
list: goods.list,
detail: {
imgs: []
},
index: 0,
listEl: null,
fixed: false
})
@@ -356,6 +408,15 @@ function toggle(i) {
activeIndexs.value.push(i)
}
}
onMounted(() => {
console.log('r', store.routeData.imgs)
state.detail = store.routeData
})
onUnmounted(() => {
console.log('onUnmounted')
})
</script>
<style scoped lang="less">
@@ -946,7 +1007,6 @@ function toggle(i) {
& > header {
padding: 15rem;
padding-bottom: 5rem;
font-weight: 900;
font-size: 15rem;
}
@@ -959,14 +1019,13 @@ function toggle(i) {
@p: 5rem;
.goods-list {
padding-bottom: 20rem;
.Scroll {
padding: 5rem;
}
.goods {
width: 50%;
box-sizing: border-box;
padding: 5rem;
margin-bottom: 10rem;
.item {
border-radius: 8rem;

View File

@@ -13,106 +13,105 @@
</div>
</div>
</div>
<Scroll class="Scroll"
fixedHeight="100"
@fixed="e=>state.fixed = e"
@pulldown="loadData">
<div class="wrapper">
<div class="card">
<div class="options">
<div class="option" @click="$no">
<Icon icon="lets-icons:order-light"/>
<div>我的订单</div>
</div>
<div class="option" @click="$no">
<Icon icon="material-symbols-light:charging-station-outline"/>
<div>手机充值</div>
</div>
<div class="option" @click="$no">
<Icon icon="lucide:message-square-quote"/>
<div>购物消息</div>
</div>
<div class="option" @click="$no">
<Icon icon="fluent:location-16-regular"/>
<div>小时达</div>
</div>
<div class="option" @click="$no">
<Icon icon="ri:refund-2-fill"/>
<div>退款/售后</div>
</div>
<div class="option" @click="$no">
<Icon icon="icon-park-outline:clothes-turtleneck"/>
<div>潮流服饰</div>
<ScrollList
class="Scroll"
:api="recommendedShop"
>
<template v-slot="{list}">
<div class="top-card">
<div class="card">
<div class="options">
<div class="option" @click="$no">
<Icon icon="lets-icons:order-light"/>
<div>我的订单</div>
</div>
<div class="option" @click="$no">
<Icon icon="material-symbols-light:charging-station-outline"/>
<div>手机充值</div>
</div>
<div class="option" @click="$no">
<Icon icon="system-uicons:message" />
<div>购物消息</div>
</div>
<div class="option" @click="$no">
<Icon icon="fluent:location-16-regular"/>
<div>小时达</div>
</div>
<div class="option" @click="$no">
<Icon icon="dashicons:money-alt" />
<div>退款/售后</div>
</div>
<div class="option" @click="$no">
<Icon icon="icon-park-outline:clothes-turtleneck"/>
<div>潮流服饰</div>
</div>
</div>
</div>
</div>
<div class="card" style="margin-bottom: 5rem;">
<div class="baiyibutie">
<div class="item">
<img src="@/assets/img/icon/shop/baiyibutie.png" alt="">
<span>38节补贴</span>
</div>
<div class="item">
<img src="@/assets/img/icon/shop/1.webp" alt="">
<span class="price">
<div class="card" style="margin-bottom: 5rem;">
<div class="baiyibutie">
<div class="item">
<img src="@/assets/img/icon/shop/baiyibutie.png" alt="">
<span>38节补贴</span>
</div>
<div class="item">
<img src="@/assets/img/icon/shop/1.webp" alt="">
<span class="price">
<span class="m"></span>
<span>470</span>
</span>
</div>
<div class="item">
<img src="@/assets/img/icon/shop/2.webp" alt="">
<span class="price">
</div>
<div class="item">
<img src="@/assets/img/icon/shop/2.webp" alt="">
<span class="price">
<span class="m"></span>
<span>699</span>
</span>
</div>
<div class="item">
<img src="@/assets/img/icon/shop/3.png" alt="">
<span class="price">
</div>
<div class="item">
<img src="@/assets/img/icon/shop/3.png" alt="">
<span class="price">
<span class="m"></span>
<span>8.8</span>
</span>
</div>
<div class="item">
<img src="@/assets/img/icon/shop/4.jpg" alt="">
<span class="price">
</div>
<div class="item">
<img src="@/assets/img/icon/shop/4.jpg" alt="">
<span class="price">
<span class="m"></span>
<span>2.99</span>
</span>
</div>
</div>
</div>
</div>
</div>
<div v-masonry class="goods-list"
:class="{fixed:state.fixed}"
transition-duration="0s"
item-selector=".goods">
<div v-masonry-tile class="goods"
@click="nav('/shop/detail')"
v-for="(item, index) in state.list">
<div class="item">
<img class="poster" :src="Utils.$imgPreview(item.cover)"/>
<div class="bottom">
<div class="desc">
{{ item.name }}
</div>
<div class="discounts" v-if="item.discount">{{ item.discount }}</div>
<div class="info">
<div class="price">
<div class="big">{{ item.price }}</div>
<WaterfallList :list="list">
<template v-slot="{item}">
<div class="goods"
@click="nav('/shop/detail',{},item)">
<div class="item">
<img class="poster" v-lazy="_checkImgUrl('goods/'+item.cover)"/>
<div class="bottom">
<div class="desc">
{{ item.name }}
</div>
<div class="discounts" v-if="item.discount">{{ item.discount }}</div>
<div class="info">
<div class="price">
<div class="big">{{ item.price }}</div>
</div>
<div class="num">已售{{ item.sold }}</div>
</div>
<div class="low" v-if="item.isLowPrice">
近30天低价
</div>
</div>
<div class="num">已售{{ item.sold }}</div>
</div>
<div class="low" v-if="item.isLowPrice">
近30天低价
</div>
</div>
</div>
</div>
</div>
</Scroll>
</template>
</WaterfallList>
</template>
</ScrollList>
<Footer v-bind:init-tab="2"
:is-white="true"
style="position: fixed;left: 0;"/>
@@ -124,9 +123,10 @@
import {onMounted, reactive} from "vue";
import {useNav} from "@/utils/hooks/useNav";
import Utils, {$no} from "@/utils";
import Scroll from "@/components/Scroll.vue";
import goods from "@/assets/data/goods";
import {$no, _checkImgUrl} from "@/utils";
import ScrollList from "@/components/ScrollList.vue";
import {recommendedShop} from "@/api/user";
import WaterfallList from "@/components/WaterfallList.vue";
defineOptions({
name: 'Shop'
@@ -134,15 +134,10 @@ defineOptions({
const nav = useNav()
const state = reactive({
list: goods.list,
listEl: null,
fixed: false
})
function loadData() {
console.log('loadData')
}
onMounted(() => {
})
</script>
@@ -155,13 +150,11 @@ onMounted(() => {
#Shop {
font-size: 14rem;
position: relative;
background: @fColor;
background: #f1f1f1;
padding: 10rem 5rem;
background: #f8f8f8;
padding: 10rem;
color: black;
.wrapper {
padding: 0 5rem;
}
.search {
@@ -174,12 +167,13 @@ onMounted(() => {
margin-bottom: 10rem;
svg {
font-size: 22rem;
color: gray;
font-size: 20rem;
}
.search-input {
border: 1rem solid rgb(140, 48, 74);
border-radius: 8rem;
border: 2rem solid red;
border-radius: 12rem;
height: 100%;
padding: 0 10rem;
padding-right: 3rem;
@@ -200,7 +194,7 @@ onMounted(() => {
background: rgb(242, 62, 92);
padding: 0 10rem;
height: 85%;
border-radius: 6rem;
border-radius: 10rem;
display: flex;
justify-content: center;
align-items: center;
@@ -284,16 +278,15 @@ onMounted(() => {
background: @fColor;
}
@p: 5rem;
.goods-list {
padding-bottom: 20rem;
.top-card {
margin-bottom: 10rem;
}
@p: 5rem;
.goods {
width: 50%;
box-sizing: border-box;
padding: 5rem;
margin-bottom: 10rem;
.item {
border-radius: 8rem;

View File

@@ -1,7 +1,6 @@
import Home from "../pages/home";
import Test from "../pages/test/Test";
import Test4 from "../pages/test/Test4";
import GoodsDetail from "@/pages/shop/GoodsDetail.vue";
const routes = [
// {path: '/', redirect: '/attention'},
@@ -93,7 +92,7 @@ const routes = [
{path: '/message/share-to-friend', component: () => import('@/pages/message/Share2Friend.vue')},
{path: '/video-detail', name: 'video-detail', component: () => import('@/pages/other/VideoDetail.vue')},
{path: '/album-detail', component: () => import('@/pages/other/AlbumDetail.vue')},
// {path: '/album-detail', component: () => import('@/pages/other/AlbumDetail.vue')},
{path: '/home/search', component: () => import('@/pages/home/SearchPage.vue')},