直播平台app二期开发

This commit is contained in:
renna 2024-12-31 10:37:30 +08:00
commit 4c3752d154
82 changed files with 8325 additions and 0 deletions

16
.env.development Normal file
View File

@ -0,0 +1,16 @@
# 开发环境
# 指定构建模式
VITE_APP_ENV = 'development'
# base_url
# VITE_APP_BASE_URL='http://10.0.0.17:8081/'
VITE_APP_BASE_URL='https://m.livejinan.cn/platform'
VITE_APP_TITLE='济南移动直播平台移动端'

13
.env.production Normal file
View File

@ -0,0 +1,13 @@
# 生产环境
# 指定构建模式
VITE_APP_ENV = 'production'
# base_url
VITE_APP_BASE_URL='https://m.livejinan.cn/platform'
VITE_APP_TITLE='济南移动直播平台移动端'

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vs/ProjectSettings.json Normal file
View File

@ -0,0 +1,3 @@
{
"CurrentProjectSetting": null
}

View File

@ -0,0 +1,6 @@
{
"ExpandedNodes": [
""
],
"PreviewInSolutionExplorer": false
}

Binary file not shown.

View File

@ -0,0 +1,23 @@
{
"Version": 1,
"WorkspaceRootPath": "D:\\dongniTeach\\\u6D4E\u5357\u5E02\u79FB\u52A8\u76F4\u64AD\u5E73\u53F0\\240129\u6700\u65B0\u62C9\u53D6\\aijinan-app-newVersion\\",
"Documents": [],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": -1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
}
]
}
]
}
]
}

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

10
README.md Normal file
View File

@ -0,0 +1,10 @@
# Vue 3 + Vite+vant4 直播平台移动端 App
<!-- import { login } from "../api/index";
// 调用登录接口函数
login().then((result) => {
})
.catch((error) => {
console.error("登录接口请求失败", error);
}); -->

17
index.html Normal file
View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>济南移动直播平台app</title>
<link rel="stylesheet" href="https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/skins/default/aliplayer-min.css" />
<script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/aliplayer-h5-min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

3230
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

33
package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "vue3-template-js",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build:pro": "vite build --mode production",
"build:dev": "vite build --mode development",
"preview": "vite preview"
},
"dependencies": {
"autoprefixer": "^10.4.19",
"axios": "^1.7.2",
"mescroll.js": "^1.4.2",
"path": "^0.12.7",
"swiper": "^11.1.4",
"vant": "^4.9.1",
"vue": "^3.3.4",
"vue-router": "^4.3.3",
"xgplayer": "^3.0.19"
},
"devDependencies": {
"@vant/auto-import-resolver": "^1.2.1",
"@vitejs/plugin-vue": "^4.2.3",
"postcss-px-to-viewport": "^1.1.1",
"sass": "^1.77.6",
"unplugin-auto-import": "^0.17.6",
"unplugin-vue-components": "^0.27.0",
"vite": "^4.4.5"
}
}

22
postcss.config.cjs Normal file
View File

@ -0,0 +1,22 @@
//postcss.config.cjs
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 375, // 设计稿的视口宽度
viewportUnit: "vw", // 希望使用的视口单位
unitPrecision: 5, // 单位转换后保留的精度
propList: ["*"], // 能转化为vw的属性列表
fontViewportUnit: "vw", // 字体使用的视口单位
selectorBlackList: [], // 需要忽略的CSS选择器不会转为视口单位使用原有的px等单位。
minPixelValue: 1, // 设置最小的转换数值如果为1的话只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: undefined, // 忽略某些文件夹下的文件或特定文件
include: undefined, // 如果设置了include那将只有匹配到的文件才会被转换
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件
landscapeUnit: "vw", // 横屏时使用的单位
landscapeWidth: 568, // 横屏时使用的视口宽度
}
}
}

BIN
public/bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
public/bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
public/bg3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
public/bg4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/bg5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
public/bg6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

11
src/App.vue Normal file
View File

@ -0,0 +1,11 @@
<script setup>
</script>
<template>
<router-view></router-view>
</template>
<style scoped>
</style>

87
src/api/index.js Normal file
View File

@ -0,0 +1,87 @@
import request from '../utils/request'
export function scene(params) {
return request({
url: '/foreign/live/scene/get/' + params.id,
method: 'get'
// params: params
});
}
// 观看人数
export function viewshow(params) {
return request({
url: '/app/live/scene/counts',
method: 'get',
params: params
});
}
// 有 caster 呼叫 /caster/streamsUrl
export function caster(params) {
return request({
url: '/caster/streamsUrl',
method: 'get',
params: params
});
}
// 报道
export function report(params) {
return request({
url: '/foreign/live/report/list',
method: 'get',
params: params
});
}
// 评论
export function commentList(params) {
return request({
url: '/foreign/live/comment/list',
method: 'get',
params: params
});
}
export function liveList(params) {
return request({
url: '/index',
method: 'get',
params: params
});
}
// export function commentList (params) {
// return request({
// url: '/comment'),
// method: 'get',
// params: params
// });
// }
// 添加评论
export function addComment(formData) {
return request({
url: '/comment/create',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: formData
});
}
// 获取微信签名
export function getWXSig(url) {
return request({
url: `/weixin/getJsApiTicket?url=${url}`,
// https://app.livejinan.cn/platform/foreign/live/weixin/getJsApiTicket?url=http%3A%2F%2F192.168.50.26%3A8080%2Flive%2Fmerge%2Fdetail%2F2057
// url: `http://test.zhongkedongxin.com/platform/foreign/live/weixin/getJsApiTicket?url=${url}`,
method: 'get',
headers: {
'Content-Type': 'multipart/form-data'
}
});
}

73
src/assets/css/index.scss Normal file
View File

@ -0,0 +1,73 @@
*{
margin: 0;
padding:0;
box-sizing: border-box;
}
p{
margin:0;
}
html,
body {
height: 100%;
width: 100%;
box-sizing: border-box;
margin: 0;
padding:0;
#app {
height: 100%;
width: 100%;
box-sizing: border-box;
}
}
.flex1{
display:flex;
}
.flex-s-b{
justify-content: space-between;
}
.live_status{
width:55px;
padding:3px;
text-align: end;
background-color: #F12742;
position: relative;
font-size: 12px;
border-radius: 5px;
}
.live_status::before{
width:10px;
height:10px;
position: absolute;
content:'';
background: url("@/assets/img/live.png") no-repeat;
background-size: 100% 100%;
top:5px;
left: 3px;
}
.live_coming {
padding:3px;
text-align: center;
width:55px;
font-size: 12px;
border-radius: 5px;
background-image: linear-gradient(to left, #EFBE26, #F58C05);
}
.live_review {
padding:3px;
text-align: center;
width:55px;
font-size: 12px;
border-radius: 5px;
background-image: linear-gradient(to left, #4BB4FB, #1678ED);
}
// @media screen and (min-width: 1024px) {
// .page {
// width: 920px;
// margin: 0 auto;
// // border:1px solid red;
// overflow: hidden;
// }
// }

3
src/assets/img/arraw.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.00219 1.21266C4.36969 0.847033 4.96594 0.847033 5.33344 1.21266L10.9941 6.83859C11.2697 7.11234 11.3391 7.51453 11.2012 7.85203C11.1553 7.96453 11.0859 8.07047 10.9941 8.16141L5.33344 13.7883C4.965 14.1539 4.36969 14.1539 4.00219 13.7883C3.63469 13.4227 3.63469 12.8302 4.00219 12.4645L8.99719 7.50047L4.00219 2.53641C3.63469 2.16984 3.63469 1.57828 4.00219 1.21266Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 498 B

BIN
src/assets/img/bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
src/assets/img/bg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
src/assets/img/bg3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
src/assets/img/bg4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
src/assets/img/bg5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
src/assets/img/bg6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
src/assets/img/big1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
src/assets/img/bigplay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
src/assets/img/defineBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/img/grid1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/assets/img/grid2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src/assets/img/grid3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
src/assets/img/grid4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
src/assets/img/hot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

4
src/assets/img/hot.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="13" height="15" viewBox="0 0 13 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.00259 14.6151C16.0395 10.9613 10.6265 3.38313 9.54389 2.70651C9.94986 3.51846 9.94986 4.73639 9.27324 5.41301C8.19064 0.811952 5.21348 0 5.21348 0C5.61946 2.30053 3.99555 4.73638 2.50697 6.63094C2.50697 5.68366 2.37165 5.14236 1.83035 4.19508C1.69502 5.81899 0.477095 7.17224 0.0711195 8.79614C-0.334856 11.0967 1.0184 13.2619 3.58958 14.3445C3.58958 14.4798 6.56673 15.5624 9.00259 14.6151Z" fill="#F43760"/>
<path d="M5.07709 11.0963H4.40046V9.87837H3.18254V11.0963H2.64124V8.11914H3.31786V9.33707H4.53579V8.11914H5.21242L5.07709 11.0963ZM5.61839 9.60772C5.61839 9.20174 5.75372 8.79577 6.02437 8.52512C6.29502 8.25447 6.56567 8.11914 6.97164 8.11914C7.37762 8.11914 7.7836 8.25447 7.91892 8.52512C8.18957 8.79577 8.3249 9.20174 8.3249 9.60772C8.3249 10.0137 8.18957 10.4197 7.91892 10.6903C7.64827 10.961 7.37762 11.0963 6.97164 11.0963C6.56567 11.0963 6.15969 10.961 5.88904 10.6903C5.75372 10.4197 5.61839 10.149 5.61839 9.60772ZM6.15969 9.60772C6.15969 9.87837 6.15969 10.149 6.43034 10.2843C6.56567 10.4197 6.70099 10.555 6.97164 10.555C7.24229 10.555 7.37762 10.4197 7.51295 10.2843C7.64827 10.149 7.7836 9.87837 7.7836 9.60772C7.7836 9.33707 7.7836 9.06642 7.64827 8.93109C7.37762 8.79577 7.24229 8.66044 6.97164 8.66044C6.70099 8.66044 6.56567 8.79577 6.43034 8.93109C6.29502 9.06642 6.15969 9.33707 6.15969 9.60772ZM10.7608 8.66044H9.9488V11.0963H9.27217V8.66044H8.46022V8.11914H10.7608V8.66044Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/assets/img/jt_black.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

BIN
src/assets/img/jt_white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

7
src/assets/img/like.svg Normal file
View File

@ -0,0 +1,7 @@
<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 17.5C-4.84295e-08 19.7981 0.452651 22.0738 1.33211 24.197C2.21157 26.3202 3.50061 28.2493 5.12563 29.8744C6.75066 31.4994 8.67984 32.7884 10.803 33.6679C12.9262 34.5473 15.2019 35 17.5 35C19.7981 35 22.0738 34.5473 24.197 33.6679C26.3202 32.7884 28.2493 31.4994 29.8744 29.8744C31.4994 28.2493 32.7884 26.3202 33.6679 24.197C34.5473 22.0738 35 19.7981 35 17.5C35 15.2019 34.5473 12.9262 33.6679 10.803C32.7884 8.67984 31.4994 6.75066 29.8744 5.12563C28.2493 3.50061 26.3202 2.21157 24.197 1.33211C22.0738 0.452651 19.7981 0 17.5 0C15.2019 0 12.9262 0.452651 10.803 1.33211C8.67984 2.21157 6.75066 3.50061 5.12563 5.12563C3.50061 6.75066 2.21157 8.67984 1.33211 10.803C0.452651 12.9262 -3.42448e-08 15.2019 0 17.5Z" fill="#EEEEEE"/>
<path d="M27.8177 13.8536C27.0969 10.4988 24.5325 8.40051 20.8869 9.15407C19.7425 9.39054 18.8384 10.0337 18.036 10.8756C17.9682 10.8456 17.9405 10.8393 17.922 10.8236C17.8712 10.7794 17.8219 10.7337 17.7757 10.6848C16.7299 9.57656 15.4362 9.06736 13.9499 9.00588C12.1725 8.93336 10.6632 9.52927 9.53266 10.9449C7.54737 13.4295 7.495 17.1547 9.35092 19.8189C10.295 21.1747 11.4379 22.335 12.6977 23.3708C13.9946 24.438 15.3207 25.4706 16.6544 26.489C17.4908 27.1275 18.3995 27.1732 19.1819 26.5947C21.4583 24.9078 23.7485 23.2352 25.6506 21.0849C27.4757 19.0165 28.423 16.6723 27.8177 13.8536ZM12.7162 18.4837C12.553 18.6051 12.362 18.6634 12.1741 18.6634C11.8876 18.6634 11.6057 18.5278 11.4255 18.2724C9.65125 15.7674 10.9327 13.5209 10.9866 13.4263C11.049 13.3199 11.1314 13.227 11.2289 13.1532C11.3264 13.0793 11.4372 13.0258 11.5549 12.9958C11.6727 12.9658 11.795 12.9598 11.915 12.9782C12.035 12.9967 12.1502 13.0391 12.2542 13.1031C12.4628 13.2315 12.6135 13.4389 12.6738 13.6803C12.734 13.9217 12.6989 14.1776 12.5761 14.3927C12.5006 14.5314 11.9015 15.7233 12.9211 17.1626C13.2214 17.5851 13.129 18.1763 12.7162 18.4837Z" fill="#F23D4F"/>
<path d="M24.7604 9.57141C23.6777 9.02151 22.3569 8.84291 20.8503 9.15311C19.7091 9.38811 18.8076 10.0273 18.0075 10.8639C17.9399 10.8341 17.9123 10.8279 17.8938 10.8122C17.8431 10.7683 17.794 10.7229 17.7479 10.6743C16.7051 9.57298 15.415 9.06694 13.933 9.00584C12.1607 8.93377 10.6556 9.52598 9.52831 10.9328C7.54866 13.4019 7.49644 17.104 9.34708 19.7516C10.2885 21.099 11.4281 22.2521 12.6844 23.2814C13.3939 23.8642 14.1142 24.4344 14.8376 25C20.6491 23.7028 25 18.4247 25 12.1063C25.0009 11.2554 24.9207 10.4064 24.7604 9.57141ZM12.7043 18.4247C12.5416 18.5453 12.3511 18.6033 12.1637 18.6033C11.8781 18.6033 11.597 18.4685 11.4173 18.2147C9.6481 15.7253 10.9259 13.4928 10.9796 13.3988C11.0419 13.293 11.124 13.2007 11.2213 13.1273C11.3185 13.0539 11.429 13.0008 11.5464 12.971C11.6638 12.9411 11.7858 12.9352 11.9054 12.9535C12.025 12.9718 12.14 13.014 12.2436 13.0776C12.4516 13.2052 12.6019 13.4113 12.662 13.6512C12.7221 13.8911 12.6871 14.1454 12.5646 14.3592C12.4893 14.497 11.8919 15.6814 12.9086 17.1118C13.2081 17.5317 13.1159 18.1192 12.7043 18.4247Z" fill="#FC4956"/>
<path d="M21.4703 9.14597C20.2742 9.37001 19.3292 9.9794 18.4904 10.777C18.4196 10.7486 18.3906 10.7426 18.3713 10.7277C18.3181 10.6859 18.2666 10.6426 18.2183 10.5963C17.1252 9.54625 15.7729 9.06382 14.2193 9.00557C12.3614 8.93686 10.7837 9.50145 9.60206 10.8427C7.52688 13.1966 7.47214 16.726 9.41209 19.2502C9.6101 19.5086 9.81939 19.7565 10.0335 20C16.2655 19.3682 21.2256 14.8202 22 9.06382C21.8261 9.08622 21.649 9.11311 21.4703 9.14597ZM12.9297 17.9851C12.7591 18.1001 12.5595 18.1554 12.3631 18.1554C12.0636 18.1554 11.769 18.0269 11.5806 17.785C9.72602 15.4116 11.0655 13.2833 11.1218 13.1936C11.1871 13.0928 11.2732 13.0048 11.3751 12.9348C11.477 12.8649 11.5928 12.8142 11.7159 12.7858C11.8389 12.7573 11.9668 12.7517 12.0922 12.7691C12.2176 12.7866 12.3381 12.8268 12.4468 12.8874C12.6648 13.0091 12.8224 13.2055 12.8854 13.4342C12.9484 13.6629 12.9117 13.9055 12.7832 14.1092C12.7044 14.2407 12.0781 15.3698 13.1439 16.7335C13.4578 17.1338 13.3612 17.6939 12.9297 17.9851Z" fill="#FF5C64"/>
<path d="M14.4142 9.00527C12.4977 8.94022 10.8702 9.47478 9.6512 10.7447C8.49698 11.9468 7.94893 13.4741 8.00374 15C8.94151 14.8274 9.85588 14.5729 10.7323 14.2406C10.8586 13.4981 11.1924 13.0117 11.2206 12.9706C11.2879 12.8751 11.3767 12.7919 11.4819 12.7256C11.587 12.6593 11.7065 12.6114 11.8334 12.5845C11.9604 12.5575 12.0923 12.5522 12.2217 12.5687C12.351 12.5852 12.4753 12.6233 12.5874 12.6807C12.8 12.7896 12.9461 12.9537 13.0192 13.1389C14.6253 12.1889 15.9811 10.9641 17 9.54266C16.2178 9.20891 15.3525 9.0378 14.4142 9.00527Z" fill="#FF716E"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
src/assets/img/live.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

BIN
src/assets/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
src/assets/img/slide1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
src/assets/img/slide2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
src/assets/img/top1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -0,0 +1,122 @@
/**
* 2020 6 1
* 微信分享
* 调用微信appid相关内容接口 1. 接口 appid ; 2. 时间戳等内容
* 参考https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#111
*/
const wx = require("./weixin-jssdk-1.6.0");
const info = require("../../../api/modules/info");
// const headLogo = require('../../img/icon-report-name.png');
// const headLogo='https://aijinan.media.zhongkedongxin.com/image/default/F94790FC1B29416881B8D64C4EE1F23B-6-2.jpg';
let link = window.location.href.split("#")[0];
if (link.indexOf("&") > -1) {
link = link.substring(0, link.indexOf("&"));
console.log("!!!!!!!!111新增测试log", link);
window.location.href = link;
}
let shareOpt = {
title: "爱济南",
desc: "爱济南",
link: "",
// imgUrl: headLogo,
};
function setShareFunc(opt) {
console.log("-------------------------setShareFunc opt", opt.imgUrl);
opt.imgUrl = opt.imgUrl.replace(/\?[\w\W]*$/, "").replace(/^https/, "http");
// opt.link=encodeURIComponent(opt.link);
// 分享给朋友 updateAppMessageShareData
wx.updateAppMessageShareData({
title: opt.title,
desc: opt.desc,
link: opt.link,
imgUrl: opt.imgUrl + "?x-oss-process=image/resize,w_400",
// imgUrl: headLogo,
});
// 分享朋友圈
wx.updateTimelineShareData({
title: opt.title,
desc: opt.desc,
link: opt.link,
// link: window.location.href,//分享链接 该链接域名或路径必须与当前页面对应的公众号一直
imgUrl: opt.imgUrl + "?x-oss-process=image/resize,w_400",
// imgUrl: headLogo,
});
// 分享到微博
wx.onMenuShareWeibo({
title: opt.title,
desc: opt.desc,
link: opt.link,
imgUrl: opt.imgUrl + "?x-oss-process=image/resize,w_400",
// imgUrl: headLogo,
});
}
// 后台传值 获取微信签名信息成功后初始化分享功能的部分
function initShare(wxSignature, opt) {
console.log("initShare opt", opt);
wx.config({
debug: false, // 上线时记得改为 false
appId: wxSignature.appid,
timestamp: wxSignature.timeStamp,
nonceStr: wxSignature.nonceStr,
signature: wxSignature.sign,
// ↓ 配置需要用到的微信接口 ↓
jsApiList: [
"checkJsApi",
"updateAppMessageShareData",
"updateTimelineShareData",
"onMenuShareWeibo",
],
});
wx.ready(function() {
setShareFunc(opt);
});
}
export default function(opt) {
console.log("!!!!!!!!!!!!!!!!!!!!opt", opt);
info
.getWXSig(encodeURIComponent(link))
.then((res) => {
console.log("Weixin Signatrue SUCCESS", res);
let resData = res.data;
if (resData.code === 0) {
opt.link = link;
// console.log("---------------",link);
// opt.link='http://test.zhongkedongxin.com/live/merge/detail/493';
initShare(resData.data, opt);
} else {
console.log("Weixin Signatrue ERROR", resData.msg);
}
})
.catch((error) => {
console.log("Weixin Signatrue ERROR", error);
});
}
// 导出分享函数
// export default function (opt) {
// console.log("!!!!!!!!!!!!!!!!!!!!opt",opt);
// // 如果微信签名信息已经获取
// if (opt.link) {
// // 设置分享内容
// setShareFunc(opt);
// } else {
// // 否则,等待微信签名信息获取完成后再初始化分享功能
// info.getWXSig(encodeURIComponent(link)).then(res => {
// console.log('Weixin Signatrue SUCCESS', res);
// let resData = res.data;
// if (resData.code === 0) {
// opt.link = link;
// initShare(resData.data, opt);// 在获取微信签名信息成功后初始化分享功能
// } else {
// console.log('Weixin Signatrue ERROR', resData.msg);
// }
// }).catch(error => {
// console.log('Weixin Signatrue ERROR', error);
// });
// }
// }

View File

@ -0,0 +1,702 @@
! function (e, n) {
"function" == typeof define && (define.amd || define.cmd) ? define(function () {
return n(e)
}) : n(e, !0)
}(window, function (o, e) {
if (!o.jWeixin) {
var n, c = {
config: "preVerifyJSAPI",
onMenuShareTimeline: "menu:share:timeline",
onMenuShareAppMessage: "menu:share:appmessage",
onMenuShareQQ: "menu:share:qq",
onMenuShareWeibo: "menu:share:weiboApp",
onMenuShareQZone: "menu:share:QZone",
previewImage: "imagePreview",
getLocation: "geoLocation",
openProductSpecificView: "openProductViewWithPid",
addCard: "batchAddCard",
openCard: "batchViewCard",
chooseWXPay: "getBrandWCPayRequest",
openEnterpriseRedPacket: "getRecevieBizHongBaoRequest",
startSearchBeacons: "startMonitoringBeacons",
stopSearchBeacons: "stopMonitoringBeacons",
onSearchBeacons: "onBeaconsInRange",
consumeAndShareCard: "consumedShareCard",
openAddress: "editAddress"
},
a = function () {
var e = {};
for (var n in c) e[c[n]] = n;
return e
}(),
i = o.document,
t = i.title,
r = navigator.userAgent.toLowerCase(),
s = navigator.platform.toLowerCase(),
d = !(!s.match("mac") && !s.match("win")),
u = -1 != r.indexOf("wxdebugger"),
l = -1 != r.indexOf("micromessenger"),
p = -1 != r.indexOf("android"),
f = -1 != r.indexOf("iphone") || -1 != r.indexOf("ipad"),
m = (n = r.match(/micromessenger\/(\d+\.\d+\.\d+)/) || r.match(/micromessenger\/(\d+\.\d+)/)) ? n[1] : "",
g = {
initStartTime: L(),
initEndTime: 0,
preVerifyStartTime: 0,
preVerifyEndTime: 0
},
h = {
version: 1,
appId: "",
initTime: 0,
preVerifyTime: 0,
networkType: "",
isPreVerifyOk: 1,
systemType: f ? 1 : p ? 2 : -1,
clientVersion: m,
url: encodeURIComponent(location.href)
},
v = {},
S = {
_completes: []
},
y = {
state: 0,
data: {}
};
O(function () {
g.initEndTime = L()
});
var I = !1,
_ = [],
w = {
config: function (e) {
B("config", v = e);
var t = !1 !== v.check;
O(function () {
if (t) M(c.config, {
verifyJsApiList: C(v.jsApiList),
verifyOpenTagList: C(v.openTagList)
}, function () {
S._complete = function (e) {
g.preVerifyEndTime = L(), y.state = 1, y.data = e
}, S.success = function (e) {
h.isPreVerifyOk = 0
}, S.fail = function (e) {
S._fail ? S._fail(e) : y.state = -1
};
var t = S._completes;
return t.push(function () {
! function () {
if (!(d || u || v.debug || m < "6.0.2" || h.systemType < 0)) {
var i = new Image;
h.appId = v.appId, h.initTime = g.initEndTime - g.initStartTime, h.preVerifyTime = g.preVerifyEndTime - g.preVerifyStartTime, w.getNetworkType({
isInnerInvoke: !0,
success: function (e) {
h.networkType = e.networkType;
var n = "https://open.weixin.qq.com/sdk/report?v=" + h.version + "&o=" + h.isPreVerifyOk + "&s=" + h.systemType + "&c=" + h.clientVersion + "&a=" + h.appId + "&n=" + h.networkType + "&i=" + h.initTime + "&p=" + h.preVerifyTime + "&u=" + h.url;
i.src = n
}
})
}
}()
}), S.complete = function (e) {
for (var n = 0, i = t.length; n < i; ++n) t[n]();
S._completes = []
}, S
}()), g.preVerifyStartTime = L();
else {
y.state = 1;
for (var e = S._completes, n = 0, i = e.length; n < i; ++n) e[n]();
S._completes = []
}
}), w.invoke || (w.invoke = function (e, n, i) {
o.WeixinJSBridge && WeixinJSBridge.invoke(e, x(n), i)
}, w.on = function (e, n) {
o.WeixinJSBridge && WeixinJSBridge.on(e, n)
})
},
ready: function (e) {
0 != y.state ? e() : (S._completes.push(e), !l && v.debug && e())
},
error: function (e) {
m < "6.0.2" || (-1 == y.state ? e(y.data) : S._fail = e)
},
checkJsApi: function (e) {
M("checkJsApi", {
jsApiList: C(e.jsApiList)
}, (e._complete = function (e) {
if (p) {
var n = e.checkResult;
n && (e.checkResult = JSON.parse(n))
}
e = function (e) {
var n = e.checkResult;
for (var i in n) {
var t = a[i];
t && (n[t] = n[i], delete n[i])
}
return e
}(e)
}, e))
},
onMenuShareTimeline: function (e) {
P(c.onMenuShareTimeline, {
complete: function () {
M("shareTimeline", {
title: e.title || t,
desc: e.title || t,
BASE_URL: e.imgUrl || "",
link: e.link || location.href,
type: e.type || "link",
data_url: e.dataUrl || ""
}, e)
}
}, e)
},
onMenuShareAppMessage: function (n) {
P(c.onMenuShareAppMessage, {
complete: function (e) {
"favorite" === e.scene ? M("sendAppMessage", {
title: n.title || t,
desc: n.desc || "",
link: n.link || location.href,
BASE_URL: n.imgUrl || "",
type: n.type || "link",
data_url: n.dataUrl || ""
}) : M("sendAppMessage", {
title: n.title || t,
desc: n.desc || "",
link: n.link || location.href,
BASE_URL: n.imgUrl || "",
type: n.type || "link",
data_url: n.dataUrl || ""
}, n)
}
}, n)
},
onMenuShareQQ: function (e) {
P(c.onMenuShareQQ, {
complete: function () {
M("shareQQ", {
title: e.title || t,
desc: e.desc || "",
BASE_URL: e.imgUrl || "",
link: e.link || location.href
}, e)
}
}, e)
},
onMenuShareWeibo: function (e) {
P(c.onMenuShareWeibo, {
complete: function () {
M("shareWeiboApp", {
title: e.title || t,
desc: e.desc || "",
BASE_URL: e.imgUrl || "",
link: e.link || location.href
}, e)
}
}, e)
},
onMenuShareQZone: function (e) {
P(c.onMenuShareQZone, {
complete: function () {
M("shareQZone", {
title: e.title || t,
desc: e.desc || "",
BASE_URL: e.imgUrl || "",
link: e.link || location.href
}, e)
}
}, e)
},
updateTimelineShareData: function (e) {
M("updateTimelineShareData", {
title: e.title,
link: e.link,
imgUrl: e.imgUrl
}, e)
},
updateAppMessageShareData: function (e) {
M("updateAppMessageShareData", {
title: e.title,
desc: e.desc,
link: e.link,
imgUrl: e.imgUrl
}, e)
},
startRecord: function (e) {
M("startRecord", {}, e)
},
stopRecord: function (e) {
M("stopRecord", {}, e)
},
onVoiceRecordEnd: function (e) {
P("onVoiceRecordEnd", e)
},
playVoice: function (e) {
M("playVoice", {
localId: e.localId
}, e)
},
pauseVoice: function (e) {
M("pauseVoice", {
localId: e.localId
}, e)
},
stopVoice: function (e) {
M("stopVoice", {
localId: e.localId
}, e)
},
onVoicePlayEnd: function (e) {
P("onVoicePlayEnd", e)
},
uploadVoice: function (e) {
M("uploadVoice", {
localId: e.localId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
}, e)
},
downloadVoice: function (e) {
M("downloadVoice", {
serverId: e.serverId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
}, e)
},
translateVoice: function (e) {
M("translateVoice", {
localId: e.localId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
}, e)
},
chooseImage: function (e) {
M("chooseImage", {
scene: "1|2",
count: e.count || 9,
sizeType: e.sizeType || ["original", "compressed"],
sourceType: e.sourceType || ["album", "camera"]
}, (e._complete = function (e) {
if (p) {
var n = e.localIds;
try {
n && (e.localIds = JSON.parse(n))
} catch (e) {}
}
}, e))
},
getLocation: function (e) {},
previewImage: function (e) {
M(c.previewImage, {
current: e.current,
urls: e.urls
}, e)
},
uploadImage: function (e) {
M("uploadImage", {
localId: e.localId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
}, e)
},
downloadImage: function (e) {
M("downloadImage", {
serverId: e.serverId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
}, e)
},
getLocalImgData: function (e) {
!1 === I ? (I = !0, M("getLocalImgData", {
localId: e.localId
}, (e._complete = function (e) {
if (I = !1, 0 < _.length) {
var n = _.shift();
wx.getLocalImgData(n)
}
}, e))) : _.push(e)
},
getNetworkType: function (e) {
M("getNetworkType", {}, (e._complete = function (e) {
e = function (e) {
var n = e.errMsg;
e.errMsg = "getNetworkType:ok";
var i = e.subtype;
if (delete e.subtype, i) e.networkType = i;
else {
var t = n.indexOf(":"),
o = n.substring(t + 1);
switch (o) {
case "wifi":
case "edge":
case "wwan":
e.networkType = o;
break;
default:
e.errMsg = "getNetworkType:fail"
}
}
return e
}(e)
}, e))
},
openLocation: function (e) {
M("openLocation", {
latitude: e.latitude,
longitude: e.longitude,
name: e.name || "",
address: e.address || "",
scale: e.scale || 28,
infoUrl: e.infoUrl || ""
}, e)
},
getLocation: function (e) {
M(c.getLocation, {
type: (e = e || {}).type || "wgs84"
}, (e._complete = function (e) {
delete e.type
}, e))
},
hideOptionMenu: function (e) {
M("hideOptionMenu", {}, e)
},
showOptionMenu: function (e) {
M("showOptionMenu", {}, e)
},
closeWindow: function (e) {
M("closeWindow", {}, e = e || {})
},
hideMenuItems: function (e) {
M("hideMenuItems", {
menuList: e.menuList
}, e)
},
showMenuItems: function (e) {
M("showMenuItems", {
menuList: e.menuList
}, e)
},
hideAllNonBaseMenuItem: function (e) {
M("hideAllNonBaseMenuItem", {}, e)
},
showAllNonBaseMenuItem: function (e) {
M("showAllNonBaseMenuItem", {}, e)
},
scanQRCode: function (e) {
M("scanQRCode", {
needResult: (e = e || {}).needResult || 0,
scanType: e.scanType || ["qrCode", "barCode"]
}, (e._complete = function (e) {
if (f) {
var n = e.resultStr;
if (n) {
var i = JSON.parse(n);
e.resultStr = i && i.scan_code && i.scan_code.scan_result
}
}
}, e))
},
openAddress: function (e) {
M(c.openAddress, {}, (e._complete = function (e) {
e = function (e) {
return e.postalCode = e.addressPostalCode, delete e.addressPostalCode, e.provinceName = e.proviceFirstStageName, delete e.proviceFirstStageName, e.cityName = e.addressCitySecondStageName, delete e.addressCitySecondStageName, e.countryName = e.addressCountiesThirdStageName, delete e.addressCountiesThirdStageName, e.detailInfo = e.addressDetailInfo, delete e.addressDetailInfo, e
}(e)
}, e))
},
openProductSpecificView: function (e) {
M(c.openProductSpecificView, {
pid: e.productId,
view_type: e.viewType || 0,
ext_info: e.extInfo
}, e)
},
addCard: function (e) {
for (var n = e.cardList, i = [], t = 0, o = n.length; t < o; ++t) {
var r = n[t],
a = {
card_id: r.cardId,
card_ext: r.cardExt
};
i.push(a)
}
M(c.addCard, {
card_list: i
}, (e._complete = function (e) {
var n = e.card_list;
if (n) {
for (var i = 0, t = (n = JSON.parse(n)).length; i < t; ++i) {
var o = n[i];
o.cardId = o.card_id, o.cardExt = o.card_ext, o.isSuccess = !!o.is_succ, delete o.card_id, delete o.card_ext, delete o.is_succ
}
e.cardList = n, delete e.card_list
}
}, e))
},
chooseCard: function (e) {
M("chooseCard", {
app_id: v.appId,
location_id: e.shopId || "",
sign_type: e.signType || "SHA1",
card_id: e.cardId || "",
card_type: e.cardType || "",
card_sign: e.cardSign,
time_stamp: e.timestamp + "",
nonce_str: e.nonceStr
}, (e._complete = function (e) {
e.cardList = e.choose_card_info, delete e.choose_card_info
}, e))
},
openCard: function (e) {
for (var n = e.cardList, i = [], t = 0, o = n.length; t < o; ++t) {
var r = n[t],
a = {
card_id: r.cardId,
code: r.code
};
i.push(a)
}
M(c.openCard, {
card_list: i
}, e)
},
consumeAndShareCard: function (e) {
M(c.consumeAndShareCard, {
consumedCardId: e.cardId,
consumedCode: e.code
}, e)
},
chooseWXPay: function (e) {
M(c.chooseWXPay, V(e), e)
},
openEnterpriseRedPacket: function (e) {
M(c.openEnterpriseRedPacket, V(e), e)
},
startSearchBeacons: function (e) {
M(c.startSearchBeacons, {
ticket: e.ticket
}, e)
},
stopSearchBeacons: function (e) {
M(c.stopSearchBeacons, {}, e)
},
onSearchBeacons: function (e) {
P(c.onSearchBeacons, e)
},
openEnterpriseChat: function (e) {
M("openEnterpriseChat", {
useridlist: e.userIds,
chatname: e.groupName
}, e)
},
launchMiniProgram: function (e) {
M("launchMiniProgram", {
targetAppId: e.targetAppId,
path: function (e) {
if ("string" == typeof e && 0 < e.length) {
var n = e.split("?")[0],
i = e.split("?")[1];
return n += ".html", void 0 !== i ? n + "?" + i : n
}
}(e.path),
envVersion: e.envVersion
}, e)
},
openBusinessView: function (e) {
M("openBusinessView", {
businessType: e.businessType,
queryString: e.queryString || "",
envVersion: e.envVersion
}, (e._complete = function (n) {
if (p) {
var e = n.extraData;
if (e) try {
n.extraData = JSON.parse(e)
} catch (e) {
n.extraData = {}
}
}
}, e))
},
miniProgram: {
navigateBack: function (e) {
e = e || {}, O(function () {
M("invokeMiniProgramAPI", {
name: "navigateBack",
arg: {
delta: e.delta || 1
}
}, e)
})
},
navigateTo: function (e) {
O(function () {
M("invokeMiniProgramAPI", {
name: "navigateTo",
arg: {
url: e.url
}
}, e)
})
},
redirectTo: function (e) {
O(function () {
M("invokeMiniProgramAPI", {
name: "redirectTo",
arg: {
url: e.url
}
}, e)
})
},
switchTab: function (e) {
O(function () {
M("invokeMiniProgramAPI", {
name: "switchTab",
arg: {
url: e.url
}
}, e)
})
},
reLaunch: function (e) {
O(function () {
M("invokeMiniProgramAPI", {
name: "reLaunch",
arg: {
url: e.url
}
}, e)
})
},
postMessage: function (e) {
O(function () {
M("invokeMiniProgramAPI", {
name: "postMessage",
arg: e.data || {}
}, e)
})
},
getEnv: function (e) {
O(function () {
e({
miniprogram: "miniprogram" === o.__wxjs_environment
})
})
}
}
},
T = 1,
k = {};
return i.addEventListener("error", function (e) {
if (!p) {
var n = e.target,
i = n.tagName,
t = n.src;
if ("IMG" == i || "VIDEO" == i || "AUDIO" == i || "SOURCE" == i)
if (-1 != t.indexOf("wxlocalresource://")) {
e.preventDefault(), e.stopPropagation();
var o = n["wx-id"];
if (o || (o = T++, n["wx-id"] = o), k[o]) return;
k[o] = !0, wx.ready(function () {
wx.getLocalImgData({
localId: t,
success: function (e) {
n.src = e.localData
}
})
})
}
}
}, !0), i.addEventListener("load", function (e) {
if (!p) {
var n = e.target,
i = n.tagName;
n.src;
if ("IMG" == i || "VIDEO" == i || "AUDIO" == i || "SOURCE" == i) {
var t = n["wx-id"];
t && (k[t] = !1)
}
}
}, !0), e && (o.wx = o.jWeixin = w), w
}
function M(n, e, i) {
o.WeixinJSBridge ? WeixinJSBridge.invoke(n, x(e), function (e) {
A(n, e, i)
}) : B(n, i)
}
function P(n, i, t) {
o.WeixinJSBridge ? WeixinJSBridge.on(n, function (e) {
t && t.trigger && t.trigger(e), A(n, e, i)
}) : B(n, t || i)
}
function x(e) {
return (e = e || {}).appId = v.appId, e.verifyAppId = v.appId, e.verifySignType = "sha1", e.verifyTimestamp = v.timestamp + "", e.verifyNonceStr = v.nonceStr, e.verifySignature = v.signature, e
}
function V(e) {
return {
timeStamp: e.timestamp + "",
nonceStr: e.nonceStr,
package: e.package,
paySign: e.paySign,
signType: e.signType || "SHA1"
}
}
function A(e, n, i) {
"openEnterpriseChat" != e && "openBusinessView" !== e || (n.errCode = n.err_code), delete n.err_code, delete n.err_desc, delete n.err_detail;
var t = n.errMsg;
t || (t = n.err_msg, delete n.err_msg, t = function (e, n) {
var i = e,
t = a[i];
t && (i = t);
var o = "ok";
if (n) {
var r = n.indexOf(":");
"confirm" == (o = n.substring(r + 1)) && (o = "ok"), "failed" == o && (o = "fail"), -1 != o.indexOf("failed_") && (o = o.substring(7)), -1 != o.indexOf("fail_") && (o = o.substring(5)), "access denied" != (o = (o = o.replace(/_/g, " ")).toLowerCase()) && "no permission to execute" != o || (o = "permission denied"), "config" == i && "function not exist" == o && (o = "ok"), "" == o && (o = "fail")
}
return n = i + ":" + o
}(e, t), n.errMsg = t), (i = i || {})._complete && (i._complete(n), delete i._complete), t = n.errMsg || "", v.debug && !i.isInnerInvoke && alert(JSON.stringify(n));
var o = t.indexOf(":");
switch (t.substring(o + 1)) {
case "ok":
i.success && i.success(n);
break;
case "cancel":
i.cancel && i.cancel(n);
break;
default:
i.fail && i.fail(n)
}
i.complete && i.complete(n)
}
function C(e) {
if (e) {
for (var n = 0, i = e.length; n < i; ++n) {
var t = e[n],
o = c[t];
o && (e[n] = o)
}
return e
}
}
function B(e, n) {
if (!(!v.debug || n && n.isInnerInvoke)) {
var i = a[e];
i && (e = i), n && n._complete && delete n._complete, console.log('"' + e + '",', n || "")
}
}
function L() {
return (new Date).getTime()
}
function O(e) {
l && (o.WeixinJSBridge ? e() : i.addEventListener && i.addEventListener("WeixinJSBridgeReady", e, !1))
}
});

1
src/assets/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,114 @@
<template>
<div class="all_list">
<div class="grid_list">
<div class="list_item" v-for="(item, index) in moreList" :key="index">
<img :src="item.img" alt="Event 1" @click="goDetail(item)" />
<p>{{item.title}}</p>
</div>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue';
import pic from '../assets/img/top1.png';
import {useRouter} from 'vue-router';
const router = useRouter();
const moreList =ref([
{
id: 1,
title: "章丘区高官寨甜瓜季乡村好时节活动",
img:pic
},
{
id: 2,
title: "中华人民共和国第十四届全国人民代表大会",
img:pic
},
{
id: 3,
title: "泉在济南·龙山文化旅游季活动",
img:pic
},
{
id: 4,
title: "乡村好时节·花开北市樱花旅游节",
img:pic
},
{
id: 5,
title: "国际学校名校展",
img:pic
},
{
id: 6,
title: "爱牙医生来",
img:pic
},
{
id: 7,
title: "宏观经济形势与政策",
img:pic
},
{
id: 8,
title: "学在济南·聚在泉城",
img:pic
},
{
id: 9,
title: "文明济南·礼安齐鲁",
img:pic
},
{
id: 10,
title: "民主主题记者会",
img:pic,
},
{
id: 8,
title: "学在济南·聚在泉城",
img:pic
},
{
id: 9,
title: "文明济南·礼安齐鲁",
img:pic
},
{
id: 10,
title: "民主主题记者会",
img:pic,
}
])
const goDetail = (item) => {
console.log(item)
router.push({
name: 'Detail',
query: { item: item.id }
})
}
</script>
<style lang="scss" scoped>
.all_list {
width: 90%;
margin: 10px auto;
height: calc(100vh - 120px);
overflow-y: scroll;
}
.grid_list {
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
.list_item {
width: 100%;
height: auto;
img {
width: 100%;
height: 102px;
}
}
}
</style>

View File

@ -0,0 +1,110 @@
//
<template>
<!-- <div class="co-main">
<div class="co-flex">
距离直播开始还有
<div class="border-bar">{{ d }}</div>
<span></span>
<div class="border-bar">{{ h }}</div>
<span></span>
<div class="border-bar">{{ m }}</div>
<span></span>
<div class="border-bar">{{ s }}</div>
<span></span>
</div>
</div> -->
<div class="main">
<div class="main_left">
<P> 距离直播开始还有</P>
<p class="time">{{ h }}:{{ m }}:{{ s }}</p>
</div>
<div class="main_left">
<p class="views">500人次观看</p>
<button>预告</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
const props = defineProps(["countdownTime"]);
const showTime = ref(0);
const timeInterval = ref(null);
const totalTime = ref(0);
const d = ref("00");
const h = ref("00");
const m = ref("00");
const s = ref("00");
onMounted(() => {
init();
});
onBeforeUnmount(() => {
clearInterval(timeInterval.value);
});
function init() {
console.log("[init] --> ", props.countdownTime);
totalTime.value = props.countdownTime;
timeInterval.value = setInterval(() => {
parseTime(totalTime.value);
totalTime.value--;
if (totalTime.value < 0) {
clearInterval(timeInterval.value);
location.replace(location.href);
}
}, 1000);
}
function parseTime(total) {
let day = Math.floor(total / (24 * 60 * 60));
let afterDay = total - day * 24 * 60 * 60;
let hour = Math.floor(afterDay / (60 * 60));
let afterHour = total - day * 24 * 60 * 60 - hour * 60 * 60;
let min = Math.floor(afterHour / 60);
let second = afterHour % 60;
d.value = day < 10 ? "0" + day : day;
h.value = hour < 10 ? "0" + hour : hour;
m.value = min < 10 ? "0" + min : min;
s.value = second < 10 ? "0" + second : second;
}
</script>
<style lang="scss" scoped>
.main{
width: 100%;
padding:10px 20px;
display: flex;
justify-content: space-between;
height:80px;
.main_left{
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
.time{
font-size: 36px;
margin-left: 10px;
}
.views{
font-size: 12px;
}
p{
font-size: 16px;
}
button{
padding:5px 15px;
background-color: #D7000F;
border:none;
color: #fff;
border-radius: 5px;
font-size: 16px;
}
}
}
</style>

76
src/components/header.vue Normal file
View File

@ -0,0 +1,76 @@
<template>
<div class="header">
<div class="top"></div>
<div class="header-content">
<div class="logo">
<img src="../assets/img/logo.png" alt="" />
</div>
<p>直播</p>
<div class="search">
<van-search
v-model="value"
shape="round"
background="transparent"
placeholder="请输入搜索关键词"
@click="onSearch"
></van-search>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRouter } from 'vue-router'
const value = ref("");
const router = useRouter()
const onSearch = () => {
console.log("1111111111");
// valuesearch
router.push({
name: 'Search',
});
};
</script>
<style lang="scss" scoped>
::v-deep(.van-search){
padding:0;
}
.header {
width: 100%;
height: 64px;
background: linear-gradient(
to bottom,
rgba(27, 163, 251, 100%),
70%,
rgba(27, 163, 251, 45%)
);
display: flex;
.header-content {
width: 90%;
padding:10px 0;
margin:0 auto;
display: flex;
justify-content: space-between;
align-items: center;
.logo {
img {
width: 30px;
height:30px;
}
}
p {
width:50px;
text-align: center;
font-size: 16px;
color: #fff;
}
.search{
width:70%;
}
}
}
</style>

View File

@ -0,0 +1,95 @@
<template>
<div class="moreLive">
<p>更多直播</p>
<div class="live_list">
<div class="moreLive-list" v-for="(item, index) in lives" :key="index" @click="goDetail(item)">
<img src="../assets/img/big1.png" />
<div class="live_info">
<div class="live_title">
<!-- <p :class="{ 'live_status': item.status === '直播中', 'live_coming': item.status === '预告', 'live_review': item.status === '精彩回顾' }">{{ item.status }}</p> -->
<p :class="{ 'live_status': getStatusClass(item.status) === '直播中', 'live_coming': getStatusClass(item.status) === '预告', 'live_review': getStatusClass(item.status) === '精彩回顾' }">{{ getStatusText(item.status) }}</p>
<p class="live_see">{{item.viewers}}人次观看</p>
</div>
<p>{{item.title}}</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue';
import { useRouter } from 'vue-router'
const router = useRouter()
const lives = ref([
{ id:'0',status: '0', viewers: '2347.7万', title: '文明清明·礼安齐鲁1' },
{ id:'1',status: '1', viewers: '1536万', title: '文明清明·礼安齐鲁2' },
{ id:'2',status: '2', viewers: '58697万', title: '文明清明·礼安齐鲁3' },
{ id:'3',status: '0', viewers: '2347.7万', title: '文明清明·礼安齐鲁' }
])
//
const statusMap = {
'0': '预告',
'1': '直播中',
'2': '精彩回顾',
};
//
const getStatusText = (status) => statusMap[status] || '';
// CSS
const getStatusClass = (status) => statusMap[status] || '';
const goDetail=(item)=>{
console.log(item)
// router.push({
// name:'Detail',
// query:{id:item.id}
// })
}
</script>
<style lang="scss" scoped>
.moreLive {
padding: 10px;
// p{
// font-size: 16px;
// }
.live_list{
width:100%;
height: calc(100vh - 50px);
overflow-y: scroll;
.moreLive-list{
width:100%;
height: 128px;
margin:10px 0;
position: relative;
.live_info{
padding:5px;
position: absolute;
bottom:0px;
font-size: 14px;
color: #fff;
.live_title{
display: flex;
align-items: center;
gap:5px;
.live_see{
background-color: rgba(0,0,0,0.5);
font-size: 10px;
padding:3px 5px;
border-radius: 5px;
}
}
}
img{
width: 100%;
height: 100%;
border-radius: 5px;
}
}
}
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<div class="abstract">
<div class="title">{{ title }}济南移动直播平台移动端开发的标题</div>
<div class="info">
<div class="info-item">
<span class="name">直播时间</span>
<span class="info-item-text">2024年5约我接地极{{ infoDate }}</span>
</div>
<div class="info-item">
<span class="name">直播地点</span>
<span class="info-item-text">济南{{ infoPlace }}</span>
</div>
<div class="info-item">
<span class="name">直播简介</span>
<van-text-ellipsis
class="info-item-text"
style="display: inline;"
rows="3"
:content="text"
expand-text="展开"
collapse-text="收起"
/>
</div>
</div>
</div>
</template>
<script setup>
const text =
"那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云。后来我才知道,生活就是个缓慢受锤的过程,人一天天老下去,奢望也一天天消失,最后变得像挨了锤的牛一样。可是我过二十一岁生日时没有预见到这一点。我觉得自己会永远生猛下去,什么也锤不了我。";
</script>
<style lang="scss" scoped>
.abstract {
// border: 1px solid blue;
margin-top: 10px;
padding: 0 20px;
// border-top: 4px solid #eee;
// padding-top: 10px;
.title {
font-size: 18px;
font-weight: bold;
font-stretch: normal;
line-height: 21px;
letter-spacing: 0px;
color: #232323;
}
.info {
margin-top: 10px;
font-size: 17px;
// font-size: 13px;
font-weight: normal;
font-stretch: normal;
letter-spacing: 0px;
color: #5c5c5c;
.info-item {
margin-bottom: 7px;
// display: flex;
// flex-wrap: wrap;
align-items: center;
.name {
color: #000;
}
.info-item-text {
color: #777882;
}
}
}
.content {
margin-top: 18px;
font-size: 17px;
// font-size: 13px;
color: #5c5c5c;
line-height: 1.6;
// line-height: 20px;
.content-title {
color: #000;
font-weight: bold;
}
.go-new-page {
color: rgb(0, 0, 238);
}
}
}
</style>

View File

@ -0,0 +1,272 @@
<template>
<div class="comment">
<div class="comment-scroll">
<!-- <mescroll-vue class="mescrollPadding" ref="mescroll" :up="mescrollUp" @init="mescrollInit"> -->
<div class="scroll-main" id="dataListComment">
<div class="comment-card" v-for="(item, index) in commentData" :key="index">
<div class="comment-card-main">
<!-- 评论头像 -->
<div class="headImg">
<img v-if="item.headImg" :src="item.headImg" alt />
<img v-else :src="imageAvatarUrl" alt />
</div>
<span class="top-name">{{item.userName||'游客'}}:&nbsp;&nbsp;</span>
<span class="content-bottom">{{item.text}}</span>
<!-- <div class="content">
<div class="content-top">
<div class="top-name">{{item.userName||'游客'}}</div>
<div class="content-bottom">{{item.text}}</div>
</div>
</div> -->
</div>
<div class="botton-line"></div>
</div>
</div>
<!-- </mescroll-vue> -->
</div>
<div class="comment-input">
<img src="../assets/img/logo.png"/>
<form action="/">
<van-search
class="input-class"
v-model="showSubmitMsg"
type="text"
placeholder="我来说几句......"
clearable
background="#ededed"
left-icon
id="vanInput"
@search="inputCommentHandler"
/>
<canvas id="thumsCanvas" width="200" height="1000"></canvas>
<div class="iconShare" @click="inputCommentHandler">
<img src="../assets/img/like.svg" alt @click="clickLikeThumb" />
</div>
</form>
</div>
</div>
</template>
<script setup>
import {onMounted, ref} from 'vue';
import {commentList,addComment} from '../api/index';
import ThumbsUpAni from '../utils/canvas'
const imageAvatarUrl = ref(null);
const commentData= ref([]);
const showSubmitMsg = ref('');
//
const getCommentList = async () => {
let params = {
sceneId:2057,
size: 10,
page: 1,
};
commentList(params)
.then((res) => {
console.log("获取评论列表",res);
commentData.value =res.data;
imageAvatarUrl.value = res.anonymousUrl
})
.catch((error) => {
console.error("登录接口请求失败", error);
});
};
//
const clickLikeThumb=()=> {
const thumbsUpAni = new ThumbsUpAni();
console.log("点击",thumbsUpAni);
setInterval(() => {
thumbsUpAni.start();
}, 300);
}
onMounted(()=>{
getCommentList();
})
//
const inputCommentHandler =()=>{
console.log("评论输入",showSubmitMsg.value);
//
if (showSubmitMsg.value.length < 1) {
return false;
}
let utoken = null;
let sceneId = 2057;
let text = JSON.parse(JSON.stringify(showSubmitMsg.value));
// formData
let formdate = new FormData();
formdate.append("utoken", utoken);
formdate.append("sceneId", sceneId);
formdate.append("text", text);
let vanInput = document.querySelector("#vanInput");
vanInput.blur();
window.scroll(0, 0);
// api
addComment(formdate)
.then(({ data }) => {
showNotify({ type: "primary", message: "评论成功" });
this.mescroll.triggerDownScroll(); // upCallback
showSubmitMsg.value = "";
})
.catch(e => {
// ,;
this.mescroll.endDownScroll();
console.log("%c 提交评论 error ", "color:red", e, "提交失败");
showNotify({ type: "danger", message: "网络繁忙,请稍后再试。" });
showSubmitMsg.value = "";
});
}
</script>
<style lang="scss" scoped>
#thumsCanvas {
position:fixed;
bottom:50px;
right:0;
width: 100px;
margin: auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: transparent!important;
}
.comment {
height: 100%;
.comment-scroll {
height: calc(100vh - 175px);
overflow: scroll;
.scroll-main {
height: 100%;
.comment-card {
// border: 1px solid blue;
padding:10px 20px;
.comment-card-main {
display:inline-block;
padding:5px 10px;
width: auto;
background-color: #EEEEEE;
border-radius: 10px;
.headImg {
display: inline-block;
vertical-align:middle;
width: 30px;
height: 30px;
img {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.top-name{
margin-left: 10px;
font-size: 16px;
color: #777882;
}
.content-bottom {
font-family: MicrosoftYaHei-Bold;
font-size: 16px;
color: rgba(0,0,0,80%);
line-height: 19px;
letter-spacing: 0px;
}
// .content {
// width: 268px;
// .content-top {
// display: flex;
// justify-content: space-between;
// margin-bottom: 10px;
// font-size: 15px;
// color: #5c5c5c;
// }
// .content-bottom {
// font-family: MicrosoftYaHei-Bold;
// font-size: 17px;
// color: #454545;
// line-height: 19px;
// letter-spacing: 0px;
// }
// }
}
// .botton-line {
// height: 1px;
// width: 100%;
// margin-left: 9px;
// background-color: rgba(160, 160, 160, 0.52);
// }
}
}
}
.comment-input {
position: fixed;
bottom:0;
display: flex;
padding:0 10px;
width: 100%;
justify-content: space-between;
align-items: center;
height: 50px;
background-color: #fff;
img{
width:40px;
height: 40px;
border-radius: 50%;
}
form {
display: flex;
width: 85%;
justify-content: space-between;
align-items: center;
// padding: 0 12px;
.iconShare {
width: 40px;
height: 40px;
img {
width: 100%;
height: 100%;
}
}
.input-class.van-search {
// padding-top: 7px;
padding: 0;
width: 85%;
.van-search__content {
background-color: #fff;
border-radius: 15px;
color: #454545;
.van-cell {
font-size: 12px !important;
padding: 5px 7px;
}
}
}
}
}
}
</style>
<style lang="scss">
.van-pull-refresh__track {
height: 100% !important;
}
</style>

View File

@ -0,0 +1,615 @@
<template>
<div id="report">
<!-- 悬浮框 正序浏览/倒序浏览 -->
<!-- <div class="reverseBtn" @click="reverseData">
<img :src="isOrder ? inOrderIcon : orderIcon" alt class="isOrder" />
</div> -->
<!-- 上下拉 -->
<div class="scroll">
<mescroll-vue
class="mescrollPadding"
ref="mescroll"
:up="mescrollUp"
@init="mescrollInit"
>
<div id="liveView" ref="liveView" class="liveView">
<div>
<van-divider>直播视角</van-divider>
<!-- 多个视角 -->
<div class="viewSelect liveTab" >
<div
class="liveTabCell"
active-class="selectedTab"
v-for="(item, index) in liveList"
:key="index"
:selected="index === liveTabIndex"
@click="goStore(index)"
>
<div class="selectPicItem" >
<div class="select-pic-item-img">
<img v-if="item.coverUrl" :src="item.coverUrl" alt />
<img v-else :src="defineBigBg" alt />
</div>
<div class="coverTitle" :title="item.name">
{{ item.name }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 报道 -->
<div class="report" id="dataList" ref="dataList">
<page-abstract></page-abstract>
<div
class="reportCell"
v-for="(item, index) in reportDataList"
:key="index"
>
<div class="reportLeft">
<img
class="reportLeftImg"
src="../assets/img/ijnan_line.png"
alt
/>
</div>
<div class="reportRight">
<div class="reportCellTopInfo">
<span class="reportDate">{{ item.reportDate }}</span>
<div class="repoterUser">
<!-- <div class="userImg">
<img class="userImgUrl" :src="userImg" alt />
</div>-->
<!-- 报道logo -->
<div class="userName">
<img class="userImgUrl" :src="imageLogoUrl" alt />
</div>
</div>
</div>
<div class="reportRightMain reportBorderLeft">
<div class="reportRightMainBg">
<!-- 修改各种 reportType 1视频 2音频 3图文 4文字 -->
<div v-if="item.reportType === '4'" class="reportRightMainTxt">
<!-- <div v-html="changeStr(item.content)"></div> -->
<van-text-ellipsis
class="imgTxtContant"
style="display: inline;"
rows="3"
:content="item.content"
expand-text="展开"
collapse-text="收起"
/>
</div>
<div v-if="item.reportType === '3'" class="reportRightMainDefine">
<div class="imgTxt" v-if="item.imgList">
<div class="singleImg" v-if="item.imgList.length === 1">
<img
v-if="item.imgList[0]"
:src="item.imgList[0]"
:οnerrοr="defineBigBg"
preview="'groupName'"
alt
/>
<img v-else :src="defineBigBg" alt />
</div>
<div class="doubleImg" v-else-if=" item.imgList.length === 2">
<div class="doubleImgRact">
<img
v-if="item.imgList[0]"
:src="item.imgList[0]"
:οnerrοr="defineBigBg"
preview="'groupName'"
alt
/>
<img v-else :src="defineBigBg" alt />
</div>
<div class="doubleImgRact">
<img
v-if="item.imgList[1]"
:src="item.imgList[1]"
:οnerrοr="defineBigBg"
preview="'groupName'"
alt
/>
<img v-else :src="defineBigBg" alt />
</div>
</div>
<div class="doubleImg" v-else-if="item.imgList.length === 3">
<div
class="doubleImgRact3"
v-for="(imgItem,index) in item.imgList"
:key="index"
>
<img
v-if="imgItem"
:src="imgItem"
:οnerrοr="defineBigBg"
preview="'groupName'"
alt
/>
<img v-else :src="defineBigBg" alt />
</div>
</div>
<div class="doubleImgOther" v-else>
<div
class="doubleImgRact"
v-for="(imgItem,index) in item.imgList"
:key="index"
>
<img
v-if="imgItem"
:src="imgItem"
:οnerrοr="defineBigBg"
preview="'groupName'"
alt
/>
<img v-else :src="defineBigBg" alt />
</div>
</div>
</div>
<!-- <div class="imgTxtContant" v-html="changeStr(item.content)"></div> -->
<van-text-ellipsis
class="imgTxtContant"
style="display: inline;"
rows="3"
:content="item.content"
expand-text="展开"
collapse-text="收起"
/>
</div>
<div v-if="item.reportType === '2'" class="reportRightMainDefine">
<div class="audioBubble" @click="audioPlay(item.mediaUrl)">
<div class="audioIcon">
<img :src="icAudio" alt />
</div>
<div
class="audio-p-time"
:ref="'audio-p-'+index"
>{{duration2Time(item.audioDuration)}}</div>
</div>
<!-- <div class="imgTxtContant" v-html="changeStr(item.content)"></div> -->
<van-text-ellipsis
class="imgTxtContant"
style="display: inline;"
rows="3"
:content="item.content"
expand-text="展开"
collapse-text="收起"
/>
</div>
<div v-if="item.reportType === '1'" class="reportRightMainDefine">
<div class="videoClass">
<div
class="posterPic"
v-if="index!==posterIndex"
@click="playIndexVideo(index,item)"
>
<img v-if="item.imgList" :src="item.imgList[0]" :οnerrοr="defineBg" alt />
<img v-else :src="defineBg" alt />
</div>
<div
v-if="index!==posterIndex"
class="posterPlay"
@click="playIndexVideo(index,item)"
>
<img :src="iconPlayVideo" alt />
</div>
<video
:src="item.mediaUrl"
:poster="item.imgList?item.imgList[0]:''"
ref="videoPlay"
@play="onVideoPlay"
controls
x5-playsinline
playsinline="true"
webkit-playsinline="true"
x-webkit-airplay="true"
x5-video-player-type="h5"
x5-video-player-fullscreen
x5-video-orientation="portraint"
></video>
</div>
<!-- <div class="imgTxtContant" v-html="changeStr(item.content)"></div> -->
<van-text-ellipsis
class="imgTxtContant"
style="display: inline;"
rows="3"
:content="item.content"
expand-text="展开"
collapse-text="收起"
/>
</div>
<div v-if="item.contentHref!=null&&item.contentHref.length>0" class="crefer">
<div class="title">相关报道</div>
<divider class="crefer-line"></divider>
<div class="crefer-a">
<a :href="item.contentHref">{{item.relatedReport?item.relatedReport:'相关链接'}}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</mescroll-vue>
</div>
<audio ref="audio" class="audioClass" controls="controls">不支持</audio>
</div>
</template>
<script setup>
import { ref } from "vue";
import MescrollVue from 'mescroll.js/mescroll.vue'
import defineBigBg from '../assets/img/defineBigBg.png';
import {report} from '../api/index';
const liveTabIndex = ref(0);
const liveList = ref([
{
"name": "珍珠泉",
"coverUrl": "https://v.livejinan.cn/image/default/B861748A8C2144E4B5D495EC2E145E36-6-2.jpg",
"playUrl": "https://play.livejinan.cn/caeUaEzv/435e3a9910934a02963efbc816bc6fb5.m3u8?auth_key=1762000667-0-0-978bb1670d6778fafd715add79508ca9"
},
{
"name": "回马泉",
"coverUrl": "https://v.livejinan.cn/image/default/A9E4B702DCDD433E9D9F96CC5B547B95-6-2.jpg",
"playUrl": "https://play.livejinan.cn/caeUaEzv/af6c1c5167944ae0b17455b475b49ad3.m3u8?auth_key=1762000667-0-0-84ac0c225eec8c42992a278f7398fbe3"
},
{
"name": "黑虎泉",
"coverUrl": "https://v.livejinan.cn/image/default/5690007EE0224437A447503EE7833466-6-2.jpg",
"playUrl": "https://play.livejinan.cn/caeUaEzv/d8808c0620ca4ea8875bf02a6f48d74b.m3u8?auth_key=1762000667-0-0-871ee007886d35f6239d31547e1ff4fc"
},
{
"name": "趵突泉",
"coverUrl": "https://v.livejinan.cn/image/default/5AD9515D81F84C5B81D1AEAA90E52D88-6-2.jpg",
"playUrl": "https://play.livejinan.cn/caeUaEzv/62575b41a6bc4e7d9bdeb7930bb60f90.m3u8?auth_key=1762000667-0-0-b78d8b16b6d8f8a1f1231fd03bce22ee"
},
{
"name": "五龙潭泉群",
"coverUrl": "https://v.livejinan.cn/image/default/6ADAC0A084CB4A02957C59B5E3E377C1-6-2.jpg",
"playUrl": "https://play.livejinan.cn/caeUaEzv/c6d0d2464deb43dd89b0c0d560596fc5.m3u8?auth_key=1762000667-0-0-6e72ab4b79fa2531d0ecfb105101e323"
}
])
const reportDataList = ref([
{
reportType: "3",
reportDate: "2021-10-09 07:32",
mediaUrl: null,
relatedReport: "",
widthAndHeight: ["1.77:1"],
id: "36377",
contentHref: "",
reporterName: "王晨",
content:
"  今年入夏以来随着降雨逐渐增多济南趵突泉地下水位创56年之最突破30米“大关”。与此同时众泉也更加欢畅奔腾。自古有“波涛声震大明湖”、“石激湍声成虎吼”、“水若涌轮”的吟咏而今天爱济南通过5G技术和“千里眼”点位带大家沉浸式体验不一样的泉水下的美丽世界。\n  甘甜的泉水清澈见底水藻飘摆水泡咕噜咕噜冒个不停不时有鱼儿穿梭其间……这里没有主持人没有解说字幕也没有配音更没有绚丽的镜头切换和后期制作这里只有最真实的泉水下的世界。\n  请你放下所有烦恼和压力快来近距离地体验和享受这段最美妙的泉城水下世界。",
imgList: [
"https://v.livejinan.cn/image/default/5D19FFA43A2A436DA89BB5CCFAD776C6-6-2.jpg?x-oss-process=image/resize,w_600",
],
},
{
reportType: "3",
reportDate: "2021-10-09 09:09",
mediaUrl: null,
relatedReport: "",
widthAndHeight: ["1.33:1"],
id: "36378",
contentHref: "",
reporterName: "张静",
content:
"  近年来济南报业坚持“融合赋能伴生城市成长”理念聚力内容、平台和技术三方面创新媒体融合的“济南模式、山东特色”更加鲜明。2020年初济南报业依托爱济南新闻客户端布局慢直播项目推出“大型城市形象宣传平台——直播济南”对济南200多个“点位”进行24小时直播在全国地方媒体中开创先河。\n\n  “直播济南”根植济南、面向全国、沟通世界以传播济南好声音、讲好济南故事为己任以助力建设“大强美富通”现代化国际大都市、塑造良好城市形象为使命。围绕这个使命“直播济南”运用图文直播、移动直播、短视频直播、航拍直播VR直播等多种直播形态让受众沉浸式体验身临其境感受泉城新面貌。\n\n  从图片到视频再到VR用直观而又有视觉冲击力的表现形式全方位、立体化展示济南的发展成绩。目前“大型城市形象宣传平台——直播济南”已陆续推出“重点项目巡礼季”、“文化旅游季”、“电商带货季”、“乡村振兴季”等大型直播。济南报业与人民视频、央视频、学习强国、今日头条、百度等重要媒体平台积极互动同步直播济南面向全网传播。同时济南报业坚持对时政活动、重点工作、市委市政府新闻发布会等进行常态化直播传播力大幅提升赢得广泛关注和认可。王晨 孙明超 苏岭红 张静)",
imgList: [
"https://v.livejinan.cn/image/default/29002AF6D93C41B7AFC5DC9A882CC476-6-2.png?x-oss-process=image/resize,w_600",
],
},
]);
// MescrollVue
const mescroll = ref(null);
// Mescroll
const mescrollUp = {
//
auto: true,
// ,
// callback: upCallback,
//
empty: {
tip: '暂无更多数据'
},
//
error: {
tip: '加载失败,点击重新加载'
}
};
// Mescroll
const mescrollInit = (mescroll) => {
console.log('Mescroll 初始化完成');
// mescroll
// this.mescroll = mescroll;
};
//
const upCallback = (page) => {
};
//
const getReportList = () => {
let params = {
sceneId:2057,
size: 10,
page: 1
};
report(params)
.then((res) => {
console.log("获取报道列表",res);
})
.catch((error) => {
console.error("登录接口请求失败", error);
});
};
</script>
<style lang="scss" scoped>
#report {
/* min-height: 102vw; */
height: 100%;
// width:720px;
// margin:0 auto;
background-color: #fff;
overflow-y: scroll;
/* padding-top: 30px; */
}
/* 悬浮框 */
.reverseBtn {
z-index: 998;
position: absolute;
bottom: 20px;
right: 0;
width: 68px;
height: 20px;
img {
width: 100%;
height: 100%;
display: block;
}
}
.scroll {
overflow: hidden;
height: 100%;
}
.mescrollPadding {
box-sizing: border-box;
/* padding-top: 15px; */
}
/* 直播视角 */
.liveView {
/* position: absolute; */
z-index: 21;
/* margin-top: 32px; */
// padding-bottom: 15px;
// box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
background-color: #fff;
// margin-bottom: 8px;
}
// --------------------------
.liveTab {
width: 100%;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
overflow-x: scroll;
overflow-y: hidden;
-webkit-backface-visibility: hidden;
-webkit-perspective: 1000;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.liveTabCell {
display: inline-block;
width: 75px;
margin: 0 5px;
// border-radius: 5px;
}
.liveTabCell .selectPicItem {
width: 100%;
// height: 45px;
position: relative;
/* height: 84px; */
// background-color: #ccc;
padding: 0;
}
.liveTabCell .selectPicItem .select-pic-item-img {
display: block;
width: 100%;
height: 42.186px;
border-radius: 5px;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
display: block;
}
}
.coverTitle {
margin-top: 6px;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
width: 75px;
// min-height: 20px;
font-size: 10px;
color: #454545;
text-align: center;
}
.viewNameSpan {
font-size: 14px;
color: #6a6a6a;
}
.van-divider {
width: 100%;
margin: 0 !important;
}
#dataList {
padding-top: 12px;
padding-bottom: 5px;
}
.btnReview {
position: fixed;
bottom: 10px;
left: 25px;
z-index: 99999;
background-color: #fff;
}
.reportCell {
// overflow: hidden;
// border:1px solid red;
}
.reportLeft {
float: left;
margin-left: 10px;
margin-top: 3px;
width: 15px;
height: 15px;
// border:1px solid red;
}
.reportLeft .reportLeftImg {
display: block;
width: 28px;
height: 28px;
border-radius: 50%;
}
.reportBorderLeft {
border-left: 1px solid rgba(136, 136, 136, 0.42);
}
.reportRight {
margin-left: 23px;
margin-bottom: 2px;
}
.reportCellTopInfo {
padding: 10px 0px 0px 20px;
height: 28px;
box-sizing: border-box;
overflow: hidden;
display: flex;
align-items: center;
}
.repoterUser {
float: left;
color: #279cfb;
}
.userImg {
width: 19px;
height: 16px;
float: left;
}
.userImg > .userImgUrl {
height: 100%;
width: 100%;
display: block;
}
.userName {
width: 45px;
height: 12px;
margin-left: 5px;
float: left;
}
.userName > .userImgUrl {
width: auto;
height: 100%;
display: block;
object-fit: cover;
}
.reportDate {
// margin-left: 5px;
// font-size: 10px;
font-size: 15px;
color: #464646;
line-height: 16px;
float: left;
}
.reportRightMain {
margin: 8px 0;
padding: 0 20px 0 15px;
padding-bottom: 10px;
}
.reportRightMainDefine {
padding: 8px 5px;
}
.singleImg {
width: 100%;
text-align: center;
width: 306px;
// height: 174px;
border-radius: 5px;
overflow: hidden;
// background-color: @imgBgColor;
/* padding-bottom: 56.25%; */
/* position: relative; */
}
.singleImg img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
/* height: 100%; */
/* position: absolute; */
/* top: 0; */
/* left: 0; */
}
.imgTxtContant {
margin-top: 12px;
padding: 1px 3px;
text-align: justify;
font-family: MicrosoftYaHei-Bold;
font-size: 17px;
// font-size: 13.5px;
font-weight: normal;
letter-spacing: 0px;
color: #777882;
line-height: 1.6;
word-break: normal;
}
.audioClass {
width: 1px !important;
height: 1px !important;
position: absolute;
top: -10px;
left: -10px;
opacity: 0 !important;
}
</style>

View File

@ -0,0 +1,150 @@
<template>
<swiper
:slidesPerView="2"
:grid="{
rows: 2,
}"
:spaceBetween="10"
:pagination="{
clickable: true,
}"
:modules="modules"
class="mySwiper"
>
<swiper-slide v-for="(item, index) in slides" :key="index">
<div class="tri">
<img :src="item.imageUrl" alt="" @click="goDetail(item)" />
<p class="title">{{ item.title }}</p>
</div>
<p class="status" :class="{ 'live_status': getStatusClass(item.status) === '直播中', 'live_coming': getStatusClass(item.status) === '预告', 'live_review': getStatusClass(item.status) === '精彩回顾' }">{{ getStatusText(item.status) }}</p>
</swiper-slide>
</swiper>
</template>
<script setup>
import { ref } from "vue";
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from "swiper/vue";
import grid1 from '../../assets/img/grid1.png';
import grid2 from '../../assets/img/grid2.png';
import grid3 from '../../assets/img/grid3.png';
import grid4 from '../../assets/img/grid4.png';
// Import Swiper styles
import "swiper/css";
import "swiper/css/grid";
import "swiper/css/pagination";
// import required modules
import { Grid } from "swiper/modules";
import {useRouter} from 'vue-router';
const router = useRouter();
const modules = [Grid];
const slides = ref([
{
id:'0',
status: '0',
imageUrl: grid1,
title: "星海扬帆",
},
{
id:'1',
status: '1',
imageUrl: grid2,
title: "防空报警试鸣",
},
{
id:'2',
status: '2',
imageUrl: grid3,
title: "泉城广场",
},
{id:'3',
status: '0',
imageUrl: grid4,
title: "防空紧急疏散",
},
{id:'4',
status: '1',
imageUrl: grid1,
title: "星海扬帆",
},
{
id:'5',
status: '0',
imageUrl: grid2,
title: "防空报警试鸣",
},
{id:'6',
status: '2',
imageUrl: grid3,
title: "泉城广场",
},
{id:'7',
status: '1',
imageUrl: grid4,
title: "防空紧急疏散",
},
]);
//
const statusMap = {
'0': '预告',
'1': '直播中',
'2': '精彩回顾',
};
//
const getStatusText = (status) => statusMap[status] || '';
// CSS
const getStatusClass = (status) => statusMap[status] || '';
const goDetail = (item) => {
console.log(item)
router.push({
name: 'Detail',
query: { item: item.id }
})
}
</script>
<style lang="scss" scoped>
.swiper {
width: 100%;
height: 250px;
margin-left: auto;
margin-right: auto;
}
.swiper-slide {
text-align: center;
font-size: 18px;
background: #fff;
height: 204px;
display: flex;
flex-direction: column;
border-radius: 10px;
img {
width: 100%;
height: 92px;
}
.tri{
position: relative;
.title {
text-align: left;
height: 20px;
width: 100%;
font-size: 16px;
}
}
.status{
top:5px;
left:5px;
position: absolute;
color:#fff;
}
}
</style>

View File

@ -0,0 +1,146 @@
<template>
<swiper
:slidesPerView="'auto'"
:spaceBetween="10"
:pagination="{
clickable: true,
}"
class="mySwiper"
>
<swiper-slide v-for="(item, index) in slides" :key="index">
<div class="tri">
<img :src="item.imageUrl" alt="" @click="goDetail(item)" />
<p class="title">{{ item.title }}</p>
</div>
<p
class="status"
:class="{
live_status: getStatusClass(item.status) === '直播中',
live_coming: getStatusClass(item.status) === '预告',
live_review: getStatusClass(item.status) === '精彩回顾',
}"
>
{{ getStatusText(item.status) }}
</p>
</swiper-slide></swiper
>
</template>
<script setup>
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from "swiper/vue";
// Import Swiper styles
import "swiper/css";
import "swiper/css/pagination";
import { ref } from "vue";
import grid1 from "../../assets/img/slide1.png";
import grid2 from "../../assets/img/slide2.png";
import grid3 from "../../assets/img/slide1.png";
import grid4 from "../../assets/img/slide2.png";
import {useRouter} from 'vue-router';
const router = useRouter();
const slides = ref([
{
id: "0",
status: "0",
imageUrl: grid1,
title: "星海扬帆",
},
{
id: "1",
status: "1",
imageUrl: grid2,
title: "防空报警试鸣",
},
{
id: "2",
status: "2",
imageUrl: grid3,
title: "泉城广场",
},
{ id: "3", status: "0", imageUrl: grid4, title: "防空紧急疏散" },
{ id: "4", status: "1", imageUrl: grid1, title: "星海扬帆" },
{
id: "5",
status: "0",
imageUrl: grid2,
title: "防空报警试鸣",
},
{ id: "6", status: "2", imageUrl: grid3, title: "泉城广场" },
{ id: "7", status: "1", imageUrl: grid4, title: "防空紧急疏散" },
]);
//
const statusMap = {
0: "直播中",
1: "预告",
2: "精彩回顾",
};
//
const getStatusText = (status) => statusMap[status] || "";
// CSS
const getStatusClass = (status) => statusMap[status] || "";
const goDetail = (item) => {
console.log(item)
router.push({
name: 'Detail',
query: { item: item.id }
})
}
</script>
<style lang="scss" scoped>
.swiper {
width: 100%;
height: 315px;
}
.swiper-slide {
text-align: center;
height: 286px;
font-size: 18px;
background: #fff;
display: flex;
flex-direction: column;
border-radius: 10px;
}
.swiper-slide img {
width: 100%;
height: 286px;
object-fit: cover;
border-radius: 10px;
}
.tri {
height: auto;
position: relative;
.title {
text-align: left;
height: 20px;
width: 100%;
font-size: 16px;
}
}
.status {
top: 5px;
left: 5px;
position: absolute;
color: #fff;
}
.swiper-slide {
width: 45%;
}
.swiper-slide:nth-child(2n) {
width: 45%;
}
.swiper-slide:nth-child(3n) {
width: 45%;
}
</style>

View File

@ -0,0 +1,67 @@
<template>
<van-swipe :autoplay="3000" lazy-render>
<van-swipe-item class="van-swipe-item" v-for="(item,index) in images" :key="index">
<img :src="item.image" @click="goDetail(item)" />
<p class="status" :class="{ 'live_status': getStatusClass(item.status) === '直播中', 'live_coming': getStatusClass(item.status) === '预告', 'live_review': getStatusClass(item.status) === '精彩回顾' }">{{ getStatusText(item.status) }}</p>
</van-swipe-item>
</van-swipe>
</template>
<script setup>
import pic from '../../assets/img/top1.png'
import {ref} from 'vue';
import {useRouter} from 'vue-router';
const router = useRouter();
const images = ref([
{
id:'0',
status: '0',
image:pic
},{
id:'1',
status: '1',
image:pic
},{
id:'2',
status: '2',
image:pic
},
])
//
const statusMap = {
'0': '预告',
'1': '直播中',
'2': '精彩回顾',
};
//
const getStatusText = (status) => statusMap[status] || '';
// CSS
const getStatusClass = (status) => statusMap[status] || '';
const goDetail = (item) => {
console.log(item)
router.push({
name: 'Detail',
query: { item: item.id }
})
}
</script>
<style lang="scss" scoped>
.van-swipe-item {
width: 100%;
height: 212px;
position: relative;
img {
width: 100%;
height: 100%;
}
.status{
top:10px;
left:25px;
position: absolute;
color:#fff;
}
}
</style>

View File

@ -0,0 +1,152 @@
<template>
<swiper
:slidesPerView="'auto'"
:spaceBetween="10"
:pagination="{
clickable: true,
}"
class="mySwiper"
>
<swiper-slide v-for="(item, index) in slides" :key="index">
<div class="tri">
<img :src="item.imageUrl" alt="" @click="goDetail(item)" />
<p class="title">{{ item.title }}</p>
</div>
<p class="status" :class="{ 'live_status': getStatusClass(item.status) === '直播中', 'live_coming': getStatusClass(item.status) === '预告', 'live_review': getStatusClass(item.status) === '精彩回顾' }">{{ getStatusText(item.status) }}</p>
</swiper-slide> </swiper>
</template>
<script setup>
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from 'swiper/vue';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/pagination';
import {ref} from 'vue';
import grid1 from '../../assets/img/big1.png';
import grid2 from '../../assets/img/grid2.png';
import grid3 from '../../assets/img/grid3.png';
import grid4 from '../../assets/img/grid4.png';
import {useRouter} from 'vue-router';
const router = useRouter();
const slides = ref([
{
id:'0',
status: '0',
imageUrl: grid1,
title: "星海扬帆",
},
{
id:'1',
status: '1',
imageUrl: grid2,
title: "防空报警试鸣",
},
{
id:'2',
status: '2',
imageUrl: grid3,
title: "泉城广场",
},
{id:'3',
status: '0',
imageUrl: grid4,
title: "防空紧急疏散",
},
{id:'4',
status: '1',
imageUrl: grid1,
title: "星海扬帆",
},
{
id:'5',
status: '0',
imageUrl: grid2,
title: "防空报警试鸣",
},
{id:'6',
status: '2',
imageUrl: grid3,
title: "泉城广场",
},
{id:'7',
status: '1',
imageUrl: grid4,
title: "防空紧急疏散",
},
]);
//
const statusMap = {
'0': '预告',
'1': '直播中',
'2': '精彩回顾',
};
//
const getStatusText = (status) => statusMap[status] || '';
// CSS
const getStatusClass = (status) => statusMap[status] || '';
const goDetail = (item) => {
console.log(item)
router.push({
name: 'Detail',
query: { item: item.id }
})
}
</script>
<style lang="scss" scoped>
.swiper {
width: 100%;
height: 220px;
}
.swiper-slide {
text-align: center;
font-size: 18px;
background: #fff;
display: flex;
flex-direction: column;
img {
width: 100%;
height: 195px;
object-fit: cover;
}
.tri{
position: relative;
.title {
text-align: left;
height: 20px;
width: 100%;
font-size: 16px;
padding-left:20px;
}
}
.status{
top:10px;
left:25px;
position: absolute;
color:#fff;
}
}
.swiper-slide {
width: 85%;
}
.swiper-slide:nth-child(2n) {
width: 85%;
}
.swiper-slide:nth-child(3n) {
width: 85%;
}
</style>

View File

@ -0,0 +1,152 @@
<template>
<swiper
:slidesPerView="'auto'"
:spaceBetween="10"
:pagination="{
clickable: true,
}"
class="mySwiper"
>
<swiper-slide v-for="(item, index) in smallSlider" :key="index">
<div class="tri">
<img :src="item.imageUrl" alt="" @click="goDetail(item)" />
<p class="title">{{ item.title }}</p>
</div>
<p class="status" :class="{ 'live_status': getStatusClass(item.status) === '直播中', 'live_coming': getStatusClass(item.status) === '预告', 'live_review': getStatusClass(item.status) === '精彩回顾' }">{{ getStatusText(item.status) }}</p>
</swiper-slide>
</swiper>
</template>
<script setup>
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from "swiper/vue";
// Import Swiper styles
import "swiper/css";
import "swiper/css/pagination";
import { ref } from "vue";
import grid1 from "../../assets/img/grid1.png";
import grid2 from "../../assets/img/grid2.png";
import grid3 from "../../assets/img/grid3.png";
import grid4 from "../../assets/img/grid4.png";
import {useRouter} from 'vue-router';
const router = useRouter();
const smallSlider = ref([
{
id:'0',
status: '0',
imageUrl: grid1,
title: "星海扬帆",
},
{
id:'1',
status: '1',
imageUrl: grid2,
title: "防空报警试鸣",
},
{
id:'2',
status: '2',
imageUrl: grid3,
title: "泉城广场",
},
{id:'3',
status: '0',
imageUrl: grid4,
title: "防空紧急疏散",
},
{id:'4',
status: '1',
imageUrl: grid1,
title: "星海扬帆",
},
{
id:'5',
status: '0',
imageUrl: grid2,
title: "防空报警试鸣",
},
{id:'6',
status: '2',
imageUrl: grid3,
title: "泉城广场",
},
{id:'7',
status: '1',
imageUrl: grid4,
title: "防空紧急疏散",
},
]);
//
const statusMap = {
'0': '预告',
'1': '直播中',
'2': '精彩回顾',
};
//
const getStatusText = (status) => statusMap[status] || '';
// CSS
const getStatusClass = (status) => statusMap[status] || '';
const goDetail = (item) => {
console.log(item)
router.push({
name: 'Detail',
query: { item: item.id }
})
}
</script>
<style lang="scss" scoped>
.swiper {
width: 100%;
height:120px;
}
.swiper-slide {
text-align: center;
font-size: 18px;
background: #fff;
height: 92px;
display: flex;
flex-direction: column;
img {
width: 100%;
height: 92px;
object-fit: cover;
}
.tri{
position: relative;
.title {
text-align: left;
height: 20px;
width: 100%;
font-size: 16px;
}
}
.status{
top:5px;
left:5px;
position: absolute;
color:#fff;
}
}
.swiper-slide {
width: 45%;
}
.swiper-slide:nth-child(2n) {
width: 45%;
}
.swiper-slide:nth-child(3n) {
width: 45%;
}
</style>

26
src/components/title.vue Normal file
View File

@ -0,0 +1,26 @@
<template>
<div class="title">
<p>直播推荐热门精选</p>
<img src="../assets/img/arraw.svg" alt="" @click="goToMore"/>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const goToMore=()=>{
console.log('跳转到更多直播')
router.push({
name:'More'
})
}
</script>
<style lang="scss" scoped>
.title{
display: flex;
justify-content: space-between;
align-items: center;
padding:10px 0;
font-size: 16px;
}
</style>

14
src/main.js Normal file
View File

@ -0,0 +1,14 @@
import { createApp } from 'vue'
import './assets/css/index.scss'
import App from './App.vue'
// 导入上面新建的路由文件
import router from './router/index'
let app = createApp(App)
app.use(router)
app.mount('#app')

24
src/router/index.js Normal file
View File

@ -0,0 +1,24 @@
import { createWebHashHistory, createRouter } from 'vue-router'
//createWebHashHistory
import HomeView from '../views/home.vue'
import SearchView from '../views/searchVue.vue'
import MoreView from '../views/more.vue'
import DetailView from '../views/detailVue.vue'
const routes = [
{ path: '/', redirect:'/home' },
{ path: '/home', name:'Home', component: HomeView,
// children:[
// { path: '/search', name:'Search', component: SearchView ,props: true },
// ]
},
{ path: '/search', name:'Search', component: SearchView },
{ path: '/more', name:'More', component: MoreView },
{ path: '/detail', name:'Detail', component: DetailView },
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router;

263
src/utils/app.js Normal file
View File

@ -0,0 +1,263 @@
// 暴露接口给原生用
var hybrid = {
};
window.Hybrid = hybrid;
if (window.Vue) {
window.Vue.use(hybrid);
}
function deviceInfo() {
var userAgentInfo = navigator.userAgent;
var Agents = ['Android', 'iPhone',
'SymbianOS', 'Windows Phone',
'iPad', 'iPod'
];
// console.log('%c 获取型号', 'color:blue', userAgentInfo);
for (var v = 0; v < Agents.length; v++) {
if (userAgentInfo.indexOf(Agents[v]) > 0) {
console.log('%c 获取型号', 'color:orange', Agents[v]);
return Agents[v];
}
}
}
function urlParse(url) {
let arr = url.split("?");
console.log('%c arrrrr---', 'color:blue', arr);
if (arr.length < 2) return false
let str = arr[1]; //获取参数
let items = str.split("&"); //a = xxx,b = xx
let obj = {};
obj.baseUrl = arr[0] + '?';
for (let i = 0; i < items.length; i++) {
let tempArr = items[i].split("=");
console.log('%c 测试---->', 'color:blue', tempArr);
if (tempArr[0] === 'lhs_end_human_s_8') {
tempArr[0] = 'lhs_vodend_human_s_8';
}
obj[tempArr[0]] = tempArr[1];
obj.baseUrl = obj.baseUrl + '&' + tempArr[0] + '=' + tempArr[1]
}
if (!obj.aliyunols && obj.aliyunols !== 'on') return false
obj.lhs_start_human_s_8 = dateParse(obj.lhs_start_human_s_8);
obj.lhs_vodend_human_s_8 = dateParse(obj.lhs_vodend_human_s_8);
console.log('%c 解析url', 'color:blue', '↓↓↓↓↓', obj.baseUrl);
console.table(obj);
return obj
}
// 20200219105943 ---> 转换为 2020-02-19 10:59:43
function dateParse(str) {
let arr = [];
let date = '';
arr = str.replace(/\d(?=(\d{2})+$)/g, "$&,").split(',');
console.log('%c date...', 'color:blue', arr);
date = arr[0] + arr[1] + '/' + arr[2] + '/' + arr[3] + ' ' + arr[4] + ':' + arr[5] + ':' + arr[6];
console.log('%c date...', 'color:blue', date);
return date
}
function BrowserType() {
// 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
const ua = navigator.userAgent.toLowerCase();
const testUa = regexp => regexp.test(ua);
const testVs = regexp => ua.match(regexp)
.toString()
.replace(/[^0-9|_.]/g, "")
.replace(/_/g, ".");
// 系统
let system = "unknow";
if (testUa(/windows|win32|win64|wow32|wow64/g)) {
system = "windows"; // windows系统
} else if (testUa(/macintosh|macintel/g)) {
system = "macos"; // macos系统
} else if (testUa(/x11/g)) {
system = "linux"; // linux系统
} else if (testUa(/android|adr/g)) {
system = "android"; // android系统
} else if (testUa(/ios|iphone|ipad|ipod|iwatch/g)) {
system = "ios"; // ios系统
}
// 系统版本
let systemVs = "unknow";
if (system === "windows") {
if (testUa(/windows nt 5.0|windows 2000/g)) {
systemVs = "2000";
} else if (testUa(/windows nt 5.1|windows xp/g)) {
systemVs = "xp";
} else if (testUa(/windows nt 5.2|windows 2003/g)) {
systemVs = "2003";
} else if (testUa(/windows nt 6.0|windows vista/g)) {
systemVs = "vista";
} else if (testUa(/windows nt 6.1|windows 7/g)) {
systemVs = "7";
} else if (testUa(/windows nt 6.2|windows 8/g)) {
systemVs = "8";
} else if (testUa(/windows nt 6.3|windows 8.1/g)) {
systemVs = "8.1";
} else if (testUa(/windows nt 10.0|windows 10/g)) {
systemVs = "10";
}
} else if (system === "macos") {
systemVs = testVs(/os x [\d._]+/g);
} else if (system === "android") {
systemVs = testVs(/android [\d._]+/g);
} else if (system === "ios") {
systemVs = testVs(/os [\d._]+/g);
}
// 平台
let platform = "unknow";
if (system === "windows" || system === "macos" || system === "linux") {
platform = "desktop"; // 桌面端
} else if (system === "android" || system === "ios" || testUa(/mobile/g)) {
platform = "mobile"; // 移动端
}
// 内核和载体
let engine = "unknow";
let supporter = "unknow";
if (testUa(/applewebkit/g)) {
engine = "webkit"; // webkit内核
if (testUa(/edge/g)) {
supporter = "edge"; // edge浏览器
} else if (testUa(/opr/g)) {
supporter = "opera"; // opera浏览器
} else if (testUa(/chrome/g)) {
supporter = "chrome"; // chrome浏览器
} else if (testUa(/safari/g)) {
supporter = "safari"; // safari浏览器
}
} else if (testUa(/gecko/g) && testUa(/firefox/g)) {
engine = "gecko"; // gecko内核
supporter = "firefox"; // firefox浏览器
} else if (testUa(/presto/g)) {
engine = "presto"; // presto内核
supporter = "opera"; // opera浏览器
} else if (testUa(/trident|compatible|msie/g)) {
engine = "trident"; // trident内核
supporter = "iexplore"; // iexplore浏览器
}
// 内核版本
let engineVs = "unknow";
if (engine === "webkit") {
engineVs = testVs(/applewebkit\/[\d._]+/g);
} else if (engine === "gecko") {
engineVs = testVs(/gecko\/[\d._]+/g);
} else if (engine === "presto") {
engineVs = testVs(/presto\/[\d._]+/g);
} else if (engine === "trident") {
engineVs = testVs(/trident\/[\d._]+/g);
}
// 载体版本
let supporterVs = "unknow";
if (supporter === "chrome") {
supporterVs = testVs(/chrome\/[\d._]+/g);
} else if (supporter === "safari") {
supporterVs = testVs(/version\/[\d._]+/g);
} else if (supporter === "firefox") {
supporterVs = testVs(/firefox\/[\d._]+/g);
} else if (supporter === "opera") {
supporterVs = testVs(/opr\/[\d._]+/g);
} else if (supporter === "iexplore") {
supporterVs = testVs(/(msie [\d._]+)|(rv:[\d._]+)/g);
} else if (supporter === "edge") {
supporterVs = testVs(/edge\/[\d._]+/g);
}
// 外壳和外壳版本
let shell = "none";
let shellVs = "unknow";
if (testUa(/micromessenger/g)) {
shell = "wechat"; // 微信浏览器
shellVs = testVs(/micromessenger\/[\d._]+/g);
} else if (testUa(/qqbrowser/g)) {
shell = "qq"; // QQ浏览器
shellVs = testVs(/qqbrowser\/[\d._]+/g);
} else if (testUa(/ucbrowser/g)) {
shell = "uc"; // UC浏览器
shellVs = testVs(/ucbrowser\/[\d._]+/g);
} else if (testUa(/qihu 360se/g)) {
shell = "360"; // 360浏览器(无版本)
} else if (testUa(/2345explorer/g)) {
shell = "2345"; // 2345浏览器
shellVs = testVs(/2345explorer\/[\d._]+/g);
} else if (testUa(/metasr/g)) {
shell = "sougou"; // 搜狗浏览器(无版本)
} else if (testUa(/lbbrowser/g)) {
shell = "liebao"; // 猎豹浏览器(无版本)
} else if (testUa(/maxthon/g)) {
shell = "maxthon"; // 遨游浏览器
shellVs = testVs(/maxthon\/[\d._]+/g);
}
return Object.assign({
engine, // webkit gecko presto trident
engineVs,
platform, // desktop mobile
supporter, // chrome safari firefox opera iexplore edge
supporterVs,
system, // windows macos linux android ios
systemVs
}, shell === "none" ? {} : {
shell, // wechat qq uc 360 2345 sougou liebao maxthon
shellVs
});
}
// 对比获取当前时间
/* 计算时间差 */
function timeDif(nowTime, startLiveTime) {
console.log('%c 【倒计时时间计算-------↓↓↓↓↓↓】', 'color:blue');
// console.log("%c 当前时间", "color:blue", nowTime);
// console.log("%c 开播时间", "color:blue", startLiveTime);
startLiveTime = startLiveTime.replace(/-/g,'/')
let nowTimeNum = new Date(nowTime);
let startLiveTimeNum = new Date(startLiveTime);
let s1 = nowTimeNum.getTime(),
s2 = startLiveTimeNum.getTime();
let total = Math.floor((s2 - s1) / 1000);
// console.log('总秒数---', total)
console.log('%c 【倒计时时间计算-------↑↑↑↑↑↑】', 'color:blue');
return total;
}
function parsTime(total) {//出于性能考虑改为vue页面中使用
let day = parseInt(total / (24 * 60 * 60)); //计算整数天数
let afterDay = total - day * 24 * 60 * 60; //取得算出天数后剩余的秒数
let hour = parseInt(afterDay / (60 * 60)); //计算整数小时数
let afterHour = total - day * 24 * 60 * 60 - hour * 60 * 60; //取得算出小时数后剩余的秒数
let min = parseInt(afterHour / 60); //计算整数分
let afterMin = total - day * 24 * 60 * 60 - hour * 60 * 60 - min * 60; //取得算出分后剩余的秒数
let second = afterMin;
// console.log('%c 当前时间', 'color:red', '-----↓↓↓----');
// console.log('时间--->', '天: ' + day, ' 小时: ' + hour, ' 分钟 ' + min, ' 秒 ' + second);
}
export {
hybrid,
deviceInfo,
urlParse,
BrowserType,
timeDif,
parsTime
};

209
src/utils/canvas.js Normal file
View File

@ -0,0 +1,209 @@
function getRandom(min, max) {
return min + Math.floor(Math.random() * (max - min + 1));
}
export default class ThumbsUpAni {
constructor() {
this.loadImages(); // 预加载图片
// 读取 canvas
const canvas = document.getElementById('thumsCanvas');
this.context = canvas.getContext('2d');
this.width = canvas.width;
this.height = canvas.height;
this.imgsList = []; // 点赞图像列表
this.renderList = []; // 渲染对象雷彪
// scaleTime - 百分比。图片从开始放大到最终大小,所用时长。
// 设置为 0.1 ,表示总共运行时间前面的 10% 的时间,点赞图片逐步放大
this.scaleTime = 0.1;
this.scanning = false; // 扫描器扫描标识,防止开启多个扫描器
}
// 预加载图片,获取图片宽高,如果某一图片加载失败,则不显示该图片
loadImages() {
// const images = [
// 'jfs/t1/93992/8/9049/4680/5e0aea04Ec9dd2be8/608efd890fd61486.png',
// 'jfs/t1/108305/14/2849/4908/5e0aea04Efb54912c/bfa59f27e654e29c.png',
// 'jfs/t1/98805/29/8975/5106/5e0aea05Ed970e2b4/98803f8ad07147b9.png',
// 'jfs/t1/94291/26/9105/4344/5e0aea05Ed64b9187/5165fdf5621d5bbf.png',
// 'jfs/t1/102753/34/8504/5522/5e0aea05E0b9ef0b4/74a73178e31bd021.png',
// 'jfs/t1/102954/26/9241/5069/5e0aea05E7dde8bda/720fcec8bc5be9d4.png',
// ];
const images = [
'./bg1.png',
'./bg2.png',
'./bg3.png',
'./bg4.png',
'./bg5.png',
'./bg6.png',
];
const promiseAll = [];
images.forEach((src) => {
const p = new Promise(function(resolve) {
const img = new Image();
img.onerror = img.onload = resolve.bind(null, img);
// img.src = 'https://img12.360buyimg.com/img/' + src;
img.src = src;
console.log("src",img.src);
});
promiseAll.push(p);
});
Promise.all(promiseAll).then((imgsList) => {
this.imgsList = imgsList.filter((d) => {
if (d && d.width > 0) return true;
return false;
});
if (this.imgsList.length == 0) {
dLog('error', 'imgsList load all error');
return;
}
});
}
createRender() {
if (this.imgsList.length == 0) return null;
// 当运行时间 diffTime 小于设置的 scaleTime 的时候按比例随着时间增大scale 变大。超过设置的时间阈值,则返回最终大小。
const basicScale = [0.6, 0.9, 1.2][getRandom(0, 2)];
const getScale = (diffTime) => {
// diffTime - 百分比。表示从动画开始运行到当前时间过了多长时间。实际值是从 0 --> 1 逐步增大。
// scaleTime - 百分比。图片从开始放大到最终大小,所用时长。
if (diffTime < this.scaleTime) {
return +(diffTime / this.scaleTime).toFixed(2) * basicScale;
} else {
return basicScale;
}
};
const context = this.context;
// 随机读取一个图片,进行渲染
const image = this.imgsList[getRandom(0, this.imgsList.length - 1)];
const offset = 20; // x轴偏移量
const basicX = this.width / 2 + getRandom(-offset, offset);
const angle = getRandom(2, 10); // 角度系数
let ratio = getRandom(10, 30) * (getRandom(0, 1) ? 1 : -1);
// 随机平滑 X 轴偏移 - 通过正弦( Math.sin )函数来实现均匀曲线
const getTranslateX = (diffTime) => {
if (diffTime < this.scaleTime) {
// 放大期间,不进行摇摆位移
return basicX;
} else {
return basicX + ratio * Math.sin(angle * (diffTime - this.scaleTime));
}
};
// Y 轴偏移 - 运行偏移从 this.height --> image.height / 2 ,即从最底部,运行到顶部留下。
const getTranslateY = (diffTime) => {
return (
image.height / 2 + (this.height - image.height / 2) * (1 - diffTime)
);
};
// 淡出
const fadeOutStage = getRandom(14, 18) / 100;
const getAlpha = (diffTime) => {
let left = 1 - +diffTime;
if (left > fadeOutStage) {
return 1;
} else {
return 1 - +((fadeOutStage - left) / fadeOutStage).toFixed(2);
}
};
return (diffTime) => {
// diffTime : 百分比。表示从动画开始运行到当前时间过了多长时间。实际值是从 0 --> 1 逐步增大。
// diffTime 为 0.4 的时候,说明是已经运行了 40% 的时间
// 时间差值满了动画结束了0 --> 1
if (diffTime >= 1) return true;
context.save();
const scale = getScale(diffTime);
// const rotate = getRotate();
const translateX = getTranslateX(diffTime);
const translateY = getTranslateY(diffTime);
context.translate(translateX, translateY); // 偏移
context.scale(scale, scale); // 缩放
// context.rotate(rotate * Math.PI / 180);
context.globalAlpha = getAlpha(diffTime); // 淡出
// 绘制
context.drawImage(
image,
-image.width / 2,
-image.height / 2,
image.width,
image.height
);
context.restore(); // 恢复画布(canvas)状态。
};
}
// 实时绘制扫描器
// 开启实时绘制扫描器,将创建的渲染对象放入 renderList 数组,数组不为空,说明 canvas 上还有动画,就需要不停的去执行 scan直到 canvas 上没有动画结束为止。
scan() {
this.context.clearRect(0, 0, this.width, this.height);
this.context.fillStyle = 'transparent';
this.context.fillRect(0, 0, 200, 400);
let index = 0;
let length = this.renderList.length;
if (length > 0) {
requestFrame(this.scan.bind(this));
this.scanning = true;
} else {
this.scanning = false;
}
// diffTime = (Date.now() - render.timestamp) / render.duration
// 如果开始的时间戳是 10000当前是100100则说明已经运行了 100 毫秒了,如果动画本来需要执行 1000 毫秒,那么 diffTime = 0.1,代表动画已经运行了 10%。
while (index < length) {
const child = this.renderList[index];
if (
!child ||
!child.render ||
child.render.call(null, (Date.now() - child.timestamp) / child.duration)
) {
// 动画结束,则删除该动画
this.renderList.splice(index, 1);
length--;
} else {
// 继续执行动画
index++;
}
}
}
// 开始/增加动画
// 调用一次 start 方法来生成渲染实例,放进渲染实例数组。
// 如果当前扫描器未开启,则需要启动扫描器,使用了 scanning 变量,防止开启多个扫描器。
start() {
const render = this.createRender();
const duration = getRandom(1500, 3000);
this.renderList.push({
render,
duration,
timestamp: Date.now(),
});
if (!this.scanning) {
this.scanning = true;
requestFrame(this.scan.bind(this));
}
return this;
}
}
function requestFrame(cb) {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
}
)(cb);
}

64
src/utils/conf copy.js Normal file
View File

@ -0,0 +1,64 @@
export const conf = {
/**
* 基础功能配置
* */
id: 'xgPlayerWrap', // 占位dom元素
width: 375, height: 211, // 视频宽高尺寸
url: 'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4', // 视频源
poster: "http://ashuai.work/static/img/avantar.png", // 视频封面
autoplay: false, // 是否自动播放,不自动播放,浏览器有限制规则
autoplayMuted: false, // 是否自动播放(静音播放)
videoInit: true, // 是否默认初始化video默认初始化默认true
playsinline: true, // 是否启用内联播放模式,仅移动端生效
defaultPlaybackRate: 1, // 默认播放速度可选0.5/0.75/1/1.5/2等
volume: 0.72, // 播放音量可选0 ~ 1
loop: false, // 是否循环播放,默认不循环播放
startTime: 0, // 点播模式下,初始起播时间
videoAttributes: {}, // video扩展属性暂且不配置
lang: 'zh-cn', // 播放器初始显示语言,设置为中文
fluid: true, // 是否流式布局宽高优先于流失布局默认16:9注掉上方宽高看效果
fitVideoSize: 'fixed', // 保持容器宽/高,不做适配,按照容器来
videoFillMode: 'auto', // 宽高不够自动底色填充fill拉伸填充等...
seekedStatus: 'play', // 跳转后继续播放
// 播放器进度条故事点信息数组
progressDot: [
{
id: 0, // 唯一标识,用于删除的时候索引
time: 30, // 展示的时间点例子为在播放到10s钟的时候展示
text: '进度条信息提示...', // hover的时候展示文案可以为空
duration: 5, // 展示时间跨度单位为s
style: { // 指定样式
backgroundColor: '#fff'
}
},
],
thumbnail: null, // 进度条预览图配置,普通业务用不到
marginControls: false, // 是否开启画面和控制栏分离模式,不开启空间多一些
domEventType: 'default', // 响应的事件类型,不用指定,用默认的即可
/**
* 交互功能配置一般使用默认即可
* */
/**
* 插件配置根据需求自选
* */
icons: {}, // 使用默认的icon图标
i18n: [], // 使用默认的中文
// 自定义一些颜色
commonStyle: {
progressColor: '#fff', // 整个进度条颜色
playedColor: 'red', // 已播放的进度条颜色
volumeColor: '#fff', // 音量大小竖向滑块颜色
},
controls: true, // 是否使用底部控制栏,默认使用
miniprogress: false, // 是否使用mini进度条当底部控制栏隐藏时生效
screenShot: false, // 关闭截图功能
rotate: false, // 是否使用视频旋转插件,默认不使用
download: false, // 是否使用下载按钮,一般不用,一般自定义控制
pip: false, // 使用使用画中画模式,默认不用
mini: false, // 是否使用小屏幕控件
cssFullscreen: true, // 是否使用网页样式全屏按钮开关
playbackRate: [0.5, 1, 1.5, 2, 3], //传入倍速可选数组
playbackRate: false, //false禁用倍速播放即控制栏不显示
keyShortcut: false, // 是否开启快捷键模式
}

84
src/utils/conf.js Normal file
View File

@ -0,0 +1,84 @@
import layout from './layout';
export default class VideoPlayer {
constructor(props) {
this.player;
this.props = props;
this.props.isLive = false;
this._setup();
this._bindEvent();
}
loadByUrl(url) {
if (this.player) this.player.loadByUrl(url);
}
dispose() {
console.log('Setting up the player with props:', this.props);
if (this.player) {
this.player.dispose();
const container = document.getElementById(this.props.id);
if (container) {
container.innerHTML = ''; // 使用原生 JavaScript 清空 DOM 内容
}
}
}
_setup() {
this.player = new Aliplayer(this.props, function(player) {
console.log("player-----------------",player);
player._switchLevel = 0;
});
}
_bindEvent() {
this.player.on('ready', (e) => {
console.log('ready');
});
this.player.on('play', (e) => {
console.log('play');
});
this.player.on('ended', (e) => {
console.log('ended');
});
this.player.on('pause', (e) => {
console.log('pause');
});
this.player.on('requestFullScreen', (e) => {
layout.adjustLayout(true);
// Attempt to exit fullscreen using the appropriate method
if (this.player.exitFullscreen) {
this.player.exitFullscreen();
} else {
console.warn('exitFullscreen method is not available.');
}
});
// Handle the exit fullscreen event
const videoElement = this.player.el();
videoElement.addEventListener("webkitfullscreenchange", () => {
if (document.webkitIsFullScreen === false) {
if (WeixinJSBridge) WeixinJSBridge.call('closeWindow');
}
});
// Ensure autoplay on iOS
document.addEventListener('WeixinJSBridgeReady', () => {
const video = videoElement.querySelector('video');
if (video) {
video.play();
}
});
}
_unbindEvent() {
this.player.off('ready');
this.player.off('play');
this.player.off('ended');
this.player.off('pause');
}
}

25
src/utils/layout.js Normal file
View File

@ -0,0 +1,25 @@
import util from './util';
export default class Layout {
static adjustLayout(excludeInputHeight = false) {
const height = util.screenHeight(); // 确保调用的是正确的方法
const commentTextbox = document.querySelector('.comment-textbox');
if (commentTextbox) {
const inputHeight = commentTextbox.offsetHeight + 18;
commentTextbox.style.top = `${height - (excludeInputHeight ? (inputHeight * -1) + 5 : inputHeight)}px`;
}
const commentList = document.querySelector('.comment-list');
if (commentList) {
const commentListHeight = commentList.offsetHeight;
commentList.style.top = `${height - commentListHeight - (commentTextbox ? commentTextbox.offsetHeight + 18 : 0)}px`;
}
const favorite = document.querySelector('.favorite-animation-container');
if (favorite) {
const favoriteHeight = favorite.offsetHeight;
favorite.style.top = `${height - favoriteHeight - (commentTextbox ? commentTextbox.offsetHeight + 18 : 0)}px`;
}
}
}

38
src/utils/request.js Normal file
View File

@ -0,0 +1,38 @@
import axios from 'axios';
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
const request = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL:import.meta.env.VITE_APP_BASE_URL,
// 超时
timeout: 10000,
});
// request拦截器
request.interceptors.request.use(
(config) => {
// 在请求发送之前做些什么
// 可以在请求头中添加token等信息
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// response拦截器
request.interceptors.response.use(
(response) => {
// 对响应数据做些什么
return response.data;
},
(error) => {
// 对响应错误做些什么
return Promise.reject(error);
}
);
export default request;

42
src/utils/util.js Normal file
View File

@ -0,0 +1,42 @@
export default class Util
{
static prefixedEvent(element, type, callback) {
let pfx = ["webkit", "moz", "MS", "o", ""];
for (var p = 0; p < pfx.length; p++) {
if (!pfx[p]) type = type.toLowerCase();
Util.addEvent(element, pfx[p] + type, callback);
}
}
static addEvent(ele, type, hander) {
if (ele.addEventListener) {
ele.addEventListener(type, hander, false);
}
if (ele.attachEvent) {
ele.attachEvent('on' + type, hander);
}
}
static screenHeight()
{
return document.body.clientHeight || document.documentElement.clientHeight || window.screen.height || window.innerHeight ;
}
static isX5()
{
let agent = navigator.userAgent
return (/micromessenger/i).test(agent) || (/qqbrowser/i).test(agent);
}
static encodeHtml (s) {
let REGX_HTML_ENCODE = /"|'|<|>|[\x00-\x20]|[\x7F-\xFF]|[\u0100-\u2700]/g
return (typeof s != "string") ? null :
s.replace(REGX_HTML_ENCODE,
function($0){
var c = $0.charCodeAt(0), r = ["&#"];
c = (c == 0x20) ? 0xA0 : c;
r.push(c); r.push(";");
return r.join("");
})
}
}

View File

@ -0,0 +1,169 @@
<template>
<div class="page">
<!-- <div class="page" v-wechat-title="$route.meta.title"> -->
<div class="top">
<div id="xgPlayerWrap"></div>
</div>
<countDown ref="coCountdown" :countdownTime="countdownTime"></countDown>
<van-tabs v-model:active="activeName" offset-top="0">
<!-- 动态显示简介和评论当countdownTime有值时 -->
<van-tab v-if="countdownTime > 0" title="简介" name="a">
<pageAbstract></pageAbstract>
</van-tab>
<van-tab v-if="countdownTime > 0" title="评论" name="c">
<pageComment />
</van-tab>
<!-- 当countdownTime无值时显示现场直播 -->
<template v-else>
<van-tab title="现场直播" name="b">
<pageReport />
</van-tab>
<van-tab title="评论" name="c">
<pageComment />
</van-tab>
</template>
</van-tabs>
<van-popup
v-model:show="showRight"
position="right"
:style="{ width: '70%', height: '100%' }"
>
<moreLiveVue />
</van-popup>
<van-floating-bubble
v-model:offset="offset"
class="van-floating-bubble"
@click="onClick"
>
<p class="bubble-content">更多直播</p>
</van-floating-bubble>
</div>
</template>
<script setup>
import countDown from "../components/countDown.vue";
import pageAbstract from "../components/pageAbstract.vue";
import pageReport from "../components/pageReport.vue";
import pageComment from "../components/pageComment.vue";
import moreLiveVue from "../components/moreLive.vue";
import { ref, onMounted } from "vue";
import Player, { Events } from "../xgplayer"; // 西
import "../xgplayer/dist/index.min.css"; // 西
import { scene } from "../api/index";
import { conf } from "../utils/conf"; // js
// conf poster
conf.poster =
"https://th.bing.com/th/id/R.9de53f9726576696b318a8d95c0946cb?rik=sWB3V9KSxHbThw&riu=http%3a%2f%2fpic.bizhi360.com%2fbbpic%2f95%2f9995_1.jpg&ehk=GcPUjJED69TBvg9XxQr2klzDzfRsQWhAfLKlIAUWHJQ%3d&risl=&pid=ImgRaw&r=0"; // URL
onMounted(() => {
init();
getSenceList();
});
let player = null; //
console.log("Events", Events);
const init = () => {
player = new Player({
...conf,
});
player.on(Events.PLAY, (ev) => {
console.log("-播放开始-", ev);
});
player.on(Events.PAUSE, (ev) => {
console.log("-播放结束-", ev);
});
player.on("loadedmetadata", (ev) => {
console.log("-媒体数据加载好了-", ev);
});
player.on(Events.SEEKED, (ev) => {
console.log("-跳着播放-", ev);
});
//
};
const showRight = ref(false);
const hasCountdown = ref(false); //
const countdownTime = ref(0);
const activeName = ref("b");
const offset = ref({ x: 375, y: 350 });
const onClick = () => {
showRight.value = true;
};
// sence
const getSenceList =()=>{
let params = {
id:2057
};
scene(params)
.then((res) => {
console.log("获取sence",res);
})
.catch((error) => {
console.error("登录接口请求失败", error);
});
}
</script>
<style>
.van-floating-bubble {
position: fixed;
left: 0;
top: 0;
right: 0 !important;
width: 50px;
height: 80px;
background: #eeeeee;
color: #fff;
border-radius: 10px;
z-index: 999;
}
.van-tabs__content {
width: 100%;
height: calc(100vh - 130px);
/* border:1px solid red; */
overflow-y: scroll;
}
</style>
<style lang="scss" scoped>
.bubble-content {
color: rgba(0, 0, 0, 80%);
position: relative;
width: 10px;
font-size: 14px;
line-height: 17px;
}
.bubble-content::before {
content: "";
width: 10px;
height: 30%;
background: url("../assets/img/jt_black.png") no-repeat;
background-size: 100% 100%;
left: -15px;
top: 30%;
position: absolute;
}
//
.top {
width: 100%;
// height: 211px;
// height: 211px;
// padding-top: 56.25%; /* 16:9 aspect ratio (9 / 16 * 100) */
// position: relative;
// overflow: hidden;
// border: 1px solid black;
box-sizing: border-box;
}
#xgPlayerWrap {
flex: auto;
}
#xgPlayerWrap video {
width: 100%;
object-fit: cover;
}
</style>

275
src/views/detailVue.vue Normal file
View File

@ -0,0 +1,275 @@
<template>
<div class="page">
<!-- <div class="page" v-wechat-title="$route.meta.title"> -->
<div class="top">
<div
class="prism-player"
id="J_prismPlayer"
style="width: 100%; height: 100%"
></div>
</div>
<countDown ref="coCountdown" :countdownTime="countdownTime"></countDown>
<van-tabs v-model:active="activeName" offset-top="0">
<!-- 动态显示简介和评论当countdownTime有值时 -->
<van-tab v-if="countdownTime > 0" title="简介" name="a">
<pageAbstract></pageAbstract>
</van-tab>
<van-tab v-if="countdownTime > 0" title="评论" name="c">
<pageComment />
</van-tab>
<!-- 当countdownTime无值时显示现场直播 -->
<template v-else>
<van-tab title="现场直播" name="b">
<pageReport />
</van-tab>
<van-tab title="评论" name="c">
<pageComment />
</van-tab>
</template>
</van-tabs>
<van-popup
v-model:show="showRight"
position="right"
:style="{ width: '70%', height: '100%' }"
>
<moreLiveVue />
</van-popup>
</div>
<van-floating-bubble
v-model:offset="offset"
class="van-floating-bubble"
@click="onClick"
>
<p class="bubble-content">更多直播</p>
</van-floating-bubble>
</template>
<script setup>
import countDown from "../components/countDown.vue";
import pageAbstract from "../components/pageAbstract.vue";
import pageReport from "../components/pageReport.vue";
import pageComment from "../components/pageComment.vue";
import moreLiveVue from "../components/moreLive.vue";
import { ref, onMounted,onBeforeUnmount } from "vue";
import { scene } from "../api/index";
import VideoPlayer from "../utils/conf"; // js
onMounted(() => {
init();
getSenceList();
});
let player;
const init = () => {
const playerProps =
{
id: "J_prismPlayer",
cover:'https://th.bing.com/th/id/R.9de53f9726576696b318a8d95c0946cb?rik=sWB3V9KSxHbThw&riu=http%3a%2f%2fpic.bizhi360.com%2fbbpic%2f95%2f9995_1.jpg&ehk=GcPUjJED69TBvg9XxQr2klzDzfRsQWhAfLKlIAUWHJQ%3d&risl=&pid=ImgRaw&r=0',
source: "https://player.alicdn.com/resource/player/qupai.mp4",
width: "100%",
height: "500px",
autoplay: true,
isLive: false,
rePlay: false,
playsinline: true,
preload: 'auto',
useH5Prism: true,
useFlashPrism:false,
controlBarVisibility: "hover", //
controlBarVisibilityOnFs: "hover", // 使
showBarTime: 5000, //
useFlashPrism: false, // 使 HTML5 Flash
language: "zh-cn", //
x5_video_position:'normal',
x5_type:'h5',// video x5-video-player-type H5
skinLayout: [
// Specify the UI elements to show/hide
{
name: "bigPlayButton",
align: "blabs",
x: 30,
y: 80
},
{
name: "H5Loading",
align: "cc"
},
{
name: "errorDisplay",
align: "tlabs",
x: 0,
y: 0
},
{
name: "infoDisplay"
},
{
name: "tooltip",
align: "blabs",
x: 0,
y: 56
},
{
name: "thumbnail"
},
{
name: "controlBar",
align: "blabs",
x: 0,
y: 0,
children: [
{
name: "progress",
align: "blabs",
x: 10, //
y: 44,
},
{
name: "playButton",
align: "tl",
x: 15,
y: 12
},
{
name: "timeDisplay",
align: "tl",
x: 10,
y: 7
},
{
name: "fullScreenButton",
align: "tr",
x: 10,
y: 12
},
{
name: "volume",
align: "tr",
x: 10,
y: 10
},
]
}
]
};
player = new VideoPlayer(playerProps);
};
onBeforeUnmount(() => {
if (player) {
player.dispose();
}
});
const showRight = ref(false);
const hasCountdown = ref(false); //
const countdownTime = ref(0);
const activeName = ref("b");
const offset = ref({ x: '100%', y: '70%' });
const onClick = () => {
showRight.value = true;
};
// sence
const getSenceList = () => {
let params = {
id: 2057,
};
scene(params)
.then((res) => {
console.log("获取sence", res);
})
.catch((error) => {
console.error("登录接口请求失败", error);
});
};
</script>
<style>
/* AliPlayer */
.prism-player .prism-controlbar .prism-setting,
.prism-player .prism-controlbar .prism-subtitle {
display: none !important;
}
.prism-player .prism-ErrorMessage
{
/* top:20%; */
display: none !important;
}
.prism-player .prism-big-play-btn
{
top:50%!important;
left:50%!important;
transform:translate(-50%, -50%);
}
.prism-progress-cursor
{
margin-left:0px !important;
}
.prism-player video
{
object-fit: cover;
object-position: 0 0;
}
.van-floating-bubble {
position: fixed;
left: 0;
top: 0;
right: 0 !important;
transform: translate3d(325.812px, 50vh, 0px)!important;
width: 50px;
height: 80px;
background: #eeeeee;
color: #fff;
border-radius: 10px;
z-index: 999;
}
.van-tabs__content {
width: 100%;
height: calc(100vh - 130px);
/* border:1px solid red; */
overflow-y: scroll;
}
</style>
<style lang="scss" scoped>
.bubble-content {
color: rgba(0, 0, 0, 80%);
position: relative;
width: 10px;
font-size: 14px;
line-height: 17px;
}
.bubble-content::before {
content: "";
width: 10px;
height: 30%;
background: url("../assets/img/jt_black.png") no-repeat;
background-size: 100% 100%;
left: -15px;
top: 30%;
position: absolute;
}
//
.top {
width: 100%;
height: 211px;
box-sizing: border-box;
.prism-player {
width: 100%;
height: 56.26667vw;
}
}
</style>

162
src/views/home.vue Normal file
View File

@ -0,0 +1,162 @@
<template>
<div class="page">
<div class="homePage">
<headerVue></headerVue>
<div class="home_scroll">
<normalSwiper />
<div class="channel bg">
<titleVue />
<gridSwiper />
</div>
<div class="channel bg">
<titleVue />
<smallSlideSwiper />
</div>
<div class="bg">
<titleVue style="padding: 10px 20px" />
<slideSwiperVue />
</div>
<div class="channel bg">
<titleVue />
<gridSwiper />
</div>
<div class="bg">
<titleVue style="padding: 10px 20px" />
<slideSwiperVue />
</div>
<div class="channel bg">
<titleVue />
<longSlideSwiper />
</div>
<div class="channel bg">
<titleVue />
<smallSlideSwiper />
</div>
<div
class="bg normal_item"
v-for="(item, index) in simpleList"
:key="index"
>
<!-- <div class="tri"> -->
<img :src="item.img" @click="goDetail(item)" />
<p class="title">{{ item.title }}</p>
<!-- </div> -->
<p
class="status"
:class="{
live_status: getStatusClass(item.status) === '直播中',
live_coming: getStatusClass(item.status) === '预告',
live_review: getStatusClass(item.status) === '精彩回顾',
}"
>
{{ getStatusText(item.status) }}
</p>
</div>
<p class="more">点击加载更多</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import headerVue from "../components/header.vue";
import normalSwiper from "../components/swipers/normalSwiper.vue";
import gridSwiper from "../components/swipers/gridSwiper.vue";
import slideSwiperVue from "../components/swipers/slideSwiper.vue";
import smallSlideSwiper from "../components/swipers/smallSlideSwiper.vue";
import longSlideSwiper from "../components/swipers/longSlideSwiper.vue";
import titleVue from "../components/title.vue";
import grid1 from "../assets/img/top1.png";
import grid2 from "../assets/img/top1.png";
import grid3 from "../assets/img/top1.png";
import grid4 from "../assets/img/top1.png";
import {useRouter} from 'vue-router';
const router = useRouter();
const simpleList = ref([
{
id: "0",
status: "0",
img: grid1,
title: "防空警报",
},
{
id: "1",
status: "1",
img: grid2,
title: "防空警报",
},
{ id: "2", status: "2", img: grid3, title: "防空警报" },
{ id: "3", status: "1", img: grid4, title: "防空警报" },
]);
//
const statusMap = {
0: "直播中",
1: "预告",
2: "精彩回顾",
};
//
const getStatusText = (status) => statusMap[status] || "";
// CSS
const getStatusClass = (status) => statusMap[status] || "";
const goDetail = (item) => {
console.log(item)
router.push({
name: 'Detail',
query: { item: item.id }
})
}
</script>
<style lang="scss" scoped>
.homePage {
width: 100%;
// width:720px;
// margin: 0 auto;
height: auto;
background-color: #f5f6f8;
.home_scroll{
height:calc(100vh - 4rem);
overflow-y: scroll;
}
}
.bg {
margin-top: 10px;
padding:0 0 10px 0;
background: #fff;
font-size: 16px;
}
.normal_item {
width: 100%;
// height: 212px;
// padding: 10px 0;
position: relative;
img {
width: 100%;
height: 212px;
}
.title {
padding: 0 20px;
}
}
.status {
top:10px;
left:25px;
position: absolute;
color: #fff;
}
.channel {
padding: 0 20px;
}
.more {
width: 100%;
text-align: center;
padding: 10px;
color: gray;
}
</style>

13
src/views/more.vue Normal file
View File

@ -0,0 +1,13 @@
<template>
<contentList/>
</template>
<script setup>
import contentList from '../components/contentList.vue'
</script>
<style lang="scss" scoped>
.grid_list{
display: grid;
grid-template-columns: 1fr 1fr;
gap:20px;
}
</style>

416
src/views/searchVue.vue Normal file
View File

@ -0,0 +1,416 @@
<template>
<div class="page">
<div class="header">
<div class="search">
<van-search
v-model="value"
shape="round"
background="transparent"
placeholder="请输入搜索关键词"
:clearable="false"
:show-action="true"
>
<template #action>
<van-icon name="clear" class="search-clear-icon" @click="onClear" />
</template>
</van-search>
</div>
<p @click="onSearch">搜索</p>
</div>
<van-dropdown-menu ref="menuRef" >
<van-dropdown-item @click="onConfirm" v-model="isDropdownOpen" title="筛选" ref="itemRef">
<div class="choice_list">
<div class="tri">
<p>开播时间</p>
<div class="grid">
<button
class="grid_item"
@click.stop="selectTime('不限')"
:class="{ active: selectedTime === '不限' }"
>
不限
</button>
<button
class="grid_item"
@click.stop="selectTime('最近一天')"
:class="{ active: selectedTime === '最近一天' }"
>
最近一天
</button>
<button
class="grid_item"
@click.stop="selectTime('最近一周')"
:class="{ active: selectedTime === '最近一周' }"
>
最近一周
</button>
<button
class="grid_item"
@click.stop="selectTime('最近一月')"
:class="{ active: selectedTime === '最近一月' }"
>
最近一月
</button>
<button
@click.stop="selectTime('自定义')"
class="grid_item color1"
:class="{ active: selectedTime === '自定义' }"
>
自定义
</button>
</div>
</div>
<div class="tri">
<p>专题栏目</p>
<div class="grid">
<button
v-for="topic in topics"
:key="topic"
@click.stop="toggleTopic(topic)"
:class="{ active: selectedTopics.includes(topic) }"
class="grid_item"
>
{{ topic }}
</button>
</div>
</div>
</div>
</van-dropdown-item>
</van-dropdown-menu>
<contentList v-show="showSearchList"/>
<van-popup
v-model:show="showBottom"
position="bottom"
:style="{ height: '50%' }"
>
<van-picker-group
v-if="showPickerGroup"
:tabs="['开始日期', '结束日期']"
@confirm="timeConfirm"
@cancel="timeCancel"
:style="{ height: '100%' }"
>
<van-date-picker
v-model="startDate"
:min-date="minDate"
:max-date="maxDate"
/>
<van-date-picker
v-model="endDate"
:min-date="minDate"
:max-date="maxDate"
/>
</van-picker-group>
</van-popup>
<div class="hot_list" v-show="!showSearchList">
<div class="hot_title">
<img aria-hidden="true" alt="fire-icon" src="../assets/img/hot.svg" />
<p>热点榜</p>
</div>
<table cellspacing="0" cellpadding="">
<tr v-for="item in topList" :key="item.id">
<td class="order" :style="{ color: getColor(item.id) }">
{{ item.id }}
</td>
<td>{{ item.content }}</td>
<td>
<span :style="{ background: getBgColor(item.id) }" class="bg_color">{{
getTipText(item.id)
}}</span>
</td>
</tr>
</table>
</div>
</div>
</template>
<script setup>
import { ref,watch } from "vue";
import contentList from "../components/contentList.vue";
import { useRoute } from "vue-router";
const route = useRoute();
const menuRef = ref(null);
const itemRef = ref(null);
const value = ref(null);
const showSearchList = ref(false);
const onSearch = () => {
console.log("搜索",value.value);
showSearchList.value = true;
};
const onClear = () => {
value.value = ""; //
showSearchList.value = false; //
};
const onConfirm = () => {
if( itemRef.value.toggle(false)){
//
selectedTopics.value = [];
selectedTime.value = "";
};
};
const isDropdownOpen = ref(false); //
const showPickerGroup = ref(false);
const topics = ["不限", "推荐", "政务", "综合", "县区", "社会", "活动", "拍客"];
const selectedTopics = ref([]);
const timeOptions = ["不限", "最近一天", "最近一周", "最近一月", "自定义"];
const selectedTime = ref("");
const showBottom = ref(false);//
const selectTime = (time,event) => {
selectedTime.value = time;
if (time === "自定义") {
showBottom.value = true;
showPickerGroup.value = true;
}
};
const startDate = ref(["2024", "05", "01"]);
const endDate = ref(["2024", "06", "01"]);
const timeConfirm = (value) => {
//
console.log("确认的值:", value);
showPickerGroup.value = false; //
showBottom.value = false;
};
const timeCancel = () => {
//
showPickerGroup.value = false; //
showBottom.value = false;
};
const topList = ref([
{
id: 1,
content: "75秒看人民海军75年巨变",
},
{
id: 2,
content: "75秒看人民海军75年巨变",
},
{
id: 3,
content: "75秒看人民海军75年巨变",
},
{
id: 4,
content: "75秒看人民海军75年巨变",
},
{
id: 5,
content: "75秒看人民海军75年巨变",
},
{
id: 6,
content: "75秒看人民海军75年巨变",
},
{
id: 7,
content: "75秒看人民海军",
},
]);
const getColor = (index) => {
if (index === 1) {
return "#FF3433";
} else if (index === 2) {
return "#FF6606";
} else if (index === 3) {
return "#FCA714";
} else {
return "#B3B3B5";
}
};
const getTipText = (id) => {
switch (id) {
case 1:
return "热";
case 2:
return "新";
case 3:
return "荐";
default:
return "";
}
};
const getBgColor = (index) => {
if (index === 1) {
return `linear-gradient(to bottom, #EFBE26 0%, #F58C05 100%)`;
} else if (index === 2) {
return `linear-gradient(to bottom, #FF809B 0%, #F84670 100%)`;
} else if (index === 3) {
return `linear-gradient(to bottom, #15D7F1 0%, #07A1C2 100%)`;
} else {
return "transparent";
}
};
const toggleTopic = (topic) => {
if (selectedTopics.value.includes(topic)) {
selectedTopics.value = selectedTopics.value.filter((t) => t !== topic);
} else {
selectedTopics.value.push(topic);
}
};
watch([selectedTopics, selectedTime], () => {
//
// const filteredList = originalList.filter(item => {
// //
// const matchesTopic = selectedTopics.includes(item.topic);
// const matchesTime = item.time >= selectedTime.start && item.time <= selectedTime.end;
// return matchesTopic && matchesTime;
// });
showSearchList.value = true;
//
// if (filteredList.length > 0) {
// showSearchList.value = true;
// contentList.value = filteredList;
// } else {
// showSearchList.value = false;
// contentList.value = originalList; //
// }
});
//
const onSearchList = () => {
console.log(selectedTopics.value);
console.log(selectedTime.value);
itemRef.value.toggle(false); //
showPickerGroup.value = false; //
showBottom.value = false; //
};
</script>
<style lang="scss" scoped>
::v-deep(.van-dropdown-menu__title--active) {
color: #004098;
}
::v-deep(.van-dropdown-menu__item) {
display: flex;
flex: 1;
padding: 0 30px;
align-items: center;
justify-content: flex-end;
min-width: 0;
}
::v-deep(.van-dropdown-menu__bar) {
box-shadow: none;
border-bottom: 1px solid #f2f2f2;
}
::v-deep(.van-search__content){
background: #fff;
}
::v-deep(.van-search__action){
font-size: 18px;
transform: translateX(-30px);
color: #cccccc;
}
.header {
width: 100%;
display: flex;
align-items: center;
// height: 4rem;
background-color: #F6F6F6;
// background-color: red;
.search{
width: 85%;
}
p{
color: #676767;
font-size: 16px;
}
}
.choice_list {
width: 90%;
margin: 0 auto;
padding-bottom:10px;
height: auto;
.tri {
// padding: 10px;
display: flex;
flex-direction: column;
justify-content: space-between;
p {
padding: 10px 0;
}
// align-items: center;
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
color:#5B5B5B;
.grid_item {
padding: 10px 0;
border: 1px solid transparent;
border-radius: 10px;
}
.grid_item.active {
border: 1px solid #183b86 !important;
color: #183b86;
background: #e4ebf4;
}
.color1 {
color: #f43760;
}
}
}
.van_btn {
width: 100%;
margin: 20px 0;
border-radius: 50px;
background-color: #004098;
border: none;
}
}
.hot_list {
width: 90%;
margin: 10px auto;
.hot_title {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 5px;
margin-bottom: 10px;
img{
width:20px;
height: 20px;
}
p {
color: #f43760;
font-weight: bold;
font-size: 20px;
}
}
table {
width: 100%;
height: 100%;
}
td {
font-size: 16px;
color: #000000;
border-top: 1px solid #f2f2f2;
padding: 15px 5px;
// width:100%;
// height:100px;
}
.order {
font-weight: bolder;
font-size: 16px;
}
.bg_color {
color: #fff;
padding: 5px;
border-radius: 3px;
font-size: 12px;
}
}
</style>

53
vite.config.js Normal file
View File

@ -0,0 +1,53 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from '@vant/auto-import-resolver';
// 安装 postcss-pxtorem 和 autoprefixer
// npm install postcss-pxtorem --save
// npm i autoprefixer
import pxtoviewport from 'postcss-px-to-viewport';
import autoprefixer from 'autoprefixer';
// https://vitejs.dev/config/
export default defineConfig({
base:'./',
plugins: [
vue(),
AutoImport({
resolvers: [VantResolver()],
}),
Components({
resolvers: [VantResolver()],
}),
],
// css: {
// postcss: {
// plugins: [
// autoprefixer(),
// pxtoviewport({
// viewportWidth: 375,
// }),
// ],
// },
// },
server: {
host: "0.0.0.0",
port: 8123,
},
resolve: {
alias:{
// 配置src目录
"@": path.resolve(__dirname,"src"),
// 导入其他目录
"components": path.resolve(__dirname, "components")
}
},
})