Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bf2dfedf2 | ||
|
|
bbbb9ba222 | ||
|
|
2de950fd12 | ||
|
|
f99ec38f14 | ||
|
|
9d8758eb31 | ||
|
|
2a519248fd | ||
|
|
a1192fe7e3 | ||
|
|
e98aac5244 | ||
|
|
8e23ec740b | ||
|
|
1128b7822d | ||
|
|
4dffcab92b | ||
|
|
3dade9855d | ||
|
|
26fc5c0781 | ||
|
|
efd7569e79 | ||
|
|
1b2a5890ae | ||
|
|
19aab02c45 | ||
|
|
722d31f414 | ||
|
|
578ecadca1 | ||
|
|
12ca067d15 | ||
|
|
26d1220508 | ||
|
|
5f37d17715 | ||
|
|
4f4dd5b409 | ||
|
|
fae5fc5979 | ||
|
|
0c73eadb45 | ||
|
|
914a1887a8 | ||
|
|
700802d96f | ||
|
|
54b9b6bf70 | ||
|
|
0c8437e669 | ||
|
|
4de26471e2 | ||
|
|
d41f49701a | ||
|
|
24b6b4076d | ||
|
|
9da5f780db | ||
|
|
6a6554aa9b | ||
|
|
596d8897dc | ||
|
|
bbc350e729 | ||
|
|
e4a4c5b480 | ||
|
|
3e559914a0 | ||
|
|
e4d9f0cbb0 | ||
|
|
4f80219a5e | ||
|
|
10494fcfe2 | ||
|
|
9b7d8c5568 | ||
|
|
7d10bdbd32 | ||
|
|
e98dfd733d | ||
|
|
8d06e9f1b6 | ||
|
|
9992926c1c | ||
|
|
6605dd7db0 | ||
|
|
54f71af752 | ||
|
|
ed2e8e478b | ||
|
|
fbb742e3d5 | ||
|
|
73f4283d6d | ||
|
|
cd8db0ce26 | ||
|
|
e974f38458 | ||
|
|
9e5a389906 | ||
|
|
8aedae15a5 | ||
|
|
1b82072f17 | ||
|
|
e8d0b46f5e | ||
|
|
79d9d613fc | ||
|
|
7c5bdc9883 | ||
|
|
1b6001f6f6 | ||
|
|
19105b676e | ||
|
|
45792655ae | ||
|
|
c45c7a7c13 | ||
|
|
51cfbd090b | ||
|
|
2d7d325343 | ||
|
|
bf591f8d91 | ||
|
|
2466511de7 | ||
|
|
be9d53813b | ||
|
|
aa1ded21ed | ||
|
|
a7665f4a23 | ||
|
|
982fdc8727 | ||
|
|
bdb5651a65 | ||
|
|
b18664a3a6 | ||
|
|
6107b19d03 | ||
|
|
15b89986b2 | ||
|
|
942ae76a0d | ||
|
|
02f8ed34cd | ||
|
|
573f3df63e | ||
|
|
1993c4d087 | ||
|
|
45e974218b | ||
|
|
7189f107ef | ||
|
|
bf0160b3bc | ||
|
|
85a5fefdf2 | ||
|
|
325057d197 | ||
|
|
934796b15e | ||
|
|
5007e88093 | ||
|
|
c64f5fad89 | ||
|
|
c1642befab | ||
|
|
09a97cbf5d | ||
|
|
d2d6a68eae | ||
|
|
c63c7e2969 | ||
|
|
db6a7b9aaf | ||
|
|
6976f58757 | ||
|
|
b8d8354c2d | ||
|
|
1e0b955462 | ||
|
|
9a846ee144 | ||
|
|
9e61d073bf | ||
|
|
97bcdd4371 | ||
|
|
f81c8300e1 | ||
|
|
70abd546a7 | ||
|
|
4eeab2e9ee | ||
|
|
7775042f67 | ||
|
|
2906e7e925 | ||
|
|
c4313f4b20 | ||
|
|
5e84f3ef5a | ||
|
|
8218d75d35 | ||
|
|
fde4597419 | ||
|
|
e14cd94fd9 | ||
|
|
5ac7c46840 |
25
.dockerignore
Normal file
@@ -0,0 +1,25 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
.DS_Store
|
||||
coverage
|
||||
*.local
|
||||
|
||||
dist
|
||||
node_modules
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
docs
|
||||
node
|
||||
72
.github/workflows/deploy-gitee-pages.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
# 将静态内容部署到 GitHub Pages 的简易工作流程
|
||||
name: Deploy Gitee Pages
|
||||
|
||||
on:
|
||||
# 仅在推送到默认分支时运行。
|
||||
push:
|
||||
branches: [ 'master' ]
|
||||
|
||||
# 这个选项可以使你手动在 Action tab 页面触发工作流
|
||||
workflow_dispatch:
|
||||
|
||||
# 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages。
|
||||
permissions:
|
||||
contents: write
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
|
||||
jobs:
|
||||
# 单次部署的工作描述
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build-gitee-pages
|
||||
|
||||
- name: Deploy to Gitee Pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./dist
|
||||
publish_branch: gitee-pages
|
||||
|
||||
- name: Sync to Gitee
|
||||
uses: wearerequired/git-mirror-action@master
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.GITEE_PRIVATE_KEY }}
|
||||
with:
|
||||
source-repo: git@github.com:zyronon/douyin.git
|
||||
destination-repo: git@gitee.com:zyronon/douyin.git
|
||||
|
||||
- name: Build Gitee Pages
|
||||
uses: yanglbme/gitee-pages-action@main
|
||||
with:
|
||||
# 注意替换为你的 Gitee 用户名
|
||||
gitee-username: zyronon
|
||||
# 注意在 Settings->Secrets 配置 GITEE_PASSWORD
|
||||
gitee-password: ${{ secrets.GITEE_PASSWORD }}
|
||||
# 注意替换为你的 Gitee 仓库,仓库名严格区分大小写,请准确填写,否则会出错
|
||||
gitee-repo: zyronon/douyin
|
||||
# 要部署的分支,默认是 master,若是其他分支,则需要指定(指定的分支必须存在)
|
||||
branch: gitee-pages
|
||||
@@ -1,5 +1,5 @@
|
||||
# 将静态内容部署到 GitHub Pages 的简易工作流程
|
||||
name: Deploy static content to Pages
|
||||
name: Deploy Github Pages
|
||||
|
||||
on:
|
||||
# 仅在推送到默认分支时运行。
|
||||
@@ -15,11 +15,6 @@ permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# 允许一个并发的部署
|
||||
concurrency:
|
||||
group: 'pages'
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# 单次部署的工作描述
|
||||
deploy:
|
||||
@@ -46,18 +41,17 @@ jobs:
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
run: pnpm run build-gp-pages
|
||||
|
||||
- name: Deploy to gh-pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
deploy_key: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./dist
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
|
||||
- name: Sync to Gitee
|
||||
uses: wearerequired/git-mirror-action@master
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.GITEE_PRIVATE_KEY }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
source-repo: git@github.com:zyronon/douyin.git
|
||||
destination-repo: git@gitee.com:zyronon/douyin.git
|
||||
# Upload dist repository
|
||||
path: './dist'
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
41
.github/workflows/docker-image-ci.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
# 这个选项可以使你手动在 Action tab 页面触发工作流
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: get version
|
||||
id: vars
|
||||
run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\/v/}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: set up docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: login ghrc hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
- name: build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/douyin:${{ steps.vars.outputs.version }}
|
||||
ghcr.io/${{ github.repository_owner }}/douyin:latest
|
||||
42
.github/workflows/docker-image.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Docker Image CI2
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
tags:
|
||||
- v*
|
||||
|
||||
# 这个选项可以使你手动在 Action tab 页面触发工作流
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: get version
|
||||
id: vars
|
||||
run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\/v/}
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: set up docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: login ghrc hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
- name: build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/douyin:${{ steps.vars.outputs.version }}
|
||||
ghcr.io/${{ github.repository_owner }}/douyin:latest
|
||||
72
.github/workflows/readme.yml
vendored
@@ -1,36 +1,36 @@
|
||||
#name: Translate README
|
||||
#
|
||||
#on:
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
#jobs:
|
||||
# build:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - name: Setup Node.js
|
||||
# uses: actions/setup-node@v1
|
||||
# with:
|
||||
# node-version: 12.x
|
||||
# # ISO Langusge Codes: https://cloud.google.com/translate/docs/languages
|
||||
# - name: Adding README - English
|
||||
# uses: dephraiim/translate-readme@main
|
||||
# with:
|
||||
# LANG: en
|
||||
# - name: Adding README - Japanese
|
||||
# uses: dephraiim/translate-readme@main
|
||||
# with:
|
||||
# LANG: ja
|
||||
# - name: Adding README - German
|
||||
# uses: dephraiim/translate-readme@main
|
||||
# with:
|
||||
# LANG: de
|
||||
# - name: Adding README - French
|
||||
# uses: dephraiim/translate-readme@main
|
||||
# with:
|
||||
# LANG: fr
|
||||
# - name: Adding README - Spanish
|
||||
# uses: dephraiim/translate-readme@main
|
||||
# with:
|
||||
# LANG: es
|
||||
name: Translate README
|
||||
|
||||
on:
|
||||
# 这个选项可以使你手动在 Action tab 页面触发工作流
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
# ISO Langusge Codes: https://cloud.google.com/translate/docs/languages
|
||||
- name: Adding README - English
|
||||
uses: dephraiim/translate-readme@main
|
||||
with:
|
||||
LANG: en
|
||||
- name: Adding README - Japanese
|
||||
uses: dephraiim/translate-readme@main
|
||||
with:
|
||||
LANG: ja
|
||||
- name: Adding README - German
|
||||
uses: dephraiim/translate-readme@main
|
||||
with:
|
||||
LANG: de
|
||||
- name: Adding README - French
|
||||
uses: dephraiim/translate-readme@main
|
||||
with:
|
||||
LANG: fr
|
||||
- name: Adding README - Spanish
|
||||
uses: dephraiim/translate-readme@main
|
||||
with:
|
||||
LANG: es
|
||||
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
# syntax = docker/dockerfile:experimental
|
||||
FROM --platform=${BUILDPLATFORM:-linux/amd64,linux/arm64} node:20-buster AS builder
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# RUN两次方便观察install和build, 也可以用pnpm cache and locked
|
||||
RUN pnpm install
|
||||
RUN npm run build
|
||||
|
||||
FROM --platform=${BUILDPLATFORM:-linux/amd64,linux/arm64} ghcr.io/rookie-luochao/nginx-runner:latest
|
||||
|
||||
COPY --from=builder /app/dist .
|
||||
16
Makefile
Normal file
@@ -0,0 +1,16 @@
|
||||
PKG = $(shell cat package.json | grep 'name' | sed -e 's/ "name": "//g' -e 's/",//g')
|
||||
VERSION = $(shell cat package.json | grep 'version' | sed -e 's/ "version": "//g' -e 's/",//g')
|
||||
|
||||
# 本地测试构建docker镜像建议删掉node_modules,node_modules存在有时会导致报错
|
||||
docker-build:
|
||||
docker build . -t $(PKG):$(VERSION)
|
||||
|
||||
# 请先创建自己的 buildx driver 实例,请看:https://juejin.cn/post/7296763284647542838
|
||||
# 显式指定可执行 docker buildx build --platform linux/amd64,linux/arm64 . -t $(PKG):$(VERSION) --push
|
||||
docker-buildx-build:
|
||||
docker buildx build --platform linux/amd64 . -t $(PKG):$(VERSION) --load
|
||||
|
||||
docker-run:
|
||||
docker run -d -p 80:80 $(PKG):$(VERSION)
|
||||
|
||||
docker-build-run: docker-build docker-run
|
||||
114
README.en.md
@@ -1,114 +0,0 @@
|
||||
<h1 align="center">
|
||||
Douyin-Vue
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.en.md">English</a> | <a href="README.es.md">Spanish</a> | <a href="README.de.md">German</a> |
|
||||
<a href="README.fr.md">French</a> | <a href="README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/zyronon/douyin/blob/master/LICENSE"><img src="https://img.shields.io/github/license/zyronon/douyin" alt="License"></a>
|
||||
<a><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"/></a>
|
||||
<a><img src="https://img.shields.io/badge/Powered%20by-Vue-blue"/></a>
|
||||
</p>
|
||||
|
||||
`douyin-vue`is a parody`抖音|TikTok`mobile short video project, which is based on[`Vue`](https://cn.vuejs.org/)、[`Vite`](https://cn.vitejs.dev/)accomplish. Used the latest`Vue`FamilyMart technology stack. Api data is saved locally in the project, and the video is collected from`抖音|TikTok`, the atlas is collected from`小红书|Xiaohongshu`,pass[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)The library intercepts the API and returns local json data to simulate real back-end requests.
|
||||
|
||||
<div>
|
||||
<img width="150px" src='docs/imgs/1.gif' />
|
||||
<img width="150px" src='docs/imgs/2.gif' />
|
||||
<img width="150px" src='docs/imgs/3.gif' />
|
||||
<img width="150px" src='docs/imgs/4.gif' />
|
||||
<img width="150px" src='docs/imgs/5.gif' />
|
||||
<img width="150px" src='docs/imgs/img-1.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-2.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-3.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-4.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-5.jpg' />
|
||||
</div>
|
||||
|
||||
## online access
|
||||
|
||||
Github pages:<https://zyronon.github.io/douyin/>
|
||||
|
||||
[//]: # "Vercel: [http://dy.ttentau.top/](http://dy.ttentau.top/) (中国推荐访问这个)~~"
|
||||
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/) (中国以外地区推荐访问这个)~~"
|
||||
|
||||
The 100G free data provided by Vercel and Netlify have been used up...🤣
|
||||
|
||||
Android Apk:<https://github.com/zyronon/douyin/releases>
|
||||
|
||||
**Notice**:`PC`You must switch the browser to mobile mode, first press`F12`To bring up the console, press`Ctrl+Shift+M`to preview normally
|
||||
|
||||
**Notice**:Please use mobile phone[via browser](https://viayoo.com/zh-cn/)or Chrome browser preview. Other browsers may force the video to full screen, causing it to not display properly.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This project is for study and research only, not for commercial use
|
||||
|
||||
## run
|
||||
|
||||
### Quickly deploy to Vercel
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/zyronon/douyin)
|
||||
|
||||
### local development
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zyronon/douyin.git
|
||||
cd douyin
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open your browser and visit:<http://127.0.0.1:3000>
|
||||
|
||||
**Note: PC must switch the browser to mobile mode, first press`F12`To bring up the console, press`Ctrl+Shift+M`to preview normally**
|
||||
|
||||
## Link
|
||||
|
||||
- `开源中国`:<https://mp.weixin.qq.com/s/TWowmZpU_ojE5G2KzXuU8g>
|
||||
- `V2EX`:<https://www.v2ex.com/t/1028678>
|
||||
- `掘金`:<https://juejin.cn/post/7352813352051687458>
|
||||
|
||||
## Data Sources
|
||||
|
||||
The video comes from the following Douyin celebrities
|
||||
|
||||
- `我是香秀 🐂🍺`:<https://v.douyin.com/iYRAPA2L/>
|
||||
- `杨老虎 🐯(磕穿下巴掉牙版)`:<https://v.douyin.com/iYRA56de/>
|
||||
- `条子`:<https://v.douyin.com/iYRAaqjr/>
|
||||
- `达莎 Digi`:<https://v.douyin.com/iYRA6rwT/>
|
||||
- `小橙子`:<https://v.douyin.com/iYRAnudw/>
|
||||
- `南恬`:<https://v.douyin.com/iYRAbKm3/>
|
||||
- `小霸宠牛排 🥩`:<https://v.douyin.com/iYRSosVB/>
|
||||
- `奶茶妹 ◕🌱`:<https://v.douyin.com/iYRACKhP/>
|
||||
- `我才是岚岚`:<https://v.douyin.com/iYRAQM1C/>
|
||||
- `周憬艺 ziran`:<https://v.douyin.com/iYRAQs4h/>
|
||||
- `刘思瑶 nice`:<https://v.douyin.com/iYRAaERn/>
|
||||
- `彭十六 elf`:<https://v.douyin.com/iYRAHrVG/>
|
||||
- `李子柒`:<https://v.douyin.com/iYRA5B88/>
|
||||
|
||||
Picture from Xiaohongshu public notes
|
||||
|
||||
The above content is all public information on the Internet
|
||||
|
||||
## Features and suggestions
|
||||
|
||||
The project is currently in the early stages of development and new features are being added continuously. If you have any features or suggestions for the software, please feel free to contact us.`Issues`raised in
|
||||
If you also like the design ideas of this software, please submit it`PR`, thank you very much for your support!
|
||||
|
||||
## contact me
|
||||
|
||||
You can contact my email<a href="mailto:zyronon@163.com">zyronon@163.com</a>
|
||||
|
||||
> Share my other open source projects:
|
||||
>
|
||||
> _[**Typing Word**- Vocabulary memorization software that can be used on the web~](https://github.com/zyronon/typing-word)<img src="https://img.shields.io/github/stars/zyronon/typing-word.svg?style=flat-square&label=Star&color=4285dd&logo=github" height="16px" />_
|
||||
> _[**Web Scripts**- Some useful Grease Monkey scripts~](https://github.com/zyronon/web-scripts)<img src="https://img.shields.io/github/stars/zyronon/web-scripts.svg?style=flat-square&label=Star&color=4285dd&logo=github" height="16px" />_
|
||||
|
||||
## agreement
|
||||
|
||||
[GPL](LICENSE)
|
||||
58
README.md
@@ -3,8 +3,8 @@
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.en.md">English</a> | <a href="README.es.md">Spanish</a> | <a href="README.de.md">German</a> |
|
||||
<a href="README.fr.md">French</a> | <a href="README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
<a href="docs/README.en.md">English</a> | <a href="docs/README.es.md">Spanish</a> | <a href="docs/README.de.md">German</a> |
|
||||
<a href="docs/README.fr.md">French</a> | <a href="README.md">简体中文</a> | <a href="docs/README.ja.md">日本語</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -13,8 +13,9 @@
|
||||
<a><img src="https://img.shields.io/badge/Powered%20by-Vue-blue"/></a>
|
||||
</p>
|
||||
|
||||
`douyin-vue` 是一个模仿 `抖音|TikTok` 的移动端短视频项目,它基于 [`Vue`](https://cn.vuejs.org/)、[`Vite`](https://cn.vitejs.dev/)
|
||||
实现。使用了最新的 `Vue` 全家桶技术栈。Api 数据保存在项目本地,视频采集自`抖音|TikTok`,图集采集自`小红书|Xiaohongshu`,通过 [`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter) 库拦截Api 并返回本地json数据,模拟出真实的后端请求
|
||||
`douyin-vue` 是一个模仿 `抖音|TikTok` 的移动端短视频项目。`Vue` 在移动端的"最佳实践",媲美原生 `App` 丝滑流畅的使用体验。使用了最新的 `Vue` 技术栈,基于 [`Vue3`](https://cn.vuejs.org/)、[`Vite5`](https://cn.vitejs.dev/)
|
||||
、[`Pinia`](https://pinia.vuejs.org/)实现。数据保存在项目本地,通过 [`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter) 库拦截Api 并返回本地json数据,模拟真实后端请求
|
||||
|
||||
|
||||
<div>
|
||||
<img width="150px" src='docs/imgs/1.gif' />
|
||||
@@ -22,44 +23,44 @@
|
||||
<img width="150px" src='docs/imgs/3.gif' />
|
||||
<img width="150px" src='docs/imgs/4.gif' />
|
||||
<img width="150px" src='docs/imgs/5.gif' />
|
||||
<img width="150px" src='docs/imgs/img-1.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-2.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-3.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-4.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-5.jpg' />
|
||||
</div>
|
||||
|
||||
## 在线访问
|
||||
|
||||
Github pages: [https://zyronon.github.io/douyin/](https://zyronon.github.io/douyin/)
|
||||
[//]: # (Gitee Pages: [https://zyronon.gitee.io/douyin/](https://zyronon.gitee.io/douyin/)(中国地区推荐访问这个地址) )
|
||||
[//]: # (注意:Gitee Pages现在无法更新,代码不是最新的。如果你能翻墙推荐访问下面地址 )
|
||||
|
||||
[//]: # (Vercel: [http://dy.ttentau.top/](http://dy.ttentau.top/) (中国推荐访问这个)~~)
|
||||
Github Pages: [https://dy.ttentau.top/](https://dy.ttentau.top/)
|
||||
|
||||
[//]: # (Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/) (中国以外地区推荐访问这个)~~)
|
||||
[//]: # (Gitee pages: [https://dy.ttentau.top/](https://dy.ttentau.top/) (中国地区推荐访问这个地址) )
|
||||
[//]: # (Github pages: [https://zyronon.github.io/douyin/](https://zyronon.github.io/douyin/) )
|
||||
[//]: # (Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/))
|
||||
[//]: # (Vercel: [https://douyins.vercel.app](https://douyins.vercel.app))
|
||||
[//]: # (Android Apk: https://github.com/zyronon/douyin/releases)
|
||||
[//]: # (**注意**:`PC` 必须将浏览器切到手机模式,先按 `F12` 调出控制台,再按 `Ctrl+Shift+M`才能正常预览)
|
||||
[//]: # (**注意**:手机请用 [Via 浏览器](https://viayoo.com/zh-cn/) 或 Chrome 浏览器预览。其它浏览器可能会强制将视频全屏,导致无法正常显示)
|
||||
|
||||
Vercel和Netlify分别送的100G免费流量已经用完了...🤣
|
||||
## 链接
|
||||
|
||||
Android Apk: https://github.com/zyronon/douyin/releases
|
||||
【模仿抖音系列】一:[200行代码实现类似Swiper.js的轮播组件](https://juejin.cn/post/7360512664317018146)
|
||||
【模仿抖音系列】二:[实现抖音 “视频无限滑动“效果](https://juejin.cn/post/7361614921519054883)
|
||||
【模仿抖音系列】三:[Vue 路由使用介绍以及添加转场动画](https://juejin.cn/post/7362528152777130025)
|
||||
|
||||
**注意**:`PC` 必须将浏览器切到手机模式,先按 `F12` 调出控制台,再按 `Ctrl+Shift+M`才能正常预览
|
||||
|
||||
**注意**:手机请用 [Via 浏览器](https://viayoo.com/zh-cn/) 或 Chrome 浏览器预览。其它浏览器可能会强制将视频全屏,导致无法正常显示
|
||||
|
||||
|
||||
## 免责声明
|
||||
|
||||
本项目仅适用于学习和研究,不得用于商业使用
|
||||
更多文章正在准备中...
|
||||
|
||||
## 运行
|
||||
注意:本项目仅适用于学习和研究,不得用于商业使用
|
||||
|
||||
### 快速部署至Vercel
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/zyronon/douyin)
|
||||
|
||||
### 本地开发
|
||||
**注意:必须 git 命令 clone 下来才能运行,下载 zip 包是无法运行的。如果 clone 速度太慢,推荐使用 gitee 地址**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zyronon/douyin.git
|
||||
git clone https://gitee.com/zyronon/douyin.git (中国使用)
|
||||
https://github.com/zyronon/douyin.git
|
||||
cd douyin
|
||||
npm install
|
||||
npm run dev
|
||||
@@ -67,13 +68,7 @@ npm run dev
|
||||
|
||||
打开浏览器并访问: [http://127.0.0.1:3000](http://127.0.0.1:3000)
|
||||
|
||||
**注意:PC 必须将浏览器切到手机模式,先按 `F12` 调出控制台,再按 `Ctrl+Shift+M` 才能正常预览**
|
||||
|
||||
## 链接
|
||||
|
||||
- `开源中国`: https://mp.weixin.qq.com/s/TWowmZpU_ojE5G2KzXuU8g
|
||||
- `V2EX`: https://www.v2ex.com/t/1028678
|
||||
- `掘金`: https://juejin.cn/post/7352813352051687458
|
||||
**注意:需要将浏览器切至手机模式,先按 `F12` 调出控制台,再按 `Ctrl+Shift+M` 才能正常预览**
|
||||
|
||||
## 数据来源
|
||||
|
||||
@@ -97,6 +92,7 @@ npm run dev
|
||||
|
||||
以上内容均是互联网公开信息
|
||||
|
||||
|
||||
## 功能与建议
|
||||
|
||||
目前项目处于开发初期,新功能正在持续添加中,如果你对软件有任何功能与建议,欢迎在 `Issues` 中提出
|
||||
@@ -114,4 +110,4 @@ npm run dev
|
||||
|
||||
## 许可协议
|
||||
|
||||
[GPL](LICENSE)
|
||||
[GPL](LICENSE)
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
|
||||
- 双指缩放
|
||||
- AutoInput组件,在真机上无法输入
|
||||
- 真机上100vh显示异常的问题
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="README.en.md">English</a> | <a href="README.es.md">Spanish</a> | <a href="README.de.md">German</a> |
|
||||
<a href="README.fr.md">French</a> | <a href="README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
<a href="README.fr.md">French</a> | <a href="../README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -13,43 +13,49 @@
|
||||
<a><img src="https://img.shields.io/badge/Powered%20by-Vue-blue"/></a>
|
||||
</p>
|
||||
|
||||
`douyin-vue`ist eine Parodie`抖音|TikTok`mobiles Kurzvideoprojekt, das auf basiert[`Vue`](https://cn.vuejs.org/)、[`Vite`](https://cn.vitejs.dev/)erreichen. Habe das Neueste verwendet`Vue`FamilyMart-Technologie-Stack. API-Daten werden lokal im Projekt gespeichert und das Video wird daraus gesammelt`抖音|TikTok`, der Atlas stammt aus`小红书|Xiaohongshu`,passieren[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)Die Bibliothek fängt die API ab und gibt lokale JSON-Daten zurück, um echte Back-End-Anfragen zu simulieren.
|
||||
`douyin-vue`ist eine Parodie`抖音|TikTok`mobiles Kurzvideoprojekt.`Vue`„Best Practices“ auf der mobilen Seite, vergleichbar mit Native`App`Seidiges und geschmeidiges Erlebnis. Habe das Neueste verwendet`Vue`Technologie-Stack, basierend auf[`Vue3`](https://cn.vuejs.org/)、[`Vite5`](https://cn.vitejs.dev/)、[`Pinia`](https://pinia.vuejs.org/)erreichen. Die Daten werden lokal im Projekt gespeichert[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)Die Bibliothek fängt die API ab und gibt lokale JSON-Daten zurück, um echte Backend-Anfragen zu simulieren.
|
||||
|
||||
<div>
|
||||
<img width="150px" src='docs/imgs/1.gif' />
|
||||
<img width="150px" src='docs/imgs/2.gif' />
|
||||
<img width="150px" src='docs/imgs/3.gif' />
|
||||
<img width="150px" src='docs/imgs/4.gif' />
|
||||
<img width="150px" src='docs/imgs/5.gif' />
|
||||
<img width="150px" src='docs/imgs/img-1.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-2.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-3.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-4.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-5.jpg' />
|
||||
<img width="150px" src='imgs/1.gif' />
|
||||
<img width="150px" src='imgs/2.gif' />
|
||||
<img width="150px" src='imgs/3.gif' />
|
||||
<img width="150px" src='imgs/4.gif' />
|
||||
<img width="150px" src='imgs/5.gif' />
|
||||
</div>
|
||||
|
||||
## Online-Zugang
|
||||
|
||||
Github-Seiten:<https://zyronon.github.io/douyin/>
|
||||
Gitee-Seiten:<https://zyronon.gitee.io/douyin/>(Es wird empfohlen, diese Adresse in China zu besuchen)
|
||||
Hinweis: Gitee-Seiten können derzeit nicht aktualisiert werden, da der Code nicht aktuell ist. Wenn Sie die Firewall umgehen können, empfehle ich den Besuch der folgenden Adresse:
|
||||
|
||||
[//]: # "Vercel: [http://dy.ttentau.top/](http://dy.ttentau.top/) (中国推荐访问这个)~~"
|
||||
Github-Seiten:<https://dy.ttentau.top/>
|
||||
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/) (中国以外地区推荐访问这个)~~"
|
||||
[//]: # "Gitee pages: [https://dy.ttentau.top/](https://dy.ttentau.top/) (中国地区推荐访问这个地址) "
|
||||
|
||||
Die 100G kostenlosen Daten von Vercel und Netlify sind aufgebraucht...🤣
|
||||
[//]: # "Github pages: [https://zyronon.github.io/douyin/](https://zyronon.github.io/douyin/) "
|
||||
|
||||
Android-Apk:<https://github.com/zyronon/douyin/releases>
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/)"
|
||||
|
||||
**Beachten**:`PC`Sie müssen den Browser in den mobilen Modus schalten, zuerst drücken`F12`Um die Konsole aufzurufen, drücken Sie`Ctrl+Shift+M`um eine normale Vorschau anzuzeigen
|
||||
[//]: # "Vercel: [https://douyins.vercel.app](https://douyins.vercel.app)"
|
||||
|
||||
**Beachten**:手机请用 [über Browser](https://viayoo.com/zh-cn/)oder Chrome-Browservorschau. Andere Browser erzwingen möglicherweise, dass das Video im Vollbildmodus angezeigt wird, was dazu führt, dass es nicht richtig angezeigt wird.
|
||||
[//]: # "Android Apk: https://github.com/zyronon/douyin/releases"
|
||||
|
||||
## Haftungsausschluss
|
||||
[//]: # "**注意**:`PC` 必须将浏览器切到手机模式,先按 `F12` 调出控制台,再按 `Ctrl+Shift+M`才能正常预览"
|
||||
|
||||
Dieses Projekt dient ausschließlich Studien- und Forschungszwecken, nicht der kommerziellen Nutzung
|
||||
[//]: # "**注意**:手机请用 [Via 浏览器](https://viayoo.com/zh-cn/) 或 Chrome 浏览器预览。其它浏览器可能会强制将视频全屏,导致无法正常显示"
|
||||
|
||||
## Verknüpfung
|
||||
|
||||
\[Imitation Douyin-Serie] 1:[200 Codezeilen zur Implementierung einer Karussellkomponente ähnlich Swiper.js](https://juejin.cn/post/7360512664317018146)
|
||||
\[Imitation Douyin-Serie] 2:[Verwirklichen Sie den „unendlich gleitenden Video“-Effekt auf Douyin](https://juejin.cn/post/7361614921519054883)
|
||||
\[Imitation Douyin-Serie] Drei:[Einführung in die Verwendung von Vue-Routing und das Hinzufügen von Übergangsanimationen](https://juejin.cn/post/7362528152777130025)
|
||||
|
||||
Weitere Artikel sind in Vorbereitung...
|
||||
|
||||
## laufen
|
||||
|
||||
Hinweis: Dieses Projekt ist nur für Studien- und Forschungszwecke geeignet, nicht für die kommerzielle Nutzung
|
||||
|
||||
### Schnelle Bereitstellung in Vercel
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/zyronon/douyin)
|
||||
@@ -57,7 +63,8 @@ Dieses Projekt dient ausschließlich Studien- und Forschungszwecken, nicht der k
|
||||
### lokale Entwicklung
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zyronon/douyin.git
|
||||
git clone https://gitee.com/zyronon/douyin.git (中国使用)
|
||||
https://github.com/zyronon/douyin.git
|
||||
cd douyin
|
||||
npm install
|
||||
npm run dev
|
||||
@@ -65,13 +72,7 @@ npm run dev
|
||||
|
||||
Öffnen Sie Ihren Browser und besuchen Sie:<http://127.0.0.1:3000>
|
||||
|
||||
**Hinweis: Der PC muss den Browser in den Mobilmodus schalten, zuerst drücken`F12`Um die Konsole aufzurufen, drücken Sie`Ctrl+Shift+M`um eine normale Vorschau anzuzeigen**
|
||||
|
||||
## Verknüpfung
|
||||
|
||||
- `开源中国`:<https://mp.weixin.qq.com/s/TWowmZpU_ojE5G2KzXuU8g>
|
||||
- `V2EX`:<https://www.v2ex.com/t/1028678>
|
||||
- `掘金`:<https://juejin.cn/post/7352813352051687458>
|
||||
**Hinweis: Sie müssen den Browser in den Mobilmodus schalten, indem Sie zuerst drücken`F12`Um die Konsole aufzurufen, drücken Sie`Ctrl+Shift+M`um eine normale Vorschau anzuzeigen**
|
||||
|
||||
## Datenquellen
|
||||
|
||||
@@ -111,4 +112,4 @@ Sie können meine E-Mail kontaktieren<a href="mailto:zyronon@163.com">zyronon@16
|
||||
|
||||
## Vereinbarung
|
||||
|
||||
[GPL](LICENSE)
|
||||
[GPL](../LICENSE)
|
||||
115
docs/README.en.md
Normal file
@@ -0,0 +1,115 @@
|
||||
<h1 align="center">
|
||||
Douyin-Vue
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.en.md">English</a> | <a href="README.es.md">Spanish</a> | <a href="README.de.md">German</a> |
|
||||
<a href="README.fr.md">French</a> | <a href="../README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/zyronon/douyin/blob/master/LICENSE"><img src="https://img.shields.io/github/license/zyronon/douyin" alt="License"></a>
|
||||
<a><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"/></a>
|
||||
<a><img src="https://img.shields.io/badge/Powered%20by-Vue-blue"/></a>
|
||||
</p>
|
||||
|
||||
`douyin-vue`is a parody`抖音|TikTok`mobile short video project.`Vue`"Best practices" on the mobile side, comparable to native`App`Silky and smooth experience. Used the latest`Vue`technology stack, based on[`Vue3`](https://cn.vuejs.org/)、[`Vite5`](https://cn.vitejs.dev/)、[`Pinia`](https://pinia.vuejs.org/)accomplish. The data is saved locally in the project through[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)The library intercepts the API and returns local json data to simulate real backend requests.
|
||||
|
||||
<div>
|
||||
<img width="150px" src='imgs/1.gif' />
|
||||
<img width="150px" src='imgs/2.gif' />
|
||||
<img width="150px" src='imgs/3.gif' />
|
||||
<img width="150px" src='imgs/4.gif' />
|
||||
<img width="150px" src='imgs/5.gif' />
|
||||
</div>
|
||||
|
||||
## Online access
|
||||
|
||||
Gitee Pages:<https://zyronon.gitee.io/douyin/>(It is recommended to visit this address in China)
|
||||
Note: Gitee Pages cannot be updated now, the code is not up to date. If you can circumvent the firewall, I recommend visiting the following address:
|
||||
|
||||
Github Pages:<https://dy.ttentau.top/>
|
||||
|
||||
[//]: # "Gitee pages: [https://dy.ttentau.top/](https://dy.ttentau.top/) (中国地区推荐访问这个地址) "
|
||||
|
||||
[//]: # "Github pages: [https://zyronon.github.io/douyin/](https://zyronon.github.io/douyin/) "
|
||||
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/)"
|
||||
|
||||
[//]: # "Vercel: [https://douyins.vercel.app](https://douyins.vercel.app)"
|
||||
|
||||
[//]: # "Android Apk: https://github.com/zyronon/douyin/releases"
|
||||
|
||||
[//]: # "**注意**:`PC` 必须将浏览器切到手机模式,先按 `F12` 调出控制台,再按 `Ctrl+Shift+M`才能正常预览"
|
||||
|
||||
[//]: # "**注意**:手机请用 [Via 浏览器](https://viayoo.com/zh-cn/) 或 Chrome 浏览器预览。其它浏览器可能会强制将视频全屏,导致无法正常显示"
|
||||
|
||||
## Link
|
||||
|
||||
\[Imitation Douyin Series] 1:[200 lines of code to implement a carousel component similar to Swiper.js](https://juejin.cn/post/7360512664317018146)
|
||||
\[Imitation Douyin Series] 2:[Realize the "infinite sliding video" effect on Douyin](https://juejin.cn/post/7361614921519054883)
|
||||
\[Imitation Douyin Series] Three:[Introduction to using Vue routing and adding transition animations](https://juejin.cn/post/7362528152777130025)
|
||||
|
||||
More articles are in preparation...
|
||||
|
||||
## run
|
||||
|
||||
Note: This project is only suitable for study and research, not for commercial use
|
||||
|
||||
### Quickly deploy to Vercel
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/zyronon/douyin)
|
||||
|
||||
### local development
|
||||
|
||||
```bash
|
||||
git clone https://gitee.com/zyronon/douyin.git (中国使用)
|
||||
https://github.com/zyronon/douyin.git
|
||||
cd douyin
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open your browser and visit:<http://127.0.0.1:3000>
|
||||
|
||||
**Note: You need to switch the browser to mobile mode, first press`F12`To bring up the console, press`Ctrl+Shift+M`to preview normally**
|
||||
|
||||
## Data Sources
|
||||
|
||||
The video comes from the following Douyin celebrities
|
||||
|
||||
- `我是香秀 🐂🍺`:<https://v.douyin.com/iYRAPA2L/>
|
||||
- `杨老虎 🐯(磕穿下巴掉牙版)`:<https://v.douyin.com/iYRA56de/>
|
||||
- `条子`:<https://v.douyin.com/iYRAaqjr/>
|
||||
- `达莎 Digi`:<https://v.douyin.com/iYRA6rwT/>
|
||||
- `小橙子`:<https://v.douyin.com/iYRAnudw/>
|
||||
- `南恬`:<https://v.douyin.com/iYRAbKm3/>
|
||||
- `小霸宠牛排 🥩`:<https://v.douyin.com/iYRSosVB/>
|
||||
- `奶茶妹 ◕🌱`:<https://v.douyin.com/iYRACKhP/>
|
||||
- `我才是岚岚`:<https://v.douyin.com/iYRAQM1C/>
|
||||
- `周憬艺 ziran`:<https://v.douyin.com/iYRAQs4h/>
|
||||
- `刘思瑶 nice`:<https://v.douyin.com/iYRAaERn/>
|
||||
- `彭十六 elf`:<https://v.douyin.com/iYRAHrVG/>
|
||||
- `李子柒`:<https://v.douyin.com/iYRA5B88/>
|
||||
|
||||
Picture from Xiaohongshu public notes
|
||||
|
||||
The above content is all public information on the Internet
|
||||
|
||||
## Features and suggestions
|
||||
|
||||
The project is currently in the early stages of development, and new features are being added continuously. If you have any features or suggestions for the software, please feel free to contact us.`Issues`raised in
|
||||
If you also like the design ideas of this software, please submit it`PR`, thank you very much for your support!
|
||||
|
||||
## 联系我
|
||||
|
||||
You can contact my email<a href="mailto:zyronon@163.com">zyronon@163.com</a>
|
||||
|
||||
> Share my other open source projects:
|
||||
>
|
||||
> _[**Typing Word**- Vocabulary memorization software that can be used on the web~](https://github.com/zyronon/typing-word)<img src="https://img.shields.io/github/stars/zyronon/typing-word.svg?style=flat-square&label=Star&color=4285dd&logo=github" height="16px" />_
|
||||
> _[**Web Scripts**- Some useful Grease Monkey scripts~](https://github.com/zyronon/web-scripts)<img src="https://img.shields.io/github/stars/zyronon/web-scripts.svg?style=flat-square&label=Star&color=4285dd&logo=github" height="16px" />_
|
||||
|
||||
## agreement
|
||||
|
||||
[GPL](../LICENSE)
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="README.en.md">English</a> | <a href="README.es.md">Spanish</a> | <a href="README.de.md">German</a> |
|
||||
<a href="README.fr.md">French</a> | <a href="README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
<a href="README.fr.md">French</a> | <a href="../README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -13,43 +13,49 @@
|
||||
<a><img src="https://img.shields.io/badge/Powered%20by-Vue-blue"/></a>
|
||||
</p>
|
||||
|
||||
`douyin-vue`es una parodia`抖音|TikTok`proyecto de vídeo corto móvil, que se basa en[`Vue`](https://cn.vuejs.org/)、[`Vite`](https://cn.vitejs.dev/)lograr. Usado lo último`Vue`Pila de tecnología FamilyMart. Los datos de API se guardan localmente en el proyecto y el video se recopila de`抖音|TikTok`, el atlas se recoge de`小红书|Xiaohongshu`,aprobar[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)La biblioteca intercepta la API y devuelve datos json locales para simular solicitudes de back-end reales.
|
||||
`douyin-vue`es una parodia`抖音|TikTok`Proyecto de vídeo corto móvil.`Vue`"Mejores prácticas" en el lado móvil, comparables a las nativas`App`Experiencia sedosa y suave. Usado lo último`Vue`pila de tecnología, basada en[`Vue3`](https://cn.vuejs.org/)、[`Vite5`](https://cn.vitejs.dev/)、[`Pinia`](https://pinia.vuejs.org/)lograr. Los datos se guardan localmente en el proyecto a través de[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)La biblioteca intercepta la API y devuelve datos json locales para simular solicitudes de backend reales.
|
||||
|
||||
<div>
|
||||
<img width="150px" src='docs/imgs/1.gif' />
|
||||
<img width="150px" src='docs/imgs/2.gif' />
|
||||
<img width="150px" src='docs/imgs/3.gif' />
|
||||
<img width="150px" src='docs/imgs/4.gif' />
|
||||
<img width="150px" src='docs/imgs/5.gif' />
|
||||
<img width="150px" src='docs/imgs/img-1.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-2.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-3.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-4.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-5.jpg' />
|
||||
<img width="150px" src='imgs/1.gif' />
|
||||
<img width="150px" src='imgs/2.gif' />
|
||||
<img width="150px" src='imgs/3.gif' />
|
||||
<img width="150px" src='imgs/4.gif' />
|
||||
<img width="150px" src='imgs/5.gif' />
|
||||
</div>
|
||||
|
||||
## Acceso en linea
|
||||
|
||||
Páginas de Github:<https://zyronon.github.io/douyin/>
|
||||
Páginas de casas rurales:<https://zyronon.gitee.io/douyin/>(Se recomienda visitar esta dirección en China)
|
||||
Nota: Las páginas de Gitee no se pueden actualizar ahora, el código no está actualizado. Si puede eludir el firewall, le recomiendo visitar la siguiente dirección:
|
||||
|
||||
[//]: # "Vercel: [http://dy.ttentau.top/](http://dy.ttentau.top/) (中国推荐访问这个)~~"
|
||||
Páginas de Github:<https://dy.ttentau.top/>
|
||||
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/) (中国以外地区推荐访问这个)~~"
|
||||
[//]: # "Gitee pages: [https://dy.ttentau.top/](https://dy.ttentau.top/) (中国地区推荐访问这个地址) "
|
||||
|
||||
Los 100G de datos gratuitos proporcionados por Vercel y Netlify se han agotado...🤣
|
||||
[//]: # "Github pages: [https://zyronon.github.io/douyin/](https://zyronon.github.io/douyin/) "
|
||||
|
||||
Android Apk:<https://github.com/zyronon/douyin/releases>
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/)"
|
||||
|
||||
**Aviso**:`PC`Debes cambiar el navegador al modo móvil, primero presiona`F12`Para abrir la consola, presione`Ctrl+Shift+M`para obtener una vista previa normalmente
|
||||
[//]: # "Vercel: [https://douyins.vercel.app](https://douyins.vercel.app)"
|
||||
|
||||
**Aviso**:Por favor utilice el teléfono móvil[a través del navegador](https://viayoo.com/zh-cn/)o vista previa del navegador Chrome. Otros navegadores pueden forzar el vídeo a pantalla completa, lo que hace que no se muestre correctamente.
|
||||
[//]: # "Android Apk: https://github.com/zyronon/douyin/releases"
|
||||
|
||||
## Descargo de responsabilidad
|
||||
[//]: # "**注意**:`PC` 必须将浏览器切到手机模式,先按 `F12` 调出控制台,再按 `Ctrl+Shift+M`才能正常预览"
|
||||
|
||||
Este proyecto es sólo para estudio e investigación, no para uso comercial.
|
||||
[//]: # "**注意**:手机请用 [Via 浏览器](https://viayoo.com/zh-cn/) 或 Chrome 浏览器预览。其它浏览器可能会强制将视频全屏,导致无法正常显示"
|
||||
|
||||
## Enlace
|
||||
|
||||
\[Serie Imitación Douyin] 1:[200 líneas de código para implementar un componente carrusel similar a Swiper.js](https://juejin.cn/post/7360512664317018146)
|
||||
\[Serie Imitación Douyin] 2:[Realice el efecto de "vídeo deslizante infinito" en Douyin](https://juejin.cn/post/7361614921519054883)
|
||||
\[Serie Imitación Douyin] Tres:[Introducción al uso del enrutamiento de Vue y la adición de animaciones de transición](https://juejin.cn/post/7362528152777130025)
|
||||
|
||||
Más artículos están en preparación...
|
||||
|
||||
## correr
|
||||
|
||||
Nota: Este proyecto sólo es apto para estudio e investigación, no para uso comercial.
|
||||
|
||||
### Implemente rápidamente en Vercel
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/zyronon/douyin)
|
||||
@@ -57,7 +63,8 @@ Este proyecto es sólo para estudio e investigación, no para uso comercial.
|
||||
### desarrollo local
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zyronon/douyin.git
|
||||
git clone https://gitee.com/zyronon/douyin.git (中国使用)
|
||||
https://github.com/zyronon/douyin.git
|
||||
cd douyin
|
||||
npm install
|
||||
npm run dev
|
||||
@@ -65,13 +72,7 @@ npm run dev
|
||||
|
||||
Abra su navegador y visite:<http://127.0.0.1:3000>
|
||||
|
||||
**Nota: La PC debe cambiar el navegador al modo móvil, primero presione`F12`Para abrir la consola, presione`Ctrl+Shift+M`para obtener una vista previa normalmente**
|
||||
|
||||
## Enlace
|
||||
|
||||
- `开源中国`:<https://mp.weixin.qq.com/s/TWowmZpU_ojE5G2KzXuU8g>
|
||||
- `V2EX`:<https://www.v2ex.com/t/1028678>
|
||||
- `掘金`:<https://juejin.cn/post/7352813352051687458>
|
||||
**Nota: Debe cambiar el navegador al modo móvil, primero presione`F12`Para abrir la consola, presione`Ctrl+Shift+M`para obtener una vista previa normalmente**
|
||||
|
||||
## Fuentes de datos
|
||||
|
||||
@@ -111,4 +112,4 @@ Puedes contactar a mi correo<a href="mailto:zyronon@163.com">zyronon@163.com</a>
|
||||
|
||||
## acuerdo
|
||||
|
||||
[GPL](LICENSE)
|
||||
[GPL](../LICENSE)
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="README.en.md">English</a> | <a href="README.es.md">Spanish</a> | <a href="README.de.md">German</a> |
|
||||
<a href="README.fr.md">French</a> | <a href="README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
<a href="README.fr.md">French</a> | <a href="../README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -13,43 +13,49 @@
|
||||
<a><img src="https://img.shields.io/badge/Powered%20by-Vue-blue"/></a>
|
||||
</p>
|
||||
|
||||
`douyin-vue`est une parodie`抖音|TikTok`projet de courte vidéo mobile, basé sur[`Vue`](https://cn.vuejs.org/)、[`Vite`](https://cn.vitejs.dev/)accomplir. Utilisé le dernier`Vue`Pile technologique FamilyMart. Les données API sont enregistrées localement dans le projet et la vidéo est collectée à partir de`抖音|TikTok`, l'atlas est collecté auprès de`小红书|Xiaohongshu`,passer[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)La bibliothèque intercepte l'API et renvoie des données json locales pour simuler de véritables requêtes back-end.
|
||||
`douyin-vue`est une parodie`抖音|TikTok`projet de courte vidéo mobile.`Vue`Des « bonnes pratiques » côté mobile, comparables au natif`App`Expérience soyeuse et douce. Utilisé le dernier`Vue`pile technologique, basée sur[`Vue3`](https://cn.vuejs.org/)、[`Vite5`](https://cn.vitejs.dev/)、[`Pinia`](https://pinia.vuejs.org/)accomplir. Les données sont enregistrées localement dans le projet via[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)La bibliothèque intercepte l'API et renvoie des données json locales pour simuler de véritables requêtes backend.
|
||||
|
||||
<div>
|
||||
<img width="150px" src='docs/imgs/1.gif' />
|
||||
<img width="150px" src='docs/imgs/2.gif' />
|
||||
<img width="150px" src='docs/imgs/3.gif' />
|
||||
<img width="150px" src='docs/imgs/4.gif' />
|
||||
<img width="150px" src='docs/imgs/5.gif' />
|
||||
<img width="150px" src='docs/imgs/img-1.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-2.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-3.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-4.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-5.jpg' />
|
||||
<img width="150px" src='imgs/1.gif' />
|
||||
<img width="150px" src='imgs/2.gif' />
|
||||
<img width="150px" src='imgs/3.gif' />
|
||||
<img width="150px" src='imgs/4.gif' />
|
||||
<img width="150px" src='imgs/5.gif' />
|
||||
</div>
|
||||
|
||||
## accès en ligne
|
||||
## Accès en ligne
|
||||
|
||||
Pages GitHub :<https://zyronon.github.io/douyin/>
|
||||
Pages du gîte :<https://zyronon.gitee.io/douyin/>(Il est recommandé de visiter cette adresse en Chine)
|
||||
Remarque : Les pages Gitee ne peuvent pas être mises à jour pour le moment, le code n'est pas à jour. Si vous pouvez contourner le pare-feu, je vous recommande de visiter l'adresse suivante :
|
||||
|
||||
[//]: # "Vercel: [http://dy.ttentau.top/](http://dy.ttentau.top/) (中国推荐访问这个)~~"
|
||||
Pages GitHub :<https://dy.ttentau.top/>
|
||||
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/) (中国以外地区推荐访问这个)~~"
|
||||
[//]: # "Gitee pages: [https://dy.ttentau.top/](https://dy.ttentau.top/) (中国地区推荐访问这个地址) "
|
||||
|
||||
Les 100G de données gratuites fournies par Vercel et Netlify ont été épuisées...🤣
|
||||
[//]: # "Github pages: [https://zyronon.github.io/douyin/](https://zyronon.github.io/douyin/) "
|
||||
|
||||
Android Apk :<https://github.com/zyronon/douyin/releases>
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/)"
|
||||
|
||||
**Avis**:`PC`Vous devez passer le navigateur en mode mobile, appuyez d'abord sur`F12`Pour afficher la console, appuyez sur`Ctrl+Shift+M`pour prévisualiser normalement
|
||||
[//]: # "Vercel: [https://douyins.vercel.app](https://douyins.vercel.app)"
|
||||
|
||||
**Avis**:Veuillez utiliser un téléphone portable[via un navigateur](https://viayoo.com/zh-cn/)ou aperçu du navigateur Chrome. D'autres navigateurs peuvent forcer la vidéo à passer en plein écran, ce qui l'empêchera de s'afficher correctement.
|
||||
[//]: # "Android Apk: https://github.com/zyronon/douyin/releases"
|
||||
|
||||
## Clause de non-responsabilité
|
||||
[//]: # "**注意**:`PC` 必须将浏览器切到手机模式,先按 `F12` 调出控制台,再按 `Ctrl+Shift+M`才能正常预览"
|
||||
|
||||
Ce projet est destiné uniquement à l'étude et à la recherche, et non à un usage commercial.
|
||||
[//]: # "**注意**:手机请用 [Via 浏览器](https://viayoo.com/zh-cn/) 或 Chrome 浏览器预览。其它浏览器可能会强制将视频全屏,导致无法正常显示"
|
||||
|
||||
## Lien
|
||||
|
||||
\[Série Imitation Douyin] 1 :[200 lignes de code pour implémenter un composant carrousel similaire à Swiper.js](https://juejin.cn/post/7360512664317018146)
|
||||
\[Série Imitation Douyin] 2 :[Réaliser l'effet "vidéo coulissante infinie" sur Douyin](https://juejin.cn/post/7361614921519054883)
|
||||
\[Série Imitation Douyin] Trois :[Introduction à l'utilisation du routage Vue et à l'ajout d'animations de transition](https://juejin.cn/post/7362528152777130025)
|
||||
|
||||
D'autres articles sont en préparation...
|
||||
|
||||
## courir
|
||||
|
||||
Remarque : ce projet convient uniquement à l'étude et à la recherche, et non à un usage commercial.
|
||||
|
||||
### Déployez rapidement sur Vercel
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/zyronon/douyin)
|
||||
@@ -57,7 +63,8 @@ Ce projet est destiné uniquement à l'étude et à la recherche, et non à un u
|
||||
### développement local
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zyronon/douyin.git
|
||||
git clone https://gitee.com/zyronon/douyin.git (中国使用)
|
||||
https://github.com/zyronon/douyin.git
|
||||
cd douyin
|
||||
npm install
|
||||
npm run dev
|
||||
@@ -65,13 +72,7 @@ npm run dev
|
||||
|
||||
Ouvrez votre navigateur et visitez :<http://127.0.0.1:3000>
|
||||
|
||||
**Remarque : le PC doit passer le navigateur en mode mobile, appuyez d'abord sur`F12`Pour afficher la console, appuyez sur`Ctrl+Shift+M`pour prévisualiser normalement**
|
||||
|
||||
## Lien
|
||||
|
||||
- `开源中国`:<https://mp.weixin.qq.com/s/TWowmZpU_ojE5G2KzXuU8g>
|
||||
- `V2EX`:<https://www.v2ex.com/t/1028678>
|
||||
- `掘金`:<https://juejin.cn/post/7352813352051687458>
|
||||
**Remarque : Vous devez passer le navigateur en mode mobile, appuyez d'abord sur`F12`Pour afficher la console, appuyez sur`Ctrl+Shift+M`pour prévisualiser normalement**
|
||||
|
||||
## Les sources de données
|
||||
|
||||
@@ -102,7 +103,7 @@ Si vous aimez également les idées de conception de ce logiciel, veuillez le so
|
||||
|
||||
## Contactez moi
|
||||
|
||||
您可以联系我的邮箱 <a href="mailto:zyronon@163.com">zyronon@163.com</a>
|
||||
Vous pouvez contacter mon email<a href="mailto:zyronon@163.com">zyronon@163.com</a>
|
||||
|
||||
> Partagez mes autres projets open source :
|
||||
>
|
||||
@@ -111,4 +112,4 @@ Si vous aimez également les idées de conception de ce logiciel, veuillez le so
|
||||
|
||||
## accord
|
||||
|
||||
[GPL](LICENSE)
|
||||
[GPL](../LICENSE)
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="README.en.md">English</a> | <a href="README.es.md">Spanish</a> | <a href="README.de.md">German</a> |
|
||||
<a href="README.fr.md">French</a> | <a href="README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
<a href="README.fr.md">French</a> | <a href="../README.md">简体中文</a> | <a href="README.ja.md">日本語</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -13,43 +13,49 @@
|
||||
<a><img src="https://img.shields.io/badge/Powered%20by-Vue-blue"/></a>
|
||||
</p>
|
||||
|
||||
`douyin-vue`パロディです`抖音|TikTok`をベースにしたモバイルショートビデオプロジェクト[`Vue`](https://cn.vuejs.org/)、[`Vite`](https://cn.vitejs.dev/)成し遂げる。最新のものを使用しました`Vue`ファミリーマートのテクノロジースタック。 API データはプロジェクトのローカルに保存され、ビデオはから収集されます。`抖音|TikTok`、アトラスはから収集されています`小红书|Xiaohongshu`、合格[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)ライブラリは API をインターセプトし、ローカルの JSON データを返して、実際のバックエンド リクエストをシミュレートします。
|
||||
`douyin-vue`パロディです`抖音|TikTok`モバイルショートビデオプロジェクト。`Vue`ネイティブと同等のモバイル側の「ベスト プラクティス」`App`シルキーで滑らかな使い心地。最新のものを使用しました`Vue`テクノロジースタック、に基づく[`Vue3`](https://cn.vuejs.org/)、[`Vite5`](https://cn.vitejs.dev/)、[`Pinia`](https://pinia.vuejs.org/)成し遂げる。データはプロジェクト内でローカルに保存されます。[`axios-mock-adapter`](https://github.com/ctimmerm/axios-mock-adapter)ライブラリは API をインターセプトし、ローカルの JSON データを返して、実際のバックエンド リクエストをシミュレートします。
|
||||
|
||||
<div>
|
||||
<img width="150px" src='docs/imgs/1.gif' />
|
||||
<img width="150px" src='docs/imgs/2.gif' />
|
||||
<img width="150px" src='docs/imgs/3.gif' />
|
||||
<img width="150px" src='docs/imgs/4.gif' />
|
||||
<img width="150px" src='docs/imgs/5.gif' />
|
||||
<img width="150px" src='docs/imgs/img-1.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-2.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-3.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-4.jpg' />
|
||||
<img width="150px" src='docs/imgs/img-5.jpg' />
|
||||
<img width="150px" src='imgs/1.gif' />
|
||||
<img width="150px" src='imgs/2.gif' />
|
||||
<img width="150px" src='imgs/3.gif' />
|
||||
<img width="150px" src='imgs/4.gif' />
|
||||
<img width="150px" src='imgs/5.gif' />
|
||||
</div>
|
||||
|
||||
## オンラインアクセス
|
||||
|
||||
Github ページ:[hっtps://zyろのん。ぎてゅb。いお/どうyいん/](https://zyronon.github.io/douyin/)
|
||||
ジーティーページ:[hっtps://zyろのん。ぎてえ。いお/どうyいん/](https://zyronon.gitee.io/douyin/)(中国ではこのアドレスにアクセスすることをお勧めします)
|
||||
注: コードが最新ではないため、現在 Gitee ページを更新できません。ファイアウォールを回避できる場合は、次のアドレスにアクセスすることをお勧めします。
|
||||
|
||||
[//]: # "Vercel: [http://dy.ttentau.top/](http://dy.ttentau.top/) (中国推荐访问这个)~~"
|
||||
Github ページ:[hっtps://dy。ってんたう。とp/](https://dy.ttentau.top/)
|
||||
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/) (中国以外地区推荐访问这个)~~"
|
||||
[//]: # "Gitee pages: [https://dy.ttentau.top/](https://dy.ttentau.top/) (中国地区推荐访问这个地址) "
|
||||
|
||||
VercelとNetlifyが提供する100Gの無料データは使い切ってしまいました…🤣
|
||||
[//]: # "Github pages: [https://zyronon.github.io/douyin/](https://zyronon.github.io/douyin/) "
|
||||
|
||||
アンドロイドAPK:[hっtps://ぎてゅb。こm/zyろのん/どうyいん/れぇあせs](https://github.com/zyronon/douyin/releases)
|
||||
[//]: # "Netlify: [https://douyins.netlify.app/](https://douyins.netlify.app/)"
|
||||
|
||||
**注意**:`PC`ブラウザをモバイル モードに切り替えて、最初に を押す必要があります。`F12`コンソールを表示するには、 を押します。`Ctrl+Shift+M`通常にプレビューする
|
||||
[//]: # "Vercel: [https://douyins.vercel.app](https://douyins.vercel.app)"
|
||||
|
||||
**注意**:携帯電話をご利用ください[ブラウザ経由](https://viayoo.com/zh-cn/)またはChromeブラウザのプレビュー。他のブラウザではビデオが強制的に全画面表示になり、正しく表示されない場合があります。
|
||||
[//]: # "Android Apk: https://github.com/zyronon/douyin/releases"
|
||||
|
||||
## 免責事項
|
||||
[//]: # "**注意**:`PC` 必须将浏览器切到手机模式,先按 `F12` 调出控制台,再按 `Ctrl+Shift+M`才能正常预览"
|
||||
|
||||
このプロジェクトは調査と研究のみを目的としており、商用目的ではありません
|
||||
[//]: # "**注意**:手机请用 [Via 浏览器](https://viayoo.com/zh-cn/) 或 Chrome 浏览器预览。其它浏览器可能会强制将视频全屏,导致无法正常显示"
|
||||
|
||||
## リンク
|
||||
|
||||
【模倣同音シリーズ】 1:[Swiper.js に似たカルーセル コンポーネントを実装するための 200 行のコード](https://juejin.cn/post/7360512664317018146)
|
||||
【模倣同音シリーズ】2:[Douyinで「無限スライドビデオ」効果を実現](https://juejin.cn/post/7361614921519054883)
|
||||
【模倣同音シリーズ】その3:[Vue ルーティングの使用とトランジション アニメーションの追加の概要](https://juejin.cn/post/7362528152777130025)
|
||||
|
||||
さらなる記事も準備中です...
|
||||
|
||||
## 走る
|
||||
|
||||
注: このプロジェクトは研究と研究のみに適しており、商業利用には適していません。
|
||||
|
||||
### Vercel への迅速な導入
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/zyronon/douyin)
|
||||
@@ -57,7 +63,8 @@ VercelとNetlifyが提供する100Gの無料データは使い切ってしまい
|
||||
### 地域開発
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zyronon/douyin.git
|
||||
git clone https://gitee.com/zyronon/douyin.git (中国使用)
|
||||
https://github.com/zyronon/douyin.git
|
||||
cd douyin
|
||||
npm install
|
||||
npm run dev
|
||||
@@ -65,13 +72,7 @@ npm run dev
|
||||
|
||||
ブラウザを開いて、以下にアクセスしてください。[hっtp://127。0。0。1:3000](http://127.0.0.1:3000)
|
||||
|
||||
**注: PC はブラウザをモバイル モードに切り替える必要があり、最初に押します。`F12`コンソールを表示するには、 を押します。`Ctrl+Shift+M`通常にプレビューする**
|
||||
|
||||
## リンク
|
||||
|
||||
- `开源中国`:[hっtps://mp。うぇいぃん。っq。こm/s/とぉwmZぷ_おじぇ5G2Kzぅう8g](https://mp.weixin.qq.com/s/TWowmZpU_ojE5G2KzXuU8g)
|
||||
- `V2EX`:[hっtps://wっw。v2えx。こm/t/1028678](https://www.v2ex.com/t/1028678)
|
||||
- `掘金`:[hっtps://じゅえじん。cん/ぽst/7352813352051687458](https://juejin.cn/post/7352813352051687458)
|
||||
**注: ブラウザをモバイル モードに切り替える必要があります。最初に を押します。`F12`コンソールを表示するには、 を押します。`Ctrl+Shift+M`通常にプレビューする**
|
||||
|
||||
## データソース
|
||||
|
||||
@@ -97,7 +98,7 @@ npm run dev
|
||||
|
||||
## 特徴と提案
|
||||
|
||||
プロジェクトは現在開発の初期段階にあり、ソフトウェアの新機能や提案があれば、お気軽にお問い合わせください。`Issues`で育ちました
|
||||
プロジェクトは現在開発の初期段階にあり、新しい機能が継続的に追加されています。ソフトウェアに関する機能や提案がありましたら、お気軽にお問い合わせください。`Issues`で育ちました
|
||||
このソフトウェアのデザインアイデアも気に入っていただけましたら、ぜひ送信してください`PR`、 ご支援ありがとうございました!
|
||||
|
||||
## 私に連絡して
|
||||
@@ -111,4 +112,4 @@ npm run dev
|
||||
|
||||
## 合意
|
||||
|
||||
[GPL](LICENSE)
|
||||
[GPL](../LICENSE)
|
||||
52
docs/README2.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
theme: cyanosis
|
||||
highlight: vs2015
|
||||
---
|
||||
|
||||
# 前言
|
||||
|
||||
---
|
||||
|
||||
这是我的 [**模仿抖音**](https://juejin.cn/column/7357362143396118528) 系列文章的第四篇:这篇我们将实现页面缓存,就像我们访问传统新闻网站一样
|
||||
> 第一篇:[200行代码实现类似Swiper.js的轮播组件](https://juejin.cn/post/7360512664317018146)
|
||||
> 第二篇:[实现抖音 “视频无限滑动“效果](https://juejin.cn/post/7361614921519054883)
|
||||
> 第三篇:[Vue 路由使用介绍以及添加转场动画](https://juejin.cn/post/7362528152777130025)
|
||||
|
||||
# 最终效果
|
||||
|
||||
在线预览:[dy.ttentau.top/](https://dy.ttentau.top/)
|
||||
|
||||
Github地址:[https://github.com/zyronon/douyin](https://github.com/zyronon/douyin)
|
||||
|
||||
路由定义源码:[routes.ts](https://github.com/zyronon/douyin/blob/master/src/router/index.ts)
|
||||
|
||||
# 实现
|
||||
|
||||
## 原理
|
||||
|
||||
KeepAlive
|
||||
组件介绍:[https://cn.vuejs.org/guide/built-ins/keep-alive.html](https://cn.vuejs.org/guide/built-ins/keep-alive.html)
|
||||
|
||||
我们知道 `Vue` 内置的 `<KeepAlive>` 组件可以缓存组件,只需要像下面这样写就可以缓存所有组件了
|
||||
|
||||
```vue
|
||||
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
```
|
||||
|
||||
可是,这样写所有组件都会被缓存,这显然不是我们希望看到的效果。
|
||||
|
||||
我们想要的效果是什么?就像 `jQuery` 时代的网站(例如:[`www.v2ex.com`](https://www.v2ex.com)
|
||||
)一样,点击一个链接,前进到下一页,浏览完成之后,返回上一页,当前页面被丢弃。再次访问的话需要重新加载
|
||||
> 吐槽:找了一圈国内的知名网站,点击链接全是单独打开一个标签页,根本没有在当前页跳转的了!
|
||||
>
|
||||
> 有喜欢逛 v2ex 的朋友可以试试我开发的这个油猴脚本:[V2Next](https://greasyfork.org/zh-CN/scripts/458024)
|
||||
> ,有UI美化、楼中楼、回复上下文、高赞回复、简洁模式、发送图片和表情 emoji、base64 解码等功能,现在每天都1800个 v 友在使用!
|
||||
|
||||
大多数情况下,用户进入一个页面,这个页面会请求数据。用户浏览完毕退出页面。如果再次进入,页面就像新开的一样,重新开始请求数据,这是符合用户预期的
|
||||
而不是像现在这样,不管重新进入多少次,页面都是一开始进入的样子,这时用户可能会怀疑自己是否断网了?我们总不能在每次页面都写 `onActivated`
|
||||
和 `onDeactivated` 方法吧!
|
||||
7
env.d.ts
vendored
@@ -1,8 +1,15 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="unplugin-vue-macros/macros-global" />
|
||||
|
||||
declare const LATEST_COMMIT_HASH: string
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
isMoved: boolean
|
||||
isMuted: boolean
|
||||
showMutedNotice: boolean
|
||||
}
|
||||
|
||||
interface Navigator {
|
||||
control: any
|
||||
webkitGetUserMedia: any
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -5734,7 +5734,7 @@
|
||||
"user_id": "1028766474441803",
|
||||
"sec_uid": "MS4wLjABAAAAonK7FndgFYn4mKBQwHc34iEiCCwvBI3tXNqGXqd18qFM9p_ZSxC1y9Gyv1e0XuG_",
|
||||
"short_user_id": "3643612610",
|
||||
"user_unique_id": "LL991221.z",
|
||||
"user_unique_id": "LL991221",
|
||||
"user_signature": "🧣我才是岚岚s\nX Eva: 我才是岚岚\n仅此一个抖音号(谨防被骗)",
|
||||
"nickname": "我才是岚岚",
|
||||
"avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_78ecf820d31560e298e32684589c00b3.jpeg?from=2956013662",
|
||||
@@ -5954,7 +5954,7 @@
|
||||
"user_id": "1028766474441803",
|
||||
"sec_uid": "MS4wLjABAAAAonK7FndgFYn4mKBQwHc34iEiCCwvBI3tXNqGXqd18qFM9p_ZSxC1y9Gyv1e0XuG_",
|
||||
"short_user_id": "3643612610",
|
||||
"user_unique_id": "LL991221.z",
|
||||
"user_unique_id": "LL991221",
|
||||
"user_signature": "🧣我才是岚岚s\nX Eva: 我才是岚岚\n仅此一个抖音号(谨防被骗)",
|
||||
"nickname": "我才是岚岚",
|
||||
"avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_78ecf820d31560e298e32684589c00b3.jpeg?from=2956013662",
|
||||
|
||||
33
node/remove-dist-json.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
let read = 'dist/data'
|
||||
|
||||
function deleteFile(url, name) {
|
||||
var files = []
|
||||
|
||||
if (fs.existsSync(url)) {
|
||||
//判断给定的路径是否存在
|
||||
|
||||
files = fs.readdirSync(url) //返回文件和子目录的数组
|
||||
|
||||
files.forEach(function (file) {
|
||||
var curPath = path.join(url, file)
|
||||
|
||||
if (fs.statSync(curPath).isDirectory()) {
|
||||
//同步读取文件夹文件,如果是文件夹,则函数回调
|
||||
deleteFile(curPath, name)
|
||||
} else {
|
||||
if (file.indexOf(name) > -1) {
|
||||
//是指定文件,则删除
|
||||
fs.unlinkSync(curPath)
|
||||
console.log('删除文件:' + curPath)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('给定的路径不存在!')
|
||||
}
|
||||
}
|
||||
|
||||
deleteFile(read, '.json')
|
||||
@@ -727,7 +727,7 @@ export const users = [
|
||||
signature: '🧣我才是岚岚s\nX Eva: 我才是岚岚\n仅此一个抖音号(谨防被骗)',
|
||||
total_favorited: 16475958,
|
||||
uid: '1028766474441803',
|
||||
unique_id: 'LL991221.z',
|
||||
unique_id: 'LL991221',
|
||||
user_age: 23,
|
||||
white_cover_url: [
|
||||
{
|
||||
|
||||
@@ -693,7 +693,7 @@
|
||||
"signature": "🧣我才是岚岚s\nX Eva: 我才是岚岚\n仅此一个抖音号(谨防被骗)",
|
||||
"total_favorited": 16475958,
|
||||
"uid": "1028766474441803",
|
||||
"unique_id": "LL991221.z",
|
||||
"unique_id": "LL991221",
|
||||
"user_age": 23,
|
||||
"white_cover_url": [
|
||||
{
|
||||
|
||||
24
package.json
@@ -7,15 +7,16 @@
|
||||
"start": "vite --host",
|
||||
"serve": "vite --host",
|
||||
"build": "vite build --mode prod",
|
||||
"test-del-json": "node node/remove-dist-json.js",
|
||||
"build-uni-app": "vite build --mode uni",
|
||||
"build-gp-pages": "vite build --mode gp_pages",
|
||||
"build-gitee-pages": "vite build --mode gitee_pages",
|
||||
"build-gitee-pages": "vite build --mode gitee_pages && pnpm run test-del-json",
|
||||
"build-only": "vite build",
|
||||
"build-check": "run-p type-check \"build-only {@}\" --",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"report": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
|
||||
"lint": "eslint --fix . --ext .vue,.js,.ts,.tsx,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/",
|
||||
"prepare": "husky",
|
||||
"commit": "git-cz"
|
||||
@@ -25,26 +26,18 @@
|
||||
"axios": "^1.6.8",
|
||||
"axios-mock-adapter": "^1.22.0",
|
||||
"core-js": "3.21.1",
|
||||
"dayjs": "1.11.0",
|
||||
"gl-matrix": "3.4.3",
|
||||
"jquery": "^3.7.1",
|
||||
"mitt": "3.0.0",
|
||||
"mobile-select": "1.1.2",
|
||||
"libarchive-wasm": "^1.1.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "3.4.21",
|
||||
"vue-router": "4.3.0",
|
||||
"vue-switches": "2.0.1",
|
||||
"libarchive-wasm": "^1.1.0"
|
||||
"vue-router": "4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.2.1",
|
||||
"@commitlint/config-conventional": "^19.1.0",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
"@tsconfig/node20": "^20.1.2",
|
||||
"@types/jquery": "3.5.29",
|
||||
"@types/node": "^20.11.28",
|
||||
"@vitejs/plugin-vue": "4.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
@@ -55,18 +48,17 @@
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"git-last-commit": "^1.0.1",
|
||||
"husky": "^9.0.11",
|
||||
"less": "4.1.3",
|
||||
"lint-staged": "^15.2.2",
|
||||
"prettier": "^3.2.5",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"typescript": "5.3.3",
|
||||
"unplugin-vue-define-options": "^1.4.1",
|
||||
"unplugin-vue-macros": "^2.9.1",
|
||||
"vite": "^5.1.7",
|
||||
"vite-plugin-cdn-import": "0.3.5",
|
||||
"vite-plugin-commonjs": "^0.10.1",
|
||||
"vue-tsc": "^2.0.6",
|
||||
"git-last-commit": "^1.0.1"
|
||||
"vue-tsc": "^2.0.6"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,vue,jsx,tsx}": [
|
||||
|
||||
1044
pnpm-lock.yaml
generated
@@ -14,7 +14,7 @@
|
||||
"user_id": "1028766474441803",
|
||||
"sec_uid": "MS4wLjABAAAAonK7FndgFYn4mKBQwHc34iEiCCwvBI3tXNqGXqd18qFM9p_ZSxC1y9Gyv1e0XuG_",
|
||||
"short_user_id": "3643612610",
|
||||
"user_unique_id": "LL991221.z",
|
||||
"user_unique_id": "LL991221",
|
||||
"user_signature": "🧣我才是岚岚s\nX Eva: 我才是岚岚\n仅此一个抖音号(谨防被骗)",
|
||||
"nickname": "我才是岚岚",
|
||||
"avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_78ecf820d31560e298e32684589c00b3.jpeg?from=2956013662",
|
||||
@@ -168,7 +168,7 @@
|
||||
"user_id": "1028766474441803",
|
||||
"sec_uid": "MS4wLjABAAAAonK7FndgFYn4mKBQwHc34iEiCCwvBI3tXNqGXqd18qFM9p_ZSxC1y9Gyv1e0XuG_",
|
||||
"short_user_id": "3643612610",
|
||||
"user_unique_id": "LL991221.z",
|
||||
"user_unique_id": "LL991221",
|
||||
"user_signature": "🧣我才是岚岚s\nX Eva: 我才是岚岚\n仅此一个抖音号(谨防被骗)",
|
||||
"nickname": "我才是岚岚",
|
||||
"avatar": "https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_78ecf820d31560e298e32684589c00b3.jpeg?from=2956013662",
|
||||
|
||||
@@ -137,6 +137,9 @@
|
||||
},
|
||||
"interact_info": { "liked_count": "2147", "liked": false },
|
||||
"cover": { "url_default": "daZ662BX0lVMTq0Y6hIwR.png" },
|
||||
"image_list": [
|
||||
{ "info_list": [{ "url": "daZ662BX0lVMTq0Y6hIwR.png" }] }
|
||||
],
|
||||
"type": "normal",
|
||||
"display_title": "无所谓了,互联网没有我在乎的人"
|
||||
},
|
||||
@@ -176,7 +179,10 @@
|
||||
"nickname": "芙•"
|
||||
},
|
||||
"interact_info": { "liked": false, "liked_count": "1966" },
|
||||
"cover": { "url_default": "4a4TtExpq7RXnKK3hylKU.png" }
|
||||
"cover": { "url_default": "4a4TtExpq7RXnKK3hylKU.png" },
|
||||
"image_list": [
|
||||
{ "info_list": [{ "url": "4a4TtExpq7RXnKK3hylKU.png" }] }
|
||||
]
|
||||
},
|
||||
"track_id": "2d0udv0xvhqne0t0nmd83",
|
||||
"ignore": false,
|
||||
@@ -233,6 +239,9 @@
|
||||
},
|
||||
"interact_info": { "liked": false, "liked_count": "1005" },
|
||||
"cover": { "url_default": "dg16eh25m2SSI9Hc1fJuE.png" },
|
||||
"image_list": [
|
||||
{ "info_list": [{ "url": "dg16eh25m2SSI9Hc1fJuE.png" }] }
|
||||
],
|
||||
"type": "normal",
|
||||
"display_title": "拍旗袍没人看 办公室随手拍就有流量是吧"
|
||||
},
|
||||
@@ -321,7 +330,10 @@
|
||||
"nick_name": "林木婷子"
|
||||
},
|
||||
"interact_info": { "liked": false, "liked_count": "2700" },
|
||||
"cover": { "url_default": "z-XSh-g9MhKpYrFDHcxQT.png" }
|
||||
"cover": { "url_default": "z-XSh-g9MhKpYrFDHcxQT.png" },
|
||||
"image_list": [
|
||||
{ "info_list": [{ "url": "z-XSh-g9MhKpYrFDHcxQT.png" }] }
|
||||
]
|
||||
},
|
||||
"track_id": "2d0udv0xvhqne0t0nmd83"
|
||||
},
|
||||
@@ -629,7 +641,10 @@
|
||||
"user_id": "5bf37f6651783a194c1e1da2"
|
||||
},
|
||||
"interact_info": { "liked": false, "liked_count": "119" },
|
||||
"cover": { "url_default": "UaxqkI4aZ5LDu7k8KLw48.png" }
|
||||
"cover": { "url_default": "UaxqkI4aZ5LDu7k8KLw48.png" },
|
||||
"image_list": [
|
||||
{ "info_list": [{ "url": "UaxqkI4aZ5LDu7k8KLw48.png" }] }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -888,6 +903,7 @@
|
||||
},
|
||||
"interact_info": { "liked": false, "liked_count": "1786" },
|
||||
"cover": { "url_default": "n0SAcEY5gLucU7Ik7yP2P.png" },
|
||||
"image_list": [{ "info_list": [{ "url": "n0SAcEY5gLucU7Ik7yP2P.png" }] }],
|
||||
"type": "normal"
|
||||
},
|
||||
"track_id": "2d0udv0xvhqne0t0nmd83",
|
||||
@@ -936,6 +952,9 @@
|
||||
"model_type": "note",
|
||||
"note_card": {
|
||||
"cover": { "url_default": "Sf5mzf68e2GwZHZv7h1G2.png" },
|
||||
"image_list": [
|
||||
{ "info_list": [{ "url": "Sf5mzf68e2GwZHZv7h1G2.png" }] }
|
||||
],
|
||||
"type": "normal",
|
||||
"display_title": "行吧,我重发 ",
|
||||
"user": {
|
||||
|
||||
@@ -400,163 +400,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"aweme_id": "7346191904205327631",
|
||||
"desc": "我的女友",
|
||||
"create_time": 1710418599,
|
||||
"music": {
|
||||
"id": 7346191940615933000,
|
||||
"title": "@条子创作的原声",
|
||||
"author": "条子",
|
||||
"cover_medium": {
|
||||
"uri": "720x720/aweme-avatar/tos-cn-avt-0015_75c5ef8973e1d665252306ea58f7d10b",
|
||||
"url_list": [
|
||||
"https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_75c5ef8973e1d665252306ea58f7d10b.jpeg?from=116350172"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"cover_thumb": {
|
||||
"uri": "100x100/aweme-avatar/tos-cn-avt-0015_75c5ef8973e1d665252306ea58f7d10b",
|
||||
"url_list": [
|
||||
"https://p3-pc.douyinpic.com/aweme/100x100/aweme-avatar/tos-cn-avt-0015_75c5ef8973e1d665252306ea58f7d10b.jpeg?from=116350172"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"play_url": {
|
||||
"uri": "https://sf5-hl-cdn-tos.douyinstatic.com/obj/ies-music/7346191933737356059.mp3",
|
||||
"url_list": [
|
||||
"https://sf5-hl-cdn-tos.douyinstatic.com/obj/ies-music/7346191933737356059.mp3",
|
||||
"https://sf6-cdn-tos.douyinstatic.com/obj/ies-music/7346191933737356059.mp3"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720,
|
||||
"url_key": "7346191940615932722"
|
||||
},
|
||||
"duration": 7,
|
||||
"user_count": 0,
|
||||
"owner_id": "83160533866",
|
||||
"owner_nickname": "条子",
|
||||
"is_original": false
|
||||
},
|
||||
"video": {
|
||||
"play_addr": {
|
||||
"uri": "v0300fg10000cnpel5bc77u9pqctt5hg",
|
||||
"url_list": [
|
||||
"https://www.douyin.com/aweme/v1/play/?video_id=v0300fg10000cnpel5bc77u9pqctt5hg&line=0&file_id=0ab371e7a5ac419fb88d8b616f1b9a70&sign=c689ddfb4d16aec5ae605822e5b2c4f6&is_play_url=1&source=PackSourceEnum_PUBLISH"
|
||||
],
|
||||
"width": 1870,
|
||||
"height": 1052,
|
||||
"url_key": "v0300fg10000cnpel5bc77u9pqctt5hg_h264_1080p_2690491",
|
||||
"data_size": 2467517,
|
||||
"file_hash": "c689ddfb4d16aec5ae605822e5b2c4f6",
|
||||
"file_cs": "c:0-7442-daae|d:0-1233757-8a1a,1233758-2467516-f27e|a:v0300fg10000cnpel5bc77u9pqctt5hg"
|
||||
},
|
||||
"cover": {
|
||||
"uri": "tos-cn-i-0813/owgAVhXilAAZ6yTAAzCsDnqefAVEDIN9qALqyg",
|
||||
"url_list": ["Ig9gQdI0FjieZ_SnquVR-.png"],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"height": 1052,
|
||||
"width": 1870,
|
||||
"ratio": "1080p",
|
||||
"use_static_cover": true,
|
||||
"duration": 7337,
|
||||
"horizontal_type": 1
|
||||
},
|
||||
"share_url": "https://www.iesdouyin.com/share/video/7346191904205327631/?region=CN&mid=7346191940615932722&u_code=13kgm680k&did=MS4wLjABAAAAiOgYyZm8XbWZMr5o3OvhR-TEOuNygb_hQOwkie-VXJpDYaR4vZfpiIGBfAWKCFHB&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&with_sec_did=1&titleType=title&share_sign=GBtbcDEKOakwrzsCjMztEGgSeaHW_xrERQNESLGFXFw-&share_version=170400&ts=1710489511&from_aid=6383&from_ssr=1",
|
||||
"statistics": {
|
||||
"admire_count": 1,
|
||||
"comment_count": 602,
|
||||
"digg_count": 17913,
|
||||
"collect_count": 2393,
|
||||
"play_count": 0,
|
||||
"share_count": 13227
|
||||
},
|
||||
"status": {
|
||||
"listen_video_status": 0,
|
||||
"is_delete": false,
|
||||
"allow_share": true,
|
||||
"is_prohibited": false,
|
||||
"in_reviewing": false,
|
||||
"part_see": 0,
|
||||
"private_status": 0,
|
||||
"review_result": {
|
||||
"review_status": 0
|
||||
}
|
||||
},
|
||||
"text_extra": [],
|
||||
"is_top": 0,
|
||||
"share_info": {
|
||||
"share_url": "https://www.iesdouyin.com/share/video/7346191904205327631/?region=CN&mid=7346191940615932722&u_code=13kgm680k&did=MS4wLjABAAAAiOgYyZm8XbWZMr5o3OvhR-TEOuNygb_hQOwkie-VXJpDYaR4vZfpiIGBfAWKCFHB&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&with_sec_did=1&titleType=title&share_sign=GBtbcDEKOakwrzsCjMztEGgSeaHW_xrERQNESLGFXFw-&share_version=170400&ts=1710489511&from_aid=6383&from_ssr=1",
|
||||
"share_link_desc": "2.89 G@V.LW mDU:/ 11/22 我的女友 %s 复制此链接,打开Dou音搜索,直接观看视频!"
|
||||
},
|
||||
"duration": 7337,
|
||||
"image_infos": null,
|
||||
"risk_infos": {
|
||||
"vote": false,
|
||||
"warn": false,
|
||||
"risk_sink": false,
|
||||
"type": 0,
|
||||
"content": ""
|
||||
},
|
||||
"position": null,
|
||||
"author_user_id": 83160533866,
|
||||
"prevent_download": false,
|
||||
"long_video": null,
|
||||
"aweme_control": {
|
||||
"can_forward": true,
|
||||
"can_share": true,
|
||||
"can_comment": true,
|
||||
"can_show_comment": true
|
||||
},
|
||||
"images": null,
|
||||
"suggest_words": {
|
||||
"suggest_words": [
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "条子被黑社会堵到超市后续",
|
||||
"word_id": "7129083023886554400",
|
||||
"info": "{\"qrec_for_search\":\"{}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "comment_top_rec",
|
||||
"icon_url": "",
|
||||
"hint_text": "大家都在搜:",
|
||||
"extra_info": "{}"
|
||||
},
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "埃安y plus70乐享版",
|
||||
"word_id": "7147998601692026127",
|
||||
"info": "{\"qrec_for_search\":\"{\\\"query_ecom\\\":\\\"1\\\"}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "feed_bottom_rec",
|
||||
"icon_url": "",
|
||||
"hint_text": "相关搜索",
|
||||
"extra_info": "{}"
|
||||
},
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "条子被黑社会堵到超市后续",
|
||||
"word_id": "7129083023886554400",
|
||||
"info": "{\"qrec_for_search\":\"{}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "detail_inbox_rex",
|
||||
"icon_url": "",
|
||||
"hint_text": "",
|
||||
"extra_info": "{}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"aweme_id": "7345439525113433384",
|
||||
"desc": "",
|
||||
|
||||
@@ -455,160 +455,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"aweme_id": "7346457849054235913",
|
||||
"desc": "#小蛮腰马甲线 他居然把脸转过去",
|
||||
"create_time": 1710480523,
|
||||
"music": {
|
||||
"id": 7346457898803154000,
|
||||
"title": "@奶茶妹◕🌱创作的原声",
|
||||
"author": "奶茶妹◕🌱",
|
||||
"cover_medium": {
|
||||
"uri": "720x720/aweme-avatar/tos-cn-avt-0015_c7784af3a0bd2e7c5fc92ece450f12e0",
|
||||
"url_list": [
|
||||
"https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_c7784af3a0bd2e7c5fc92ece450f12e0.jpeg?from=116350172"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"cover_thumb": {
|
||||
"uri": "100x100/aweme-avatar/tos-cn-avt-0015_c7784af3a0bd2e7c5fc92ece450f12e0",
|
||||
"url_list": [
|
||||
"https://p3-pc.douyinpic.com/aweme/100x100/aweme-avatar/tos-cn-avt-0015_c7784af3a0bd2e7c5fc92ece450f12e0.jpeg?from=116350172"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"play_url": {
|
||||
"uri": "https://sf5-hl-cdn-tos.douyinstatic.com/obj/ies-music/7346457902686882570.mp3",
|
||||
"url_list": [
|
||||
"https://sf5-hl-cdn-tos.douyinstatic.com/obj/ies-music/7346457902686882570.mp3",
|
||||
"https://sf6-cdn-tos.douyinstatic.com/obj/ies-music/7346457902686882570.mp3"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720,
|
||||
"url_key": "7346457898803153673"
|
||||
},
|
||||
"duration": 11,
|
||||
"user_count": 0,
|
||||
"owner_id": "62790495105",
|
||||
"owner_nickname": "奶茶妹◕🌱",
|
||||
"is_original": false
|
||||
},
|
||||
"video": {
|
||||
"play_addr": {
|
||||
"uri": "v0d00fg10000cnptoirc77u3atmcsskg",
|
||||
"url_list": [
|
||||
"https://www.douyin.com/aweme/v1/play/?video_id=v0d00fg10000cnptoirc77u3atmcsskg&line=0&file_id=3b2bcdfe44f0468aacc265bb778c2e2c&sign=fccec71c7a910d752161cb82b89c8474&is_play_url=1&source=PackSourceEnum_PUBLISH"
|
||||
],
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"url_key": "v0d00fg10000cnptoirc77u3atmcsskg_h264_1080p_2062873",
|
||||
"data_size": 3017468,
|
||||
"file_hash": "fccec71c7a910d752161cb82b89c8474",
|
||||
"file_cs": "c:0-10842-2631|d:0-1508733-7add,1508734-3017467-357f|a:v0d00fg10000cnptoirc77u3atmcsskg"
|
||||
},
|
||||
"cover": {
|
||||
"uri": "tos-cn-i-0813c001/oIzsg8zAAIlAfDH5ANtC9BE6IANk3eAyAhAn0E",
|
||||
"url_list": ["R3o8KAoPnLSiZLkSs66H3.png"],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"height": 1920,
|
||||
"width": 1080,
|
||||
"ratio": "1080p",
|
||||
"use_static_cover": true,
|
||||
"duration": 11702
|
||||
},
|
||||
"share_url": "https://www.iesdouyin.com/share/video/7346457849054235913/?region=CN&mid=7346457898803153673&u_code=13kgm680k&did=MS4wLjABAAAAiOgYyZm8XbWZMr5o3OvhR-TEOuNygb_hQOwkie-VXJpDYaR4vZfpiIGBfAWKCFHB&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&with_sec_did=1&titleType=title&share_sign=KmI.XiEwwiCE.iLBUp3QNtoJoUyXFirMExGYSa9SBI8-&share_version=170400&ts=1710488702&from_aid=6383&from_ssr=1",
|
||||
"statistics": {
|
||||
"admire_count": 0,
|
||||
"comment_count": 20,
|
||||
"digg_count": 1572,
|
||||
"collect_count": 137,
|
||||
"play_count": 0,
|
||||
"share_count": 112
|
||||
},
|
||||
"status": {
|
||||
"listen_video_status": 0,
|
||||
"is_delete": false,
|
||||
"allow_share": true,
|
||||
"is_prohibited": false,
|
||||
"in_reviewing": false,
|
||||
"part_see": 0,
|
||||
"private_status": 0,
|
||||
"review_result": {
|
||||
"review_status": 0
|
||||
}
|
||||
},
|
||||
"text_extra": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 7,
|
||||
"type": 1,
|
||||
"hashtag_name": "小蛮腰马甲线",
|
||||
"hashtag_id": "1622790756032686",
|
||||
"is_commerce": false,
|
||||
"caption_start": 0,
|
||||
"caption_end": 7
|
||||
}
|
||||
],
|
||||
"is_top": 0,
|
||||
"share_info": {
|
||||
"share_url": "https://www.iesdouyin.com/share/video/7346457849054235913/?region=CN&mid=7346457898803153673&u_code=13kgm680k&did=MS4wLjABAAAAiOgYyZm8XbWZMr5o3OvhR-TEOuNygb_hQOwkie-VXJpDYaR4vZfpiIGBfAWKCFHB&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&with_sec_did=1&titleType=title&share_sign=KmI.XiEwwiCE.iLBUp3QNtoJoUyXFirMExGYSa9SBI8-&share_version=170400&ts=1710488702&from_aid=6383&from_ssr=1",
|
||||
"share_link_desc": "8.99 09/14 P@X.MJ dAT:/ # 小蛮腰马甲线 他居然把脸转过去 %s 复制此链接,打开Dou音搜索,直接观看视频!"
|
||||
},
|
||||
"duration": 11702,
|
||||
"image_infos": null,
|
||||
"risk_infos": {
|
||||
"vote": false,
|
||||
"warn": false,
|
||||
"risk_sink": false,
|
||||
"type": 0,
|
||||
"content": ""
|
||||
},
|
||||
"position": null,
|
||||
"author_user_id": 62790495105,
|
||||
"prevent_download": false,
|
||||
"long_video": null,
|
||||
"aweme_control": {
|
||||
"can_forward": true,
|
||||
"can_share": true,
|
||||
"can_comment": true,
|
||||
"can_show_comment": true
|
||||
},
|
||||
"images": null,
|
||||
"suggest_words": {
|
||||
"suggest_words": [
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "章若楠",
|
||||
"word_id": "6585508016810890499",
|
||||
"info": "{\"qrec_for_search\":\"{}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "comment_top_rec",
|
||||
"icon_url": "",
|
||||
"hint_text": "大家都在搜:",
|
||||
"extra_info": "{}"
|
||||
},
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "章若楠",
|
||||
"word_id": "6585508016810890499",
|
||||
"info": "{\"qrec_for_search\":\"{}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "detail_inbox_rex",
|
||||
"icon_url": "",
|
||||
"hint_text": "",
|
||||
"extra_info": "{}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"aweme_id": "7346190579807702310",
|
||||
"desc": "#背影杀 哈~",
|
||||
|
||||
@@ -449,184 +449,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"aweme_id": "7345796955571784997",
|
||||
"desc": "体验调酒师的一天🍸 Bartender\n#微醺时刻 #记录生活",
|
||||
"create_time": 1710326646,
|
||||
"music": {
|
||||
"id": 7023044224681511000,
|
||||
"title": "Starboy (抖音热播)",
|
||||
"author": "佐助",
|
||||
"cover_medium": {
|
||||
"uri": "tos-cn-v-2774c002/b3583f56f54e4451b7d301187585bcdb",
|
||||
"url_list": [
|
||||
"https://p11.douyinpic.com/aweme/200x200/tos-cn-v-2774c002/b3583f56f54e4451b7d301187585bcdb.jpeg",
|
||||
"https://p3.douyinpic.com/aweme/200x200/tos-cn-v-2774c002/b3583f56f54e4451b7d301187585bcdb.jpeg"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"cover_thumb": {
|
||||
"uri": "tos-cn-v-2774c002/b3583f56f54e4451b7d301187585bcdb",
|
||||
"url_list": [
|
||||
"https://p11.douyinpic.com/aweme/100x100/tos-cn-v-2774c002/b3583f56f54e4451b7d301187585bcdb.jpeg",
|
||||
"https://p3.douyinpic.com/aweme/100x100/tos-cn-v-2774c002/b3583f56f54e4451b7d301187585bcdb.jpeg"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"play_url": {
|
||||
"uri": "https://sf5-hl-cdn-tos.douyinstatic.com/obj/tos-cn-ve-2774/ogjEQCutCnDe2gAG2KGBb8ieUACWSoFhZRgMQg",
|
||||
"url_list": [
|
||||
"https://sf5-hl-cdn-tos.douyinstatic.com/obj/tos-cn-ve-2774/ogjEQCutCnDe2gAG2KGBb8ieUACWSoFhZRgMQg",
|
||||
"https://sf3-cdn-tos.douyinstatic.com/obj/tos-cn-ve-2774/ogjEQCutCnDe2gAG2KGBb8ieUACWSoFhZRgMQg"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720,
|
||||
"url_key": "7023044224681510949"
|
||||
},
|
||||
"duration": 194,
|
||||
"user_count": 0,
|
||||
"owner_nickname": "",
|
||||
"is_original": false
|
||||
},
|
||||
"video": {
|
||||
"play_addr": {
|
||||
"uri": "v0d00fg10000cnoo6mjc77ubta3125dg",
|
||||
"url_list": [
|
||||
"https://www.douyin.com/aweme/v1/play/?video_id=v0d00fg10000cnoo6mjc77ubta3125dg&line=0&file_id=b4fc9157732e4eee81b0d3d071b4d32f&sign=8057edcee0fd27ffce9615b1048d2e2c&is_play_url=1&source=PackSourceEnum_PUBLISH"
|
||||
],
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"url_key": "v0d00fg10000cnoo6mjc77ubta3125dg_h264_1080p_2264177",
|
||||
"data_size": 5443649,
|
||||
"file_hash": "8057edcee0fd27ffce9615b1048d2e2c",
|
||||
"file_cs": "c:0-17318-d0c8|d:0-2721823-7d6d,2721824-5443648-e625|a:v0d00fg10000cnoo6mjc77ubta3125dg"
|
||||
},
|
||||
"cover": {
|
||||
"uri": "tos-cn-i-0813c001/oo3M3ze0IAN67MAAJAghvhGARED9gAfElxC0BN",
|
||||
"url_list": ["2ww4W1Pu86xeLjl07K28u.png"],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"height": 1920,
|
||||
"width": 1080,
|
||||
"ratio": "1080p",
|
||||
"use_static_cover": true,
|
||||
"duration": 19234
|
||||
},
|
||||
"share_url": "https://www.iesdouyin.com/share/video/7345796955571784997/?region=CN&mid=7023044224681510949&u_code=13kgm680k&did=MS4wLjABAAAAiOgYyZm8XbWZMr5o3OvhR-TEOuNygb_hQOwkie-VXJpDYaR4vZfpiIGBfAWKCFHB&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&with_sec_did=1&titleType=title&share_sign=XUuhtQ9LwR_2_xNHoNSw2nfWpq_SiDhebnwzUPGq.Yw-&share_version=170400&ts=1710491939&from_aid=6383&from_ssr=1",
|
||||
"statistics": {
|
||||
"admire_count": 59,
|
||||
"comment_count": 1644,
|
||||
"digg_count": 256919,
|
||||
"collect_count": 5279,
|
||||
"play_count": 0,
|
||||
"share_count": 8961
|
||||
},
|
||||
"status": {
|
||||
"listen_video_status": 0,
|
||||
"is_delete": false,
|
||||
"allow_share": true,
|
||||
"is_prohibited": false,
|
||||
"in_reviewing": false,
|
||||
"part_see": 0,
|
||||
"private_status": 0,
|
||||
"review_result": {
|
||||
"review_status": 0
|
||||
}
|
||||
},
|
||||
"text_extra": [
|
||||
{
|
||||
"start": 21,
|
||||
"end": 26,
|
||||
"type": 1,
|
||||
"hashtag_name": "微醺时刻",
|
||||
"hashtag_id": "1647996185380875",
|
||||
"is_commerce": false,
|
||||
"caption_start": 21,
|
||||
"caption_end": 26
|
||||
},
|
||||
{
|
||||
"start": 27,
|
||||
"end": 32,
|
||||
"type": 1,
|
||||
"hashtag_name": "记录生活",
|
||||
"hashtag_id": "1767586437643341",
|
||||
"is_commerce": false,
|
||||
"caption_start": 27,
|
||||
"caption_end": 32
|
||||
}
|
||||
],
|
||||
"is_top": 0,
|
||||
"share_info": {
|
||||
"share_url": "https://www.iesdouyin.com/share/video/7345796955571784997/?region=CN&mid=7023044224681510949&u_code=13kgm680k&did=MS4wLjABAAAAiOgYyZm8XbWZMr5o3OvhR-TEOuNygb_hQOwkie-VXJpDYaR4vZfpiIGBfAWKCFHB&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&with_sec_did=1&titleType=title&share_sign=XUuhtQ9LwR_2_xNHoNSw2nfWpq_SiDhebnwzUPGq.Yw-&share_version=170400&ts=1710491939&from_aid=6383&from_ssr=1",
|
||||
"share_link_desc": "5.10 02/04 z@t.eo GvF:/ 体验调酒师的一天🍸 Bartender # 微醺时刻 # 记录生活 %s 复制此链接,打开Dou音搜索,直接观看视频!"
|
||||
},
|
||||
"duration": 19234,
|
||||
"image_infos": null,
|
||||
"risk_infos": {
|
||||
"vote": false,
|
||||
"warn": false,
|
||||
"risk_sink": false,
|
||||
"type": 0,
|
||||
"content": ""
|
||||
},
|
||||
"position": null,
|
||||
"author_user_id": 24058267831,
|
||||
"prevent_download": false,
|
||||
"long_video": null,
|
||||
"aweme_control": {
|
||||
"can_forward": true,
|
||||
"can_share": true,
|
||||
"can_comment": true,
|
||||
"can_show_comment": true
|
||||
},
|
||||
"images": null,
|
||||
"suggest_words": {
|
||||
"suggest_words": [
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "冰淇淋kiki",
|
||||
"word_id": "6732949666079446276",
|
||||
"info": "{\"qrec_for_search\":\"{}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "comment_top_rec",
|
||||
"icon_url": "",
|
||||
"hint_text": "大家都在搜:",
|
||||
"extra_info": "{}"
|
||||
},
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "调酒师工资一般是多少",
|
||||
"word_id": "6572390336566400259",
|
||||
"info": "{\"qrec_for_search\":\"{}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "feed_bottom_rec",
|
||||
"icon_url": "",
|
||||
"hint_text": "相关搜索",
|
||||
"extra_info": "{}"
|
||||
},
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "冰淇淋kiki",
|
||||
"word_id": "6732949666079446276",
|
||||
"info": "{\"qrec_for_search\":\"{}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "detail_inbox_rex",
|
||||
"icon_url": "",
|
||||
"hint_text": "",
|
||||
"extra_info": "{}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"aweme_id": "7345057414233836850",
|
||||
"desc": "开在酒瓶里的春天呀🌷🌸",
|
||||
|
||||
@@ -425,149 +425,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"aweme_id": "7338089394705894682",
|
||||
"desc": "陈年老库存🐿️",
|
||||
"create_time": 1708532099,
|
||||
"music": {
|
||||
"id": 7338089490520509000,
|
||||
"title": "@周子然JingYi创作的原声",
|
||||
"author": "周子然JingYi",
|
||||
"cover_medium": {
|
||||
"uri": "720x720/aweme-avatar/tos-cn-avt-0015_f59bfced5c6a3b56d152f1e0437f06ec",
|
||||
"url_list": [
|
||||
"https://p3-pc.douyinpic.com/aweme/720x720/aweme-avatar/tos-cn-avt-0015_f59bfced5c6a3b56d152f1e0437f06ec.jpeg?from=116350172"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"cover_thumb": {
|
||||
"uri": "100x100/aweme-avatar/tos-cn-avt-0015_f59bfced5c6a3b56d152f1e0437f06ec",
|
||||
"url_list": [
|
||||
"https://p3-pc.douyinpic.com/aweme/100x100/aweme-avatar/tos-cn-avt-0015_f59bfced5c6a3b56d152f1e0437f06ec.jpeg?from=116350172"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"play_url": {
|
||||
"uri": "https://sf6-cdn-tos.douyinstatic.com/obj/ies-music/7338089476511550234.mp3",
|
||||
"url_list": [
|
||||
"https://sf6-cdn-tos.douyinstatic.com/obj/ies-music/7338089476511550234.mp3",
|
||||
"https://sf5-hl-cdn-tos.douyinstatic.com/obj/ies-music/7338089476511550234.mp3"
|
||||
],
|
||||
"width": 720,
|
||||
"height": 720,
|
||||
"url_key": "7338089490520509235"
|
||||
},
|
||||
"duration": 9,
|
||||
"user_count": 0,
|
||||
"owner_id": "62839305427",
|
||||
"owner_nickname": "周子然JingYi",
|
||||
"is_original": false
|
||||
},
|
||||
"video": {
|
||||
"play_addr": {
|
||||
"uri": "v0300fg10000cnb217rc77uah3ri5o00",
|
||||
"url_list": [
|
||||
"https://www.douyin.com/aweme/v1/play/?video_id=v0300fg10000cnb217rc77uah3ri5o00&line=0&file_id=d0de6a0d7fc0498a812e8527e94a887a&sign=ab93708a219baa2ffde338e8bf652e0d&is_play_url=1&source=PackSourceEnum_PUBLISH"
|
||||
],
|
||||
"width": 1080,
|
||||
"height": 1920,
|
||||
"url_key": "v0300fg10000cnb217rc77uah3ri5o00_h264_1080p_2556391",
|
||||
"data_size": 3078535,
|
||||
"file_hash": "ab93708a219baa2ffde338e8bf652e0d",
|
||||
"file_cs": "c:0-9310-07ec|d:0-1539266-16e6,1539267-3078534-e6cd|a:v0300fg10000cnb217rc77uah3ri5o00"
|
||||
},
|
||||
"cover": {
|
||||
"uri": "tos-cn-i-0813c001/oAiCAVAbDWOleAAIEGYb9IengDrADCAjAW1pIA",
|
||||
"url_list": ["7SFxN5HcILUxUdJe-EXfX.png"],
|
||||
"width": 720,
|
||||
"height": 720
|
||||
},
|
||||
"height": 3840,
|
||||
"width": 2160,
|
||||
"ratio": "1080p",
|
||||
"use_static_cover": true,
|
||||
"duration": 9634
|
||||
},
|
||||
"share_url": "https://www.iesdouyin.com/share/video/7338089394705894682/?region=CN&mid=7338089490520509235&u_code=13kgm680k&did=MS4wLjABAAAAiOgYyZm8XbWZMr5o3OvhR-TEOuNygb_hQOwkie-VXJpDYaR4vZfpiIGBfAWKCFHB&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&with_sec_did=1&titleType=title&share_sign=ig8SDm7Oq1NBxQweWS4eMzrdjlHmjkr7WIazmFWKoZY-&share_version=170400&ts=1710490324&from_aid=6383&from_ssr=1",
|
||||
"statistics": {
|
||||
"admire_count": 2,
|
||||
"comment_count": 149,
|
||||
"digg_count": 6367,
|
||||
"collect_count": 635,
|
||||
"play_count": 0,
|
||||
"share_count": 784
|
||||
},
|
||||
"status": {
|
||||
"listen_video_status": 0,
|
||||
"is_delete": false,
|
||||
"allow_share": true,
|
||||
"is_prohibited": false,
|
||||
"in_reviewing": false,
|
||||
"part_see": 0,
|
||||
"private_status": 0,
|
||||
"review_result": {
|
||||
"review_status": 0
|
||||
}
|
||||
},
|
||||
"text_extra": [],
|
||||
"is_top": 0,
|
||||
"share_info": {
|
||||
"share_url": "https://www.iesdouyin.com/share/video/7338089394705894682/?region=CN&mid=7338089490520509235&u_code=13kgm680k&did=MS4wLjABAAAAiOgYyZm8XbWZMr5o3OvhR-TEOuNygb_hQOwkie-VXJpDYaR4vZfpiIGBfAWKCFHB&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&with_sec_did=1&titleType=title&share_sign=ig8SDm7Oq1NBxQweWS4eMzrdjlHmjkr7WIazmFWKoZY-&share_version=170400&ts=1710490324&from_aid=6383&from_ssr=1",
|
||||
"share_link_desc": "3.07 CUL:/ 05/15 n@Q.kp 陈年老库存🐿️ %s 复制此链接,打开Dou音搜索,直接观看视频!"
|
||||
},
|
||||
"duration": 9634,
|
||||
"image_infos": null,
|
||||
"risk_infos": {
|
||||
"vote": false,
|
||||
"warn": false,
|
||||
"risk_sink": false,
|
||||
"type": 0,
|
||||
"content": ""
|
||||
},
|
||||
"position": null,
|
||||
"author_user_id": 62839305427,
|
||||
"prevent_download": false,
|
||||
"long_video": null,
|
||||
"aweme_control": {
|
||||
"can_forward": true,
|
||||
"can_share": true,
|
||||
"can_comment": true,
|
||||
"can_show_comment": true
|
||||
},
|
||||
"images": null,
|
||||
"suggest_words": {
|
||||
"suggest_words": [
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "周子然早期照片",
|
||||
"word_id": "6900178207296673032",
|
||||
"info": "{\"qrec_for_search\":\"{}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "comment_top_rec",
|
||||
"icon_url": "",
|
||||
"hint_text": "大家都在搜:",
|
||||
"extra_info": "{}"
|
||||
},
|
||||
{
|
||||
"words": [
|
||||
{
|
||||
"word": "我有个朋友周子然",
|
||||
"word_id": "6925278082983957772",
|
||||
"info": "{\"qrec_for_search\":\"{}\"}"
|
||||
}
|
||||
],
|
||||
"scene": "detail_inbox_rex",
|
||||
"icon_url": "",
|
||||
"hint_text": "",
|
||||
"extra_info": "{}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"aweme_id": "7322483619946040585",
|
||||
"desc": "🍦。我是难伺候的小雪糕\n没有礼貌,随时在考虑化掉",
|
||||
|
||||
@@ -693,7 +693,7 @@
|
||||
"signature": "🧣我才是岚岚s\nX Eva: 我才是岚岚\n仅此一个抖音号(谨防被骗)",
|
||||
"total_favorited": 16475958,
|
||||
"uid": "1028766474441803",
|
||||
"unique_id": "LL991221.z",
|
||||
"unique_id": "LL991221",
|
||||
"user_age": 23,
|
||||
"white_cover_url": [
|
||||
{
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 61 KiB |
BIN
public/images/out1.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
public/images/out2.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/images/out3.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
public/images/out4.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
public/images/out6.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
public/images/out7.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
public/images/out8.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
public/images/out9.jpg
Normal file
|
After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 47 KiB |
41
src/App.vue
@@ -6,16 +6,17 @@
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
<BaseMask v-if="!isMobile" />
|
||||
<div v-if="!isMobile" class="guide">
|
||||
<Icon icon="mynaui:danger-triangle" />
|
||||
<div class="txt">
|
||||
<h2>切换至手机模式才可正常使用</h2>
|
||||
<h3>1. 按 F12 调出控制台</h3>
|
||||
<h3>2. 按 Ctrl+Shift+M,或点击下面图标</h3>
|
||||
</div>
|
||||
<img src="@/assets/img/guide.png" alt="" />
|
||||
<BaseMask v-if="!isMobile" @click="isMobile = true" />
|
||||
<div v-if="!isMobile" class="guide">
|
||||
<Icon class="danger" icon="mynaui:danger-triangle" />
|
||||
<Icon class="close" icon="simple-line-icons:close" @click="isMobile = true" />
|
||||
<div class="txt">
|
||||
<h2>切换至手机模式获取最佳体验</h2>
|
||||
<h3>1. 按 F12 调出控制台</h3>
|
||||
<h3>2. 按 Ctrl+Shift+M,或点击下面图标</h3>
|
||||
</div>
|
||||
<img src="@/assets/img/guide.png" alt="" />
|
||||
</div>
|
||||
<Call />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@@ -42,7 +43,7 @@ watch(
|
||||
() => route.path,
|
||||
(to, from) => {
|
||||
store.setMaskDialog({ state: false, mode: store.maskDialogMode })
|
||||
//footer下面的5个按钮,对跳不要用动画
|
||||
//底部tab的按钮,跳转是不需要用动画的
|
||||
let noAnimation = [
|
||||
'/',
|
||||
'/home',
|
||||
@@ -92,6 +93,15 @@ onMounted(() => {
|
||||
font-size: 14rem;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
#app {
|
||||
width: 500px !important;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.guide {
|
||||
color: white;
|
||||
z-index: 999;
|
||||
@@ -104,12 +114,21 @@ onMounted(() => {
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
.danger {
|
||||
margin-top: 10rem;
|
||||
font-size: 40rem;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.close {
|
||||
cursor: pointer;
|
||||
font-size: 18rem;
|
||||
color: white;
|
||||
position: absolute;
|
||||
right: 15rem;
|
||||
top: 15rem;
|
||||
}
|
||||
|
||||
.txt {
|
||||
text-align: left;
|
||||
padding: 0 24rem;
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import request from '../utils/request'
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
export function userinfo(params, data) {
|
||||
export function userinfo(params?: any, data?: any) {
|
||||
return request({ url: '/user/userinfo', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function userVideoList(params, data) {
|
||||
export function userVideoList(params?: any, data?: any) {
|
||||
return request({ url: '/user/video_list', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function panel(params, data) {
|
||||
export function panel(params?: any, data?: any) {
|
||||
return request({ url: '/user/panel', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function friends(params, data) {
|
||||
export function friends(params?: any, data?: any) {
|
||||
return request({ url: '/user/friends', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function userCollect(params, data) {
|
||||
export function userCollect(params?: any, data?: any) {
|
||||
return request({ url: '/user/collect', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function recommendedPost(params, data) {
|
||||
export function recommendedPost(params?: any, data?: any) {
|
||||
return request({ url: '/post/recommended', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function recommendedShop(params, data) {
|
||||
export function recommendedShop(params?: any, data?: any) {
|
||||
return request({ url: '/shop/recommended', method: 'get', params, data })
|
||||
}
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import request from '../utils/request'
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
export function historyOther(params, data) {
|
||||
export function historyOther(params?: any, data?: any) {
|
||||
return request({ url: '/video/historyOther', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function historyVideo(params, data) {
|
||||
export function historyVideo(params?: any, data?: any) {
|
||||
return request({ url: '/video/history', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function recommendedVideo(params, data) {
|
||||
export function recommendedVideo(params?: any, data?: any) {
|
||||
return request({ url: '/video/recommended', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function myVideo(params, data) {
|
||||
export function recommendedLongVideo(params?: any, data?: any) {
|
||||
return request({ url: '/video/long/recommended/', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function myVideo(params?: any, data?: any) {
|
||||
return request({ url: '/video/my', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function privateVideo(params, data) {
|
||||
export function privateVideo(params?: any, data?: any) {
|
||||
return request({ url: '/video/private', method: 'get', params, data })
|
||||
}
|
||||
|
||||
export function likeVideo(params, data) {
|
||||
export function likeVideo(params?: any, data?: any) {
|
||||
return request({ url: '/video/like', method: 'get', params, data })
|
||||
}
|
||||
export function videoComments(params, data) {
|
||||
|
||||
export function videoComments(params?: any, data?: any) {
|
||||
return request({ url: '/video/comments', method: 'get', params, data })
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
--color-me: rgb(21, 23, 35);
|
||||
--color-user: rgb(22, 24, 36);
|
||||
--color-message: rgb(21, 21, 21);
|
||||
--color-share-bg: rgb(38, 38, 38);
|
||||
--home-header-height: 44rem;
|
||||
--footer-height: 56rem;
|
||||
--common-header-height: 50rem;
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
<template>
|
||||
<img class="close" ref="img" :src="src" />
|
||||
<img class="close" ref="imgEl" :src="src" />
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Back',
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'gray'
|
||||
},
|
||||
img: {
|
||||
type: String,
|
||||
default: 'back'
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
scale: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { _css } from '@/utils/dom'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Back'
|
||||
})
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'gray'
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
img: {
|
||||
type: String,
|
||||
default: 'back'
|
||||
},
|
||||
computed: {
|
||||
src() {
|
||||
return new URL(`../assets/img/icon/components/${this.mode}-${this.img}.png`, import.meta.url)
|
||||
.href
|
||||
}
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
mounted() {
|
||||
this.$setCss(
|
||||
this.$refs.img,
|
||||
'transform',
|
||||
`rotate(${this.direction === 'left' ? '0' : '180'}deg) scale(${this.scale})`
|
||||
)
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
scale: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
}
|
||||
})
|
||||
|
||||
const imgEl = $ref()
|
||||
const src = $computed(() => {
|
||||
return new URL(`../assets/img/icon/components/${props.mode}-${props.img}.png`, import.meta.url)
|
||||
.href
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
_css(
|
||||
imgEl,
|
||||
'transform',
|
||||
`rotate(${props.direction === 'left' ? '0' : '180'}deg) scale(${props.scale})`
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -134,7 +134,7 @@ export default {
|
||||
}
|
||||
|
||||
&.dark2 {
|
||||
background: rgb(36, 36, 36);
|
||||
background: rgb(51, 51, 51);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@ export default {
|
||||
bus.off(EVENT_KEY.EXIT_FULLSCREEN)
|
||||
},
|
||||
methods: {
|
||||
$nav(path) {
|
||||
this.$router.push(path)
|
||||
},
|
||||
tab(index) {
|
||||
switch (index) {
|
||||
case 1:
|
||||
@@ -128,6 +131,7 @@ export default {
|
||||
}
|
||||
|
||||
.add-ctn {
|
||||
cursor: pointer;
|
||||
@height: 27rem;
|
||||
@width: 36rem;
|
||||
height: @height;
|
||||
@@ -147,6 +151,8 @@ export default {
|
||||
}
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
|
||||
font-weight: bold;
|
||||
opacity: 0.7;
|
||||
|
||||
@@ -156,8 +162,8 @@ export default {
|
||||
}
|
||||
|
||||
.badge {
|
||||
right: 10rem;
|
||||
top: 6rem;
|
||||
right: 14rem;
|
||||
top: 12rem;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,50 @@
|
||||
<template>
|
||||
<div class="music-wrapper">
|
||||
<div
|
||||
class="mute-icon"
|
||||
:class="showMutedNotice && 'notice'"
|
||||
v-click="() => bus.emit(EVENT_KEY.REMOVE_MUTED)"
|
||||
v-if="isMuted"
|
||||
>
|
||||
<div class="wrap">
|
||||
<Icon icon="flowbite:volume-mute-solid" />
|
||||
<span :style="{ opacity: showMutedNotice ? 1 : 0 }">取消静音</span>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
class="music"
|
||||
:src="props.item.music?.cover_thumb.url_list[0]"
|
||||
:src="item.music?.cover_thumb.url_list[0]"
|
||||
:style="style"
|
||||
@click.stop="
|
||||
bus.emit(EVENT_KEY.NAV, {
|
||||
path: '/home/music',
|
||||
query: { id: props.item.id }
|
||||
})
|
||||
v-click="
|
||||
() =>
|
||||
bus.emit(EVENT_KEY.NAV, {
|
||||
path: '/home/music',
|
||||
query: { id: item.aweme_id }
|
||||
})
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { computed, inject } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { inject, onMounted } from 'vue'
|
||||
import bus, { EVENT_KEY } from '@/utils/bus'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useClick } from '@/utils/hooks/useClick'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
const isPlaying = inject<boolean>('isPlaying')
|
||||
const isMuted = inject('isMuted')
|
||||
const item = inject<any>('item')
|
||||
const vClick = useClick()
|
||||
let showMutedNotice = $ref(window.showMutedNotice)
|
||||
|
||||
const isPlaying = inject('isPlaying')
|
||||
|
||||
const style = computed(() => {
|
||||
const style = $computed(() => {
|
||||
return { webkitAnimationPlayState: isPlaying.value ? 'running' : 'paused' }
|
||||
})
|
||||
onMounted(() => {
|
||||
bus.on(EVENT_KEY.HIDE_MUTED_NOTICE, () => {
|
||||
showMutedNotice = false
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@@ -38,11 +52,14 @@ const style = computed(() => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@w: 45rem;
|
||||
width: @w;
|
||||
height: @w;
|
||||
position: relative;
|
||||
|
||||
.music {
|
||||
border-radius: 50%;
|
||||
width: @w;
|
||||
height: @w;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -59,5 +76,43 @@ const style = computed(() => {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.mute-icon {
|
||||
.music;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
right: 0;
|
||||
background: white;
|
||||
animation: unset;
|
||||
color: black;
|
||||
transition: all 0.5s;
|
||||
overflow: hidden;
|
||||
|
||||
.wrap {
|
||||
width: 100rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.notice {
|
||||
border-radius: 50rem;
|
||||
width: 100rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 22rem;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 5rem;
|
||||
font-size: 13rem;
|
||||
word-break: keep-all;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div
|
||||
class="call-float"
|
||||
v-if="isSmall"
|
||||
v-if="state.isSmall"
|
||||
:style="callFloatStyle"
|
||||
@touchmove="touchmove"
|
||||
@touchend="touchend"
|
||||
@click="isSmall = false"
|
||||
@click="state.isSmall = false"
|
||||
>
|
||||
<img src="@/assets/img/icon/message/chat/call-float.png" alt="" />
|
||||
<span>呼叫中</span>
|
||||
@@ -14,134 +14,145 @@
|
||||
<transition name="scale">
|
||||
<div
|
||||
class="audio-call"
|
||||
:style="isSmall ? callFloatStyle : { zIndex: 10 }"
|
||||
:class="isSmall ? 'small' : ''"
|
||||
v-if="isShowAudioCall"
|
||||
:style="state.isSmall ? callFloatStyle : { zIndex: 10 }"
|
||||
:class="state.isSmall ? 'small' : ''"
|
||||
v-if="state.isShowAudioCall"
|
||||
>
|
||||
<div class="float">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<img @click="isSmall = true" src="@/assets/img/icon/message/chat/narrow.png" alt="" />
|
||||
<img
|
||||
@click="state.isSmall = true"
|
||||
src="@/assets/img/icon/message/chat/narrow.png"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<span class="center">等待对方接听...</span>
|
||||
<div class="right">
|
||||
<div class="option">
|
||||
<img
|
||||
v-show="!isOpenCamera"
|
||||
@click="isOpenCamera = !isOpenCamera"
|
||||
v-show="!state.isOpenCamera"
|
||||
@click="state.isOpenCamera = !state.isOpenCamera"
|
||||
src="@/assets/img/icon/message/chat/disabled-camera.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
v-show="isOpenCamera"
|
||||
@click="isOpenCamera = !isOpenCamera"
|
||||
v-show="state.isOpenCamera"
|
||||
@click="state.isOpenCamera = !state.isOpenCamera"
|
||||
src="@/assets/img/icon/message/chat/able-camera.png"
|
||||
alt=""
|
||||
/>
|
||||
<span>摄像头</span>
|
||||
</div>
|
||||
<div class="option" v-if="isExpand">
|
||||
<div class="option" v-if="state.isExpand">
|
||||
<img
|
||||
v-show="!isOpenAudio"
|
||||
@click="isOpenAudio = !isOpenAudio"
|
||||
v-show="!state.isOpenAudio"
|
||||
@click="state.isOpenAudio = !state.isOpenAudio"
|
||||
src="@/assets/img/icon/message/chat/disabled-volume.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
v-show="isOpenAudio"
|
||||
@click="isOpenAudio = !isOpenAudio"
|
||||
v-show="state.isOpenAudio"
|
||||
@click="state.isOpenAudio = !state.isOpenAudio"
|
||||
src="@/assets/img/icon/message/chat/able-volume.png"
|
||||
alt=""
|
||||
/>
|
||||
<span>免提</span>
|
||||
</div>
|
||||
<div class="option">
|
||||
<dy-back mode="light" @click="isExpand = !isExpand" img="back" class="shrink" />
|
||||
<dy-back
|
||||
mode="light"
|
||||
@click="state.isExpand = !state.isExpand"
|
||||
img="back"
|
||||
class="shrink"
|
||||
/>
|
||||
<!-- <img src="@/assets/img/icon/message/chat/narrow.png" alt="">-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img src="@/assets/img/icon/avatar/2.png" alt="" class="big-avatar" />
|
||||
<div class="footer">
|
||||
<img @click="isShowAudioCall = false" src="@/assets/img/icon/message/chat/call-end.png" />
|
||||
<img
|
||||
@click="state.isShowAudioCall = false"
|
||||
src="@/assets/img/icon/message/chat/call-end.png"
|
||||
/>
|
||||
<span>挂断</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script>
|
||||
import { inject } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, watch } from 'vue'
|
||||
import bus, { EVENT_KEY } from '@/utils/bus'
|
||||
|
||||
export default {
|
||||
name: 'Call',
|
||||
components: {},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
}
|
||||
defineOptions({
|
||||
name: 'Call'
|
||||
})
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mitt: inject('mitt'),
|
||||
callFloatTransitionTime: 300,
|
||||
callFloatLeft: 15,
|
||||
callFloatTop: 100,
|
||||
isOpenCamera: false,
|
||||
isOpenAudio: true,
|
||||
isExpand: true,
|
||||
isSmall: false,
|
||||
isShowAudioCall: false,
|
||||
height: 0,
|
||||
width: 0
|
||||
}
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
callFloatTransitionTime: 300,
|
||||
callFloatLeft: 15,
|
||||
callFloatTop: 100,
|
||||
isOpenCamera: false,
|
||||
isOpenAudio: true,
|
||||
isExpand: true,
|
||||
isSmall: false,
|
||||
isShowAudioCall: false,
|
||||
height: 0,
|
||||
width: 0
|
||||
})
|
||||
|
||||
const callFloatStyle = $computed(() => {
|
||||
return {
|
||||
'transition-duration': state.callFloatTransitionTime + 'ms',
|
||||
left: state.callFloatLeft + 'px',
|
||||
top: state.callFloatTop + 'px'
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => state.isShowAudioCall,
|
||||
(newVal) => {
|
||||
if (!newVal) {
|
||||
state.isOpenCamera = false
|
||||
state.isOpenAudio = true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
callFloatStyle() {
|
||||
return {
|
||||
'transition-duration': this.callFloatTransitionTime + 'ms',
|
||||
left: this.callFloatLeft + 'px',
|
||||
top: this.callFloatTop + 'px'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
bus.on(EVENT_KEY.SHOW_AUDIO_CALL, () => {
|
||||
if (state.isShowAudioCall) {
|
||||
state.isSmall = false
|
||||
} else {
|
||||
state.isShowAudioCall = true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isShowAudioCall(newVal) {
|
||||
if (!newVal) {
|
||||
this.isOpenCamera = false
|
||||
this.isOpenAudio = true
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
touchmove(e) {
|
||||
this.callFloatTransitionTime = 0
|
||||
this.callFloatLeft = e.touches[0].pageX - 35
|
||||
this.callFloatTop = e.touches[0].pageY - 40
|
||||
},
|
||||
touchend() {
|
||||
this.callFloatTransitionTime = 300
|
||||
if (this.callFloatLeft < this.width / 2) {
|
||||
this.callFloatLeft = 15
|
||||
} else {
|
||||
this.callFloatLeft = this.width - 15 - 70
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.mitt.on('showAudioCall', () => {
|
||||
if (this.isShowAudioCall) {
|
||||
this.isSmall = false
|
||||
} else {
|
||||
this.isShowAudioCall = true
|
||||
}
|
||||
})
|
||||
this.height = document.body.clientHeight
|
||||
this.width = document.body.clientWidth
|
||||
})
|
||||
state.height = document.body.clientHeight
|
||||
state.width = document.body.clientWidth
|
||||
})
|
||||
|
||||
function touchmove(e) {
|
||||
state.callFloatTransitionTime = 0
|
||||
state.callFloatLeft = e.touches[0].pageX - 35
|
||||
state.callFloatTop = e.touches[0].pageY - 40
|
||||
}
|
||||
|
||||
function touchend() {
|
||||
state.callFloatTransitionTime = 300
|
||||
if (state.callFloatLeft < state.width / 2) {
|
||||
state.callFloatLeft = 15
|
||||
} else {
|
||||
state.callFloatLeft = state.width - 15 - 70
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<dy-back mode="dark" img="close" direction="right" style="opacity: 0" />
|
||||
<div class="num">{{ _formatNumber(comments.length) }}条评论</div>
|
||||
<div class="right">
|
||||
<Icon icon="prime:arrow-up-right-and-arrow-down-left-from-center" @click.stop="$no" />
|
||||
<Icon icon="ic:round-close" @click.stop="cancel" />
|
||||
<Icon icon="prime:arrow-up-right-and-arrow-down-left-from-center" @click.stop="_no" />
|
||||
<Icon icon="ic:round-close" v-click="cancel" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -157,7 +157,7 @@
|
||||
<AutoInput v-model="comment" placeholder="善语结善缘,恶言伤人心"></AutoInput>
|
||||
<div class="right">
|
||||
<img src="../assets/img/icon/message/call.png" @click="isCall = !isCall" />
|
||||
<img src="../assets/img/icon/message/emoji-black.png" @click="$no" />
|
||||
<img src="../assets/img/icon/message/emoji-black.png" @click="_no" />
|
||||
</div>
|
||||
</div>
|
||||
<img v-if="comment" src="../assets/img/icon/message/up.png" @click="send" />
|
||||
@@ -178,9 +178,9 @@ import FromBottomDialog from './dialog/FromBottomDialog.vue'
|
||||
import Loading from './Loading.vue'
|
||||
import Search from './Search.vue'
|
||||
import {
|
||||
$no,
|
||||
_checkImgUrl,
|
||||
_formatNumber,
|
||||
_no,
|
||||
_showSelectDialog,
|
||||
_sleep,
|
||||
_time,
|
||||
@@ -251,10 +251,10 @@ export default {
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
_no,
|
||||
_time,
|
||||
_formatNumber,
|
||||
_checkImgUrl,
|
||||
$no,
|
||||
async handShowChildren(item) {
|
||||
this.loadChildrenItemCId = item.comment_id
|
||||
this.loadChildren = true
|
||||
@@ -328,10 +328,8 @@ export default {
|
||||
@import '../assets/less/index';
|
||||
|
||||
.title {
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 40rem;
|
||||
padding: 0 15rem;
|
||||
background: white;
|
||||
@@ -370,16 +368,13 @@ export default {
|
||||
|
||||
.comment {
|
||||
color: #000;
|
||||
width: 100vw;
|
||||
height: v-bind(height);
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
z-index: 5;
|
||||
border-radius: 10rem 10rem 0 0;
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding-top: 40rem;
|
||||
padding-bottom: 60rem;
|
||||
}
|
||||
|
||||
@@ -529,7 +524,7 @@ export default {
|
||||
border-radius: 10rem 10rem 0 0;
|
||||
background: white;
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
z-index: 3;
|
||||
|
||||
@@ -601,7 +596,7 @@ export default {
|
||||
}
|
||||
|
||||
.auto-input {
|
||||
width: calc(100vw - 180rem);
|
||||
width: calc(100% - 160rem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,23 +16,23 @@
|
||||
<div class="toolbar">
|
||||
<div class="title">分享到</div>
|
||||
<div class="shares">
|
||||
<div class="share-to" @click="$no">
|
||||
<div class="share-to" @click="_no">
|
||||
<img src="../assets/img/icon/components/video/toqq.webp" alt="" />
|
||||
<span>QQ好友</span>
|
||||
</div>
|
||||
<div class="share-to" @click="$no">
|
||||
<div class="share-to" @click="_no">
|
||||
<img src="../assets/img/icon/components/video/tozone.webp" alt="" />
|
||||
<span>QQ空间</span>
|
||||
</div>
|
||||
<div class="share-to" @click="$no">
|
||||
<div class="share-to" @click="_no">
|
||||
<img src="../assets/img/icon/components/video/towechatchat.webp" alt="" />
|
||||
<span>微信好友</span>
|
||||
</div>
|
||||
<div class="share-to" @click="$no">
|
||||
<div class="share-to" @click="_no">
|
||||
<img src="../assets/img/icon/components/video/towechat.webp" alt="" />
|
||||
<span>朋友圈</span>
|
||||
</div>
|
||||
<div class="share-to" @click="$no">
|
||||
<div class="share-to" @click="_no">
|
||||
<img src="../assets/img/icon/components/video/todownload.webp" alt="" />
|
||||
<span>保存到相册</span>
|
||||
</div>
|
||||
@@ -43,7 +43,7 @@
|
||||
</transition>
|
||||
</template>
|
||||
<script>
|
||||
import { _checkImgUrl } from '@/utils'
|
||||
import { _checkImgUrl, _no } from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'DouyinCode',
|
||||
@@ -62,6 +62,7 @@ export default {
|
||||
computed: {},
|
||||
created() {},
|
||||
methods: {
|
||||
_no,
|
||||
_checkImgUrl,
|
||||
cancel() {
|
||||
this.$emit('update:modelValue', false)
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
<div class="posters">
|
||||
<div class="poster-item" :key="index" v-for="(i, index) in list" @click="goDetail(index)">
|
||||
<img class="poster" v-lazy="_checkImgUrl(i.video.cover.url_list[0])" alt="" />
|
||||
<div class="num" v-if="mode === 'normal'">
|
||||
<Icon icon="icon-park-outline:like" />
|
||||
<span>{{ _formatNumber(i.statistics.digg_count) }}</span>
|
||||
</div>
|
||||
<template v-if="mode === 'normal'">
|
||||
<div class="num">
|
||||
<Icon icon="icon-park-outline:like" />
|
||||
<span>{{ _formatNumber(i.statistics.digg_count) }}</span>
|
||||
</div>
|
||||
<div class="top" v-if="i.is_top">置顶</div>
|
||||
</template>
|
||||
<div class="date" v-if="mode === 'date'">
|
||||
<div class="day">{{ getDay(i.create_time) }}</div>
|
||||
<div class="month">{{ getMonth(i.create_time) }}</div>
|
||||
@@ -87,11 +90,12 @@ function getMonth(time) {
|
||||
<style scoped lang="less">
|
||||
.posters {
|
||||
display: grid;
|
||||
grid-template-columns: 33.33vw 33.33vw 33.33vw;
|
||||
grid-template-columns: 33.33% 33.33% 33.33%;
|
||||
}
|
||||
|
||||
.poster-item {
|
||||
height: calc(33.33vw * 1.2);
|
||||
height: 200rem;
|
||||
max-height: calc(33.33vw * 1.3);
|
||||
border: 0.5px solid black;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@@ -103,6 +107,7 @@ function getMonth(time) {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.top,
|
||||
.music {
|
||||
position: absolute;
|
||||
font-size: 12rem;
|
||||
|
||||
@@ -123,6 +123,7 @@ export default {
|
||||
@import '../assets/less/index';
|
||||
|
||||
.scroll-wrapper {
|
||||
touch-action: pan-y;
|
||||
overflow: auto;
|
||||
|
||||
.scroll-content {
|
||||
|
||||
@@ -61,5 +61,3 @@ async function getData(refresh = false) {
|
||||
|
||||
onMounted(getData)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:show-heng-gang="false"
|
||||
:touch-moved="false"
|
||||
maskMode="light"
|
||||
height="370rem"
|
||||
height="320rem"
|
||||
mode="dark"
|
||||
>
|
||||
<div class="share">
|
||||
@@ -15,221 +15,229 @@
|
||||
<span>分享给朋友</span>
|
||||
<dy-back mode="light" img="close" direction="right" @click.stop="closeShare"></dy-back>
|
||||
</div>
|
||||
<div class="friends list">
|
||||
<div
|
||||
class="option"
|
||||
:key="i"
|
||||
v-for="(item, i) in friends.all"
|
||||
@click.stop="toggleCall(item)"
|
||||
>
|
||||
<img
|
||||
:style="item.select ? 'opacity: .5;' : ''"
|
||||
class="avatar"
|
||||
:src="$imgPreview(item.avatar)"
|
||||
alt=""
|
||||
/>
|
||||
<span>{{ item.name }}</span>
|
||||
<img
|
||||
v-if="item.select"
|
||||
class="checked"
|
||||
src="../assets/img/icon/components/check/check-red-share.png"
|
||||
/>
|
||||
<div class="content">
|
||||
<div class="friends list">
|
||||
<div
|
||||
class="option"
|
||||
:key="i"
|
||||
v-for="(item, i) in store.friends.all"
|
||||
@click.stop="toggleCall(item)"
|
||||
>
|
||||
<img
|
||||
:style="item.select ? 'opacity: .5;' : ''"
|
||||
class="avatar"
|
||||
:src="_checkImgUrl(item.avatar)"
|
||||
alt=""
|
||||
/>
|
||||
<span>{{ item.name }}</span>
|
||||
<img
|
||||
v-if="item.select"
|
||||
class="checked"
|
||||
src="../assets/img/icon/components/check/check-red-share.png"
|
||||
/>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($router.push('/message/share-to-friend'))">
|
||||
<dy-back class="more" mode="light" direction="right"></dy-back>
|
||||
<span>更多朋友</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($nav('/message/share-to-friend'))">
|
||||
<dy-back class="more" mode="light" direction="right"></dy-back>
|
||||
<span>更多朋友</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shares list">
|
||||
<template v-if="mode === 'video'">
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<img class="avatar" src="../assets/img/icon/components/video/torichang.png" alt="" />
|
||||
<span>转发</span>
|
||||
<div class="bottom">
|
||||
<div class="share2friend" v-if="store.selectFriends.length">
|
||||
<div class="line"></div>
|
||||
<div class="comment">
|
||||
<textarea placeholder="有什么想和好友说的..."></textarea>
|
||||
<img class="poster" src="../assets/img/poster/1.jpg" alt="" />
|
||||
</div>
|
||||
<div class="btns">
|
||||
<dy-button type="dark2" radius="7" v-if="store.selectFriends.length > 1" @click="_no"
|
||||
>建群并发送
|
||||
</dy-button>
|
||||
<dy-button type="primary" radius="7" @click="_no"
|
||||
>{{ store.selectFriends.length > 1 ? '分别发送' : '发送' }}
|
||||
</dy-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<Icon icon="icon-park-solid:good-two" />
|
||||
<span>推荐给朋友</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="copyLink">
|
||||
<Icon icon="humbleicons:link" />
|
||||
<span>复制链接</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="$no">
|
||||
<img class="small" src="../assets/img/icon/components/video/comeonplay.png" alt="" />
|
||||
<span>合拍</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="$no">
|
||||
<img class="small" src="../assets/img/icon/components/video/dou.webp" alt="" />
|
||||
<span>帮上热门</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="$nav('/home/report', { mode: this.mode })">
|
||||
<img class="small" src="../assets/img/icon/components/video/warring.png" alt="" />
|
||||
<span>举报</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<Icon icon="ion:paper-plane" />
|
||||
<span>私信朋友</span>
|
||||
</div>
|
||||
<div class="option" v-if="canDownload" @click.stop="closeShare($emit('download'))">
|
||||
<Icon icon="mingcute:download-fill" />
|
||||
<span>保存本地</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="$no">
|
||||
<!--TODO icon不对 -->
|
||||
<img class="small" src="../assets/img/icon/components/video/feedback.webp" alt="" />
|
||||
<span>建群分享</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="$no">
|
||||
<img class="small" src="../assets/img/icon/components/video/comeonlook.webp" alt="" />
|
||||
<span>一起看视频</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('dislike'))">
|
||||
<img class="small" src="../assets/img/icon/components/video/dislike.png" alt="" />
|
||||
<span>不感兴趣</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('showDouyinCode'))">
|
||||
<Icon icon="tabler:photo" />
|
||||
<span>生成图片</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="$no">
|
||||
<img class="small" src="../assets/img/icon/components/video/bizhi.webp" alt="" />
|
||||
<span>动态壁纸</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('play-feedback'))">
|
||||
<img class="small" src="../assets/img/icon/components/video/feedback.webp" alt="" />
|
||||
<span>播放反馈</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="mode === 'music'">
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<img class="small" src="../assets/img/icon/components/video/tofriend.webp" alt="" />
|
||||
<span>私信朋友</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="$nav('/home/report', { mode: this.mode })">
|
||||
<img class="small" src="../assets/img/icon/components/video/warring.png" alt="" />
|
||||
<span>举报音乐</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="mode === 'my-music'">
|
||||
<div class="option" @click.stop="$no">
|
||||
<img class="small" src="../assets/img/icon/components/video/torichang.png" alt="" />
|
||||
<span>转发到日常</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<img class="small" src="../assets/img/icon/components/video/tofriend.webp" alt="" />
|
||||
<span>私信朋友</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="share2friend" v-if="selectFriends.length">
|
||||
<div class="comment">
|
||||
<textarea placeholder="有什么想和好友说的..."></textarea>
|
||||
<img class="poster" src="../assets/img/poster/1.jpg" alt="" />
|
||||
</div>
|
||||
<div class="btns">
|
||||
<dy-button type="dark2" radius="7" v-if="selectFriends.length > 1" @click.stop="$no"
|
||||
>建群并发送
|
||||
</dy-button>
|
||||
<dy-button type="primary" radius="7" @click.stop="$no"
|
||||
>{{ selectFriends.length > 1 ? '分别发送' : '发送' }}
|
||||
</dy-button>
|
||||
<div class="shares list" v-else>
|
||||
<template v-if="mode === 'video'">
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<img
|
||||
class="avatar"
|
||||
src="../assets/img/icon/components/video/torichang.png"
|
||||
alt=""
|
||||
/>
|
||||
<span>转发</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<Icon icon="icon-park-solid:good-two" />
|
||||
<span>推荐给朋友</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="copyLink">
|
||||
<Icon icon="humbleicons:link" />
|
||||
<span>复制链接</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="_no">
|
||||
<img
|
||||
class="small"
|
||||
src="../assets/img/icon/components/video/comeonplay.png"
|
||||
alt=""
|
||||
/>
|
||||
<span>合拍</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="_no">
|
||||
<img class="small" src="../assets/img/icon/components/video/dou.webp" alt="" />
|
||||
<span>帮上热门</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="$router.push('/home/report', { mode: this.mode })">
|
||||
<img class="small" src="../assets/img/icon/components/video/warring.png" alt="" />
|
||||
<span>举报</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<Icon icon="ion:paper-plane" />
|
||||
<span>私信朋友</span>
|
||||
</div>
|
||||
<div class="option" v-if="canDownload" @click.stop="closeShare($emit('download'))">
|
||||
<Icon icon="mingcute:download-fill" />
|
||||
<span>保存本地</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="_no">
|
||||
<!--TODO icon不对 -->
|
||||
<img class="small" src="../assets/img/icon/components/video/feedback.webp" alt="" />
|
||||
<span>建群分享</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="_no">
|
||||
<img
|
||||
class="small"
|
||||
src="../assets/img/icon/components/video/comeonlook.webp"
|
||||
alt=""
|
||||
/>
|
||||
<span>一起看视频</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('dislike'))">
|
||||
<img class="small" src="../assets/img/icon/components/video/dislike.png" alt="" />
|
||||
<span>不感兴趣</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('showDouyinCode'))">
|
||||
<Icon icon="tabler:photo" />
|
||||
<span>生成图片</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="_no">
|
||||
<img class="small" src="../assets/img/icon/components/video/bizhi.webp" alt="" />
|
||||
<span>动态壁纸</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('play-feedback'))">
|
||||
<img class="small" src="../assets/img/icon/components/video/feedback.webp" alt="" />
|
||||
<span>播放反馈</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="mode === 'music'">
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<img class="small" src="../assets/img/icon/components/video/tofriend.webp" alt="" />
|
||||
<span>私信朋友</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="$router.push('/home/report', { mode: this.mode })">
|
||||
<img class="small" src="../assets/img/icon/components/video/warring.png" alt="" />
|
||||
<span>举报音乐</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="mode === 'my-music'">
|
||||
<div class="option" @click.stop="_no">
|
||||
<img class="small" src="../assets/img/icon/components/video/torichang.png" alt="" />
|
||||
<span>转发到日常</span>
|
||||
</div>
|
||||
<div class="option" @click.stop="closeShare($emit('ShareToFriend'))">
|
||||
<img class="small" src="../assets/img/icon/components/video/tofriend.webp" alt="" />
|
||||
<span>私信朋友</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</from-bottom-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'pinia'
|
||||
<script setup>
|
||||
import FromBottomDialog from './dialog/FromBottomDialog'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { $no, _copy } from '@/utils'
|
||||
import { _checkImgUrl, _copy, _hideLoading, _no, _notice, _showLoading, _sleep } from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'Share',
|
||||
components: {
|
||||
FromBottomDialog
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
}
|
||||
},
|
||||
item: {},
|
||||
videoId: {
|
||||
type: String,
|
||||
default() {
|
||||
return null
|
||||
}
|
||||
},
|
||||
pageId: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'home-index'
|
||||
}
|
||||
},
|
||||
canDownload: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'video'
|
||||
//music
|
||||
//qrcode
|
||||
}
|
||||
defineOptions({
|
||||
name: 'Share'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useBaseStore, ['friends']),
|
||||
selectFriends() {
|
||||
return this.friends.all.filter((v) => v.select)
|
||||
item: {},
|
||||
videoId: {
|
||||
type: String,
|
||||
default() {
|
||||
return null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
pageId: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'home-index'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
$no,
|
||||
async copyLink() {
|
||||
this.closeShare()
|
||||
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('复制成功')
|
||||
},
|
||||
|
||||
toggleCall(item) {
|
||||
item.select = !item.select
|
||||
},
|
||||
closeShare() {
|
||||
this.friends.all = this.friends.all.map((v) => {
|
||||
v.select = false
|
||||
return v
|
||||
})
|
||||
this.$emit('update:modelValue', false)
|
||||
canDownload: {
|
||||
type: Boolean,
|
||||
default() {
|
||||
return true
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'video'
|
||||
//music
|
||||
//qrcode
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const store = useBaseStore()
|
||||
const emit = defineEmits(['update:item'])
|
||||
|
||||
async function copyLink() {
|
||||
closeShare()
|
||||
_showLoading()
|
||||
await _sleep(500)
|
||||
_hideLoading()
|
||||
_copy(props.item.share_info.share_link_desc + props.item.share_info.share_url)
|
||||
//TODO 抖音样式改了
|
||||
_notice('复制成功')
|
||||
}
|
||||
|
||||
function toggleCall(item) {
|
||||
item.select = !item.select
|
||||
}
|
||||
|
||||
function closeShare() {
|
||||
store.friends.all = store.friends.all.map((v) => {
|
||||
v.select = false
|
||||
return v
|
||||
})
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '../assets/less/index';
|
||||
|
||||
.share {
|
||||
width: 100%;
|
||||
background: black;
|
||||
height: 100%;
|
||||
background: var(--color-share-bg);
|
||||
border-radius: 10px 10px 0 0;
|
||||
color: white;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@space-width: 26rem;
|
||||
@avatar-width: 58rem;
|
||||
@@ -253,12 +261,25 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rem;
|
||||
|
||||
.bottom {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
overflow-x: scroll;
|
||||
display: flex;
|
||||
padding: 0 20rem;
|
||||
gap: 22rem;
|
||||
padding-bottom: 50rem;
|
||||
}
|
||||
|
||||
@c: rgb(51, 51, 51);
|
||||
@@ -321,13 +342,11 @@ export default {
|
||||
}
|
||||
|
||||
.share2friend {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
padding: 20rem;
|
||||
padding-top: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100vw;
|
||||
height: 180rem;
|
||||
background: black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
@@ -335,9 +354,11 @@ export default {
|
||||
.comment {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin-top: 15rem;
|
||||
|
||||
textarea {
|
||||
flex: 1;
|
||||
font-size: 14rem;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
@@ -346,8 +367,10 @@ export default {
|
||||
|
||||
.poster {
|
||||
margin-left: 20rem;
|
||||
height: 40rem;
|
||||
width: 40rem;
|
||||
height: 60rem;
|
||||
width: 60rem;
|
||||
object-fit: cover;
|
||||
border-radius: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="UserPanel" @scroll="scroll" ref="page">
|
||||
<div id="UserPanel" @scroll="scroll" @dragstart="(e) => _stopPropagation(e)" ref="page">
|
||||
<div ref="float" class="float" :class="state.floatFixed ? 'fixed' : ''">
|
||||
<div class="left">
|
||||
<Icon @click="emit('back')" class="icon" icon="eva:arrow-ios-back-fill" />
|
||||
@@ -32,7 +32,7 @@
|
||||
<span>求更新</span>
|
||||
</div>
|
||||
</transition>
|
||||
<Icon class="icon" icon="ion:search" @click.stop="$no()" />
|
||||
<Icon class="icon" icon="ion:search" @click.stop="_no" />
|
||||
<Icon class="icon" icon="ri:more-line" @click.stop="emit('showFollowSetting')" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,7 +76,7 @@
|
||||
<img
|
||||
src="@/assets/img/icon/me/copy.png"
|
||||
alt=""
|
||||
@click.stop="Utils.copy(_getUserDouyinId(props.currentItem))"
|
||||
@click.stop="_copy(_getUserDouyinId(props.currentItem))"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,20 +85,16 @@
|
||||
<div class="info">
|
||||
<div class="heat">
|
||||
<div class="text">
|
||||
<span class="num">{{
|
||||
Utils.formatNumber(props.currentItem.author.total_favorited)
|
||||
}}</span>
|
||||
<span class="num">{{ _formatNumber(props.currentItem.author.total_favorited) }}</span>
|
||||
<span>获赞</span>
|
||||
</div>
|
||||
<div class="text">
|
||||
<span class="num">{{
|
||||
Utils.formatNumber(props.currentItem.author.following_count)
|
||||
}}</span>
|
||||
<span class="num">{{ _formatNumber(props.currentItem.author.following_count) }}</span>
|
||||
<span>关注</span>
|
||||
</div>
|
||||
<div class="text">
|
||||
<span class="num">{{
|
||||
Utils.formatNumber(props.currentItem.author.mplatform_followers_count)
|
||||
_formatNumber(props.currentItem.author.mplatform_followers_count)
|
||||
}}</span>
|
||||
<span>粉丝</span>
|
||||
</div>
|
||||
@@ -234,7 +230,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import Utils, { $no, _checkImgUrl, _getUserDouyinId } from '@/utils'
|
||||
import {
|
||||
_checkImgUrl,
|
||||
_copy,
|
||||
_formatNumber,
|
||||
_getUserDouyinId,
|
||||
_no,
|
||||
_stopPropagation
|
||||
} from '@/utils'
|
||||
import { useNav } from '@/utils/hooks/useNav'
|
||||
import Posters from '@/components/Posters.vue'
|
||||
import { DefaultUser } from '@/utils/const_var'
|
||||
@@ -405,6 +408,7 @@ function touchEnd() {
|
||||
}
|
||||
|
||||
#UserPanel {
|
||||
touch-action: pan-y;
|
||||
position: fixed;
|
||||
background: var(--color-user);
|
||||
height: 100%;
|
||||
@@ -423,8 +427,8 @@ function touchEnd() {
|
||||
justify-content: center;
|
||||
|
||||
.resource {
|
||||
width: 100vw;
|
||||
max-height: 100vw;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.download {
|
||||
@@ -442,7 +446,7 @@ function touchEnd() {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
z-index: 3;
|
||||
}
|
||||
@@ -500,7 +504,7 @@ function touchEnd() {
|
||||
grid-template-columns: 33.33% 33.33% 33.33%;
|
||||
|
||||
.item {
|
||||
height: calc(33.33vw * 1.3);
|
||||
height: calc(33.33% * 1.3);
|
||||
padding: 2rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@@ -572,7 +576,7 @@ function touchEnd() {
|
||||
.poster {
|
||||
border-radius: 4rem;
|
||||
width: 100%;
|
||||
height: calc((100vw - 34rem) / 3);
|
||||
height: calc((100% - 34rem) / 3);
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -592,7 +596,7 @@ function touchEnd() {
|
||||
.cover {
|
||||
height: 220rem;
|
||||
object-fit: cover;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
//transition: height .3s;
|
||||
}
|
||||
|
||||
@@ -977,7 +981,7 @@ function touchEnd() {
|
||||
.float {
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -1,83 +1,65 @@
|
||||
<template>
|
||||
<div class="ConfirmDialog" @click="$emit('dismiss')" v-if="visible">
|
||||
<div class="content" @click.stop="stop">
|
||||
<div class="ConfirmDialog" @click="onDismiss" v-if="visible">
|
||||
<div class="content">
|
||||
<slot name="header"></slot>
|
||||
<div class="body">
|
||||
<div class="title" v-if="title">{{ title }}</div>
|
||||
<div class="subtitle" :class="subtitleColor" v-if="subtitle">
|
||||
<div :class="['subtitle', subtitleColor]" v-if="subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="cancel" :class="cancelTextColor" @click.stop="cancel">
|
||||
<div :class="['cancel', cancelTextColor]" @click.stop="onCancel">
|
||||
{{ cancelText }}
|
||||
</div>
|
||||
<div class="ok" @click.stop="ok">{{ okText }}</div>
|
||||
<div class="ok" @click.stop="onOk">{{ okText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
/*TODO 单独使用时,没有mark*/
|
||||
export default {
|
||||
name: 'ConfirmDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
subtitleColor: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'gray'
|
||||
}
|
||||
},
|
||||
okText: {
|
||||
type: String,
|
||||
default() {
|
||||
return '确定'
|
||||
}
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default() {
|
||||
return '取消'
|
||||
}
|
||||
},
|
||||
cancelTextColor: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'gray'
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
stop() {},
|
||||
ok() {
|
||||
this.$emit('ok')
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('cancel')
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'ConfirmDialog' })
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
subtitle?: string
|
||||
subtitleColor?: string
|
||||
okText?: string
|
||||
cancelText?: string
|
||||
cancelTextColor?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
title: '',
|
||||
subtitle: '',
|
||||
subtitleColor: 'gray',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
cancelTextColor: 'gray'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'ok'): void
|
||||
(ev: 'cancel'): void
|
||||
(ev: 'dismiss'): void
|
||||
}>()
|
||||
|
||||
const visible = defineModel<boolean>('visible', { type: Boolean, default: true })
|
||||
|
||||
const onOk = () => {
|
||||
visible.value = false
|
||||
emit('ok')
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
visible.value = false
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const onDismiss = () => {
|
||||
emit('dismiss')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,16 +3,9 @@
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'FadeDialog',
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
created() {},
|
||||
methods: {}
|
||||
}
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'FadeDialog' })
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -1,206 +1,177 @@
|
||||
<template>
|
||||
<!-- <transition name="from-bottom"> -->
|
||||
<transition
|
||||
@before-enter="beforeEnter"
|
||||
@enter="enter"
|
||||
@after-enter="afterEnter"
|
||||
@before-leave="beforeLeave"
|
||||
@leave="leave"
|
||||
@after-leave="afterLeave"
|
||||
:css="false"
|
||||
>
|
||||
<Transition name="test">
|
||||
<div
|
||||
ref="dialog"
|
||||
class="FromBottomDialog"
|
||||
v-if="modelValue"
|
||||
:class="[mode, showHengGang ? '' : 'no-heng-gang']"
|
||||
:style="{ 'max-height': height }"
|
||||
@touchstart="start"
|
||||
@touchmove="move"
|
||||
@touchend="end"
|
||||
:class="['FromBottomDialog', mode, showHengGang ? '' : 'no-heng-gang']"
|
||||
@touchstart="onStart"
|
||||
@touchmove="onMove"
|
||||
@touchend="onEnd"
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
<div class="heng-gang" :class="mode" v-if="showHengGang">
|
||||
<div :class="['heng-gang', mode]" v-if="showHengGang">
|
||||
<div class="content"></div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
<div class="wrapper" ref="wrapper">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Transition>
|
||||
</template>
|
||||
<script>
|
||||
import Dom from '../../utils/dom'
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import Dom, { _css } from '../../utils/dom'
|
||||
import bus, { EVENT_KEY } from '@/utils/bus'
|
||||
import { _stopPropagation } from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'FromBottomDialog',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'dark'
|
||||
// default: 'light'
|
||||
// default: 'white'
|
||||
},
|
||||
maskMode: {
|
||||
type: String,
|
||||
default: 'dark'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'calc(var(--vh, 1vh) * 70)'
|
||||
},
|
||||
showHengGang: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
pageId: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: true
|
||||
},
|
||||
borderRadius: {
|
||||
type: String,
|
||||
default: '15rem 15rem 0 0'
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(newVal) {
|
||||
let page = document.getElementById(this.pageId)
|
||||
if (newVal) {
|
||||
this.pagePosition = this.$getCss2(page, 'position')
|
||||
page.style.position = 'absolute'
|
||||
this.scroll = document.documentElement.scrollTop
|
||||
document.body.style.position = 'fixed'
|
||||
document.body.style.top = -this.scroll + 'px'
|
||||
defineOptions({ name: 'FromBottomDialog' })
|
||||
|
||||
let maskTemplate = `<div class="Mask fade-in ${this.maskMode}"></div>`
|
||||
let mask = new Dom().create(maskTemplate)
|
||||
mask.on('click', (e) => {
|
||||
this.$stopPropagation(e)
|
||||
this.hide(false)
|
||||
interface Props {
|
||||
modelValue?: boolean
|
||||
mode?: 'dark' | 'light' | 'white'
|
||||
maskMode?: 'dark' | 'light' | 'white'
|
||||
height?: string
|
||||
showHengGang?: boolean
|
||||
pageId: string
|
||||
borderRadius?: string
|
||||
tag?: string
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(ev: 'update:modelValue', val: boolean): void
|
||||
(ev: 'cancel'): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
mode: 'dark',
|
||||
maskMode: 'dark',
|
||||
height: 'calc(var(--vh, 1vh) * 70)',
|
||||
showHengGang: true,
|
||||
borderRadius: '15rem 15rem 0 0',
|
||||
tag: ''
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const dialog = ref<HTMLElement | null>(null)
|
||||
|
||||
const wrapper = ref<HTMLElement | null>(null)
|
||||
|
||||
const scroll = ref(0)
|
||||
|
||||
const startY = ref(0)
|
||||
|
||||
const moveY = ref(0)
|
||||
|
||||
const startTime = ref(0)
|
||||
|
||||
const pagePosition = ref(null)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal: boolean) => {
|
||||
const page = document.getElementById(props.pageId)
|
||||
if (!page) return
|
||||
if (newVal) {
|
||||
pagePosition.value = _css(page, 'position')
|
||||
page.style.position = 'absolute'
|
||||
scroll.value = document.documentElement.scrollTop
|
||||
document.body.style.position = 'fixed'
|
||||
document.body.style.top = -scroll.value + 'px'
|
||||
|
||||
const maskTemplate = `<div class="Mask fade-in ${props.maskMode}"></div>`
|
||||
const mask = new Dom().create(maskTemplate)
|
||||
setTimeout(() => {
|
||||
mask.on('click', (e: Event) => {
|
||||
_stopPropagation(e)
|
||||
onHide()
|
||||
})
|
||||
page.appendChild(mask.els[0])
|
||||
} else {
|
||||
let page = document.getElementById(this.pageId)
|
||||
page.style.position = this.pagePosition || 'fixed'
|
||||
document.body.style.position = 'static'
|
||||
document.documentElement.scrollTop = this.scroll
|
||||
}, 200)
|
||||
page.appendChild(mask.els[0])
|
||||
} else {
|
||||
page.style.position = pagePosition.value || 'fixed'
|
||||
document.body.style.position = 'static'
|
||||
document.documentElement.scrollTop = scroll.value
|
||||
|
||||
let mask = new Dom('.Mask').replaceClass('fade-in', 'fade-out')
|
||||
setTimeout(() => {
|
||||
mask.remove()
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
scroll: 0,
|
||||
startLocationY: 0,
|
||||
moveYDistance: 0,
|
||||
startTime: 0,
|
||||
pagePosition: null
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
created() {},
|
||||
methods: {
|
||||
beforeEnter(el) {
|
||||
this.$setCss(el, 'transition-duration', `250ms`)
|
||||
this.$setCss(el, 'transform', `translate3d(0,${this.height},0)`)
|
||||
},
|
||||
enter(el, done) {
|
||||
const mask = new Dom('.Mask').replaceClass('fade-in', 'fade-out')
|
||||
setTimeout(() => {
|
||||
this.$setCss(el, 'transform', `translate3d(0,0,0)`)
|
||||
}, 0)
|
||||
setTimeout(() => {
|
||||
// this.$setCss(el, 'transition-duration', `0ms`)
|
||||
this.$setCss(el, 'transform', `none`)
|
||||
done()
|
||||
mask.remove()
|
||||
}, 250)
|
||||
},
|
||||
afterEnter() {},
|
||||
beforeLeave(el) {
|
||||
this.$setCss(el, 'transition-duration', `250ms`)
|
||||
this.$setCss(el, 'transform', `translate3d(0,0,0)`)
|
||||
},
|
||||
leave(el, done) {
|
||||
//ref获取不到
|
||||
let maxHeight = new Dom('.FromBottomDialog').css('max-height')
|
||||
this.$setCss(el, 'transform', `translate3d(0,${maxHeight},0)`)
|
||||
setTimeout(done, 250)
|
||||
},
|
||||
afterLeave() {},
|
||||
|
||||
hide(val = false) {
|
||||
this.$emit('update:modelValue', val)
|
||||
this.$emit('cancel')
|
||||
},
|
||||
start(e) {
|
||||
if (this.$refs.dialog.scrollTop !== 0) return
|
||||
this.startLocationY = e.touches[0].pageY
|
||||
this.startTime = Date.now()
|
||||
this.$setCss(this.$refs.dialog, 'transition-duration', `0ms`)
|
||||
},
|
||||
move(e) {
|
||||
if (this.$refs.dialog.scrollTop !== 0) return
|
||||
this.moveYDistance = e.touches[0].pageY - this.startLocationY
|
||||
if (this.moveYDistance > 0) {
|
||||
bus.emit(EVENT_KEY.DIALOG_MOVE, {
|
||||
tag: this.tag,
|
||||
e: this.moveYDistance
|
||||
})
|
||||
this.$setCss(this.$refs.dialog, 'transform', `translate3d(0,${this.moveYDistance}px,0)`)
|
||||
}
|
||||
},
|
||||
end() {
|
||||
//点击
|
||||
if (Date.now() - this.startTime < 150 && Math.abs(this.moveYDistance) < 30) {
|
||||
return
|
||||
}
|
||||
//滑动
|
||||
if (this.$refs.dialog.scrollTop !== 0) return
|
||||
let clientHeight = this.$refs.dialog.clientHeight
|
||||
this.$setCss(this.$refs.dialog, 'transition-duration', `250ms`)
|
||||
if (Math.abs(this.moveYDistance) > clientHeight / 2) {
|
||||
this.$setCss(this.$refs.dialog, 'transform', `translate3d(0,${clientHeight}px,0)`)
|
||||
bus.emit(EVENT_KEY.DIALOG_END, { tag: this.tag, isClose: true })
|
||||
setTimeout(this.hide, 250)
|
||||
} else {
|
||||
this.$setCss(this.$refs.dialog, 'transform', `translate3d(0,0,0)`)
|
||||
bus.emit(EVENT_KEY.DIALOG_END, { tag: this.tag, isClose: false })
|
||||
setTimeout(() => {
|
||||
this.$setCss(this.$refs.dialog, 'transform', 'none')
|
||||
// this.$setCss(this.$refs.dialog, 'transition-duration', `0ms`)
|
||||
}, 250)
|
||||
}
|
||||
this.moveYDistance = 0
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const onHide = (val = false) => {
|
||||
emit('update:modelValue', val)
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const onStart = (e: TouchEvent) => {
|
||||
if (wrapper.value?.scrollTop !== 0) return
|
||||
startY.value = e.touches[0].clientY
|
||||
startTime.value = Date.now()
|
||||
_css(dialog.value, 'transition-duration', '0ms')
|
||||
}
|
||||
|
||||
const onMove = (e: TouchEvent) => {
|
||||
if (wrapper.value?.scrollTop !== 0) return
|
||||
moveY.value = e.touches[0].pageY - startY.value
|
||||
if (moveY.value > 0) {
|
||||
bus.emit(EVENT_KEY.DIALOG_MOVE, {
|
||||
tag: props.tag,
|
||||
e: moveY.value
|
||||
})
|
||||
_css(dialog.value, 'transform', `translate3d(0, ${moveY.value}px, 0)`)
|
||||
}
|
||||
}
|
||||
|
||||
const onEnd = () => {
|
||||
// 如果是外部改变 modelValue 值的话,这里会没有 ref
|
||||
if (!dialog.value) return
|
||||
if (Date.now() - startTime.value < 150 && Math.abs(moveY.value) < 30) return
|
||||
const clientHeight = dialog.value?.clientHeight
|
||||
_css(dialog.value, 'transition-duration', `250ms`)
|
||||
if (Math.abs(moveY.value) > clientHeight / 2) {
|
||||
_css(dialog.value, 'transform', `translate3d(0,100%,0)`)
|
||||
bus.emit(EVENT_KEY.DIALOG_END, { tag: props.tag, isClose: true })
|
||||
setTimeout(onHide, 250)
|
||||
} else {
|
||||
_css(dialog.value, 'transform', `translate3d(0,0,0)`)
|
||||
bus.emit(EVENT_KEY.DIALOG_END, { tag: props.tag, isClose: false })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import '../../assets/less/index';
|
||||
|
||||
.test-enter-active,
|
||||
.test-leave-active {
|
||||
transition-duration: 250ms !important;
|
||||
}
|
||||
|
||||
.test-enter-from,
|
||||
.test-leave-to {
|
||||
transform: translate3d(0, 101%, 0) !important;
|
||||
}
|
||||
|
||||
.FromBottomDialog {
|
||||
z-index: 9;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
padding-top: 24rem;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
border-radius: v-bind(borderRadius);
|
||||
transition: all 0.3s;
|
||||
border-radius: 15rem 15rem 0 0;
|
||||
transform: translate3d(0, 0, 0);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
height: v-bind(height);
|
||||
max-height: v-bind(height);
|
||||
flex-direction: column;
|
||||
|
||||
&.dark {
|
||||
background: var(--main-bg);
|
||||
@@ -228,6 +199,7 @@ export default {
|
||||
transform: translateY(-24rem);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
touch-action: pan-y;
|
||||
|
||||
&.dark {
|
||||
background: var(--main-bg);
|
||||
@@ -259,5 +231,10 @@ export default {
|
||||
width: 30rem;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,57 +1,51 @@
|
||||
<template>
|
||||
<div class="NoticeDialog" @click="$emit('dismiss')">
|
||||
<div class="content" @click.stop="stop">
|
||||
<div class="NoticeDialog" @click="onDismiss">
|
||||
<div class="content">
|
||||
<div class="body">
|
||||
<div class="title">{{ title }}</div>
|
||||
<div class="subtitle" :class="subtitleColor" v-if="subtitle">
|
||||
<div :class="['subtitle', subtitleColor]" v-if="subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="cancel" @click.stop="$emit('cancel')">{{ cancelText }}</div>
|
||||
<div class="cancel" @click.stop="onCancel">{{ cancelText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'NoticeDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
subtitleColor: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'gray'
|
||||
}
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default() {
|
||||
return '取消'
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
stop() {}
|
||||
}
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'NoticeDialog' })
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
subtitle?: string
|
||||
subtitleColor?: string
|
||||
cancelText?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
title: '',
|
||||
subtitle: '',
|
||||
subtitleColor: 'gray',
|
||||
cancelText: '取消'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'ok'): void
|
||||
(ev: 'cancel'): void
|
||||
(ev: 'dismiss'): void
|
||||
}>()
|
||||
|
||||
const visible = defineModel<boolean>('visible', { type: Boolean, default: true })
|
||||
|
||||
const onCancel = () => {
|
||||
visible.value = false
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const onDismiss = () => {
|
||||
emit('dismiss')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
<template>
|
||||
<div class="SelectDialog" @click="$emit('cancel')">
|
||||
<div class="SelectDialog" @click="onCancel">
|
||||
<div class="content">
|
||||
<div class="item" :key="i" v-for="(item, i) in list" @click.stop="$emit('ok', item)">
|
||||
<div class="item" :key="i" v-for="(item, i) in list" @click.stop="onOk(item)">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'SelectDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
|
||||
<script setup lang="ts" generic="T">
|
||||
defineOptions({ name: 'SelectDialog' })
|
||||
|
||||
type Item = { name: string } & T
|
||||
|
||||
interface Props {
|
||||
visible?: boolean
|
||||
list?: Item[]
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
list: () => []
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'ok', item: Item): void
|
||||
(ev: 'cancel'): void
|
||||
}>()
|
||||
|
||||
const onOk = (item: Item) => {
|
||||
emit('ok', item)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
emit('cancel')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,47 +1,50 @@
|
||||
<template>
|
||||
<div class="SimpleConfirmDialog" @click="$emit('dismiss')">
|
||||
<div class="content" @click.stop="stop">
|
||||
<div class="SimpleConfirmDialog" @click="onDismiss">
|
||||
<div class="content">
|
||||
<div class="item">{{ title }}</div>
|
||||
<div class="footer">
|
||||
<div class="cancel" @click.stop="$emit('cancel')">{{ cancelText }}</div>
|
||||
<div class="ok" @click.stop="$emit('ok')">{{ okText }}</div>
|
||||
<div class="cancel" @click.stop="onCancel">{{ cancelText }}</div>
|
||||
<div class="ok" @click.stop="onOk">{{ okText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'SimpleConfirmDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default() {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
okText: {
|
||||
type: String,
|
||||
default() {
|
||||
return '保存'
|
||||
}
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default() {
|
||||
return '放弃'
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
stop() {}
|
||||
}
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'SimpleConfirmDialog' })
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
okText?: string
|
||||
cancelText?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
title: '',
|
||||
okText: '确定',
|
||||
cancelText: '取消'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'ok'): void
|
||||
(ev: 'cancel'): void
|
||||
(ev: 'dismiss'): void
|
||||
}>()
|
||||
|
||||
const visible = defineModel<boolean>('visible', { type: Boolean, default: true })
|
||||
|
||||
const onOk = () => {
|
||||
visible.value = false
|
||||
emit('ok')
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
visible.value = false
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const onDismiss = () => {
|
||||
emit('dismiss')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,738 +0,0 @@
|
||||
<template>
|
||||
<div class="video-wrapper" ref="videoWrapper" :class="positionName">
|
||||
<Loading v-if="loading" style="position: absolute" />
|
||||
<!-- <video :src="item.video + '?v=123'"-->
|
||||
<video
|
||||
:src="item.video.play_addr.url_list[0]"
|
||||
:poster="poster"
|
||||
ref="video"
|
||||
muted
|
||||
preload
|
||||
loop
|
||||
x5-video-player-type="h5-page"
|
||||
:x5-video-player-fullscreen="false"
|
||||
:webkit-playsinline="true"
|
||||
:x5-playsinline="true"
|
||||
:playsinline="true"
|
||||
:fullscreen="false"
|
||||
:autoplay="isPlay"
|
||||
>
|
||||
<p>您的浏览器不支持 video 标签。</p>
|
||||
</video>
|
||||
<Icon icon="fluent:play-28-filled" class="pause-icon" v-if="!isPlaying" />
|
||||
<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" />
|
||||
<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">
|
||||
<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
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Utils, { _checkImgUrl } from '../../utils'
|
||||
import Loading from '../Loading'
|
||||
import ItemToolbar from './ItemToolbar'
|
||||
import ItemDesc from './ItemDesc'
|
||||
import bus, { EVENT_KEY } from '../../utils/bus'
|
||||
import { SlideItemPlayStatus } from '@/utils/const_var'
|
||||
import { computed } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
export default {
|
||||
name: 'BVideo',
|
||||
components: {
|
||||
Loading,
|
||||
ItemToolbar,
|
||||
ItemDesc,
|
||||
Icon
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
// isPlaying: computed(() => this.status)
|
||||
isPlaying: computed(() => this.isPlaying)
|
||||
}
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
position: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
//用于第一条数据,自动播放,如果都用事件去触发播放,有延迟
|
||||
isPlay: {
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return true
|
||||
}
|
||||
},
|
||||
isMy: {
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false
|
||||
}
|
||||
},
|
||||
isLive: {
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
poster() {
|
||||
return _checkImgUrl(this.item.video.poster ?? this.item.video.cover.url_list[0])
|
||||
},
|
||||
durationStyle() {
|
||||
return { width: this.playX + 'px' }
|
||||
},
|
||||
progressClass() {
|
||||
if (this.isMove) {
|
||||
return 'move'
|
||||
} else {
|
||||
return this.isPlaying ? '' : 'stop'
|
||||
}
|
||||
},
|
||||
positionName() {
|
||||
return 'item-' + Object.values(this.position).join('-')
|
||||
},
|
||||
isPlaying() {
|
||||
return this.status === SlideItemPlayStatus.Play
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
paused: false,
|
||||
status: this.isPlay ? SlideItemPlayStatus.Play : SlideItemPlayStatus.Pause,
|
||||
duration: 0,
|
||||
step: 0,
|
||||
currentTime: -1,
|
||||
playX: 0,
|
||||
start: { x: 0 },
|
||||
last: { x: 0, time: 0 },
|
||||
height: 0,
|
||||
width: 0,
|
||||
isMove: false,
|
||||
ignoreWaiting: false, //忽略waiting事件。因为改变进度会触发waiting事件,烦的一批
|
||||
test: [1, 2],
|
||||
localItem: this.item,
|
||||
progressBarRect: {},
|
||||
videoScreenHeight: 0,
|
||||
commentVisible: false,
|
||||
LUtils: Utils
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// console.log('video', this.localItem.id)
|
||||
// console.log(this.commentVisible)
|
||||
this.height = document.body.clientHeight
|
||||
this.width = document.body.clientWidth
|
||||
let video = this.$refs.video
|
||||
video.currentTime = 0
|
||||
let fun = (e) => {
|
||||
this.currentTime = Math.ceil(e.target.currentTime)
|
||||
this.playX = (this.currentTime - 1) * this.step
|
||||
}
|
||||
video.addEventListener('loadedmetadata', () => {
|
||||
this.videoScreenHeight = video.videoHeight / (video.videoWidth / this.width)
|
||||
this.duration = video.duration
|
||||
this.progressBarRect = this.$refs.progress.getBoundingClientRect()
|
||||
this.step = this.progressBarRect.width / Math.floor(this.duration)
|
||||
video.addEventListener('timeupdate', fun)
|
||||
})
|
||||
|
||||
let eventTester = (e) => {
|
||||
video.addEventListener(
|
||||
e,
|
||||
() => {
|
||||
// console.log('eventTester', e, this.item.id)
|
||||
if (e === 'playing') this.loading = false
|
||||
if (e === 'waiting') {
|
||||
if (!this.paused && !this.ignoreWaiting) {
|
||||
this.loading = true
|
||||
}
|
||||
}
|
||||
// console.log(e, t)
|
||||
},
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
// eventTester("loadstart", '客户端开始请求数据'); //客户端开始请求数据
|
||||
// eventTester("abort", '客户端主动终止下载(不是因为错误引起)'); //客户端主动终止下载(不是因为错误引起)
|
||||
// eventTester("loadstart", '客户端开始请求数据'); //客户端开始请求数据
|
||||
// eventTester("progress", '客户端正在请求数据'); //客户端正在请求数据
|
||||
// // eventTester("suspend", '延迟下载'); //延迟下载
|
||||
// eventTester("abort", '客户端主动终止下载(不是因为错误引起),'); //客户端主动终止下载(不是因为错误引起),
|
||||
// eventTester("error", '请求数据时遇到错误'); //请求数据时遇到错误
|
||||
// eventTester("stalled", '网速失速'); //网速失速
|
||||
// eventTester("play", 'play()和autoplay开始播放时触发'); //play()和autoplay开始播放时触发
|
||||
// eventTester("pause", 'pause()触发'); //pause()触发
|
||||
// eventTester("loadedmetadata", '成功获取资源长度'); //成功获取资源长度
|
||||
// eventTester("loadeddata"); //
|
||||
eventTester('waiting', '等待数据,并非错误') //等待数据,并非错误
|
||||
eventTester('playing', '开始回放') //开始回放
|
||||
// eventTester("canplay", '/可以播放,但中途可能因为加载而暂停'); //可以播放,但中途可能因为加载而暂停
|
||||
// eventTester("canplaythrough", '可以播放,歌曲全部加载完毕'); //可以播放,歌曲全部加载完毕
|
||||
// eventTester("seeking", '寻找中'); //寻找中
|
||||
// eventTester("seeked", '寻找完毕'); //寻找完毕
|
||||
// // eventTester("timeupdate",'播放时间改变'); //播放时间改变
|
||||
// eventTester("ended", '播放结束'); //播放结束
|
||||
// eventTester("ratechange", '播放速率改变'); //播放速率改变
|
||||
// eventTester("durationchange", '资源长度改变'); //资源长度改变
|
||||
// eventTester("volumechange", '音量改变'); //音量改变
|
||||
|
||||
// console.log('mounted')
|
||||
// bus.off('singleClickBroadcast')
|
||||
bus.on(EVENT_KEY.SINGLE_CLICK_BROADCAST, this.click)
|
||||
bus.on(EVENT_KEY.DIALOG_MOVE, this.onDialogMove)
|
||||
bus.on(EVENT_KEY.DIALOG_END, this.onDialogEnd)
|
||||
bus.on(EVENT_KEY.OPEN_COMMENTS, this.onOpenComments)
|
||||
bus.on(EVENT_KEY.CLOSE_COMMENTS, this.onCloseComments)
|
||||
bus.on(EVENT_KEY.OPEN_SUB_TYPE, this.onOpenSubType)
|
||||
bus.on(EVENT_KEY.CLOSE_SUB_TYPE, this.onCloseSubType)
|
||||
},
|
||||
unmounted() {
|
||||
// console.log('unmounted')
|
||||
bus.off(EVENT_KEY.SINGLE_CLICK_BROADCAST, this.click)
|
||||
bus.off(EVENT_KEY.DIALOG_MOVE, this.onDialogMove)
|
||||
bus.off(EVENT_KEY.DIALOG_END, this.onDialogEnd)
|
||||
bus.off(EVENT_KEY.OPEN_COMMENTS, this.onOpenComments)
|
||||
bus.off(EVENT_KEY.CLOSE_COMMENTS, this.onCloseComments)
|
||||
bus.off(EVENT_KEY.OPEN_SUB_TYPE, this.onOpenSubType)
|
||||
bus.off(EVENT_KEY.CLOSE_SUB_TYPE, this.onCloseSubType)
|
||||
},
|
||||
methods: {
|
||||
_checkImgUrl,
|
||||
onOpenSubType() {
|
||||
this.commentVisible = true
|
||||
},
|
||||
onCloseSubType() {
|
||||
this.commentVisible = false
|
||||
},
|
||||
onDialogMove({ tag, e }) {
|
||||
if (this.commentVisible && tag === 'comment') {
|
||||
Utils.$setCss(this.$refs.video, 'transition-duration', `0ms`)
|
||||
Utils.$setCss(this.$refs.video, 'height', `calc(var(--vh, 1vh) * 30 + ${e}px)`)
|
||||
}
|
||||
},
|
||||
onDialogEnd({ tag, isClose }) {
|
||||
if (this.commentVisible && tag === 'comment') {
|
||||
console.log('isClose', isClose)
|
||||
Utils.$setCss(this.$refs.video, 'transition-duration', `300ms`)
|
||||
if (isClose) {
|
||||
this.commentVisible = false
|
||||
Utils.$setCss(this.$refs.video, 'height', '100%')
|
||||
} else {
|
||||
Utils.$setCss(this.$refs.video, 'height', 'calc(var(--vh, 1vh) * 30)')
|
||||
}
|
||||
}
|
||||
},
|
||||
onOpenComments(id) {
|
||||
if (id === this.item.id) {
|
||||
Utils.$setCss(this.$refs.video, 'transition-duration', `300ms`)
|
||||
Utils.$setCss(this.$refs.video, 'height', 'calc(var(--vh, 1vh) * 30)')
|
||||
this.commentVisible = true
|
||||
}
|
||||
},
|
||||
onCloseComments() {
|
||||
if (this.commentVisible) {
|
||||
Utils.$setCss(this.$refs.video, 'transition-duration', `300ms`)
|
||||
Utils.$setCss(this.$refs.video, 'height', '100%')
|
||||
this.commentVisible = false
|
||||
}
|
||||
},
|
||||
click({ uniqueId, index, type }) {
|
||||
if (this.position.uniqueId === uniqueId && this.position.index === index) {
|
||||
if (type === EVENT_KEY.ITEM_TOGGLE) {
|
||||
if (this.isLive) {
|
||||
if (type === EVENT_KEY.ITEM_TOGGLE) {
|
||||
this.pause()
|
||||
bus.emit(EVENT_KEY.NAV, {
|
||||
path: '/home/live',
|
||||
query: { id: this.item.id }
|
||||
})
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
},
|
||||
play() {
|
||||
this.status = SlideItemPlayStatus.Play
|
||||
this.$refs.video.volume = 1
|
||||
this.$refs.video.play()
|
||||
},
|
||||
pause() {
|
||||
this.status = SlideItemPlayStatus.Pause
|
||||
this.$refs.video.pause()
|
||||
},
|
||||
touchstart(e) {
|
||||
Utils.$stopPropagation(e)
|
||||
this.start.x = e.touches[0].pageX
|
||||
this.last.x = this.playX
|
||||
this.last.time = this.currentTime
|
||||
},
|
||||
touchmove(e) {
|
||||
// console.log('move',e)
|
||||
Utils.$stopPropagation(e)
|
||||
this.isMove = true
|
||||
this.pause()
|
||||
let dx = e.touches[0].pageX - this.start.x
|
||||
this.playX = this.last.x + dx
|
||||
this.currentTime = this.last.time + Math.ceil(Math.ceil(dx) / this.step)
|
||||
if (this.currentTime <= 0) this.currentTime = 0
|
||||
if (this.currentTime >= this.duration) this.currentTime = this.duration
|
||||
},
|
||||
touchend(e) {
|
||||
// console.log('end', e)
|
||||
Utils.$stopPropagation(e)
|
||||
if (this.isPlaying) return
|
||||
setTimeout(() => (this.isMove = false), 1000)
|
||||
this.$refs.video.currentTime = this.currentTime
|
||||
this.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: transform 0.5s linear;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
font-size: 14rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
video {
|
||||
max-width: 100vw;
|
||||
height: 100%;
|
||||
transition:
|
||||
height,
|
||||
margin-top 0.3s;
|
||||
//background: black;
|
||||
/*position: absolute;*/
|
||||
}
|
||||
|
||||
.float {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.normal {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
transition: all 0.3s;
|
||||
|
||||
.toolbar {
|
||||
//width: 40px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 5px;
|
||||
color: #fff;
|
||||
|
||||
.avatar-ctn {
|
||||
position: relative;
|
||||
|
||||
.avatar {
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.options {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
margin: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -5px;
|
||||
background: red;
|
||||
//background: black;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 1s;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
.yes {
|
||||
opacity: 0;
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
|
||||
&.attention {
|
||||
background: white;
|
||||
|
||||
.no {
|
||||
opacity: 0;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.yes {
|
||||
opacity: 1;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.love,
|
||||
.message,
|
||||
.share {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@width: 35rem;
|
||||
|
||||
img {
|
||||
width: @width;
|
||||
height: @width;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
.loved {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 75%;
|
||||
//display: flex;
|
||||
//flex-direction: column;
|
||||
|
||||
.location-wrapper {
|
||||
display: flex;
|
||||
|
||||
.location {
|
||||
margin-bottom: 10rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12rem;
|
||||
padding: 4rem;
|
||||
border-radius: 3rem;
|
||||
background: var(--second-btn-color-tran);
|
||||
|
||||
.gang {
|
||||
height: 8rem;
|
||||
width: 1.5px;
|
||||
margin: 0 5rem;
|
||||
background: gray;
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: 7rem;
|
||||
width: 18rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.music {
|
||||
position: relative;
|
||||
width: 60%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.music-image {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.comment {
|
||||
.type-comment {
|
||||
display: flex;
|
||||
background: rgb(130, 21, 44);
|
||||
border-radius: 50px;
|
||||
padding: 3px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.right {
|
||||
margin: 0 10px;
|
||||
color: var(--second-text-color);
|
||||
|
||||
.name {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loveds {
|
||||
}
|
||||
|
||||
.type-loved {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
animation: test 1s;
|
||||
animation-delay: 0.5s;
|
||||
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.loved {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: red;
|
||||
padding: 3px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes test {
|
||||
from {
|
||||
display: block;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
to {
|
||||
display: none;
|
||||
transform: translate3d(0, -60px, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress {
|
||||
z-index: 10;
|
||||
@w: 90%;
|
||||
position: absolute;
|
||||
bottom: -1rem;
|
||||
height: 10rem;
|
||||
left: calc((100% - @w) / 2);
|
||||
width: @w;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.time {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
font-size: 24px;
|
||||
bottom: 50px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
color: white;
|
||||
text-align: center;
|
||||
|
||||
.duration {
|
||||
color: darkgray;
|
||||
}
|
||||
}
|
||||
|
||||
@radius: 10rem;
|
||||
|
||||
@h: 2rem;
|
||||
@tr: height 0.3s;
|
||||
|
||||
.bg {
|
||||
transition: @tr;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: @h;
|
||||
background: #4f4f4f;
|
||||
border-radius: @radius;
|
||||
}
|
||||
|
||||
@p: 50px;
|
||||
|
||||
.progress-line {
|
||||
transition: @tr;
|
||||
height: calc(@h + 0.5rem);
|
||||
width: @p;
|
||||
border-radius: @radius 0 0 @radius;
|
||||
background: #777777;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.point {
|
||||
transition: all 0.2s;
|
||||
width: @h+2;
|
||||
height: @h+2;
|
||||
border-radius: 50%;
|
||||
background: gray;
|
||||
z-index: 2;
|
||||
transform: translate(-1rem, 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
& .move {
|
||||
@h: 10rem;
|
||||
|
||||
.bg {
|
||||
height: @h;
|
||||
background: var(--active-main-bg);
|
||||
}
|
||||
|
||||
.progress-line {
|
||||
height: @h;
|
||||
background: var(--second-text-color);
|
||||
}
|
||||
|
||||
.point {
|
||||
width: @h+2;
|
||||
height: @h+2;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
& .stop {
|
||||
@h: 4rem;
|
||||
|
||||
.bg {
|
||||
height: @h;
|
||||
}
|
||||
|
||||
.progress-line {
|
||||
height: @h;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.point {
|
||||
width: @h+2;
|
||||
height: @h+2;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
621
src/components/slide/BaseVideo.vue
Normal file
@@ -0,0 +1,621 @@
|
||||
<template>
|
||||
<div class="video-wrapper" ref="videoWrapper" :class="positionName">
|
||||
<Loading v-if="state.loading" style="position: absolute" />
|
||||
<!-- <video :src="item.video + '?v=123'"-->
|
||||
<video
|
||||
:src="item.video.play_addr.url_list[0]"
|
||||
:poster="poster"
|
||||
ref="videoEl"
|
||||
:muted="state.isMuted"
|
||||
preload="true"
|
||||
loop
|
||||
x5-video-player-type="h5-page"
|
||||
:x5-video-player-fullscreen="false"
|
||||
:webkit-playsinline="true"
|
||||
:x5-playsinline="true"
|
||||
:playsinline="true"
|
||||
:fullscreen="false"
|
||||
:autoplay="isPlay"
|
||||
>
|
||||
<p>您的浏览器不支持 video 标签。</p>
|
||||
</video>
|
||||
<Icon icon="fluent:play-28-filled" class="pause-icon" v-if="!isPlaying" />
|
||||
<div class="float">
|
||||
<template v-if="isLive">
|
||||
<div class="living">点击进入直播间</div>
|
||||
<ItemDesc :is-live="true" v-model:item="state.localItem" :position="position" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div :style="{ opacity: state.isMove ? 0 : 1 }" class="normal">
|
||||
<template v-if="!state.commentVisible">
|
||||
<ItemToolbar v-model:item="state.localItem" />
|
||||
<ItemDesc v-model:item="state.localItem" />
|
||||
</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 state.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
|
||||
class="progress"
|
||||
:class="progressClass"
|
||||
ref="progressEl"
|
||||
@click="null"
|
||||
@touchstart="touchstart"
|
||||
@touchmove="touchmove"
|
||||
@touchend="touchend"
|
||||
>
|
||||
<div class="time" v-if="state.isMove">
|
||||
<span class="currentTime">{{ _duration(state.currentTime) }}</span>
|
||||
<span class="duration"> / {{ _duration(state.duration) }}</span>
|
||||
</div>
|
||||
<template v-if="state.duration > 15 || state.isMove || !isPlaying">
|
||||
<div class="bg"></div>
|
||||
<div class="progress-line" :style="durationStyle"></div>
|
||||
<div class="point"></div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { _checkImgUrl, _duration, _stopPropagation } from '@/utils'
|
||||
import Loading from '../Loading.vue'
|
||||
import ItemToolbar from './ItemToolbar.vue'
|
||||
import ItemDesc from './ItemDesc.vue'
|
||||
import bus, { EVENT_KEY } from '../../utils/bus'
|
||||
import { SlideItemPlayStatus } from '@/utils/const_var'
|
||||
import { computed, onMounted, onUnmounted, provide, reactive } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { _css } from '@/utils/dom'
|
||||
|
||||
defineOptions({
|
||||
name: 'BaseVideo'
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
position: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
//用于第一条数据,自动播放,如果都用事件去触发播放,有延迟
|
||||
isPlay: {
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return true
|
||||
}
|
||||
},
|
||||
isMy: {
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false
|
||||
}
|
||||
},
|
||||
isLive: {
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
provide(
|
||||
'isPlaying',
|
||||
computed(() => isPlaying)
|
||||
)
|
||||
provide(
|
||||
'isMuted',
|
||||
computed(() => state.isMuted)
|
||||
)
|
||||
provide(
|
||||
'position',
|
||||
computed(() => props.position)
|
||||
)
|
||||
provide(
|
||||
'item',
|
||||
computed(() => props.item)
|
||||
)
|
||||
|
||||
const videoEl = $ref<HTMLVideoElement>()
|
||||
const progressEl = $ref<HTMLDivElement>()
|
||||
let state = reactive({
|
||||
loading: false,
|
||||
paused: false,
|
||||
isMuted: window.isMuted,
|
||||
status: props.isPlay ? SlideItemPlayStatus.Play : SlideItemPlayStatus.Pause,
|
||||
duration: 0,
|
||||
step: 0,
|
||||
currentTime: -1,
|
||||
playX: 0,
|
||||
start: { x: 0 },
|
||||
last: { x: 0, time: 0 },
|
||||
height: 0,
|
||||
width: 0,
|
||||
isMove: false,
|
||||
ignoreWaiting: false, //忽略waiting事件。因为改变进度会触发waiting事件,烦的一批
|
||||
test: [1, 2],
|
||||
localItem: props.item,
|
||||
progressBarRect: {
|
||||
height: 0,
|
||||
width: 0
|
||||
},
|
||||
videoScreenHeight: 0,
|
||||
commentVisible: false
|
||||
})
|
||||
const poster = $computed(() => {
|
||||
return _checkImgUrl(props.item.video.poster ?? props.item.video.cover.url_list[0])
|
||||
})
|
||||
const durationStyle = $computed(() => {
|
||||
return { width: state.playX + 'px' }
|
||||
})
|
||||
const isPlaying = $computed(() => {
|
||||
return state.status === SlideItemPlayStatus.Play
|
||||
})
|
||||
const positionName = $computed(() => {
|
||||
return 'item-' + Object.values(props.position).join('-')
|
||||
})
|
||||
const progressClass = $computed(() => {
|
||||
if (state.isMove) {
|
||||
return 'move'
|
||||
} else {
|
||||
return isPlaying ? '' : 'stop'
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('video', this.localItem.aweme_id)
|
||||
// console.log(this.commentVisible)
|
||||
state.height = document.body.clientHeight
|
||||
state.width = document.body.clientWidth
|
||||
videoEl.currentTime = 0
|
||||
let fun = (e) => {
|
||||
state.currentTime = Math.ceil(e.target.currentTime)
|
||||
state.playX = (state.currentTime - 1) * state.step
|
||||
}
|
||||
videoEl.addEventListener('loadedmetadata', () => {
|
||||
state.videoScreenHeight = videoEl.videoHeight / (videoEl.videoWidth / state.width)
|
||||
state.duration = videoEl.duration
|
||||
state.progressBarRect = progressEl.getBoundingClientRect()
|
||||
state.step = state.progressBarRect.width / Math.floor(state.duration)
|
||||
videoEl.addEventListener('timeupdate', fun)
|
||||
})
|
||||
|
||||
let eventTester = (e, t: string) => {
|
||||
videoEl.addEventListener(
|
||||
e,
|
||||
() => {
|
||||
// console.log('eventTester', e, state.item.aweme_id)
|
||||
if (e === 'playing') state.loading = false
|
||||
if (e === 'waiting') {
|
||||
if (!state.paused && !state.ignoreWaiting) {
|
||||
state.loading = true
|
||||
}
|
||||
}
|
||||
let s = false
|
||||
if (s) {
|
||||
console.log(e, t)
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
// eventTester("loadstart", '客户端开始请求数据'); //客户端开始请求数据
|
||||
// eventTester("abort", '客户端主动终止下载(不是因为错误引起)'); //客户端主动终止下载(不是因为错误引起)
|
||||
// eventTester("loadstart", '客户端开始请求数据'); //客户端开始请求数据
|
||||
// eventTester("progress", '客户端正在请求数据'); //客户端正在请求数据
|
||||
// // eventTester("suspend", '延迟下载'); //延迟下载
|
||||
// eventTester("abort", '客户端主动终止下载(不是因为错误引起),'); //客户端主动终止下载(不是因为错误引起),
|
||||
// eventTester("error", '请求数据时遇到错误'); //请求数据时遇到错误
|
||||
// eventTester("stalled", '网速失速'); //网速失速
|
||||
// eventTester("play", 'play()和autoplay开始播放时触发'); //play()和autoplay开始播放时触发
|
||||
// eventTester("pause", 'pause()触发'); //pause()触发
|
||||
// eventTester("loadedmetadata", '成功获取资源长度'); //成功获取资源长度
|
||||
// eventTester("loadeddata"); //
|
||||
eventTester('waiting', '等待数据,并非错误') //等待数据,并非错误
|
||||
eventTester('playing', '开始回放') //开始回放
|
||||
// eventTester("canplay", '/可以播放,但中途可能因为加载而暂停'); //可以播放,但中途可能因为加载而暂停
|
||||
// eventTester("canplaythrough", '可以播放,歌曲全部加载完毕'); //可以播放,歌曲全部加载完毕
|
||||
// eventTester("seeking", '寻找中'); //寻找中
|
||||
// eventTester("seeked", '寻找完毕'); //寻找完毕
|
||||
// // eventTester("timeupdate",'播放时间改变'); //播放时间改变
|
||||
// eventTester("ended", '播放结束'); //播放结束
|
||||
// eventTester("ratechange", '播放速率改变'); //播放速率改变
|
||||
// eventTester("durationchange", '资源长度改变'); //资源长度改变
|
||||
// eventTester("volumechange", '音量改变'); //音量改变
|
||||
|
||||
// console.log('mounted')
|
||||
// bus.off('singleClickBroadcast')
|
||||
bus.on(EVENT_KEY.SINGLE_CLICK_BROADCAST, click)
|
||||
bus.on(EVENT_KEY.DIALOG_MOVE, onDialogMove)
|
||||
bus.on(EVENT_KEY.DIALOG_END, onDialogEnd)
|
||||
bus.on(EVENT_KEY.OPEN_COMMENTS, onOpenComments)
|
||||
bus.on(EVENT_KEY.CLOSE_COMMENTS, onCloseComments)
|
||||
bus.on(EVENT_KEY.OPEN_SUB_TYPE, onOpenSubType)
|
||||
bus.on(EVENT_KEY.CLOSE_SUB_TYPE, onCloseSubType)
|
||||
|
||||
bus.on(EVENT_KEY.REMOVE_MUTED, removeMuted)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// console.log('unmounted')
|
||||
bus.off(EVENT_KEY.SINGLE_CLICK_BROADCAST, click)
|
||||
bus.off(EVENT_KEY.DIALOG_MOVE, onDialogMove)
|
||||
bus.off(EVENT_KEY.DIALOG_END, onDialogEnd)
|
||||
bus.off(EVENT_KEY.OPEN_COMMENTS, onOpenComments)
|
||||
bus.off(EVENT_KEY.CLOSE_COMMENTS, onCloseComments)
|
||||
bus.off(EVENT_KEY.OPEN_SUB_TYPE, onOpenSubType)
|
||||
bus.off(EVENT_KEY.CLOSE_SUB_TYPE, onCloseSubType)
|
||||
bus.off(EVENT_KEY.REMOVE_MUTED, removeMuted)
|
||||
})
|
||||
|
||||
function removeMuted() {
|
||||
state.isMuted = false
|
||||
}
|
||||
|
||||
function onOpenSubType() {
|
||||
state.commentVisible = true
|
||||
}
|
||||
|
||||
function onCloseSubType() {
|
||||
state.commentVisible = false
|
||||
}
|
||||
|
||||
function onDialogMove({ tag, e }) {
|
||||
if (state.commentVisible && tag === 'comment') {
|
||||
_css(videoEl, 'transition-duration', `0ms`)
|
||||
_css(videoEl, 'height', `calc(var(--vh, 1vh) * 30 + ${e}px)`)
|
||||
}
|
||||
}
|
||||
|
||||
function onDialogEnd({ tag, isClose }) {
|
||||
if (state.commentVisible && tag === 'comment') {
|
||||
console.log('isClose', isClose)
|
||||
_css(videoEl, 'transition-duration', `300ms`)
|
||||
if (isClose) {
|
||||
state.commentVisible = false
|
||||
_css(videoEl, 'height', '100%')
|
||||
} else {
|
||||
_css(videoEl, 'height', 'calc(var(--vh, 1vh) * 30)')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onOpenComments(id) {
|
||||
if (id === props.item.aweme_id) {
|
||||
_css(videoEl, 'transition-duration', `300ms`)
|
||||
_css(videoEl, 'height', 'calc(var(--vh, 1vh) * 30)')
|
||||
state.commentVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
function onCloseComments() {
|
||||
if (state.commentVisible) {
|
||||
_css(videoEl, 'transition-duration', `300ms`)
|
||||
_css(videoEl, 'height', '100%')
|
||||
state.commentVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
function click({ uniqueId, index, type }) {
|
||||
if (props.position.uniqueId === uniqueId && props.position.index === index) {
|
||||
if (type === EVENT_KEY.ITEM_TOGGLE) {
|
||||
if (props.isLive) {
|
||||
pause()
|
||||
bus.emit(EVENT_KEY.NAV, {
|
||||
path: '/home/live',
|
||||
query: { id: props.item.aweme_id }
|
||||
})
|
||||
} else {
|
||||
if (state.status === SlideItemPlayStatus.Play) {
|
||||
pause()
|
||||
} else {
|
||||
play()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type === EVENT_KEY.ITEM_STOP) {
|
||||
videoEl.currentTime = 0
|
||||
state.ignoreWaiting = true
|
||||
pause()
|
||||
setTimeout(() => (state.ignoreWaiting = false), 300)
|
||||
}
|
||||
if (type === EVENT_KEY.ITEM_PLAY) {
|
||||
videoEl.currentTime = 0
|
||||
state.ignoreWaiting = true
|
||||
play()
|
||||
setTimeout(() => (state.ignoreWaiting = false), 300)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function play() {
|
||||
state.status = SlideItemPlayStatus.Play
|
||||
videoEl.volume = 1
|
||||
videoEl.play()
|
||||
}
|
||||
|
||||
function pause() {
|
||||
state.status = SlideItemPlayStatus.Pause
|
||||
videoEl.pause()
|
||||
}
|
||||
|
||||
function touchstart(e) {
|
||||
_stopPropagation(e)
|
||||
state.start.x = e.touches[0].pageX
|
||||
state.last.x = state.playX
|
||||
state.last.time = state.currentTime
|
||||
}
|
||||
|
||||
function touchmove(e) {
|
||||
// console.log('move',e)
|
||||
_stopPropagation(e)
|
||||
state.isMove = true
|
||||
pause()
|
||||
let dx = e.touches[0].pageX - state.start.x
|
||||
state.playX = state.last.x + dx
|
||||
state.currentTime = state.last.time + Math.ceil(Math.ceil(dx) / state.step)
|
||||
if (state.currentTime <= 0) state.currentTime = 0
|
||||
if (state.currentTime >= state.duration) state.currentTime = state.duration
|
||||
}
|
||||
|
||||
function touchend(e) {
|
||||
// console.log('end', e)
|
||||
_stopPropagation(e)
|
||||
if (isPlaying) return
|
||||
setTimeout(() => (state.isMove = false), 1000)
|
||||
videoEl.currentTime = state.currentTime
|
||||
play()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
font-size: 14rem;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
transition:
|
||||
height,
|
||||
margin-top 0.3s;
|
||||
//background: black;
|
||||
/*position: absolute;*/
|
||||
}
|
||||
|
||||
.float {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.normal {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
transition: all 0.3s;
|
||||
|
||||
.comment-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.comment {
|
||||
.type-comment {
|
||||
display: flex;
|
||||
background: rgb(130, 21, 44);
|
||||
border-radius: 50px;
|
||||
padding: 3px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.right {
|
||||
margin: 0 10px;
|
||||
color: var(--second-text-color);
|
||||
|
||||
.name {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loveds {
|
||||
}
|
||||
|
||||
.type-loved {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
animation: test 1s;
|
||||
animation-delay: 0.5s;
|
||||
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.loved {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: red;
|
||||
padding: 3px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes test {
|
||||
from {
|
||||
display: block;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
to {
|
||||
display: none;
|
||||
transform: translate3d(0, -60px, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress {
|
||||
z-index: 10;
|
||||
@w: 90%;
|
||||
position: absolute;
|
||||
bottom: -1rem;
|
||||
height: 10rem;
|
||||
left: calc((100% - @w) / 2);
|
||||
width: @w;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.time {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
font-size: 24px;
|
||||
bottom: 50px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
color: white;
|
||||
text-align: center;
|
||||
|
||||
.duration {
|
||||
color: darkgray;
|
||||
}
|
||||
}
|
||||
|
||||
@radius: 10rem;
|
||||
|
||||
@h: 2rem;
|
||||
@tr: height 0.3s;
|
||||
|
||||
.bg {
|
||||
transition: @tr;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: @h;
|
||||
background: #4f4f4f;
|
||||
border-radius: @radius;
|
||||
}
|
||||
|
||||
@p: 50px;
|
||||
|
||||
.progress-line {
|
||||
transition: @tr;
|
||||
height: calc(@h + 0.5rem);
|
||||
width: @p;
|
||||
border-radius: @radius 0 0 @radius;
|
||||
background: #777777;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.point {
|
||||
transition: all 0.2s;
|
||||
width: @h+2;
|
||||
height: @h+2;
|
||||
border-radius: 50%;
|
||||
background: gray;
|
||||
z-index: 2;
|
||||
transform: translate(-1rem, 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
& .move {
|
||||
@h: 10rem;
|
||||
|
||||
.bg {
|
||||
height: @h;
|
||||
background: var(--active-main-bg);
|
||||
}
|
||||
|
||||
.progress-line {
|
||||
height: @h;
|
||||
background: var(--second-text-color);
|
||||
}
|
||||
|
||||
.point {
|
||||
width: @h+2;
|
||||
height: @h+2;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
& .stop {
|
||||
@h: 4rem;
|
||||
|
||||
.bg {
|
||||
height: @h;
|
||||
}
|
||||
|
||||
.progress-line {
|
||||
height: @h;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.point {
|
||||
width: @h+2;
|
||||
height: @h+2;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="jsx">
|
||||
import bus from '../../utils/bus'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { _css } from '@/utils/dom'
|
||||
|
||||
export default {
|
||||
name: 'Indicator',
|
||||
@@ -91,8 +92,8 @@ export default {
|
||||
this.currentSlideItemIndex = index
|
||||
this.$attrs['onUpdate:activeIndex'] &&
|
||||
this.$emit('update:active-index', this.currentSlideItemIndex)
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
this.$setCss(
|
||||
_css(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] + 'px'
|
||||
@@ -103,7 +104,7 @@ export default {
|
||||
this.indicatorRef = this.$refs.indicator
|
||||
for (let i = 0; i < tabs.children.length; i++) {
|
||||
let item = tabs.children[i]
|
||||
this.tabWidth = this.$getCss(item, 'width')
|
||||
this.tabWidth = _css(item, 'width')
|
||||
this.tabIndicatorRelationActiveIndexLefts.push(
|
||||
item.getBoundingClientRect().x -
|
||||
tabs.children[0].getBoundingClientRect().x +
|
||||
@@ -112,15 +113,15 @@ export default {
|
||||
}
|
||||
this.indicatorSpace =
|
||||
this.tabIndicatorRelationActiveIndexLefts[1] - this.tabIndicatorRelationActiveIndexLefts[0]
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
this.$setCss(
|
||||
_css(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] + 'px'
|
||||
)
|
||||
},
|
||||
move(e) {
|
||||
this.$setCss(
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] -
|
||||
@@ -131,14 +132,14 @@ export default {
|
||||
end(index) {
|
||||
// console.log(index)
|
||||
this.currentSlideItemIndex = index
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
this.$setCss(
|
||||
_css(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] + 'px'
|
||||
)
|
||||
setTimeout(() => {
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
_css(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="jsx">
|
||||
import bus from '../../utils/bus'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { _css } from '@/utils/dom'
|
||||
|
||||
export default {
|
||||
name: 'IndicatorLight',
|
||||
@@ -85,8 +86,8 @@ export default {
|
||||
this.currentSlideItemIndex = index
|
||||
this.$attrs['onUpdate:activeIndex'] &&
|
||||
this.$emit('update:active-index', this.currentSlideItemIndex)
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
this.$setCss(
|
||||
_css(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] + 'px'
|
||||
@@ -95,10 +96,10 @@ export default {
|
||||
initTabs() {
|
||||
let tabs = this.$refs.tabs
|
||||
this.indicatorRef = this.$refs.indicator
|
||||
let indicatorWidth = this.$getCss(this.indicatorRef, 'width')
|
||||
let indicatorWidth = _css(this.indicatorRef, 'width')
|
||||
for (let i = 0; i < tabs.children.length; i++) {
|
||||
let item = tabs.children[i]
|
||||
this.tabWidth = this.$getCss(item, 'width')
|
||||
this.tabWidth = _css(item, 'width')
|
||||
this.tabIndicatorRelationActiveIndexLefts.push(
|
||||
item.getBoundingClientRect().x -
|
||||
tabs.children[0].getBoundingClientRect().x +
|
||||
@@ -108,15 +109,15 @@ export default {
|
||||
|
||||
this.indicatorSpace =
|
||||
this.tabIndicatorRelationActiveIndexLefts[1] - this.tabIndicatorRelationActiveIndexLefts[0]
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
this.$setCss(
|
||||
_css(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] + 'px'
|
||||
)
|
||||
},
|
||||
move(e) {
|
||||
this.$setCss(
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] -
|
||||
@@ -127,14 +128,14 @@ export default {
|
||||
end(index) {
|
||||
// console.log(index)
|
||||
this.currentSlideItemIndex = index
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
this.$setCss(
|
||||
_css(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] + 'px'
|
||||
)
|
||||
setTimeout(() => {
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
_css(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { inject, reactive } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
position: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
isMy: {
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
@@ -28,6 +16,8 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const item = inject<any>('item')
|
||||
|
||||
const state = reactive({
|
||||
isAttention: false,
|
||||
test: [1, 2]
|
||||
@@ -36,26 +26,26 @@ const state = reactive({
|
||||
<template>
|
||||
<div class="item-desc ml1r mb1r">
|
||||
<div class="content" v-if="!props.isMy">
|
||||
<div class="location-wrapper" v-if="props.item.city || item.address">
|
||||
<div class="location-wrapper" v-if="item.city || item.address">
|
||||
<div class="location">
|
||||
<img src="../../assets/img/icon/location.webp" alt="" />
|
||||
<span>{{ props.item.city }}</span>
|
||||
<template v-if="props.item.address">
|
||||
<span>{{ item.city }}</span>
|
||||
<template v-if="item.address">
|
||||
<div class="gang"></div>
|
||||
</template>
|
||||
<span>{{ props.item.address }}</span>
|
||||
<span>{{ 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 }}
|
||||
@{{ item.author.nickname }}
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ props.item.desc }}
|
||||
{{ 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>-->
|
||||
<!-- <span>{{ item.music.title }}</span>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<div v-else class="comment-status">
|
||||
@@ -85,10 +75,10 @@ const state = reactive({
|
||||
.item-desc {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 70%;
|
||||
|
||||
.content {
|
||||
color: #fff;
|
||||
width: 75vw;
|
||||
text-align: left;
|
||||
|
||||
.location-wrapper {
|
||||
|
||||
@@ -1,46 +1,53 @@
|
||||
<script setup>
|
||||
import BaseMusic from '../BaseMusic'
|
||||
import Utils from '../../utils'
|
||||
<script setup lang="ts">
|
||||
import BaseMusic from '../BaseMusic.vue'
|
||||
import { _formatNumber, cloneDeep } from '@/utils'
|
||||
import bus, { EVENT_KEY } from '@/utils/bus'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useClick } from '@/utils/hooks/useClick'
|
||||
import { inject } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
position: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
isMy: {
|
||||
type: Boolean,
|
||||
default: () => {
|
||||
return false
|
||||
}
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const position = inject<any>('position')
|
||||
|
||||
const emit = defineEmits(['update:item', 'goUserInfo', 'showComments', 'showShare', 'goMusic'])
|
||||
|
||||
function _updateItem(props, key, val) {
|
||||
const old = cloneDeep(props.item)
|
||||
old[key] = val
|
||||
emit('update:item', old)
|
||||
bus.emit(EVENT_KEY.UPDATE_ITEM, { position: position.value, item: old })
|
||||
}
|
||||
|
||||
function loved() {
|
||||
Utils.updateItem(props, 'isLoved', !props.item.isLoved, emit)
|
||||
_updateItem(props, 'isLoved', !props.item.isLoved)
|
||||
}
|
||||
|
||||
function attention(e) {
|
||||
e.currentTarget.classList.add('attention')
|
||||
setTimeout(() => {
|
||||
Utils.updateItem(props, 'isAttention', true, emit)
|
||||
_updateItem(props, 'isAttention', true)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function showComments() {
|
||||
// emit('showComments')
|
||||
bus.emit(EVENT_KEY.OPEN_COMMENTS, props.item.id)
|
||||
bus.emit(EVENT_KEY.OPEN_COMMENTS, props.item.aweme_id)
|
||||
}
|
||||
|
||||
const vClick = useClick()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -48,49 +55,51 @@ function showComments() {
|
||||
<div class="avatar-ctn mb2r">
|
||||
<img
|
||||
class="avatar"
|
||||
:src="props.item.author.avatar_168x168.url_list[0]"
|
||||
:src="item.author.avatar_168x168.url_list[0]"
|
||||
alt=""
|
||||
@click.stop="bus.emit(EVENT_KEY.GO_USERINFO)"
|
||||
v-click="() => bus.emit(EVENT_KEY.GO_USERINFO)"
|
||||
/>
|
||||
<transition name="fade">
|
||||
<div v-if="!props.item.isAttention" @click.stop="attention" class="options">
|
||||
<div v-if="!item.isAttention" v-click="attention" class="options">
|
||||
<img class="no" src="../../assets/img/icon/add-light.png" alt="" />
|
||||
<img class="yes" src="../../assets/img/icon/ok-red.png" alt="" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="love mb2r" @click.stop="loved($event)">
|
||||
<div class="love mb2r" v-click="loved">
|
||||
<div>
|
||||
<img src="../../assets/img/icon/love.svg" class="love-image" v-if="!props.item.isLoved" />
|
||||
<img src="../../assets/img/icon/loved.svg" class="love-image" v-if="props.item.isLoved" />
|
||||
<img src="../../assets/img/icon/love.svg" class="love-image" v-if="!item.isLoved" />
|
||||
<img src="../../assets/img/icon/loved.svg" class="love-image" v-if="item.isLoved" />
|
||||
</div>
|
||||
<span>{{ Utils.formatNumber(props.item.statistics.digg_count) }}</span>
|
||||
<span>{{ _formatNumber(item.statistics.digg_count) }}</span>
|
||||
</div>
|
||||
<div class="message mb2r" @click.stop="showComments">
|
||||
<div class="message mb2r" v-click="showComments">
|
||||
<Icon icon="mage:message-dots-round-fill" class="icon" style="color: white" />
|
||||
<span>{{ Utils.formatNumber(props.item.statistics.comment_count) }}</span>
|
||||
<span>{{ _formatNumber(item.statistics.comment_count) }}</span>
|
||||
</div>
|
||||
<!--TODO -->
|
||||
<div
|
||||
class="message mb2r"
|
||||
@click.stop="Utils.updateItem(props, 'isCollect', !props.item.isCollect, emit)"
|
||||
>
|
||||
<Icon v-if="props.item.isCollect" icon="ic:round-star" class="icon" style="color: yellow" />
|
||||
<div class="message mb2r" v-click="() => _updateItem(props, 'isCollect', !item.isCollect)">
|
||||
<Icon
|
||||
v-if="item.isCollect"
|
||||
icon="ic:round-star"
|
||||
class="icon"
|
||||
style="color: rgb(252, 179, 3)"
|
||||
/>
|
||||
<Icon v-else icon="ic:round-star" class="icon" style="color: white" />
|
||||
<span>{{ Utils.formatNumber(props.item.statistics.comment_count) }}</span>
|
||||
<span>{{ _formatNumber(item.statistics.comment_count) }}</span>
|
||||
</div>
|
||||
<div v-if="!props.isMy" class="share mb2r" @click.stop="bus.emit(EVENT_KEY.SHOW_SHARE)">
|
||||
<div v-if="!props.isMy" class="share mb2r" v-click="() => bus.emit(EVENT_KEY.SHOW_SHARE)">
|
||||
<img src="../../assets/img/icon/share-white-full.png" alt="" class="share-image" />
|
||||
<span>{{ Utils.formatNumber(props.item.statistics.share_count) }}</span>
|
||||
<span>{{ _formatNumber(item.statistics.share_count) }}</span>
|
||||
</div>
|
||||
<div v-else class="share mb2r" @click.stop="bus.emit(EVENT_KEY.SHOW_SHARE)">
|
||||
<div v-else class="share mb2r" v-click="() => bus.emit(EVENT_KEY.SHOW_SHARE)">
|
||||
<img src="../../assets/img/icon/menu-white.png" alt="" class="share-image" />
|
||||
</div>
|
||||
<!-- <BaseMusic-->
|
||||
<!-- :cover="props.item.music.cover"-->
|
||||
<!-- @click.stop="$nav('/home/music')"-->
|
||||
<!-- :cover="item.music.cover"-->
|
||||
<!-- v-click="$router.push('/home/music')"-->
|
||||
<!-- /> -->
|
||||
<BaseMusic :item="props.item" />
|
||||
<BaseMusic />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -99,8 +108,11 @@ function showComments() {
|
||||
//width: 40px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 5px;
|
||||
right: 10rem;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.avatar-ctn {
|
||||
position: relative;
|
||||
@@ -123,8 +135,8 @@ function showComments() {
|
||||
bottom: -5px;
|
||||
background: red;
|
||||
//background: black;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 18rem;
|
||||
height: 18rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -132,8 +144,8 @@ function showComments() {
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 14rem;
|
||||
height: 14rem;
|
||||
transition: all 1s;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="img-slide-wrapper">
|
||||
<div
|
||||
class="img-slide-list"
|
||||
ref="wrapperEl"
|
||||
ref="slideListEl"
|
||||
@touchstart.passive="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
@@ -23,13 +23,13 @@
|
||||
/>
|
||||
|
||||
<template v-if="state.operationStatus === SlideAlbumOperationStatus.Normal">
|
||||
<ItemToolbar
|
||||
class="mb3r"
|
||||
v-model:item="state.localItem"
|
||||
:position="position"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<ItemDesc class="mb3r" v-model:item="state.localItem" :position="position" />
|
||||
<!-- <ItemToolbar-->
|
||||
<!-- class="mb3r"-->
|
||||
<!-- v-model:item="state.localItem"-->
|
||||
<!-- :position="position"-->
|
||||
<!-- v-bind="$attrs"-->
|
||||
<!-- />-->
|
||||
<!-- <ItemDesc class="mb3r" v-model:item="state.localItem" :position="position" />-->
|
||||
</template>
|
||||
<!--不知为啥touch事件,在下部20px的空间内不触发,加上click事件不好了 -->
|
||||
<div
|
||||
@@ -70,7 +70,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="right">
|
||||
<Icon icon="heroicons-outline:menu-alt-1" @click="Utils.$no" />
|
||||
<Icon icon="heroicons-outline:menu-alt-1" @click="Utils._no" />
|
||||
<Icon
|
||||
icon="fluent:play-28-filled"
|
||||
v-if="state.status === SlideItemPlayStatus.Pause"
|
||||
@@ -78,7 +78,7 @@
|
||||
@click="startPlay"
|
||||
/>
|
||||
<Icon icon="bi:pause-fill" v-else class="pause" @click="stopPlay" />
|
||||
<Icon icon="system-uicons:push-down" @click="$notice('已保存到系统相册')" />
|
||||
<Icon icon="system-uicons:push-down" @click="_notice('已保存到系统相册')" />
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
@@ -88,7 +88,7 @@
|
||||
<script setup lang="jsx">
|
||||
import enums from '../../utils/enums'
|
||||
import Utils from '../../utils'
|
||||
import GM, { $notice } from '../../utils'
|
||||
import GM, { _notice } from '../../utils'
|
||||
import { mat4 } from 'gl-matrix'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import {
|
||||
@@ -106,15 +106,12 @@ import {
|
||||
slideInit,
|
||||
slideReset,
|
||||
slideTouchEnd,
|
||||
slidePointerMove,
|
||||
slidePointerDown
|
||||
slideTouchMove,
|
||||
slideTouchStart
|
||||
} from '@/utils/slide'
|
||||
import { SlideAlbumOperationStatus, SlideItemPlayStatus, SlideType } from '../../utils/const_var'
|
||||
import ItemToolbar from './ItemToolbar'
|
||||
import ItemDesc from './ItemDesc'
|
||||
import { cloneDeep } from '@/utils'
|
||||
import bus, { EVENT_KEY } from '../../utils/bus'
|
||||
import $ from 'jquery'
|
||||
|
||||
let out = new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
let ov = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1])
|
||||
@@ -236,8 +233,7 @@ const props = defineProps({
|
||||
}
|
||||
}
|
||||
})
|
||||
const judgeValue = 20
|
||||
const wrapperEl = ref(null)
|
||||
const slideListEl = ref(null)
|
||||
|
||||
//用于解决,touch事件触发startPlay,然后click事件又触发stopLoop的问题
|
||||
let lockDatetime = 0
|
||||
@@ -305,7 +301,7 @@ function startLoop() {
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
slideInit(wrapperEl.value, state, SlideType.HORIZONTAL)
|
||||
slideInit(slideListEl.value, state, SlideType.HORIZONTAL)
|
||||
startPlay()
|
||||
// setTimeout(() => {
|
||||
// state.operationStatus = SlideAlbumOperationStatus.Zooming
|
||||
@@ -352,11 +348,11 @@ onBeforeUpdate(() => {
|
||||
watch(
|
||||
() => state.localIndex,
|
||||
() => {
|
||||
GM.$setCss(wrapperEl.value, 'transition-duration', `300ms`)
|
||||
GM.$setCss(slideListEl.value, 'transition-duration', `300ms`)
|
||||
GM.$setCss(
|
||||
wrapperEl.value,
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(${getSlideOffset(state, wrapperEl.value)}px, 0, 0)`
|
||||
`translate3d(${getSlideOffset(state, slideListEl.value)}px, 0, 0)`
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -410,7 +406,7 @@ function touchStart(e) {
|
||||
// Utils.$showNoticeDialog('start'+e.touches.length)
|
||||
console.log('start', e.touches.length)
|
||||
if (e.touches.length === 1) {
|
||||
slidePointerDown(e, wrapperEl.value, state)
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
} else {
|
||||
if (state.operationStatus === SlideAlbumOperationStatus.Zooming) {
|
||||
// state.start.center = Utils.getCenter(state.start.point1, state.start.point2)
|
||||
@@ -431,6 +427,9 @@ function touchStart(e) {
|
||||
}
|
||||
|
||||
function touchMove(e) {
|
||||
const s = true
|
||||
if (s) return
|
||||
|
||||
// Utils.$showNoticeDialog('move'+e.touches.length)
|
||||
// console.log('move', e.touches.length, state.operationStatus)
|
||||
let current1 = { x: e.touches[0].pageX, y: e.touches[0].pageY }
|
||||
@@ -453,9 +452,9 @@ function touchMove(e) {
|
||||
} else {
|
||||
// console.log('m2')
|
||||
state.isAutoPlay = false
|
||||
slidePointerMove(
|
||||
slideTouchMove(
|
||||
e,
|
||||
wrapperEl.value,
|
||||
slideListEl.value,
|
||||
state,
|
||||
canNext,
|
||||
() => {
|
||||
@@ -482,10 +481,11 @@ function touchMove(e) {
|
||||
if (rectMap.has(state.localIndex)) {
|
||||
rect = rectMap.get(state.localIndex)
|
||||
} else {
|
||||
//TODO 这里去掉jquery
|
||||
//getBoundingClientRect在手机上获取不到值
|
||||
let offset = $(state.itemRefs[state.localIndex]).offset()
|
||||
rect = { x: offset.left, y: offset.top }
|
||||
rectMap.set(state.localIndex, rect)
|
||||
// let offset = $(state.itemRefs[state.localIndex]).offset()
|
||||
// rect = { x: offset.left, y: offset.top }
|
||||
// rectMap.set(state.localIndex, rect)
|
||||
}
|
||||
|
||||
let current2 = { x: e.touches[1].pageX, y: e.touches[1].pageY }
|
||||
@@ -580,7 +580,7 @@ function touchEnd(e) {
|
||||
startLoop()
|
||||
}
|
||||
)
|
||||
slideReset(wrapperEl.value, state)
|
||||
slideReset(e, slideListEl.value, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
||||
import GM from '../../utils'
|
||||
import {
|
||||
getSlideOffset,
|
||||
slideInit,
|
||||
slidePointerDown,
|
||||
slidePointerMove,
|
||||
slideReset,
|
||||
slideTouchEnd
|
||||
slideTouchEnd,
|
||||
slideTouchMove,
|
||||
slideTouchStart
|
||||
} from '@/utils/slide'
|
||||
import { SlideType } from '@/utils/const_var'
|
||||
import { _css } from '@/utils/dom'
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
@@ -22,6 +22,14 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
indicator: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
//改变index,是否使用动画
|
||||
changeActiveIndexUseAnim: {
|
||||
type: Boolean,
|
||||
@@ -31,16 +39,20 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update:index'])
|
||||
|
||||
let ob = null
|
||||
const wrapperEl = ref(null)
|
||||
//slide-list的ref引用
|
||||
const slideListEl = ref(null)
|
||||
|
||||
const state = reactive({
|
||||
judgeValue: 20,
|
||||
type: SlideType.HORIZONTAL,
|
||||
judgeValue: 20, //一个用于判断滑动朝向的固定值
|
||||
type: SlideType.HORIZONTAL, //组件类型
|
||||
name: props.name,
|
||||
localIndex: props.index,
|
||||
needCheck: true,
|
||||
next: false,
|
||||
start: { x: 0, y: 0, time: 0 },
|
||||
move: { x: 0, y: 0 },
|
||||
localIndex: props.index, //当前下标
|
||||
needCheck: true, //是否需要检测,每次按下都需要检测,up事件会重置为true
|
||||
next: false, //能否滑动
|
||||
isDown: false, //是否按下,用于move事件判断
|
||||
start: { x: 0, y: 0, time: 0 }, //按下时的起点坐标
|
||||
move: { x: 0, y: 0 }, //移动时的坐标
|
||||
//slide-list的宽度和子元素数量
|
||||
wrapper: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
@@ -55,56 +67,100 @@ watch(
|
||||
if (state.localIndex !== newVal) {
|
||||
state.localIndex = newVal
|
||||
if (props.changeActiveIndexUseAnim) {
|
||||
GM.$setCss(wrapperEl.value, 'transition-duration', `300ms`)
|
||||
_css(slideListEl.value, 'transition-duration', `300ms`)
|
||||
}
|
||||
GM.$setCss(
|
||||
wrapperEl.value,
|
||||
_css(
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(${getSlideOffset(state, wrapperEl.value)}px, 0, 0)`
|
||||
`translate3d(${getSlideOffset(state, slideListEl.value)}px, 0, 0)`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
slideInit(wrapperEl.value, state)
|
||||
slideInit(slideListEl.value, state)
|
||||
|
||||
if (props.autoplay) {
|
||||
setInterval(() => {
|
||||
if (state.localIndex === state.wrapper.childrenLength - 1) {
|
||||
emit('update:index', 0)
|
||||
} else {
|
||||
emit('update:index', state.localIndex + 1)
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
//观察子元素数量变动,获取最新数量
|
||||
//childrenLength用于canNext方法判断当前页是否是最后一页,是则不能滑动,不捕获事件
|
||||
ob = new MutationObserver(() => {
|
||||
state.wrapper.childrenLength = wrapperEl.value.children.length
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
})
|
||||
ob.observe(wrapperEl.value, { childList: true })
|
||||
ob.observe(slideListEl.value, { childList: true })
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
ob.disconnect()
|
||||
})
|
||||
|
||||
function touchStart(e: TouchEvent) {
|
||||
slidePointerDown(e, wrapperEl.value, state)
|
||||
function touchStart(e) {
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchMove(e: TouchEvent) {
|
||||
slidePointerMove(e, wrapperEl.value, state)
|
||||
function touchMove(e) {
|
||||
slideTouchMove(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchEnd(e: TouchEvent) {
|
||||
function touchEnd(e) {
|
||||
slideTouchEnd(e, state)
|
||||
slideReset(wrapperEl.value, state, emit)
|
||||
slideReset(e, slideListEl.value, state, emit)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="slide horizontal">
|
||||
<div class="indicator-bullets" v-if="indicator && state.wrapper.childrenLength">
|
||||
<div
|
||||
class="bullet"
|
||||
:class="{ active: state.localIndex === item - 1 }"
|
||||
:key="i"
|
||||
v-for="(item, i) in state.wrapper.childrenLength"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="slide-list"
|
||||
ref="wrapperEl"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
ref="slideListEl"
|
||||
@pointerdown.prevent="touchStart"
|
||||
@pointermove.prevent="touchMove"
|
||||
@pointerup.prevent="touchEnd"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.indicator-bullets {
|
||||
position: absolute;
|
||||
bottom: 10rem;
|
||||
z-index: 2;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 7rem;
|
||||
|
||||
.bullet {
|
||||
@width: 5rem;
|
||||
width: @width;
|
||||
height: @width;
|
||||
border-radius: 50%;
|
||||
background: var(--second-btn-color);
|
||||
|
||||
&.active {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
<script>
|
||||
import bus from '../../utils/bus'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { _css } from '@/utils/dom'
|
||||
import { _stopPropagation } from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'BaseSlideList',
|
||||
@@ -157,14 +159,14 @@ export default {
|
||||
methods: {
|
||||
changeIndex(init = false, index = null) {
|
||||
this.currentSlideItemIndex = index !== null ? index : this.activeIndex
|
||||
!init && this.$setCss(this.slideList, 'transition-duration', `300ms`)
|
||||
this.$setCss(
|
||||
!init && _css(this.slideList, 'transition-duration', `300ms`)
|
||||
_css(
|
||||
this.slideList,
|
||||
'transform',
|
||||
`translate3d(${-this.getWidth(this.currentSlideItemIndex) + this.moveXDistance}px, 0px, 0px)`
|
||||
)
|
||||
if (this.isHome) {
|
||||
this.$setCss(
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] + 'px'
|
||||
@@ -178,7 +180,7 @@ export default {
|
||||
this.indicatorRef = this.$refs.indicator
|
||||
for (let i = 0; i < tabs.children.length; i++) {
|
||||
let item = tabs.children[i]
|
||||
this.tabWidth = this.$getCss(item, 'width')
|
||||
this.tabWidth = _css(item, 'width')
|
||||
//TODO 这里算得不对,两个字时正常,字一多就会出问题,修改参考IndicatorLight.vue
|
||||
this.tabIndicatorRelationActiveIndexLefts.push(
|
||||
item.getBoundingClientRect().x -
|
||||
@@ -190,8 +192,8 @@ export default {
|
||||
this.indicatorSpace =
|
||||
this.tabIndicatorRelationActiveIndexLefts[1] - this.tabIndicatorRelationActiveIndexLefts[0]
|
||||
if (this.isHome) {
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
this.$setCss(
|
||||
_css(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] + 'px'
|
||||
@@ -201,16 +203,16 @@ export default {
|
||||
async checkChildren() {
|
||||
this.slideList = this.$refs.slideList
|
||||
this.slideItems = this.slideList.children
|
||||
this.wrapperWidth = this.$getCss(this.slideList, 'width')
|
||||
this.wrapperHeight = this.$getCss(this.slideList, 'height')
|
||||
this.wrapperWidth = _css(this.slideList, 'width')
|
||||
this.wrapperHeight = _css(this.slideList, 'height')
|
||||
for (let i = 0; i < this.slideItems.length; i++) {
|
||||
let el = this.slideItems[i]
|
||||
this.slideItemsWidths.push(this.$getCss(el, 'width'))
|
||||
this.slideItemsWidths.push(_css(el, 'width'))
|
||||
}
|
||||
},
|
||||
touchStart(e) {
|
||||
this.$setCss(this.slideList, 'transition-duration', `0ms`)
|
||||
this.isHome && this.$setCss(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
_css(this.slideList, 'transition-duration', `0ms`)
|
||||
this.isHome && _css(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
this.toolbarStyleTransitionDuration = 0
|
||||
|
||||
this.startLocationX = e.touches[0].pageX
|
||||
@@ -218,7 +220,6 @@ export default {
|
||||
this.startTime = Date.now()
|
||||
},
|
||||
touchMove(e) {
|
||||
// this.$stopPropagation(e)
|
||||
if (!this.canMove) return
|
||||
this.moveXDistance = e.touches[0].pageX - this.startLocationX
|
||||
this.moveYDistance = e.touches[0].pageY - this.startLocationY
|
||||
@@ -244,8 +245,8 @@ export default {
|
||||
x: { distance: this.moveXDistance, isDrawRight: this.isDrawRight }
|
||||
})
|
||||
|
||||
this.$stopPropagation(e)
|
||||
this.$setCss(
|
||||
_stopPropagation(e)
|
||||
_css(
|
||||
this.slideList,
|
||||
'transform',
|
||||
`translate3d(${
|
||||
@@ -256,7 +257,7 @@ export default {
|
||||
)
|
||||
|
||||
this.isHome &&
|
||||
this.$setCss(
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] -
|
||||
@@ -278,8 +279,8 @@ export default {
|
||||
if (this.currentSlideItemIndex === 0 && !this.isDrawRight) return
|
||||
if (this.currentSlideItemIndex === this.slideItems.length - 1 && this.isDrawRight) return
|
||||
|
||||
this.$setCss(this.slideList, 'transition-duration', `300ms`)
|
||||
this.isHome && this.$setCss(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
_css(this.slideList, 'transition-duration', `300ms`)
|
||||
this.isHome && _css(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
let endTime = Date.now()
|
||||
let gapTime = endTime - this.startTime
|
||||
|
||||
@@ -287,7 +288,7 @@ export default {
|
||||
// this.$stopPropagation(e)//todo 如果是嵌套竖状的slide,会出问题,会到moveYDistance停下,不会移到
|
||||
//this.getWidth(this.currentSlideItemIndex)位置,但是不禁示冒泡的话,又会出现划动过快,把父级也会移动。
|
||||
if (this.moveXDistance !== 0) {
|
||||
this.$stopPropagation(e)
|
||||
_stopPropagation(e)
|
||||
}
|
||||
if (Math.abs(this.moveXDistance) < 20) gapTime = 1000
|
||||
if (Math.abs(this.moveXDistance) > this.wrapperWidth / 3) gapTime = 100
|
||||
@@ -298,13 +299,13 @@ export default {
|
||||
this.currentSlideItemIndex -= 1
|
||||
}
|
||||
}
|
||||
this.$setCss(
|
||||
_css(
|
||||
this.slideList,
|
||||
'transform',
|
||||
`translate3d(${-this.getWidth(this.currentSlideItemIndex)}px, 0px, 0px)`
|
||||
)
|
||||
if (this.isHome) {
|
||||
this.$setCss(
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.tabIndicatorRelationActiveIndexLefts[this.currentSlideItemIndex] + 'px'
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
class="poster-item"
|
||||
:key="index"
|
||||
v-for="(i, index) in modelValue.videos.slice(0, 3)"
|
||||
@click="globalMethods.$no"
|
||||
@click="_no"
|
||||
>
|
||||
<img class="poster" :src="globalMethods.$imgPreview(i.cover)" />
|
||||
<img class="poster" :src="_checkImgUrl(i.cover)" />
|
||||
<div class="num">
|
||||
<img class="love" src="../../assets/img/icon/love.svg" alt="" />
|
||||
<span>{{ globalMethods.formatNumber(i.digg_count) }}</span>
|
||||
<span>{{ _formatNumber(i.digg_count) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import globalMethods from '../../utils'
|
||||
import { _checkImgUrl, _formatNumber, _no } from '../../utils'
|
||||
import BaseButton from '../BaseButton'
|
||||
|
||||
export default {
|
||||
@@ -240,17 +240,12 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
globalMethods
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
watch: {},
|
||||
created() {
|
||||
console.log('modelValue', this.modelValue)
|
||||
},
|
||||
methods: {}
|
||||
methods: { _formatNumber, _checkImgUrl, _no }
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch } from 'vue'
|
||||
import GM from '../../utils'
|
||||
import {
|
||||
getSlideOffset,
|
||||
slideInit,
|
||||
slideReset,
|
||||
slideTouchEnd,
|
||||
slidePointerMove,
|
||||
slidePointerDown
|
||||
slideTouchMove,
|
||||
slideTouchStart
|
||||
} from '@/utils/slide'
|
||||
import { SlideType } from '@/utils/const_var'
|
||||
import { _css } from '@/utils/dom'
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
@@ -22,21 +22,34 @@ const props = defineProps({
|
||||
changeActiveIndexUseAnim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: () => 'SlideVertical'
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:index'])
|
||||
|
||||
const wrapperEl = ref(null)
|
||||
//slide-list的ref引用
|
||||
const slideListEl = ref(null)
|
||||
|
||||
const state = reactive({
|
||||
judgeValue: 20,
|
||||
type: SlideType.HORIZONTAL,
|
||||
name: 'SlideVertical',
|
||||
localIndex: props.index,
|
||||
needCheck: true,
|
||||
next: false,
|
||||
start: { x: 0, y: 0, time: 0 },
|
||||
move: { x: 0, y: 0 },
|
||||
wrapper: { width: 0, height: 0, childrenLength: 0 }
|
||||
judgeValue: 20, //一个用于判断滑动朝向的固定值
|
||||
type: SlideType.VERTICAL, //组件类型
|
||||
name: props.name,
|
||||
localIndex: props.index, //当前下标
|
||||
needCheck: true, //是否需要检测,每次按下都需要检测,up事件会重置为true
|
||||
next: false, //能否滑动
|
||||
isDown: false, //是否按下,用于move事件判断
|
||||
start: { x: 0, y: 0, time: 0 }, //按下时的起点坐标
|
||||
move: { x: 0, y: 0 }, //移动时的坐标
|
||||
//slide-list的宽度和子元素数量
|
||||
wrapper: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
//childrenLength用于canNext方法判断当前页是否是最后一页,是则不能滑动,不捕获事件
|
||||
childrenLength: 0
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -45,32 +58,32 @@ watch(
|
||||
if (state.localIndex !== newVal) {
|
||||
state.localIndex = newVal
|
||||
if (props.changeActiveIndexUseAnim) {
|
||||
GM.$setCss(wrapperEl.value, 'transition-duration', `300ms`)
|
||||
_css(slideListEl.value, 'transition-duration', `300ms`)
|
||||
}
|
||||
GM.$setCss(
|
||||
wrapperEl.value,
|
||||
_css(
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(0,${getSlideOffset(state, wrapperEl.value)}px, 0)`
|
||||
`translate3d(0,${getSlideOffset(state, slideListEl.value)}px, 0)`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
slideInit(wrapperEl.value, state)
|
||||
slideInit(slideListEl.value, state)
|
||||
})
|
||||
|
||||
function touchStart(e) {
|
||||
slidePointerDown(e, wrapperEl.value, state)
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchMove(e) {
|
||||
slidePointerMove(e, wrapperEl.value, state)
|
||||
slideTouchMove(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchEnd(e) {
|
||||
slideTouchEnd(e, state)
|
||||
slideReset(wrapperEl.value, state, emit)
|
||||
slideReset(e, slideListEl.value, state, emit)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -78,10 +91,10 @@ function touchEnd(e) {
|
||||
<div class="slide v">
|
||||
<div
|
||||
class="slide-list flex-direction-column"
|
||||
ref="wrapperEl"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
ref="slideListEl"
|
||||
@pointerdown.prevent="touchStart"
|
||||
@pointermove.prevent="touchMove"
|
||||
@pointerup.prevent="touchEnd"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
<script setup lang="jsx">
|
||||
<script setup lang="tsx">
|
||||
import { createApp, onMounted, reactive, ref, render as vueRender, watch } from 'vue'
|
||||
import GM from '../../utils'
|
||||
import {
|
||||
getSlideOffset,
|
||||
slideInit,
|
||||
slideReset,
|
||||
slideTouchEnd,
|
||||
slidePointerMove,
|
||||
slidePointerDown
|
||||
slideTouchMove,
|
||||
slideTouchStart
|
||||
} from '@/utils/slide'
|
||||
import { SlideType } from '@/utils/const_var'
|
||||
import SlideItem from '@/components/slide/SlideItem.vue'
|
||||
import bus, { EVENT_KEY } from '../../utils/bus'
|
||||
import Loading from '@/components/Loading.vue'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import $ from 'jquery'
|
||||
import { _css } from '@/utils/dom'
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
@@ -35,6 +34,7 @@ const props = defineProps({
|
||||
return []
|
||||
}
|
||||
},
|
||||
//页面中同时存在多少个SlideItem
|
||||
virtualTotal: {
|
||||
type: Number,
|
||||
default: () => 5
|
||||
@@ -60,14 +60,15 @@ const emit = defineEmits(['update:index', 'loadMore', 'refresh'])
|
||||
|
||||
const appInsMap = new Map()
|
||||
const itemClassName = 'slide-item'
|
||||
const wrapperEl = ref(null)
|
||||
const slideListEl = ref<HTMLDivElement>(null)
|
||||
const state = reactive({
|
||||
judgeValue: 20,
|
||||
type: SlideType.VERTICAL,
|
||||
type: SlideType.VERTICAL_INFINITE,
|
||||
name: props.name,
|
||||
localIndex: props.index,
|
||||
needCheck: true,
|
||||
next: false,
|
||||
isDown: false,
|
||||
start: { x: 0, y: 0, time: 0 },
|
||||
move: { x: 0, y: 0 },
|
||||
wrapper: { width: 0, height: 0, childrenLength: 0 }
|
||||
@@ -78,27 +79,32 @@ watch(
|
||||
() => props.list,
|
||||
(newVal, oldVal) => {
|
||||
// console.log('watch-list', newVal.length, oldVal.length, newVal)
|
||||
//新数据比老数据小,是刷新
|
||||
//新数据长度比老数据长度小,说明是刷新
|
||||
if (newVal.length < oldVal.length) {
|
||||
insertContent()
|
||||
} else {
|
||||
//没数据就直接插入
|
||||
if (oldVal.length === 0) {
|
||||
insertContent()
|
||||
} else {
|
||||
let lastSlideItem = $(wrapperEl.value).find(`.${itemClassName}:last`)
|
||||
let top = lastSlideItem.css('top')
|
||||
let lastIndex = Number(lastSlideItem.attr('data-index')) + 1
|
||||
console.log('lastIndex', lastIndex)
|
||||
newVal.slice(lastIndex, lastIndex + 3).map((item, index) => {
|
||||
let el = getInsEl(item, lastIndex + index)
|
||||
//这里必须要设置个top值,不然会把前面的条目给覆盖掉
|
||||
//2022-3-27,这里不用计算,直接用已用slide-item最后一条的top值,
|
||||
//因为有一条情况:当滑动最后一条和二条的时候top值不会继续加。此时新增的数据如果还
|
||||
// 计算top值的,会和前面的对不上
|
||||
$(el).css('top', top)
|
||||
wrapperEl.value.appendChild(el)
|
||||
state.wrapper.childrenLength++
|
||||
})
|
||||
// 走到这里,说明是通过接口加载了下一页的数据,
|
||||
// 为了在用户快速滑动时,无需频繁等待请求接口加载数据,给用户更好的使用体验
|
||||
// 这里额外加载3条数据。所以此刻,html里面有原本的5个加新增的3个,一共8个dom
|
||||
// 用户往下滑动时只删除前面多余的dom,等滑动到临界值(virtualTotal/2+1)时,再去执行新增逻辑
|
||||
// let lastSlideItem = slideListEl.value.querySelector(`.${itemClassName}:last-child`)
|
||||
// let top = _css(lastSlideItem, 'top')
|
||||
// let newListStartIndex = Number(lastSlideItem.getAttribute('data-index')) + 1
|
||||
// // console.log('newListStartIndex', newListStartIndex)
|
||||
// newVal.slice(newListStartIndex, newListStartIndex + 3).map((item, index) => {
|
||||
// let el = getInsEl(item, newListStartIndex + index)
|
||||
// //这里必须要设置个top值,不然会把前面的条目给覆盖掉
|
||||
// //2022-3-27,这里不用计算,直接用已用slide-item最后一条的top值,
|
||||
// //因为有一条情况:当滑动最后一条和二条的时候top值不会继续加。此时新增的数据如果还
|
||||
// // 计算top值的,会和前面的对不上
|
||||
// _css(el, 'top', top)
|
||||
// slideListEl.value.appendChild(el)
|
||||
// state.wrapper.childrenLength++
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,74 +135,95 @@ watch(
|
||||
watch(
|
||||
() => props.active,
|
||||
(newVal) => {
|
||||
//当激活此页时,如果list为空,那么向上发射事件通知父组件请求数据
|
||||
if (newVal && !props.list.length) {
|
||||
return emit('refresh')
|
||||
}
|
||||
let t = newVal ? 0 : 200
|
||||
// console.log('active', 'newVal', newVal, 'oldVal', oldVal)
|
||||
if (newVal) {
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, props.list[state.localIndex])
|
||||
}
|
||||
bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
|
||||
uniqueId: props.uniqueId,
|
||||
index: state.localIndex,
|
||||
type: newVal === false ? EVENT_KEY.ITEM_STOP : EVENT_KEY.ITEM_PLAY
|
||||
})
|
||||
setTimeout(() => {
|
||||
bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
|
||||
uniqueId: props.uniqueId,
|
||||
index: state.localIndex,
|
||||
type: newVal === false ? EVENT_KEY.ITEM_STOP : EVENT_KEY.ITEM_PLAY
|
||||
})
|
||||
}, t)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
slideInit(wrapperEl.value, state, SlideType.VERTICAL)
|
||||
insertContent()
|
||||
slideInit(slideListEl.value, state)
|
||||
})
|
||||
|
||||
function insertContent(list = props.list) {
|
||||
if (!list.length) return
|
||||
$(wrapperEl.value).empty()
|
||||
/**
|
||||
* 插入SlideItem
|
||||
*/
|
||||
function insertContent() {
|
||||
if (!props.list.length) return
|
||||
//清空SlideList
|
||||
slideListEl.value.innerHTML = ''
|
||||
let half = (props.virtualTotal - 1) / 2
|
||||
//因为我们只渲染 props.virtualTotal 条数据到dom中,并且当前index有可能不是0,所以需要计算出起始下标和结束下标
|
||||
let start = 0
|
||||
if (state.localIndex >= half) {
|
||||
if (state.localIndex > half) {
|
||||
start = state.localIndex - half
|
||||
}
|
||||
let end = start + props.virtualTotal
|
||||
if (end >= list.length) {
|
||||
end = list.length
|
||||
if (end >= props.list.length) {
|
||||
end = props.list.length
|
||||
start = end - props.virtualTotal
|
||||
}
|
||||
if (start < 0) start = 0
|
||||
// console.log('start', start, end)
|
||||
list.slice(start, end).map((item, index) => {
|
||||
//自动播放,当前条(可能是0,可能是其他),试了下用jq来找元素,然后trigger play事件,要慢点样
|
||||
let el = getInsEl(item, start + index, start + index === state.localIndex)
|
||||
wrapperEl.value.appendChild(el)
|
||||
})
|
||||
GM.$setCss(wrapperEl.value, 'transform', `translate3d(0px,${getSlideOffset(state)}px, 0px)`)
|
||||
|
||||
if (state.localIndex > 2 && list.length > 5) {
|
||||
$(wrapperEl.value)
|
||||
.find(`.${itemClassName}`)
|
||||
.each(function () {
|
||||
if (list.length - state.localIndex > 2) {
|
||||
$(this).css('top', (state.localIndex - 2) * state.wrapper.height)
|
||||
} else {
|
||||
$(this).css('top', start * state.wrapper.height)
|
||||
}
|
||||
})
|
||||
// console.log('start', start, end)
|
||||
//插入start到end范围内的数据到dom中
|
||||
props.list.slice(start, end).map((item, index) => {
|
||||
let el = getInsEl(item, start + index, start + index === state.localIndex)
|
||||
slideListEl.value.appendChild(el)
|
||||
})
|
||||
|
||||
//设置SlideList的偏移量
|
||||
_css(
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(0px,${getSlideOffset(state, slideListEl.value)}px, 0px)`
|
||||
)
|
||||
|
||||
//因为index有可能不是0,所以要设置Item的top偏移量
|
||||
if (state.localIndex > 2 && props.list.length > 5) {
|
||||
let list = slideListEl.value.querySelectorAll(`.${itemClassName}`)
|
||||
list.forEach((item) => {
|
||||
if (list.length - state.localIndex > 2) {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
} else {
|
||||
_css(item, 'top', start * state.wrapper.height)
|
||||
}
|
||||
})
|
||||
}
|
||||
state.wrapper.childrenLength = wrapperEl.value.children.length
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
// console.log('list[state.localIndex]',list[state.localIndex])
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, list[state.localIndex])
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, props.list[state.localIndex])
|
||||
}
|
||||
|
||||
function dislike(item) {
|
||||
let currentItem = $(wrapperEl.value).find(`.${itemClassName}[data-index=${state.localIndex}]`)
|
||||
let replaceItem = getInsEl(item, state.localIndex, true)
|
||||
$(replaceItem).css('top', currentItem.css('top'))
|
||||
currentItem.replaceWith(replaceItem)
|
||||
function dislike() {
|
||||
// let currentItem = $(slideListEl.value).find(`.${itemClassName}[data-index=${state.localIndex}]`)
|
||||
// let replaceItem = getInsEl(item, state.localIndex, true)
|
||||
// $(replaceItem).css('top', currentItem.css('top'))
|
||||
// currentItem.replaceWith(replaceItem)
|
||||
}
|
||||
|
||||
defineExpose({ dislike })
|
||||
|
||||
/**
|
||||
* 获取Vue组件渲染之后的dom元素
|
||||
* @param item
|
||||
* @param index
|
||||
* @param play
|
||||
*/
|
||||
function getInsEl(item, index, play = false) {
|
||||
// console.log('index', cloneDeep(item), index, play)
|
||||
let slideVNode = props.render(item, index, play, props.uniqueId)
|
||||
@@ -205,6 +232,7 @@ function getInsEl(item, index, play = false) {
|
||||
if (import.meta.env.PROD) {
|
||||
parent.classList.add('slide-item')
|
||||
parent.setAttribute('data-index', index)
|
||||
//将Vue组件渲染到一个div上
|
||||
vueRender(slideVNode, parent)
|
||||
appInsMap.set(index, {
|
||||
unmount: () => {
|
||||
@@ -214,6 +242,7 @@ function getInsEl(item, index, play = false) {
|
||||
})
|
||||
return parent
|
||||
} else {
|
||||
//创建一个新的Vue实例,并挂载到一个div上
|
||||
const app = createApp({
|
||||
render() {
|
||||
return <SlideItem data-index={index}>{slideVNode}</SlideItem>
|
||||
@@ -226,12 +255,11 @@ function getInsEl(item, index, play = false) {
|
||||
}
|
||||
|
||||
function touchStart(e) {
|
||||
slidePointerDown(e, wrapperEl.value, state)
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
//TODO 2022-3-28:在最顶部,反复滑动会抖动一下,初步猜测是因为方向变了,导致的加判断距离变成了减
|
||||
function touchMove(e) {
|
||||
slidePointerMove(e, wrapperEl.value, state, canNext)
|
||||
slideTouchMove(e, slideListEl.value, state, canNext)
|
||||
}
|
||||
|
||||
function touchEnd(e) {
|
||||
@@ -244,76 +272,61 @@ function touchEnd(e) {
|
||||
emit('refresh')
|
||||
}
|
||||
slideTouchEnd(e, state, canNext, (isNext) => {
|
||||
let half = (props.virtualTotal + 1) / 2
|
||||
let half = (props.virtualTotal - 1) / 2
|
||||
if (props.list.length > props.virtualTotal) {
|
||||
//手指往上滑(即列表展示下一条内容)
|
||||
if (isNext) {
|
||||
if (state.localIndex > props.list.length - props.virtualTotal && state.localIndex >= half) {
|
||||
//删除最前面的 `dom` ,然后在最后面添加一个 `dom`
|
||||
if (state.localIndex > props.list.length - props.virtualTotal && state.localIndex > half) {
|
||||
emit('loadMore')
|
||||
}
|
||||
let addItemIndex = state.localIndex + 2
|
||||
let res = $(wrapperEl.value).find(`.${itemClassName}[data-index=${addItemIndex}]`)
|
||||
if (state.wrapper.childrenLength < props.virtualTotal) {
|
||||
if (res.length === 0) {
|
||||
wrapperEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
|
||||
// console.log('props.list.length', props.list.length, state.localIndex)
|
||||
if (state.localIndex > half && state.localIndex < props.list.length - half) {
|
||||
let addItemIndex = state.localIndex + half
|
||||
let res = slideListEl.value.querySelector(
|
||||
`.${itemClassName}[data-index='${addItemIndex}']`
|
||||
)
|
||||
if (!res) {
|
||||
slideListEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
}
|
||||
}
|
||||
if (
|
||||
state.wrapper.childrenLength === props.virtualTotal &&
|
||||
state.localIndex >= (props.virtualTotal + 1) / 2 &&
|
||||
state.localIndex <= props.list.length - 3
|
||||
) {
|
||||
if (res.length === 0) {
|
||||
wrapperEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
appInsMap
|
||||
.get($(wrapperEl.value).find(`.${itemClassName}:first`).data('index'))
|
||||
.unmount()
|
||||
// $(wrapperEl.value).find(".base-slide-item:first").remove()
|
||||
$(wrapperEl.value)
|
||||
.find(`.${itemClassName}`)
|
||||
.each(function () {
|
||||
$(this).css('top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (state.wrapper.childrenLength > props.virtualTotal) {
|
||||
$(wrapperEl.value)
|
||||
.find(`.${itemClassName}`)
|
||||
.each(function () {
|
||||
let index = $(this).data('index')
|
||||
if (index < state.localIndex - 2) {
|
||||
appInsMap.get(index).unmount()
|
||||
}
|
||||
$(this).css('top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:first-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - half) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let addItemIndex = state.localIndex - 2
|
||||
let res = $(wrapperEl.value).find(`.${itemClassName}[data-index=${addItemIndex}]`)
|
||||
|
||||
if (state.localIndex > 1 && state.localIndex <= props.list.length - 4) {
|
||||
if (res.length === 0) {
|
||||
wrapperEl.value.prepend(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
appInsMap.get($(wrapperEl.value).find(`.${itemClassName}:last`).data('index')).unmount()
|
||||
// $(wrapperEl.value).find(".base-slide-item:last").remove()
|
||||
$(wrapperEl.value)
|
||||
.find(`.${itemClassName}`)
|
||||
.each(function () {
|
||||
$(this).css('top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
//删除最后面的 `dom` ,然后在最前面添加一个 `dom`
|
||||
if (state.localIndex >= half && state.localIndex < props.list.length - (half + 1)) {
|
||||
let addIndex = state.localIndex - half
|
||||
if (addIndex >= 0) {
|
||||
let res = slideListEl.value.querySelector(`.${itemClassName}[data-index='${addIndex}']`)
|
||||
if (!res) {
|
||||
slideListEl.value.prepend(getInsEl(props.list[addIndex], addIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:last-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
|
||||
if (state.wrapper.childrenLength > props.virtualTotal) {
|
||||
appInsMap.get($(wrapperEl.value).find(`.${itemClassName}:last`).data('index')).unmount()
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - half) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
}
|
||||
state.wrapper.childrenLength = wrapperEl.value.children.length
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
}
|
||||
})
|
||||
slideReset(wrapperEl.value, state, emit)
|
||||
slideReset(e, slideListEl.value, state, emit)
|
||||
}
|
||||
|
||||
function canNext(state, isNext) {
|
||||
function canNext(state, isNext: boolean) {
|
||||
return !(
|
||||
(state.localIndex === 0 && !isNext) ||
|
||||
(state.localIndex === props.list.length - 1 && isNext)
|
||||
@@ -326,11 +339,10 @@ function canNext(state, isNext) {
|
||||
<Loading v-if="props.loading && props.list.length === 0" />
|
||||
<div
|
||||
class="slide-list flex-direction-column"
|
||||
ref="wrapperEl"
|
||||
@click="null"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
ref="slideListEl"
|
||||
@pointerdown.prevent="touchStart"
|
||||
@pointermove.prevent="touchMove"
|
||||
@pointerup.prevent="touchEnd"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
365
src/components/slide/SlideVerticalInfinite2.txt
Normal file
@@ -0,0 +1,365 @@
|
||||
<script setup lang="tsx">
|
||||
import { createApp, onMounted, reactive, ref, render as vueRender, watch } from 'vue'
|
||||
import {
|
||||
getSlideOffset,
|
||||
slideInit,
|
||||
slideReset,
|
||||
slideTouchEnd,
|
||||
slideTouchMove,
|
||||
slideTouchStart
|
||||
} from '@/utils/slide'
|
||||
import { SlideType } from '@/utils/const_var'
|
||||
import SlideItem from '@/components/slide/SlideItem.vue'
|
||||
import bus, { EVENT_KEY } from '../../utils/bus'
|
||||
import Loading from '@/components/Loading.vue'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { _css } from '@/utils/dom'
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
default: () => {
|
||||
return -1
|
||||
}
|
||||
},
|
||||
render: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
return null
|
||||
}
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//页面中同时存在多少个SlideItem
|
||||
virtualTotal: {
|
||||
type: Number,
|
||||
default: () => 5
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
uniqueId: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:index', 'loadMore', 'refresh'])
|
||||
|
||||
const appInsMap = new Map()
|
||||
const itemClassName = 'slide-item'
|
||||
const slideListEl = ref<HTMLDivElement>(null)
|
||||
const state = reactive({
|
||||
judgeValue: 20,
|
||||
type: SlideType.VERTICAL_INFINITE,
|
||||
name: props.name,
|
||||
localIndex: props.index,
|
||||
needCheck: true,
|
||||
next: false,
|
||||
isDown: false,
|
||||
start: { x: 0, y: 0, time: 0 },
|
||||
move: { x: 0, y: 0 },
|
||||
wrapper: { width: 0, height: 0, childrenLength: 0 }
|
||||
})
|
||||
const baseStore = useBaseStore()
|
||||
|
||||
watch(
|
||||
() => props.list,
|
||||
(newVal, oldVal) => {
|
||||
// console.log('watch-list', newVal.length, oldVal.length, newVal)
|
||||
//新数据长度比老数据长度小,说明是刷新
|
||||
if (newVal.length < oldVal.length) {
|
||||
insertContent()
|
||||
} else {
|
||||
//没数据就直接插入
|
||||
if (oldVal.length === 0) {
|
||||
insertContent()
|
||||
} else {
|
||||
// 走到这里,说明是通过接口加载了下一页的数据,
|
||||
// 为了在用户快速滑动时,无需频繁等待请求接口加载数据,给用户更好的使用体验
|
||||
// 这里额外加载3条数据。所以此刻,html里面有原本的5个加新增的3个,一共8个dom
|
||||
// 用户往下滑动时只删除前面多余的dom,等滑动到临界值(virtualTotal/2+1)时,再去执行新增逻辑
|
||||
// let lastSlideItem = slideListEl.value.querySelector(`.${itemClassName}:last-child`)
|
||||
// let top = _css(lastSlideItem, 'top')
|
||||
// let newListStartIndex = Number(lastSlideItem.getAttribute('data-index')) + 1
|
||||
// // console.log('newListStartIndex', newListStartIndex)
|
||||
// newVal.slice(newListStartIndex, newListStartIndex + 3).map((item, index) => {
|
||||
// let el = getInsEl(item, newListStartIndex + index)
|
||||
// //这里必须要设置个top值,不然会把前面的条目给覆盖掉
|
||||
// //2022-3-27,这里不用计算,直接用已用slide-item最后一条的top值,
|
||||
// //因为有一条情况:当滑动最后一条和二条的时候top值不会继续加。此时新增的数据如果还
|
||||
// // 计算top值的,会和前面的对不上
|
||||
// _css(el, 'top', top)
|
||||
// slideListEl.value.appendChild(el)
|
||||
// state.wrapper.childrenLength++
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.index,
|
||||
(newVal, oldVal) => {
|
||||
state.localIndex = newVal
|
||||
// console.log('watch-index', newVal, oldVal)
|
||||
if (!props.list.length) return
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, props.list[newVal])
|
||||
bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
|
||||
uniqueId: props.uniqueId,
|
||||
index: newVal,
|
||||
type: EVENT_KEY.ITEM_PLAY
|
||||
})
|
||||
setTimeout(() => {
|
||||
bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
|
||||
uniqueId: props.uniqueId,
|
||||
index: oldVal,
|
||||
type: EVENT_KEY.ITEM_STOP
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.active,
|
||||
(newVal) => {
|
||||
//当激活此页时,如果list为空,那么向上发射事件通知父组件请求数据
|
||||
if (newVal && !props.list.length) {
|
||||
return emit('refresh')
|
||||
}
|
||||
// console.log('active', 'newVal', newVal, 'oldVal', oldVal)
|
||||
if (newVal) {
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, props.list[state.localIndex])
|
||||
}
|
||||
bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
|
||||
uniqueId: props.uniqueId,
|
||||
index: state.localIndex,
|
||||
type: newVal === false ? EVENT_KEY.ITEM_STOP : EVENT_KEY.ITEM_PLAY
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
slideInit(slideListEl.value, state)
|
||||
})
|
||||
|
||||
/**
|
||||
* 插入SlideItem
|
||||
*/
|
||||
function insertContent() {
|
||||
if (!props.list.length) return
|
||||
//清空SlideList
|
||||
slideListEl.value.innerHTML = ''
|
||||
let half = (props.virtualTotal - 1) / 2
|
||||
//因为我们只渲染 props.virtualTotal 条数据到dom中,并且当前index有可能不是0,所以需要计算出起始下标和结束下标
|
||||
let start = 0
|
||||
if (state.localIndex > half) {
|
||||
start = state.localIndex - half
|
||||
}
|
||||
let end = start + props.virtualTotal
|
||||
if (end >= props.list.length) {
|
||||
end = props.list.length
|
||||
start = end - props.virtualTotal
|
||||
}
|
||||
if (start < 0) start = 0
|
||||
|
||||
// console.log('start', start, end)
|
||||
//插入start到end范围内的数据到dom中
|
||||
props.list.slice(start, end).map((item, index) => {
|
||||
let el = getInsEl(item, start + index, start + index === state.localIndex)
|
||||
slideListEl.value.appendChild(el)
|
||||
})
|
||||
|
||||
//设置SlideList的偏移量
|
||||
_css(
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(0px,${getSlideOffset(state, slideListEl.value)}px, 0px)`
|
||||
)
|
||||
|
||||
//因为index有可能不是0,所以要设置Item的top偏移量
|
||||
if (state.localIndex > 2 && props.list.length > 5) {
|
||||
let list = slideListEl.value.querySelectorAll(`.${itemClassName}`)
|
||||
list.forEach((item) => {
|
||||
if (list.length - state.localIndex > 2) {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
} else {
|
||||
_css(item, 'top', start * state.wrapper.height)
|
||||
}
|
||||
})
|
||||
}
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
// console.log('list[state.localIndex]',list[state.localIndex])
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, props.list[state.localIndex])
|
||||
}
|
||||
|
||||
function dislike() {
|
||||
// let currentItem = $(slideListEl.value).find(`.${itemClassName}[data-index=${state.localIndex}]`)
|
||||
// let replaceItem = getInsEl(item, state.localIndex, true)
|
||||
// $(replaceItem).css('top', currentItem.css('top'))
|
||||
// currentItem.replaceWith(replaceItem)
|
||||
}
|
||||
|
||||
defineExpose({ dislike })
|
||||
|
||||
/**
|
||||
* 获取Vue组件渲染之后的dom元素
|
||||
* @param item
|
||||
* @param index
|
||||
* @param play
|
||||
*/
|
||||
function getInsEl(item, index, play = false) {
|
||||
// console.log('index', cloneDeep(item), index, play)
|
||||
let slideVNode = props.render(item, index, play, props.uniqueId)
|
||||
const parent = document.createElement('div')
|
||||
//TODO 打包到线上时用这个,这个在开发时任何修改都会刷新页面
|
||||
if (import.meta.env.PROD) {
|
||||
parent.classList.add('slide-item')
|
||||
parent.setAttribute('data-index', index)
|
||||
//将Vue组件渲染到一个div上
|
||||
vueRender(slideVNode, parent)
|
||||
appInsMap.set(index, {
|
||||
unmount: () => {
|
||||
vueRender(null, parent)
|
||||
parent.remove()
|
||||
}
|
||||
})
|
||||
return parent
|
||||
} else {
|
||||
//创建一个新的Vue实例,并挂载到一个div上
|
||||
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) {
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchMove(e) {
|
||||
slideTouchMove(e, slideListEl.value, state, canNext)
|
||||
}
|
||||
|
||||
function touchEnd(e) {
|
||||
let isNext = state.move.y < 0
|
||||
if (
|
||||
state.localIndex === 0 &&
|
||||
!isNext &&
|
||||
state.move.y > baseStore.homeRefresh + baseStore.judgeValue
|
||||
) {
|
||||
emit('refresh')
|
||||
}
|
||||
slideTouchEnd(e, state, canNext, (isNext) => {
|
||||
let half = (props.virtualTotal - 1) / 2
|
||||
if (props.list.length > props.virtualTotal) {
|
||||
//往下滑
|
||||
if (isNext) {
|
||||
if (state.localIndex > props.list.length - props.virtualTotal && state.localIndex > half) {
|
||||
emit('loadMore')
|
||||
}
|
||||
let addItemIndex = state.localIndex + 2
|
||||
console.log('addItemIndex', addItemIndex)
|
||||
let res = slideListEl.value.querySelector(`.${itemClassName}[data-index='${addItemIndex}']`)
|
||||
if (state.wrapper.childrenLength < props.virtualTotal) {
|
||||
if (!res) {
|
||||
slideListEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
}
|
||||
}
|
||||
if (
|
||||
state.wrapper.childrenLength === props.virtualTotal &&
|
||||
state.localIndex > half &&
|
||||
state.localIndex <= props.list.length - 3
|
||||
) {
|
||||
if (!res) {
|
||||
slideListEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:first-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (state.wrapper.childrenLength > props.virtualTotal) {
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
let index = Number(item.getAttribute('data-index'))
|
||||
if (index < state.localIndex - 2) {
|
||||
appInsMap.get(index).unmount()
|
||||
}
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let addItemIndex = state.localIndex - 2
|
||||
let res = slideListEl.value.querySelector(`.${itemClassName}[data-index='${addItemIndex}']`)
|
||||
|
||||
if (state.localIndex > 1 && state.localIndex <= props.list.length - 4) {
|
||||
if (!res) {
|
||||
slideListEl.value.prepend(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:last-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
// $(slideListEl.value).find(".base-slide-item:last").remove()
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (state.wrapper.childrenLength > props.virtualTotal) {
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:last-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
}
|
||||
}
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
}
|
||||
})
|
||||
slideReset(e, slideListEl.value, state, emit)
|
||||
}
|
||||
|
||||
function canNext(state, isNext) {
|
||||
return !(
|
||||
(state.localIndex === 0 && !isNext) ||
|
||||
(state.localIndex === props.list.length - 1 && isNext)
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="slide slide-infinite">
|
||||
<Loading v-if="props.loading && props.list.length === 0" />
|
||||
<div
|
||||
class="slide-list flex-direction-column"
|
||||
ref="slideListEl"
|
||||
@click="null"
|
||||
@pointerdown="touchStart"
|
||||
@pointermove="touchMove"
|
||||
@pointerup="touchEnd"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -7,12 +7,14 @@ const BASE_URL_MAP = {
|
||||
DEV: '',
|
||||
PROD: '',
|
||||
// GP_PAGES: '/dist',
|
||||
GP_PAGES: '/douyin',
|
||||
GITEE_PAGES: '/gitee-dy',
|
||||
GP_PAGES: '',
|
||||
GITEE_PAGES: '/douyin',
|
||||
UNI: 'https://dy.ttentau.top'
|
||||
}
|
||||
|
||||
export const IS_SUB_DOMAIN = ['GITEE_PAGES', 'GP_PAGES'].includes(import.meta.env.VITE_ENV)
|
||||
export const IS_GITEE_PAGES = ['GITEE_PAGES'].includes(import.meta.env.VITE_ENV)
|
||||
export const BASE_URL = BASE_URL_MAP[import.meta.env.VITE_ENV]
|
||||
export const IMG_URL = BASE_URL + '/images/'
|
||||
export const FILE_URL = BASE_URL + '/data/'
|
||||
export const IS_DEV = process.env.NODE_ENV !== 'production'
|
||||
|
||||
40
src/main.ts
@@ -1,18 +1,42 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import mitt from 'mitt'
|
||||
import './assets/less/index.less'
|
||||
import { startMock } from '@/mock'
|
||||
import router from './router'
|
||||
import mixin from './utils/mixin'
|
||||
import VueLazyload from '@jambonn/vue-lazyload'
|
||||
import { createPinia } from 'pinia'
|
||||
import { useClick } from '@/utils/hooks/useClick'
|
||||
import bus, { EVENT_KEY } from '@/utils/bus'
|
||||
|
||||
window.isMoved = false
|
||||
window.isMuted = true
|
||||
window.showMutedNotice = true
|
||||
HTMLElement.prototype.addEventListener = new Proxy(HTMLElement.prototype.addEventListener, {
|
||||
apply(target, ctx, args) {
|
||||
const eventName = args[0]
|
||||
const listener = args[1]
|
||||
if (listener instanceof Function && eventName === 'click') {
|
||||
args[1] = new Proxy(listener, {
|
||||
apply(target1, ctx1, args1) {
|
||||
// console.log('e', args1)
|
||||
// console.log('click点击', window.isMoved)
|
||||
if (window.isMoved) return
|
||||
try {
|
||||
return target1.apply(ctx1, args1)
|
||||
} catch (e) {
|
||||
console.error(`[proxyPlayerEvent][${eventName}]`, listener, e)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return target.apply(ctx, args)
|
||||
}
|
||||
})
|
||||
|
||||
const vClick = useClick()
|
||||
const pinia = createPinia()
|
||||
const emitter = mitt()
|
||||
const app = createApp(App)
|
||||
app.config.globalProperties.emitter = emitter
|
||||
app.provide('mitt', emitter)
|
||||
app.mixin(mixin)
|
||||
const loadImage = new URL('./assets/img/icon/img-loading.png', import.meta.url).href
|
||||
app.use(VueLazyload, {
|
||||
@@ -23,6 +47,14 @@ app.use(VueLazyload, {
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
app.directive('click', vClick)
|
||||
|
||||
//放到最后才可以使用pinia
|
||||
startMock()
|
||||
setTimeout(() => {
|
||||
bus.emit(EVENT_KEY.HIDE_MUTED_NOTICE)
|
||||
window.showMutedNotice = false
|
||||
}, 2000)
|
||||
bus.on(EVENT_KEY.REMOVE_MUTED, () => {
|
||||
window.isMuted = false
|
||||
})
|
||||
|
||||
@@ -3,10 +3,10 @@ import posts6 from '@/assets/data/posts6.json'
|
||||
import { _fetch, cloneDeep, random } from '@/utils'
|
||||
import { BASE_URL, FILE_URL } from '@/config'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import axiosInstance from '@/utils/request'
|
||||
import { axiosInstance } from '@/utils/request'
|
||||
import MockAdapter from 'axios-mock-adapter'
|
||||
|
||||
const mock = new MockAdapter(axiosInstance, { delayResponse: 300 })
|
||||
const mock = new MockAdapter(axiosInstance)
|
||||
|
||||
function getPage2(params: any): { limit: number; offset: number; pageNo: number } {
|
||||
const offset = params.pageNo * params.pageSize
|
||||
@@ -28,23 +28,71 @@ const t = [
|
||||
type: 'imgs',
|
||||
src: `https://imgapi.cn/bing.php`,
|
||||
author: {
|
||||
unique_id: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'user',
|
||||
src: `https://imgapi.cn/bing.php`,
|
||||
author: {
|
||||
unique_id: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'img',
|
||||
src: `https://imgapi.cn/bing.php`,
|
||||
author: {
|
||||
unique_id: 3
|
||||
unique_id: 1,
|
||||
avatar_168x168: {
|
||||
url_list: []
|
||||
},
|
||||
avatar_300x300: {
|
||||
url_list: []
|
||||
},
|
||||
cover_url: [
|
||||
{
|
||||
url_list: []
|
||||
}
|
||||
],
|
||||
white_cover_url: [
|
||||
{
|
||||
url_list: []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
// {
|
||||
// type: 'user',
|
||||
// src: `https://imgapi.cn/bing.php`,
|
||||
// author: {
|
||||
// unique_id: 2,
|
||||
// avatar_168x168: {
|
||||
// url_list: []
|
||||
// },
|
||||
// avatar_300x300: {
|
||||
// url_list: []
|
||||
// },
|
||||
// cover_url: [
|
||||
// {
|
||||
// url_list: []
|
||||
// }
|
||||
// ],
|
||||
// white_cover_url: [
|
||||
// {
|
||||
// url_list: []
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// type: 'img',
|
||||
// src: `https://imgapi.cn/bing.php`,
|
||||
// author: {
|
||||
// unique_id: 3,
|
||||
// avatar_168x168: {
|
||||
// url_list: []
|
||||
// },
|
||||
// avatar_300x300: {
|
||||
// url_list: []
|
||||
// },
|
||||
// cover_url: [
|
||||
// {
|
||||
// url_list: []
|
||||
// }
|
||||
// ],
|
||||
// white_cover_url: [
|
||||
// {
|
||||
// url_list: []
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
]
|
||||
|
||||
// allRecommendVideos.unshift(...t)
|
||||
@@ -86,14 +134,28 @@ async function fetchData() {
|
||||
//TODO 有个bug,一开始只返回了6条数据,但第二次前端传过来的pageNo是2了,就是会从第10条数据开始返回,导致中间漏了4条
|
||||
export async function startMock() {
|
||||
mock.onGet(/video\/recommended/).reply(async (config) => {
|
||||
const page = getPage2(config.params)
|
||||
console.log('allRecommendVideos', cloneDeep(allRecommendVideos.length), page)
|
||||
const { start, pageSize } = config.params
|
||||
// console.log('allRecommendVideos', cloneDeep(allRecommendVideos.length), config.params)
|
||||
return [
|
||||
200,
|
||||
{
|
||||
data: {
|
||||
total: 844,
|
||||
list: allRecommendVideos.slice(page.offset, page.limit) // list: allRecommendVideos.slice(0, 6),
|
||||
list: allRecommendVideos.slice(start, start + pageSize) // list: allRecommendVideos.slice(0, 6),
|
||||
},
|
||||
code: 200,
|
||||
msg: ''
|
||||
}
|
||||
]
|
||||
})
|
||||
mock.onGet(/video\/long\/recommended/).reply(async (config) => {
|
||||
const page = getPage2(config.params)
|
||||
return [
|
||||
200,
|
||||
{
|
||||
data: {
|
||||
total: 844,
|
||||
list: allRecommendVideos.slice(page.offset, page.limit)
|
||||
},
|
||||
code: 200,
|
||||
msg: ''
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<img src="../../assets/img/icon/avatar/2.png" alt="" class="round" />
|
||||
<img src="../../assets/img/icon/avatar/3.png" alt="" class="round" />
|
||||
<div class="round count">107</div>
|
||||
<dy-back class="round close" img="close" mode="light" @click="$back" />
|
||||
<dy-back class="round close" img="close" mode="light" @click="$router.back()" />
|
||||
</div>
|
||||
<div class="more">
|
||||
<div class="wrapper">
|
||||
@@ -117,7 +117,7 @@ import Dom from '../../utils/dom'
|
||||
import { nextTick } from 'vue'
|
||||
import { mapState } from 'pinia'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { _checkImgUrl, random } from '@/utils'
|
||||
import { _checkImgUrl, _sleep, random } from '@/utils'
|
||||
import Mock from 'mockjs'
|
||||
|
||||
export default {
|
||||
@@ -205,7 +205,7 @@ export default {
|
||||
this.page = this.$refs.page
|
||||
this.timer1 = setInterval(async () => {
|
||||
this.sendGift()
|
||||
await this.$sleep(300)
|
||||
await _sleep(300)
|
||||
this.sendGift()
|
||||
this.joinUser()
|
||||
}, 1000)
|
||||
@@ -376,7 +376,7 @@ export default {
|
||||
.barrage {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
transform: translateX(100vw);
|
||||
transform: translateX(100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12rem;
|
||||
@@ -384,7 +384,7 @@ export default {
|
||||
|
||||
@keyframes anim {
|
||||
from {
|
||||
transform: translateX(100vw);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(-100%);
|
||||
@@ -467,14 +467,14 @@ export default {
|
||||
@import '../../assets/less/index';
|
||||
|
||||
.LivePage {
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
color: white;
|
||||
font-size: 14rem;
|
||||
position: relative;
|
||||
|
||||
.live-wrapper {
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
background: black;
|
||||
display: flex;
|
||||
@@ -487,7 +487,7 @@ export default {
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
color: rgb(229, 229, 229);
|
||||
}
|
||||
@@ -496,7 +496,7 @@ export default {
|
||||
.float {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
height: calc(var(--vh, 1vh) * 100);
|
||||
|
||||
@tag-bg: rgba(58, 58, 70, 0.3);
|
||||
@@ -634,7 +634,7 @@ export default {
|
||||
.bottom {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: var(--page-padding);
|
||||
padding-bottom: 10rem;
|
||||
|
||||
@@ -75,11 +75,11 @@
|
||||
</Scroll>
|
||||
</div>
|
||||
<div class="options">
|
||||
<div class="l-button white" @click="$no">
|
||||
<div class="l-button white" @click="_no">
|
||||
<img src="../../assets/img/icon/home/music3.png" alt="" />
|
||||
<span>分享到日常</span>
|
||||
</div>
|
||||
<div class="l-button primary" @click="$no">
|
||||
<div class="l-button primary" @click="_no">
|
||||
<img src="../../assets/img/icon/home/record.png" alt="" />
|
||||
<span>拍同款</span>
|
||||
</div>
|
||||
@@ -130,7 +130,7 @@ import { myVideo } from '@/api/videos'
|
||||
import { onDeactivated, onMounted, onUnmounted, reactive, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useNav } from '@/utils/hooks/useNav'
|
||||
import { $no, $notice, _checkImgUrl, _formatNumber } from '@/utils'
|
||||
import { _checkImgUrl, _formatNumber, _no, _notice } from '@/utils'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -203,7 +203,7 @@ async function loadData(init = false) {
|
||||
if (data.loading) return
|
||||
if (!init) {
|
||||
if (data.total <= data.videos.length) {
|
||||
return $notice('暂时没有更多了')
|
||||
_notice('暂时没有更多了')
|
||||
}
|
||||
data.pageNo++
|
||||
}
|
||||
@@ -376,7 +376,7 @@ function stopPlay() {
|
||||
|
||||
.options {
|
||||
font-size: 14rem;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 20rem;
|
||||
display: flex;
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed, onDeactivated, onMounted, onUnmounted, reactive } from 'vue'
|
||||
import { $notice, _checkImgUrl, _dateFormat, _duration, _formatNumber } from '@/utils/index.jsx'
|
||||
import { _checkImgUrl, _dateFormat, _duration, _formatNumber, _notice } from '@/utils/index.jsx'
|
||||
import { useNav } from '@/utils/hooks/useNav'
|
||||
|
||||
defineOptions({
|
||||
@@ -523,9 +523,9 @@ function scroll(e) {
|
||||
function toggleCollect(item) {
|
||||
item.is_collect = !item.is_collect
|
||||
if (item.is_collect) {
|
||||
$notice('收藏成功')
|
||||
_notice('收藏成功')
|
||||
} else {
|
||||
$notice('取消收藏')
|
||||
_notice('取消收藏')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
品牌榜
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO 滚动到下面的时候,应该禁止slide-move,因为第个slideitem的高度不一样,高的切到矮的,会闪屏-->
|
||||
<!-- TODO 滚动到下面的时候,应该禁止slide-move,因为每个slideitem的高度不一样,高的切到矮的,会闪屏-->
|
||||
<SlideHorizontal v-model:index="data.slideIndex" :style="slideListHeight">
|
||||
<SlideItem>
|
||||
<div class="slide0" ref="slide0">
|
||||
@@ -179,7 +179,7 @@
|
||||
<div class="brands">
|
||||
<div
|
||||
class="brand"
|
||||
@click="toggleKey(key)"
|
||||
@click="toggleKey(key, i)"
|
||||
:key="i"
|
||||
:class="{ active: key === data.selectBrandKey }"
|
||||
v-for="(key, i) in Object.keys(data.brandRankList)"
|
||||
@@ -198,7 +198,6 @@
|
||||
<div class="avatar-wrapper" :class="item.living ? 'living' : ''">
|
||||
<div class="avatar-out-line"></div>
|
||||
<img v-lazy="_checkImgUrl(item.logo)" alt="" class="avatar" />
|
||||
<!-- <img :src="item.logo" class="avatar">-->
|
||||
</div>
|
||||
<div class="desc">{{ item.name }}</div>
|
||||
</div>
|
||||
@@ -211,7 +210,7 @@
|
||||
<div class="more" @click="_no">查看完整品牌榜 ></div>
|
||||
</div>
|
||||
|
||||
<SlideRowList :autoplay="true" indicatorType="bullets">
|
||||
<SlideHorizontal v-model:index="data.adIndex" :autoplay="true" indicator>
|
||||
<SlideItem>
|
||||
<div class="ad">AD1</div>
|
||||
</SlideItem>
|
||||
@@ -236,7 +235,7 @@
|
||||
<SlideItem>
|
||||
<div class="ad">AD8</div>
|
||||
</SlideItem>
|
||||
</SlideRowList>
|
||||
</SlideHorizontal>
|
||||
</div>
|
||||
</SlideItem>
|
||||
</SlideHorizontal>
|
||||
@@ -247,11 +246,8 @@
|
||||
<script setup lang="ts">
|
||||
import Search from '../../components/Search.vue'
|
||||
import Dom from '../../utils/dom'
|
||||
import { computed, nextTick, watch } from 'vue'
|
||||
import { computed, nextTick, onMounted, reactive, watch } from 'vue'
|
||||
import { _checkImgUrl, _formatNumber, _no, _showSimpleConfirmDialog, sampleSize } from '@/utils'
|
||||
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { onMounted, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useNav } from '@/utils/hooks/useNav'
|
||||
|
||||
@@ -261,9 +257,9 @@ defineOptions({
|
||||
|
||||
const router = useRouter()
|
||||
const nav = useNav()
|
||||
const store = useBaseStore()
|
||||
const data = reactive({
|
||||
isExpand: false,
|
||||
adIndex: 0,
|
||||
history: [
|
||||
'历史记录1',
|
||||
'历史记录2',
|
||||
@@ -696,7 +692,7 @@ watch(
|
||||
} else {
|
||||
data.selectBrandKeyIndex++
|
||||
}
|
||||
data.selectBrandKey = brandListKeys[data.selectBrandKeyIndex]
|
||||
data.selectBrandKey = brandListKeys.value[data.selectBrandKeyIndex]
|
||||
}, 3000)
|
||||
} else {
|
||||
clearInterval(data.timer)
|
||||
@@ -710,8 +706,9 @@ onMounted(() => {
|
||||
refresh()
|
||||
})
|
||||
|
||||
function toggleKey(key) {
|
||||
function toggleKey(key: string, i: number) {
|
||||
data.selectBrandKey = key
|
||||
data.selectBrandKeyIndex = i
|
||||
clearInterval(data.timer)
|
||||
}
|
||||
|
||||
@@ -779,7 +776,7 @@ function toggle() {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--line-color);
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
top: 0;
|
||||
|
||||
@@ -913,9 +910,10 @@ function toggle() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-width: 0;
|
||||
|
||||
.center {
|
||||
width: calc(100vw - 140rem);
|
||||
width: calc(100% - 140rem);
|
||||
box-sizing: border-box;
|
||||
//padding: 0 1rem;
|
||||
//flex: 1;
|
||||
@@ -983,7 +981,7 @@ function toggle() {
|
||||
justify-content: space-between;
|
||||
|
||||
.center {
|
||||
width: calc(100vw - 160rem);
|
||||
width: calc(100% - 160rem);
|
||||
box-sizing: border-box;
|
||||
//padding: 0 1rem;
|
||||
//flex: 1;
|
||||
@@ -1025,6 +1023,7 @@ function toggle() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
font-size: 10rem;
|
||||
color: var(--second-text-color);
|
||||
margin-left: 5rem;
|
||||
@@ -1095,7 +1094,7 @@ function toggle() {
|
||||
justify-content: space-between;
|
||||
|
||||
.center {
|
||||
width: calc(100vw - 150rem);
|
||||
width: calc(100% - 150rem);
|
||||
box-sizing: border-box;
|
||||
//padding: 0 1rem;
|
||||
//flex: 1;
|
||||
@@ -1200,7 +1199,7 @@ function toggle() {
|
||||
justify-content: space-between;
|
||||
|
||||
.center {
|
||||
width: calc(100vw - 150rem);
|
||||
width: calc(100% - 150rem);
|
||||
box-sizing: border-box;
|
||||
//padding: 0 1rem;
|
||||
//flex: 1;
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
<img src="../../../assets/img/icon/components/follow/share.png" alt="" />
|
||||
<span>分享主页</span>
|
||||
</div>
|
||||
<div class="option" @click="cancel((e) => $nav('/message/chat'))">
|
||||
<div class="option" @click="cancel((e) => $router.push('/message/chat'))">
|
||||
<img src="../../../assets/img/icon/components/follow/private-chat.png" alt="" />
|
||||
<span>发私信</span>
|
||||
</div>
|
||||
<div class="option" @click="cancel((e) => $nav('/home/report', { mode: 'chat' }))">
|
||||
<div class="option" @click="cancel((e) => $router.push('/home/report', { mode: 'chat' }))">
|
||||
<img src="../../../assets/img/icon/components/follow/report.png" alt="" />
|
||||
<span>举报</span>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<div class="indicator-home" :class="{ isLight }">
|
||||
<transition name="fade">
|
||||
<div class="mask" v-if="open" @click="open = false"></div>
|
||||
</transition>
|
||||
<div class="notice" :style="noticeStyle"><span>下拉刷新内容</span></div>
|
||||
<div class="toolbar" ref="toolbar" :style="toolbarStyle">
|
||||
<Icon
|
||||
@@ -13,13 +10,8 @@
|
||||
/>
|
||||
<div class="tab-ctn">
|
||||
<div class="tabs" ref="tabs">
|
||||
<div class="tab" :class="tabOneClass" @click.stop="change(0)">
|
||||
<div class="tab" :class="{ active: index === 0 }" @click.stop="change(0)">
|
||||
<span>热点</span>
|
||||
<img
|
||||
v-show="index === 0"
|
||||
src="../../../assets/img/icon/arrow-up-white.png"
|
||||
class="tab1-img"
|
||||
/>
|
||||
</div>
|
||||
<div class="tab" :class="{ active: index === 1 }" @click.stop="change(1)">
|
||||
<span>长视频</span>
|
||||
@@ -37,17 +29,13 @@
|
||||
</div>
|
||||
<div class="indicator" ref="indicator"></div>
|
||||
</div>
|
||||
<Icon v-hide="loading" icon="ion:search" class="search" @click="$nav('/home/search')" />
|
||||
<Icon
|
||||
v-hide="loading"
|
||||
icon="ion:search"
|
||||
class="search"
|
||||
@click="$router.push('/home/search')"
|
||||
/>
|
||||
</div>
|
||||
<div class="toggle-type" :class="{ open }">
|
||||
<div class="l-button" :class="{ active: type === 0 }" @click="toggleType(0)">
|
||||
<span>同城</span>
|
||||
<img v-if="type === 0" src="../../../assets/img/icon/switch.png" alt="" />
|
||||
</div>
|
||||
<div class="l-button" :class="{ active: type === 1 }" @click="toggleType(1)">学习</div>
|
||||
<div class="l-button" :class="{ active: type === 2 }" @click="toggleType(2)">热点</div>
|
||||
</div>
|
||||
|
||||
<Loading :style="loadingStyle" class="loading" style="width: 40rem" :is-full-screen="false" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -56,6 +44,7 @@ import Loading from '../../../components/Loading.vue'
|
||||
import bus from '../../../utils/bus'
|
||||
import { mapState } from 'pinia'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { _css } from '@/utils/dom'
|
||||
|
||||
export default {
|
||||
name: 'IndicatorHome',
|
||||
@@ -92,16 +81,12 @@ export default {
|
||||
indicatorRef: null,
|
||||
lefts: [],
|
||||
indicatorSpace: 0,
|
||||
open: false,
|
||||
type: 1,
|
||||
moveY: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useBaseStore, ['judgeValue', 'homeRefresh']),
|
||||
tabOneClass() {
|
||||
return { active: this.index === 0, open: this.open }
|
||||
},
|
||||
transform() {
|
||||
return `translate3d(0, ${this.moveY - this.judgeValue > this.homeRefresh ? this.homeRefresh : this.moveY - this.judgeValue}px, 0)`
|
||||
},
|
||||
@@ -166,29 +151,18 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleType(type) {
|
||||
if (type !== this.type) {
|
||||
this.type = type
|
||||
this.open = false
|
||||
}
|
||||
},
|
||||
change(index) {
|
||||
if (this.index === 0 && index === 0) {
|
||||
this.open = !this.open
|
||||
} else {
|
||||
this.open = false
|
||||
}
|
||||
this.$emit('update:index', index)
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
this.$setCss(this.indicatorRef, 'left', this.lefts[index] + 'px')
|
||||
_css(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
_css(this.indicatorRef, 'left', this.lefts[index] + 'px')
|
||||
},
|
||||
initTabs() {
|
||||
let tabs = this.$refs.tabs
|
||||
this.indicatorRef = this.$refs.indicator
|
||||
let indicatorWidth = this.$getCss(this.indicatorRef, 'width')
|
||||
let indicatorWidth = _css(this.indicatorRef, 'width')
|
||||
for (let i = 0; i < tabs.children.length; i++) {
|
||||
let item = tabs.children[i]
|
||||
let tabWidth = this.$getCss(item, 'width')
|
||||
let tabWidth = _css(item, 'width')
|
||||
this.lefts.push(
|
||||
item.getBoundingClientRect().x -
|
||||
tabs.children[0].getBoundingClientRect().x +
|
||||
@@ -196,12 +170,12 @@ export default {
|
||||
)
|
||||
}
|
||||
this.indicatorSpace = this.lefts[1] - this.lefts[0]
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
this.$setCss(this.indicatorRef, 'left', this.lefts[this.index] + 'px')
|
||||
_css(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
_css(this.indicatorRef, 'left', this.lefts[this.index] + 'px')
|
||||
},
|
||||
move(e) {
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
this.$setCss(
|
||||
_css(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
_css(
|
||||
this.indicatorRef,
|
||||
'left',
|
||||
this.lefts[this.index] - e / (this.baseStore.bodyWidth / this.indicatorSpace) + 'px'
|
||||
@@ -209,10 +183,10 @@ export default {
|
||||
},
|
||||
end(index) {
|
||||
this.moveY = 0
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
this.$setCss(this.indicatorRef, 'left', this.lefts[index] + 'px')
|
||||
_css(this.indicatorRef, 'transition-duration', `300ms`)
|
||||
_css(this.indicatorRef, 'left', this.lefts[index] + 'px')
|
||||
setTimeout(() => {
|
||||
this.$setCss(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
_css(this.indicatorRef, 'transition-duration', `0ms`)
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
@@ -275,15 +249,16 @@ export default {
|
||||
color: rgba(white, 0.7);
|
||||
position: relative;
|
||||
font-size: 17rem;
|
||||
cursor: pointer;
|
||||
|
||||
.tab1-img {
|
||||
position: absolute;
|
||||
@width: 1rem;
|
||||
@width: 12rem;
|
||||
width: @width;
|
||||
height: @width;
|
||||
margin-left: 4rem;
|
||||
transition: all 0.3s;
|
||||
margin-top: 7rem;
|
||||
// margin-top: 7rem;
|
||||
}
|
||||
|
||||
.tab2-img {
|
||||
@@ -293,12 +268,6 @@ export default {
|
||||
top: -5rem;
|
||||
}
|
||||
|
||||
&.open {
|
||||
.tab1-img {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: white;
|
||||
}
|
||||
@@ -309,8 +278,8 @@ export default {
|
||||
//transition: left .3s;
|
||||
position: absolute;
|
||||
bottom: -6rem;
|
||||
height: 2rem;
|
||||
width: 20rem;
|
||||
height: 2.6rem;
|
||||
width: 26rem;
|
||||
//width: calc(100% / 5);
|
||||
background: #fff;
|
||||
border-radius: 5rem;
|
||||
@@ -323,56 +292,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-type {
|
||||
@height: 100rem;
|
||||
position: absolute;
|
||||
height: @height;
|
||||
//padding-top: @height;
|
||||
padding-left: 10rem;
|
||||
padding-right: 10rem;
|
||||
padding-bottom: 10rem;
|
||||
width: 100%;
|
||||
background: var(--main-bg);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
box-sizing: border-box;
|
||||
font-size: 12rem;
|
||||
top: -@height;
|
||||
transition: all 0.3s;
|
||||
opacity: 0;
|
||||
|
||||
&.open {
|
||||
top: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.l-button {
|
||||
flex: 1;
|
||||
margin: 0 3rem;
|
||||
height: 28rem;
|
||||
background: rgb(33, 36, 45);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 20rem;
|
||||
color: rgb(157, 161, 170);
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
background: rgb(57, 57, 65);
|
||||
color: white;
|
||||
}
|
||||
|
||||
img {
|
||||
@width: 9rem;
|
||||
width: @width;
|
||||
height: @width;
|
||||
margin-left: 8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mask {
|
||||
top: 0;
|
||||
position: absolute;
|
||||
|
||||
@@ -161,6 +161,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import Check from '../../../components/Check'
|
||||
import { _hideLoading, _notice, _showLoading, _sleep } from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'Test',
|
||||
@@ -222,10 +223,10 @@ export default {
|
||||
},
|
||||
async submit() {
|
||||
this.cancel()
|
||||
this.$showLoading()
|
||||
await this.$sleep(1000)
|
||||
this.$hideLoading()
|
||||
this.$notice('感谢你的反馈,我们会尽快答复!')
|
||||
_showLoading()
|
||||
await _sleep(1000)
|
||||
_hideLoading()
|
||||
_notice('感谢你的反馈,我们会尽快答复!')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,67 +15,67 @@
|
||||
</dy-button>
|
||||
|
||||
<template v-if="canDownload">
|
||||
<dy-button type="green" v-if="showShare2WeChatZone" @click="$no">
|
||||
<dy-button type="green" v-if="showShare2WeChatZone" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/wechatzone-white.webp" alt="" />
|
||||
</template>
|
||||
发送视频到朋友圈
|
||||
</dy-button>
|
||||
<dy-button type="green" v-if="showShare2WeChat" @click="$no">
|
||||
<dy-button type="green" v-if="showShare2WeChat" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/wechat-white.webp" alt="" />
|
||||
</template>
|
||||
发送视频到微信
|
||||
</dy-button>
|
||||
<dy-button type="qqzone" v-if="showShare2QQZone" @click="$no">
|
||||
<dy-button type="qqzone" v-if="showShare2QQZone" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/qqzone-white.png" alt="" />
|
||||
</template>
|
||||
发送视频到QQ空间
|
||||
</dy-button>
|
||||
<dy-button type="qq" v-if="showShare2QQ" @click="$no">
|
||||
<dy-button type="qq" v-if="showShare2QQ" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/qq-white.webp" alt="" />
|
||||
</template>
|
||||
发送视频到QQ
|
||||
</dy-button>
|
||||
<dy-button type="webo" v-if="showShare2Webo" @click="$no">
|
||||
<dy-button type="webo" v-if="showShare2Webo" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/webo-white.webp" alt="" />
|
||||
</template>
|
||||
发送视频到微博
|
||||
</dy-button>
|
||||
<dy-button v-if="!showDownload" class="mt1r" type="white" @click="$no"
|
||||
<dy-button v-if="!showDownload" class="mt1r" type="white" @click="_no"
|
||||
>复制口令发给好友
|
||||
</dy-button>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<dy-button type="green" v-if="showShare2WeChatZone" @click="$no">
|
||||
<dy-button type="green" v-if="showShare2WeChatZone" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/wechatzone-white.webp" alt="" />
|
||||
</template>
|
||||
复制口令发给好友
|
||||
</dy-button>
|
||||
<dy-button type="green" v-if="showShare2WeChat" @click="$no">
|
||||
<dy-button type="green" v-if="showShare2WeChat" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/wechat-white.webp" alt="" />
|
||||
</template>
|
||||
复制口令发给好友
|
||||
</dy-button>
|
||||
<dy-button type="qqzone" v-if="showShare2QQZone" @click="$no">
|
||||
<dy-button type="qqzone" v-if="showShare2QQZone" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/qqzone-white.png" alt="" />
|
||||
</template>
|
||||
复制口令发给好友
|
||||
</dy-button>
|
||||
<dy-button type="qq" v-if="showShare2QQ" @click="$no">
|
||||
<dy-button type="qq" v-if="showShare2QQ" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/qq-white.webp" alt="" />
|
||||
</template>
|
||||
复制口令发给好友
|
||||
</dy-button>
|
||||
<dy-button type="webo" v-if="showShare2Webo" @click="$no">
|
||||
<dy-button type="webo" v-if="showShare2Webo" @click="_no">
|
||||
<template v-slot:prefix>
|
||||
<img src="../../../assets/img/icon/components/video/webo-white.webp" alt="" />
|
||||
</template>
|
||||
@@ -84,12 +84,12 @@
|
||||
</template>
|
||||
|
||||
<template v-if="showDownload">
|
||||
<dy-button type="primary" @click="$no">
|
||||
<dy-button type="primary" @click="_no">
|
||||
已保存,请去相册查看
|
||||
<dy-back scale="0.7" mode="light" direction="right"></dy-back>
|
||||
</dy-button>
|
||||
|
||||
<dy-button class="mt1r" type="white" @click="$no">
|
||||
<dy-button class="mt1r" type="white" @click="_no">
|
||||
<img src="../../../assets/img/icon/components/video/wechat.webp" alt="" />
|
||||
发送视频到微信
|
||||
</dy-button>
|
||||
@@ -102,14 +102,14 @@
|
||||
v-for="(item, i) in localFriends.all"
|
||||
@click="share(item)"
|
||||
>
|
||||
<img :src="$imgPreview(item.avatar)" alt="" />
|
||||
<img :src="_checkImgUrl(item.avatar)" alt="" />
|
||||
<div class="right">
|
||||
<span>{{ item.name }}</span>
|
||||
<div class="share-btn" v-if="!item.select">分享</div>
|
||||
<div class="share-btn shared" v-else>已私信</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more" @click="cancel($nav('/message/share-to-friend'))">
|
||||
<div class="more" @click="cancel($router.push('/message/share-to-friend'))">
|
||||
<img src="../../../assets/img/icon/components/video/more-dark.png" />
|
||||
<div class="right">
|
||||
<span>更多好友</span>
|
||||
@@ -123,6 +123,7 @@
|
||||
import FromBottomDialog from '../../../components/dialog/FromBottomDialog'
|
||||
import { mapState } from 'pinia'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { _checkImgUrl, _no, _notice, _storageGet, _storageSet, cloneDeep } from '@/utils'
|
||||
/*
|
||||
* 分享到各种工具
|
||||
* */
|
||||
@@ -150,7 +151,7 @@ export default {
|
||||
this.change(newVal)
|
||||
},
|
||||
showShareDialog() {
|
||||
this.localFriends = this.$clone(this.friends)
|
||||
this.localFriends = cloneDeep(this.friends)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -173,11 +174,13 @@ export default {
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
_checkImgUrl,
|
||||
_no,
|
||||
async change(newVal) {
|
||||
if (newVal === -1) return
|
||||
this.showShareDialog = true
|
||||
if (this.canDownload) {
|
||||
let downloadedVideo = this.$storageGet('downloadedVideo', [])
|
||||
let downloadedVideo = _storageGet('downloadedVideo', [])
|
||||
if (!downloadedVideo.find((v) => v === this.videoId) && !this.downloading) {
|
||||
await this.downloadVideo()
|
||||
}
|
||||
@@ -213,9 +216,9 @@ export default {
|
||||
this.downloading = true
|
||||
let time = setInterval(() => {
|
||||
if (this.progress >= 100) {
|
||||
let downloadedVideo = this.$storageGet('downloadedVideo', [])
|
||||
let downloadedVideo = _storageGet('downloadedVideo', [])
|
||||
downloadedVideo.push(this.videoId)
|
||||
this.$storageSet('downloadedVideo', downloadedVideo)
|
||||
_storageSet('downloadedVideo', downloadedVideo)
|
||||
clearInterval(time)
|
||||
this.downloading = false
|
||||
resolve()
|
||||
@@ -227,7 +230,7 @@ export default {
|
||||
},
|
||||
share(item) {
|
||||
if (item.select) {
|
||||
this.$notice('已分享给朋友')
|
||||
_notice('已分享给朋友')
|
||||
}
|
||||
item.select = true
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
v-for="(item, i) in searchResult"
|
||||
@click="handleClick2(item)"
|
||||
>
|
||||
<img class="left" v-lazy="$imgPreview(item.avatar)" alt="" />
|
||||
<img class="left" v-lazy="_checkImgUrl(item.avatar)" alt="" />
|
||||
<div class="right">
|
||||
<div class="info">
|
||||
<span class="name">
|
||||
@@ -60,7 +60,7 @@
|
||||
<div class="friend-list">
|
||||
<div class="index">所有朋友</div>
|
||||
<div class="friend-item" :key="i" v-for="(item, i) in localFriends">
|
||||
<img class="left" v-lazy="$imgPreview(item.avatar)" alt="" />
|
||||
<img class="left" v-lazy="_checkImgUrl(item.avatar)" alt="" />
|
||||
<div class="right">
|
||||
<span>{{ item.name }}</span>
|
||||
<dy-button :type="item.shared ? 'dark' : 'primary'" @click="item.shared = true">
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
<div class="chat-list">
|
||||
<div class="chat-item" :key="i" v-for="(item, i) in localFriends">
|
||||
<img class="left" v-lazy="$imgPreview(item.avatar)" alt="" />
|
||||
<img class="left" v-lazy="_checkImgUrl(item.avatar)" alt="" />
|
||||
<div class="right">
|
||||
<div class="title">
|
||||
<div class="name">{{ text }}</div>
|
||||
@@ -101,6 +101,7 @@ import FromBottomDialog from '../../../components/dialog/FromBottomDialog'
|
||||
import { mapState } from 'pinia'
|
||||
import Search from '../../../components/Search'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { _checkImgUrl, cloneDeep } from '@/utils'
|
||||
/*
|
||||
分享给朋友
|
||||
* */
|
||||
@@ -136,7 +137,7 @@ export default {
|
||||
watch: {
|
||||
searchKey(newVal) {
|
||||
if (newVal) {
|
||||
let temp = this.$clone(this.localFriends)
|
||||
let temp = cloneDeep(this.localFriends)
|
||||
this.searchResult = temp.filter((v) => {
|
||||
// return v.name.includes(newVal) || v.account.includes(newVal);
|
||||
return v.name.includes(newVal)
|
||||
@@ -147,7 +148,7 @@ export default {
|
||||
},
|
||||
modelValue(newVal) {
|
||||
if (newVal) {
|
||||
this.localFriends = this.$clone(this.friends.all)
|
||||
this.localFriends = cloneDeep(this.friends.all)
|
||||
this.localFriends.map((v) => (v.shared = false))
|
||||
} else {
|
||||
this.searchKey = ''
|
||||
@@ -165,6 +166,7 @@ export default {
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
_checkImgUrl,
|
||||
handleClick() {
|
||||
this.isShowRightText = true
|
||||
this.height = 'calc(var(--vh, 1vh) * 100)'
|
||||
|
||||
@@ -38,11 +38,11 @@
|
||||
:can-download="canDownload"
|
||||
@click="closeShare()"
|
||||
/>
|
||||
<to-share item-type="report" @click="$nav('/home/report', { mode: this.mode })" />
|
||||
<to-share item-type="report" @click="$router.push('/home/report', { mode: this.mode })" />
|
||||
<to-share item-type="copyPassword" @click="copyLink" />
|
||||
<to-share :item-type="isCollect ? 'collectYellow' : 'collect'" @click="toggleCollect" />
|
||||
<to-share item-type="comeon" @click="$no" />
|
||||
<to-share item-type="dou" @click="$no" />
|
||||
<to-share item-type="comeon" @click="_no" />
|
||||
<to-share item-type="dou" @click="_no" />
|
||||
<to-share item-type="copyLink" @click="copyLink" />
|
||||
<template v-if="isShowMore">
|
||||
<to-share item-type="duoshan" @click="isShowMore = true" />
|
||||
@@ -50,12 +50,12 @@
|
||||
</template>
|
||||
<to-share v-else item-type="share" @click="isShowMore = true" />
|
||||
<to-share item-type="dislike" @click="isShowMore = true" />
|
||||
<to-share item-type="bizhi" @click="$no" />
|
||||
<to-share item-type="code" @click="$no" />
|
||||
<to-share item-type="bizhi" @click="_no" />
|
||||
<to-share item-type="code" @click="_no" />
|
||||
</div>
|
||||
<div class="friends">
|
||||
<div class="item" :key="i" v-for="(item, i) in friends.all">
|
||||
<img class="left" v-lazy="$imgPreview(item.avatar)" alt="" />
|
||||
<img class="left" v-lazy="_checkImgUrl(item.avatar)" alt="" />
|
||||
<div class="right">
|
||||
<span>{{ item.name }}</span>
|
||||
<dy-button
|
||||
@@ -67,7 +67,7 @@
|
||||
</dy-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more" @click="closeShare($nav('/message/share-to-friend'))">
|
||||
<div class="more" @click="closeShare($router.push('/message/share-to-friend'))">
|
||||
<img class="left" src="../../../assets/img/icon/components/video/more-dark.png" />
|
||||
<span>更多朋友</span>
|
||||
</div>
|
||||
@@ -81,6 +81,15 @@ import { mapState } from 'pinia'
|
||||
import FromBottomDialog from '../../../components/dialog/FromBottomDialog'
|
||||
import LoadingCircle from './LoadingCircle'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import {
|
||||
_checkImgUrl,
|
||||
_hideLoading,
|
||||
_no,
|
||||
_notice,
|
||||
_showLoading,
|
||||
_sleep,
|
||||
_stopPropagation
|
||||
} from '@/utils'
|
||||
// import DouyinCode from "./DouyinCode";
|
||||
|
||||
export default {
|
||||
@@ -149,17 +158,17 @@ export default {
|
||||
click(e) {
|
||||
if (!this.canDownload) {
|
||||
if (this.itemType === 'download') {
|
||||
this.$stopPropagation(e)
|
||||
_stopPropagation(e)
|
||||
} else {
|
||||
this.$notice('作者已关闭下载功能')
|
||||
_notice('作者已关闭下载功能')
|
||||
this.$emit('copy')
|
||||
}
|
||||
return
|
||||
}
|
||||
if (this.needDown) this.$stopPropagation(e)
|
||||
if (this.needDown) _stopPropagation(e)
|
||||
else return
|
||||
if (this.progress === 100) {
|
||||
this.$notice('未实现分享跳转到其他App')
|
||||
_notice('未实现分享跳转到其他App')
|
||||
} else {
|
||||
this.loading = true
|
||||
let interval = setInterval(() => {
|
||||
@@ -170,7 +179,7 @@ export default {
|
||||
this.loading = false
|
||||
this.$emit('click')
|
||||
this.$emit('click')
|
||||
this.$notice('未实现分享跳转到其他App')
|
||||
_notice('未实现分享跳转到其他App')
|
||||
}
|
||||
}, 10)
|
||||
}
|
||||
@@ -249,6 +258,8 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_checkImgUrl,
|
||||
_no,
|
||||
displayText(type) {
|
||||
if (this.loading[type]) {
|
||||
return this.progress !== 100 ? '下载中' : this.text[type]
|
||||
@@ -257,17 +268,17 @@ export default {
|
||||
},
|
||||
async copyLink() {
|
||||
this.closeShare()
|
||||
this.$showLoading()
|
||||
await this.$sleep(500)
|
||||
this.$hideLoading()
|
||||
this.$notice('复制成功')
|
||||
_showLoading()
|
||||
await _sleep(500)
|
||||
_hideLoading()
|
||||
_notice('复制成功')
|
||||
},
|
||||
toggleCollect() {
|
||||
this.closeShare()
|
||||
if (this.isCollect) {
|
||||
this.$notice('取消收藏成功')
|
||||
_notice('取消收藏成功')
|
||||
} else {
|
||||
this.$notice('收藏成功')
|
||||
_notice('收藏成功')
|
||||
}
|
||||
this.isCollect = !this.isCollect
|
||||
},
|
||||
|
||||