Compare commits

...

26 Commits

Author SHA1 Message Date
pengxiaolong
9a34d0cea6 优化代码 2025-09-29 20:47:58 +08:00
pengxiaolong
aa74346232 优化代码 2025-08-29 13:31:08 +08:00
pengxiaolong
d294f62469 优化代码 2025-08-27 22:06:45 +08:00
pengxiaolong
50f3126fc1 优化代码 2025-08-26 22:04:33 +08:00
pengxiaolong
20cb7bc0d1 优化代码 2025-08-26 15:47:09 +08:00
pengxiaolong
2a5ae6c0cc 优化代码 2025-08-26 13:31:21 +08:00
pengxiaolong
cb34cd9673 优化代码 2025-08-21 22:07:46 +08:00
pengxiaolong
89014e306e 优化代码 2025-08-21 21:36:08 +08:00
pengxiaolong
5a39e461fe 优化代码 2025-08-21 18:11:18 +08:00
pengxiaolong
d902254bf6 优化代码 2025-08-21 14:57:27 +08:00
pengxiaolong
9c82553013 优化代码 2025-08-20 22:11:41 +08:00
pengxiaolong
ce21a633ec 优化代码 2025-08-15 22:11:55 +08:00
pengxiaolong
15335e8921 优化代码 2025-08-15 13:05:19 +08:00
pengxiaolong
784a19bdda 优化代码 2025-08-12 22:05:06 +08:00
pengxiaolong
b260caa2bd 优化代码 2025-08-05 22:07:07 +08:00
pengxiaolong
d9b2d496be 优化代码 2025-07-31 22:07:21 +08:00
pengxiaolong
60be63e2d2 创建仓库 2025-07-28 13:32:04 +08:00
pengxiaolong
8cabf98bd9 优化 2025-07-09 13:27:43 +08:00
pengxiaolong
51ce853bde 优化 2025-07-07 18:46:02 +08:00
pengxiaolong
a46cbf79f6 优化 2025-07-03 19:34:19 +08:00
pengxiaolong
52415dee0b 优化 2025-07-03 19:14:34 +08:00
pengxiaolong
01e9c26821 上传代码 2025-07-01 21:22:43 +08:00
560581f1a4 yolo 2025-06-24 13:35:33 +08:00
17d2251a70 添加 计时器功能 详细筛选功能 分配状态字段 2025-05-15 18:29:53 +08:00
dcd677bbab 近7日数据改为updata 2025-05-12 21:09:32 +08:00
60f6fc4873 版本号更新 2025-05-06 15:38:23 +08:00
94 changed files with 13233 additions and 2439 deletions

View File

@@ -1,5 +1,5 @@
# tk-page
备注前面带gj的是国家化文本
## Project setup
```
npm install

BIN
dist.rar

Binary file not shown.

88
package-lock.json generated
View File

@@ -8,13 +8,16 @@
"name": "tk-page",
"version": "0.1.0",
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.8.4",
"core-js": "^3.8.3",
"echarts": "^5.6.0",
"element-plus": "^2.9.7",
"pinia": "^3.0.1",
"element-plus": "^2.10.4",
"goeasy": "^2.13.21",
"pinia": "^3.0.3",
"qwebchannel": "^6.2.0",
"vue": "^3.2.13",
"vue-i18n": "^11.1.11",
"vue-router": "^4.0.3",
"vuex": "^4.0.0"
},
@@ -2871,6 +2874,50 @@
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@intlify/core-base": {
"version": "11.1.11",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.1.11.tgz",
"integrity": "sha512-1Z0N8jTfkcD2Luq9HNZt+GmjpFe4/4PpZF3AOzoO1u5PTtSuXZcfhwBatywbfE2ieB/B5QHIoOFmCXY2jqVKEQ==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "11.1.11",
"@intlify/shared": "11.1.11"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "11.1.11",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.1.11.tgz",
"integrity": "sha512-7PC6neomoc/z7a8JRjPBbu0T2TzR2MQuY5kn2e049MP7+o32Ve7O8husylkA7K9fQRe4iNXZWTPnDJ6vZdtS1Q==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "11.1.11",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "11.1.11",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.1.11.tgz",
"integrity": "sha512-RIBFTIqxZSsxUqlcyoR7iiC632bq7kkOwYvZlvcVObHfrF4NhuKc4FKvu8iPCrEO+e3XsY7/UVpfgzg+M7ETzA==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -6538,9 +6585,9 @@
"license": "ISC"
},
"node_modules/element-plus": {
"version": "2.9.7",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.9.7.tgz",
"integrity": "sha512-6vjZh5SXBncLhUwJGTVKS5oDljfgGMh6J4zVTeAZK3YdMUN76FgpvHkwwFXocpJpMbii6rDYU3sgie64FyPerQ==",
"version": "2.10.4",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.10.4.tgz",
"integrity": "sha512-UD4elWHrCnp1xlPhbXmVcaKFLCRaRAY6WWRwemGfGW3ceIjXm9fSYc9RNH3AiOEA6Ds1p9ZvhCs76CR9J8Vd+A==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
@@ -7375,6 +7422,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/goeasy": {
"version": "2.13.21",
"resolved": "https://registry.npmjs.org/goeasy/-/goeasy-2.13.21.tgz",
"integrity": "sha512-KxzYC9KRu02tt3Cv+9QHqP/yD6kl/kmI07UZw6BzRFjJUpNNuefZTD/bKC9a9nYbmLWYxYneoQqowHwl2GaEPw=="
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -9709,9 +9761,9 @@
}
},
"node_modules/pinia": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.1.tgz",
"integrity": "sha512-WXglsDzztOTH6IfcJ99ltYZin2mY8XZCXujkYWVIJlBjqsP6ST7zw+Aarh63E1cDVYeyUcPCxPHzJpEOmzB6Wg==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz",
"integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^7.7.2"
@@ -13292,6 +13344,26 @@
"dev": true,
"license": "MIT"
},
"node_modules/vue-i18n": {
"version": "11.1.11",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.1.11.tgz",
"integrity": "sha512-LvyteQoXeQiuILbzqv13LbyBna/TEv2Ha+4ZWK2AwGHUzZ8+IBaZS0TJkCgn5izSPLcgZwXy9yyTrewCb2u/MA==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "11.1.11",
"@intlify/shared": "11.1.11",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-loader": {
"version": "17.4.2",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz",

View File

@@ -7,13 +7,16 @@
"build": "vue-cli-service build"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.8.4",
"core-js": "^3.8.3",
"echarts": "^5.6.0",
"element-plus": "^2.9.7",
"pinia": "^3.0.1",
"element-plus": "^2.10.4",
"goeasy": "^2.13.21",
"pinia": "^3.0.3",
"qwebchannel": "^6.2.0",
"vue": "^3.2.13",
"vue-i18n": "^11.1.11",
"vue-router": "^4.0.3",
"vuex": "^4.0.0"
},

View File

@@ -1,8 +1,8 @@
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 1600, // 视窗的宽度,对应设计稿宽度
viewportHeight: 900, // 视窗的高度,对应设计稿高度
viewportWidth: 1920, // 视窗的宽度,对应设计稿宽度
viewportHeight: 1080, // 视窗的高度,对应设计稿高度
unitPrecision: 3, // 指定 px 转换为视窗单位值的小数位数
viewportUnit: 'vw', // 指定需要转换成的视窗单位vw 或者 vh
selectorBlackList: ['.ignore', '.hairlines'], // 指定不需要转换的类

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -9,7 +9,6 @@
<title>
<%= webpackConfig.name %>
</title>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
</head>
<body>

View File

@@ -6,22 +6,25 @@
<script>
const debounce = (fn, delay) => {
let timer
return (...args) => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn(...args)
}, delay)
}
}
const _ResizeObserver = window.ResizeObserver
window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
constructor(callback) {
callback = debounce(callback, 200)
super(callback)
}
}
</script>
// const debounce = (fn, delay) => {
// let timer
// return (...args) => {
// if (timer) {
// clearTimeout(timer)
// }
// timer = setTimeout(() => {
// fn(...args)
// }, delay)
// }
// }
// const _ResizeObserver = window.ResizeObserver
// window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
// constructor(callback) {
// callback = debounce(callback, 200)
// super(callback)
// }
// }
</script>
<style lang="less">
@import "@/static/css/app.less";
</style>

View File

@@ -1,47 +1,168 @@
import { getAxios, postAxios, downFile } from '@/utils/axios.js'
export function apiGetCart() {
return getAxios({ url: '/cgi-bin/cart/latest' })
import axios from 'axios'
import { ElMessage } from "element-plus";
//获取vx二维码
export function getVxQrcode() {
return getAxios({ url: 'user/qrcode' })
}
//获取扫码结果
export function getScanResult(data) {
return getAxios({ url: 'user/check/' + data})
}
//获取用户信息
export function getUserInfo(data) {
return postAxios({ url: 'user/getUserInfo',data})
}
//修改用户信息
export function editUserInfo(data) {
return postAxios({ url: 'user/updateUserInfo', data })
}
//检查用户名
export function checkUsername(data) {
return postAxios({ url: 'user/checkUserName', data })
}
//注册
export function register(data) {
return postAxios({ url: 'user/registerWithMail', data })
}
//查询验证状态
export function checkStatus(data) {
return postAxios({ url: 'user/getUserInfo', data })
}
//重发邮件
export function resendEmail(data) {
return postAxios({ url: 'user/resendMail', data })
}
//登录
export function login(data) {
return postAxios({ url: 'api/account/login', data })
return postAxios({ url: 'user/loginWithMail', data })
}
export function cheekalive(data) {
return postAxios({ url: 'api/account/cheekalive', data })
//获取pk列表
export function getPkList(data) {
return postAxios({ url: 'pk/pkList', data })
}
export function tkhostdata(data) {
return postAxios({ url: 'api/tkinfo/tkhostdata', data })
//站内信列表
export function getNoticeList(data) {
return postAxios({ url: 'systemMessage/list', data })
}
export function dicts(data) {
return postAxios({ url: 'api/param/dicts', data })
//主播库列表
export function getAnchorList(data) {
return postAxios({ url: 'anchor/list', data })
}
export function tkhostdetail(data) {
return postAxios({ url: 'api/tkinfo/tkhostdetail', data })
//主播库添加主播
export function addAnchor(data) {
return postAxios({ url: 'anchor/add', data })
}
//导出表格
export function exporthosts(data) {
return postAxios({ url: 'api/export/hostsinfo', data })
//主播库删除主播
export function delAnchor(data) {
return postAxios({ url: 'anchor/deleteMyAnchor', data })
}
//主播库修改主播
export function editAnchor(data) {
return postAxios({ url: 'anchor/updateAnchorInfo', data })
}
export function downList(url, data) {
return downFile(url, data)
//获取主播头像
export function getAnchorAvatar(data) {
const url = "https://python.yolojt.com/api/" + data.name
return new Promise((resolve, reject) => {
axios.get(
url
).then(res => {
if (res.code == 200) {
resolve(res.data)
}else {
reject(res);
}
}).catch(err => {
reject(err)
})
})
}
//查询tk账号查询次数
export function tkaccountuseinfo(data) {
return postAxios({ url: 'api/tkinfo/tkaccountuseinfo', data })
//获取PK信息
export function getPkInfo(data) {
return postAxios({ url: 'user/queryMyAllPkData', data })
}
//查询员工
export function getStaffList(data) {
return postAxios({ url: 'api/account/list', data })
//发布PK信息
export function releasePkInfo(data) {
return postAxios({ url: 'pk/addPkData', data })
}
//分配主播
export function managerhosts(data) {
return postAxios({ url: 'api/account/managerhosts', data })
//修改PK信息
export function editPkInfo(data) {
return postAxios({ url: 'pk/updatePkInfoById', data })
}
//编辑主播
export function upholdinfo(data) {
return postAxios({ url: 'api/tkinfo/upholdinfo', data })
//删除PK信息
export function delPkInfo(data) {
return postAxios({ url: 'pk/deletePkDataWithId', data })
}
//获取
export function getCountryinfo(data) {
return postAxios({ url: 'api/tkinfo/countryinfo', data })
//置顶PK信息
export function topPkInfo(data) {
return postAxios({ url: 'user/pinToTop', data })
}
//取消置顶PK信息
export function cancelTopPkInfo(data) {
return postAxios({ url: 'user/cancelPin', data })
}
//获取积分详情
export function getIntegralDetail(data) {
return postAxios({ url: 'user/pointsDetail', data })
}
//获取PK记录
export function getPkRecord(data) {
return postAxios({ url: 'user/handlePkInfo', data })
}
//签到
export function signIn(data) {
return postAxios({ url: 'user/signIn', data })
}
//修改邮箱
export function editEmail(data) {
return postAxios({ url: 'user/updateUserMail', data })
}
//获取OTP
export function getOtp() {
return getAxios({ url: 'otp/getotp' })
}
//根据用户id查询该用户已发布的未被邀请的主播列表
export function getAnchorListById(data) {
return postAxios({ url: 'pk/listUninvitedPublishedAnchorsByUserId', data })
}
//创建pk记录
export function createPkRecord(data) {
return postAxios({ url: 'pk/createPkRecord', data })
}
//查询pk记录信息
export function queryPkRecord(data) {
return postAxios({ url: 'pk/singleRecord', data })
}
//pk文章详情
export function pkArticleDetail(data) {
return postAxios({ url: 'pk/pkInfoDetail', data })
}
//更新pk记录状态(同意/拒绝)
export function updatePkRecordStatus(data) {
return postAxios({ url: 'pk/updatePkStatus', data })
}
//查询pk中每个场次的详细数据
export function queryPkDetail(data) {
return postAxios({ url: 'pk/fetchDetailPkDataWithId', data })
}
//退出登录
export function logout(data) {
return postAxios({ url: 'user/logout',data})
}
//忘记密码
export function forgetPassword(data) {
return postAxios({ url: 'user/forgetMail', data })
}
//重设密码
export function resetPassword(data) {
return postAxios({ url: 'user/resetPassword', data })
}
//激活邮箱
export function activeEmail(data) {
return getAxios({ url: 'user/activate?token=' + data})
}
//验证账号
export function checkAccount(data) {
return getAxios({ url: 'user/verificationMail?token=' + data})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
src/assets/Delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

BIN
src/assets/Editor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
src/assets/Email.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/assets/InTotal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src/assets/Invitation.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
src/assets/PKRecord.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
src/assets/PKbackground.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

BIN
src/assets/Password.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
src/assets/Points.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
src/assets/PointsList.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
src/assets/Publish.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
src/assets/Reset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
src/assets/Search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
src/assets/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 KiB

BIN
src/assets/embellish.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

BIN
src/assets/messageVS.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
src/assets/switchEmail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
src/assets/switchvx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
src/assets/topPosition.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

BIN
src/assets/unpinned.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 993 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 993 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

1583
src/components/Appaside.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,168 +0,0 @@
<template>
<div ref="chart" style="width: 500px; height: 300px;"></div>
</template>
<script>
import * as echarts from 'echarts';
import { tkhostdata, dicts, tkhostdetail } from '@/api/account';
export default {
name: 'EChartsComponent',
props: {
title: {
type: String,
required: true
},
id: {
type: String,
required: true
},
dataType: {
type: String,
required: true
},
time: {
type: String,
required: true
}
},
data() {
return {
seriesData: [],
num: 0,
inputTime: '',
}
},
mounted() {
this.inputTime = this.time
this.getTkhostdetail();
console.log(this.getPrevious7Days(this.inputTime))
},
methods: {
initChart() {
if (!this.$refs.chart) {
console.error('DOM element not found');
return;
}
const myChart = echarts.init(this.$refs.chart);
const option = {
title: {
text: this.title
},
tooltip: {},
legend: {
data: [this.title]
},
xAxis: {
data: [this.getPrevious7Days(this.inputTime)[0].slice(4), this.getPrevious7Days(this.inputTime)[1].slice(4), this.getPrevious7Days(this.inputTime)[2].slice(4), this.getPrevious7Days(this.inputTime)[3].slice(4), this.getPrevious7Days(this.inputTime)[4].slice(4), this.getPrevious7Days(this.inputTime)[5].slice(4), this.getPrevious7Days(this.inputTime)[6].slice(4)]
},
yAxis: {},
series: [
{
name: this.title,
type: 'line',
data: [this.seriesData[0], this.seriesData[1], this.seriesData[2], this.seriesData[3], this.seriesData[4], this.seriesData[5], this.seriesData[6]],
smooth: true
}
]
};
myChart.setOption(option);
console.log(this.dataTime)
},
getTkhostdetail() {
tkhostdetail({
hostId: this.id,
dataType: this.dataType,
searchTimeStart: this.getPrevious7Days(this.inputTime)[0],
searchTimeEnd: this.getPrevious7Days(this.inputTime)[6]
}).then(res => {
// console.log("返回数据", res[0][this.getPrevious7Days(this.inputTime)[2]])
//echarts 数据初始化
this.seriesData = [
res[0][this.getPrevious7Days(this.inputTime)[0]] == null ? 0 : Number(res[0][this.getPrevious7Days(this.inputTime)[0]][this.dataType]),
res[0][this.getPrevious7Days(this.inputTime)[1]] == null ? 0 : Number(res[0][this.getPrevious7Days(this.inputTime)[1]][this.dataType]),
res[0][this.getPrevious7Days(this.inputTime)[2]] == null ? 0 : Number(res[0][this.getPrevious7Days(this.inputTime)[2]][this.dataType]),
res[0][this.getPrevious7Days(this.inputTime)[3]] == null ? 0 : Number(res[0][this.getPrevious7Days(this.inputTime)[3]][this.dataType]),
res[0][this.getPrevious7Days(this.inputTime)[4]] == null ? 0 : Number(res[0][this.getPrevious7Days(this.inputTime)[4]][this.dataType]),
res[0][this.getPrevious7Days(this.inputTime)[5]] == null ? 0 : Number(res[0][this.getPrevious7Days(this.inputTime)[5]][this.dataType]),
res[0][this.getPrevious7Days(this.inputTime)[6]] == null ? 0 : Number(res[0][this.getPrevious7Days(this.inputTime)[6]][this.dataType]),
]
// this.seriesData = {
// [this.getPrevious7Days(this.inputTime)[0]]: res[0][this.getPrevious7Days(this.inputTime)[0]] == null ? 0 : res[0][this.getPrevious7Days(this.inputTime)[0]][this.dataType],
// [this.getPrevious7Days(this.inputTime)[1]]: res[0][this.getPrevious7Days(this.inputTime)[1]] == null ? 0 : res[0][this.getPrevious7Days(this.inputTime)[1]][this.dataType],
// [this.getPrevious7Days(this.inputTime)[2]]: res[0][this.getPrevious7Days(this.inputTime)[2]] == null ? 0 : res[0][this.getPrevious7Days(this.inputTime)[2]][this.dataType],
// [this.getPrevious7Days(this.inputTime)[3]]: res[0][this.getPrevious7Days(this.inputTime)[3]] == null ? 0 : res[0][this.getPrevious7Days(this.inputTime)[3]][this.dataType],
// [this.getPrevious7Days(this.inputTime)[4]]: res[0][this.getPrevious7Days(this.inputTime)[4]] == null ? 0 : res[0][this.getPrevious7Days(this.inputTime)[4]][this.dataType],
// [this.getPrevious7Days(this.inputTime)[5]]: res[0][this.getPrevious7Days(this.inputTime)[5]] == null ? 0 : res[0][this.getPrevious7Days(this.inputTime)[5]][this.dataType],
// [this.getPrevious7Days(this.inputTime)[6]]: res[0][this.getPrevious7Days(this.inputTime)[6]] == null ? 0 : res[0][this.getPrevious7Days(this.inputTime)[6]][this.dataType],
// }
this.initChart();
this.num++
console.log("返回数据", this.seriesData)
console.log("返回数据", this.num)
})
},
// getCurrentDate() {
// const dates = [];
// const today = new Date();
// for (let i = 6; i >= 0; i--) {
// const date = new Date(today);
// date.setDate(today.getDate() - i);
// const year = date.getFullYear();
// const month = String(date.getMonth() + 1).padStart(2, '0');
// const day = String(date.getDate()).padStart(2, '0');
// dates.push(`${year}${month}${day}`);
// }
// return dates;
// },
getPrevious7Days(dateStr) {
// 验证输入格式是否正确
if (!/^\d{8}$/.test(dateStr)) {
console.error('输入的格式不是YYYYMMDD.');
return [];
}
// 解析输入的日期字符串
const year = parseInt(dateStr.substring(0, 4));
const month = parseInt(dateStr.substring(4, 6)) - 1; // 月份从 0 开始
const day = parseInt(dateStr.substring(6, 8));
// 创建日期对象
const date = new Date(year, month, day);
// 存储结果的数组
const result = [];
// 计算前 7 天的日期
for (let i = 0; i <= 6; i++) {
const currentDate = new Date(date);
currentDate.setDate(date.getDate() - i); // 减去 i 天
// 格式化为 YYYYMMDD
const formattedDate = `${currentDate.getFullYear()}${(currentDate.getMonth() + 1).toString().padStart(2, '0')}${currentDate.getDate().toString().padStart(2, '0')}`;
result.push(formattedDate);
}
// 按时间顺序排序(从最早到最晚)
result.sort((a, b) => a.localeCompare(b));
return result;
}
}
};
</script>

View File

@@ -0,0 +1,56 @@
<template>
<div class="language-switcher">
<el-select-v2
v-model="currentLanguage"
:options="languageOptions"
style="vertical-align: middle"
class="select"
@change="setLanguage(currentLanguage)"
/>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { setLocale } from "@/i18n";
const { locale } = useI18n();
const currentLanguage = ref(locale.value);
const languageOptions = [
{ label: "中文", value: "ZH" },
{ label: "English", value: "EN" },
];
function setLanguage(lang) {
setLocale(lang);
currentLanguage.value = lang;
}
</script>
<style scoped>
.language-switcher {
width: 150px;
height: 30px;
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
button {
width: 80px;
height: 30px;
padding: 5px 10px;
margin: 0 5px;
cursor: pointer;
background: #f0f0f0;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
button.active {
background: #4fcacd;
color: white;
}
</style>

View File

@@ -1,140 +0,0 @@
<template>
<div class="sidebar">
<div class="logo">
<img style="margin-right: 10px;" src="@/assets/logo.png">
<img src="@/assets/logotext.png">
</div>
<ul>
<li @click="updateActiveIndex(1)" v-show="userInfo.userType == 3">
<div>
<img v-show="activeIndex == 1" src="@/assets/navAction.png" autoplay loop muted class="background-img">
<div style="display: flex;">
<img v-show="activeIndex == 1" src="@/assets/workAction.png" style="margin-right: 10px;">
<img v-show="activeIndex == 2" src="@/assets/workAction.png" style="margin-right: 10px;">
<div :style="activeIndex == 1 ? 'color: #000' : 'color: #fff'" class="center-justify">工作台</div>
</div>
</div>
</li>
<li @click="updateActiveIndex(2)">
<div>
<img v-show="activeIndex == 2" src="@/assets/navAction.png" autoplay loop muted class="background-img">
<div style="display: flex;">
<img v-show="activeIndex == 2" src="@/assets/listAction.png" style="margin-right: 10px;">
<img v-show="activeIndex == 1" src="@/assets/listAction.png" style="margin-right: 10px;">
<div :style="activeIndex == 2 ? 'color: #000' : 'color: #fff'" class="center-justify">主播列表</div>
</div>
</div>
</li>
</ul>
<a @click="$router.push('/')" href="javascript:void(0);"
style="position: absolute; bottom: 30px; color: aliceblue; font-size: 20px; font-weight: 500;">
退出登录
</a>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { getUser } from '@/utils/storage'
import { defineEmits } from 'vue';
const userInfo = ref(getUser())
let activeIndex = ref(userInfo.value.userType == 3 ? 1 : 2);
const emit = defineEmits(['update:activeIndex']);
const updateActiveIndex = (index) => {
activeIndex.value = index;
emit('activeIndex', index);
};
</script>
<style scoped>
.sidebar {
position: fixed;
left: 0;
top: 0;
height: 900px;
width: 280px;
background-color: #338F6A;
padding: 20px;
box-sizing: border-box;
.logo {
border-bottom: 1px solid #fff;
padding-bottom: 29px;
img:nth-of-type(1) {
height: 40px;
}
img:nth-of-type(2) {
height: 29px;
}
}
}
.sidebar ul {
list-style: none;
padding: 0;
}
.sidebar li {
margin-top: 50px;
padding-top: 30px;
padding-left: 30px;
margin-bottom: 50px;
height: 64px;
position: relative;
display: flex;
align-items: center;
cursor: pointer;
}
.sidebar a {
text-decoration: none;
color: #000000;
display: block;
padding: 8px;
border-radius: 4px;
font-family: Source Han Sans SC;
font-weight: 400;
font-size: 22px;
}
/* .sidebar a:hover {
background-color: #e0e0e0;
} */
.background-img {
position: absolute;
top: 0;
left: 13px;
width: 247px;
height: 126px;
object-fit: cover;
z-index: -1;
/* 确保视频在内容之下 */
}
.center-justify {
display: flex;
justify-content: space-around;
align-items: center;
}
</style>

View File

@@ -0,0 +1,614 @@
<template>
<div class="pk-message">
<div class="pk-message-left">
<div class="left-content">
<div class="AvatarandName">
<div class="Avatar">
<img class="AvatarImg" :src="ArticleDetailsA.anchorIcon" alt="" />
</div>
<div class="information">
<div class="name">{{ ArticleDetailsA.anchorId }}</div>
<div class="genderAndCountry">
<div
class="gender"
:style="{
background: ArticleDetailsA.sex == 1 ? '#59D8DB' : '#F3876F',
}"
>
{{ ArticleDetailsA.sex == 1 ? t("man") : t("woman")}}
<!-- gj男/ -->
</div>
<div class="Country">{{ ArticleDetailsA.country }}</div>
</div>
<div class="time">
{{t("PKTime") + TimestamptolocalTime(PkIDInfodata.pkTime * 1000) }}
<!-- gjPK时间 -->
</div>
</div>
</div>
<!-- -->
<div class="PKinformation">
<div class="gold">
<img
class="gold-img"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png"
alt=""
/>
<div class="sessions-content">
{{t('GoldCoin') }}
<!-- gj金币 -->
<div class="gold-num">{{ ArticleDetailsA.coin }}K</div>
</div>
</div>
<div class="sessions">
<img
class="sessions-img"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png"
alt=""
/>
<div class="sessions-content">
{{ t('session') }}
<!-- gj场次 -->
<div class="gold-num">{{ PkIDInfodata.pkNumber+t('match')}}</div>
<!-- gj场 -->
</div>
</div>
</div>
<!-- -->
<div class="Remarks">{{t('Note')+ArticleDetailsA.remark }}</div>
<!-- gj备注 -->
</div>
</div>
<div class="pk-message-center">
<img class="messageVS" src="../../assets/messageVS.png" alt="" />
<div
class="messagebtn"
v-if="PkIDInfodata.pkStatus === 0 && ArticleDetailsB.senderId != info.id"
>
<div class="messagebtn-left" @click="agree()">{{ t('agree') }}</div>
<!-- gj同意 -->
<div class="messagebtn-right" @click="refuse()">{{ t('Refuse') }}</div>
<!-- gj拒绝 -->
</div>
<div v-if="PkIDInfodata.pkStatus === 1" class="messageHint">{{ t('HaveAgreedToTheInvitation') }}</div>
<!-- gj已同意 -->
<div v-if="PkIDInfodata.pkStatus === 2" class="messageHint">{{ t('HaveRefusedTheInvitation') }}</div>
<!-- gj已拒绝 -->
<div
v-if="PkIDInfodata.pkStatus === 0 && ArticleDetailsB.senderId == info.id"
class="messageHint"
>
{{ t('WaitForTheOtherPartyResponse') }}
<!-- gj等待对方响应 -->
</div>
</div>
<div class="pk-message-right">
<div class="right-content">
<div class="AvatarandName">
<div class="informationright">
<div class="nameright">{{ ArticleDetailsB.anchorId }}</div>
<div class="genderAndCountry">
<div class="Country">{{ ArticleDetailsB.country }}</div>
<div
class="gender"
:style="{
background: ArticleDetailsB.sex == 1 ? '#59D8DB' : '#F3876F',
}"
>
{{ ArticleDetailsB.sex == 1 ? t("man") : t("woman")}}
<!-- gj男/ -->
</div>
</div>
<div class="time">
{{t("PKTime") + TimestamptolocalTime(PkIDInfodata.pkTime * 1000) }}
<!-- gjPK时间 -->
</div>
</div>
<div class="Avatar">
<img class="AvatarImg" :src="ArticleDetailsB.anchorIcon" alt="" />
</div>
</div>
<!-- -->
<div class="PKinformation">
<div class="gold">
<img
class="gold-img"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png"
alt=""
/>
<div class="sessions-content">
{{t('GoldCoin') }}
<!-- gj金币 -->
<div class="gold-num">{{ ArticleDetailsB.coin }}K</div>
</div>
</div>
<div class="sessions">
<img
class="sessions-img"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png"
alt=""
/>
<div class="sessions-content">
{{ t('session') }}
<!-- gj场次 -->
<div class="gold-num">{{ PkIDInfodata.pkNumber+t('match')}}</div>
</div>
</div>
</div>
<!-- -->
<div class="Remarks">{{t('Note')+ArticleDetailsB.remark }}</div>
</div>
</div>
</div>
<!-- 同意邀请提示 -->
<el-dialog v-model="agreedialog" center :title="t('Hint')" width="400" align-center>
<div class="dialog-content">
<div class="dialog-content-text">
<div>
{{ t('AfterASuccessfulInvitationThePKCannotBeModifiedOrDeletedPleaseOperateWithCaution') }}
<!-- gj邀请成功后PK不可修改或删除请谨慎操作 -->
</div>
</div>
<!-- -->
<div class="myanchor-dialog-btn">
<div class="remindermyAnchorDialogReset" @click="agreedialog = false">{{ t('Cancel') }}</div>
<!-- gj取消 -->
<div class="remindermyAnchorDialogConfirm" @click="agreedialogConfirm">
{{ t('Confirm') }}
<!-- gj确认 -->
</div>
</div>
</div>
</el-dialog>
<!-- 拒绝邀请提示 -->
<el-dialog v-model="refusedialog" center :title="t('Hint')" width="400" align-center>
<div class="dialog-content">
<!-- -->
<div class="dialog-content-text">
<div>
{{ t('AreYouSureYouWantToDeclineThisInvitation') }}
<!-- gj确定拒绝邀请 -->
</div>
</div>
<div class="myanchor-dialog-btn">
<div class="remindermyAnchorDialogReset" @click="refusedialog = false">{{ t('Cancel') }}</div>
<div class="remindermyAnchorDialogConfirm" @click="refusedialogConfirm">
{{ t('Confirm') }}
</div>
</div>
</div>
</el-dialog>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
} from "vue";
import { queryPkRecord, pkArticleDetail,updatePkRecordStatus} from "@/api/account";
import {
setStorage,
getStorage,
getPromiseStorage,
clearStorage,
} from "@/utils/storage.js";
import { TimestamptolocalTime } from "@/utils/timeConversion.js";
import { ElMessage } from "element-plus";
//
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
window['$t'] = t
//
const props = defineProps({
item: {
type: Object,
required: true,
},
});
const info = ref({}); // 用户信息
const PkIDInfodata = ref({}); //pk信息
const ArticleDetailsA = ref({}); //pK文章详情A
const ArticleDetailsB = ref({}); //pk文章详情B
const agreedialog = ref(false); //同意邀请提示
const refusedialog = ref(false); //拒绝邀请提示
const newValitem = ref({});
//确认同意
function agreedialogConfirm(){
updatePkRecordStatus({
id: newValitem.value.payload.customData.id,
pkStatus: 1,
}).then(() => {
ElMessage.success(t('AgreeToSuccess'));
//gj同意成功
PkIDInfodata.value.pkStatus = 1;
agreedialog.value = false
}).catch((err) => {});
}
//确认拒绝
function refusedialogConfirm(){
updatePkRecordStatus({
id: newValitem.value.payload.customData.id,
pkStatus: 2,
}).then(() => {
ElMessage.success(t('RefuseSuccess'));
//gj拒绝成功
PkIDInfodata.value.pkStatus = 2;
refusedialog.value = false
}).catch((err) => {});
}
//同意邀请
const agree = () => {
agreedialog.value = true;
};
//拒绝邀请
const refuse = () => {
refusedialog.value = true;
};
//监听props传值
watch(
() => props.item,
(newVal) => {
console.log(newVal);// 用户可以替换为自己的方法
newValitem.value = newVal
queryPkRecord({
id: newVal.payload.customData.id,
}).then((res) => {
PkIDInfodata.value = res;
console.log("PkIDInfodata", res);
}).catch((err) => {});
pkArticleDetail({
id: newVal.payload.customData.pkIdA,
userId: info.value.id,
from: 2,
}).then((res) => {
ArticleDetailsA.value = res;
console.log("ArticleDetailsA", res);
}).catch((err) => {});
pkArticleDetail({
id: newVal.payload.customData.pkIdB,
userId: info.value.id,
from: 2,
}).then((res) => {
ArticleDetailsB.value = res;
console.log("ArticleDetailsB", res);
}).catch((err) => {});
},
{ immediate: true }
);
onMounted(() => {
getPromiseStorage("user").then((res) => {
info.value = res;
}).catch((err) => {});
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
const refname = ref("");
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
</script>
<style scoped>
.pk-message {
width: 1100px;
height: 273px;
border-radius: 16px;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
padding: 10px;
border-radius: 10px;
display: flex;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.pk-message-left {
width: 50%;
height: 100%;
background-image: url("../../assets/pkMessageleft.png");
background-size: 100% 100%;
}
.pk-message-center {
width: 300px;
height: 100%;
margin-left: -150px;
margin-right: -150px;
z-index: 2;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.messageVS {
width: 67px;
height: 67px;
}
.messagebtn {
width: 100%;
height: 50px;
display: flex;
justify-content: space-between;
align-items: center;
}
.messageHint {
width: 100%;
height: 50px;
color: #999;
font-size: 20px;
text-align: center;
line-height: 50px;
font-weight: bold;
}
.messagebtn-left {
width: 100px;
height: 50px;
background-color: #f0836c;
border-radius: 10px;
color: #fff;
font-size: 16px;
text-align: center;
line-height: 50px;
transition: all 0.4s ease;
}
.messagebtn-left:hover {
transform: scale(1.05);
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
}
.messagebtn-left:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.messagebtn-right {
width: 100px;
height: 50px;
background-color: #4fcacd;
border-radius: 10px;
color: #fff;
font-size: 16px;
text-align: center;
line-height: 50px;
transition: all 0.4s ease;
}
.messagebtn-right:hover {
transform: scale(1.05);
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
}
.messagebtn-right:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.pk-message-right {
width: 50%;
height: 100%;
background-image: url("../../assets/pkMessageright.png");
background-size: 100% 100%;
display: flex;
justify-content: end;
}
/* */
.left-content {
width: 470px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.right-content {
width: 470px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.AvatarandName {
display: flex;
align-items: center;
margin-top: 10px;
}
.Avatar {
width: 100px;
height: 100px;
border-radius: 50%;
margin-left: 10px;
margin-right: 10px;
background-color: #dadada;
}
.AvatarImg {
width: 100%;
height: 100%;
border-radius: 50%;
}
.information {
width: 350px;
height: 100px;
}
.informationright {
width: 350px;
height: 100px;
display: flex;
flex-direction: column;
align-items: end;
}
.name {
width: 100%;
height: 25px;
font-size: 20px;
font-weight: bold;
color: #333;
white-space: nowrap; /* 防止换行 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis;
}
.nameright {
width: 100%;
height: 25px;
font-size: 20px;
font-weight: bold;
color: #333;
text-align: end;
white-space: nowrap; /* 防止换行 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis;
}
.genderAndCountry {
margin-top: 10px;
display: flex;
}
.gender {
font-size: 14px;
padding: 5px 20px 5px 20px;
background-color: #999;
border-radius: 20px;
color: #fff;
}
.Country {
font-size: 14px;
padding: 5px 20px 5px 20px;
background-color: #e4f9f9;
border-radius: 20px;
color: #03aba8;
margin-left: 10px;
margin-right: 10px;
}
.time {
margin-top: 10px;
font-size: 18px;
color: #999;
}
.PKinformation {
width: 70%;
height: 50px;
display: flex;
align-items: center;
justify-content: space-around;
}
.gold {
display: flex;
}
.gold-img {
width: 20px;
height: 20px;
}
.sessions {
margin-left: 10px;
display: flex;
}
.sessions-img {
width: 20px;
height: 20px;
}
.sessions-content {
font-size: 16px;
color: #999;
display: flex;
}
.gold-num {
font-size: 16px;
font-weight: bold;
color: #333;
margin-left: 5px;
}
.Remarks {
width: 70%;
font-size: 18px;
color: #999;
margin-bottom: 10px;
}
.dialog-content {
width: 100%;
height: 300px;
display: flex;
flex-direction: column;
align-items: center;
}
.dialog-content-text{
width: 90%;
height: 200px;
background-color: #C0E8E8;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #03aba8;
border: 1px solid #03aba8;
}
.myanchor-dialog-btn {
width: 100%;
display: flex;
justify-content: space-around;
margin-top: 20px;
}
.remindermyAnchorDialogReset {
width: 150px;
height: 40px;
margin-top: 30px;
text-align: center;
line-height: 40px;
background: linear-gradient(0deg, #e4ffff, #ffffff);
color: #03aba8;
font-size: 18px;
transition: all 0.4s ease;
border-radius: 100px;
border: 1px solid #4fcacd;
}
.remindermyAnchorDialogReset:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
transform: scale(1.1);
opacity: 0.8;
}
.remindermyAnchorDialogReset:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.remindermyAnchorDialogConfirm {
width: 150px;
height: 40px;
margin-top: 30px;
text-align: center;
line-height: 40px;
background: linear-gradient(0deg, #4fcacd, #5fdbde);
color: #ffffff;
font-size: 18px;
transition: all 0.4s ease;
border-radius: 100px;
}
.remindermyAnchorDialogConfirm:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
transform: scale(1.1);
opacity: 0.8;
}
.remindermyAnchorDialogConfirm:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<div class="picture-message" @click="dialogVisibleClick">
<img class="picture-message-img" :src="item.payload.url" alt="">
</div>
<el-dialog v-model="dialogVisible" fullscreen>
<div class="dialog-content">
<img class="dialog-img" :src="item.payload.url" alt="">
</div>
</el-dialog>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
} from "vue";
const props = defineProps({
item: {
type: Object,
required: true
}
});
// 保存到桌面
function saveToDesktop() {
const link = document.createElement('a');
link.href = props.item.payload.url;
link.download = props.item.payload.filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 弹窗相关
const dialogVisible = ref(false);
function dialogVisibleClick() {
dialogVisible.value = true;
}
const refname = ref('');
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
onMounted(() => {
// 组件挂载完成后执行
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped>
.picture-message{
padding: 0;
margin: 0;
/* display: inline-block; */
max-width: 100%;
border-radius: 10px;
}
.picture-message-img{
max-width: 100%;
height: auto;
display: block;
border-radius: 10px;
}
.dialog-content{
width: 98vw;
height: 95vh;
display: flex;
justify-content: center;
align-items: center;
}
.dialog-img{
max-width: 100%;
height: auto;
display: block;
}
</style>

View File

@@ -0,0 +1,574 @@
<template>
<div class="chat-message-mini-pk">
<!-- 用户A -->
<div class="userA">
<!-- -->
<div class="Avatar">
<img class="AvatarImg" :src="ArticleDetailsA.anchorIcon" alt="" />
<div class="name">{{ ArticleDetailsA.anchorId }}</div>
</div>
<!-- -->
<div class="genderAndCountry">
<div
class="gender"
:style="{
background: ArticleDetailsA.sex == 1 ? '#59D8DB' : '#F3876F',
}"
>
{{ ArticleDetailsA.sex == 1 ? t("man") : t("woman") }}
<!-- gj男/ -->
</div>
<div class="Country">{{ ArticleDetailsA.country }}</div>
</div>
<!-- -->
<div class="time">
{{ t("PKTime") + TimestamptolocalTime(PkIDInfodata.pkTime * 1000) }}
<!-- gjPK时间 -->
</div>
<!-- -->
<div class="PKinformation">
<div class="gold">
<img
class="gold-img"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png"
alt=""
/>
<div class="sessions-content">
{{t('GoldCoin') }}
<!-- gj金币 -->
<div class="gold-num">{{ ArticleDetailsA.coin }}K</div>
</div>
</div>
<div class="sessions">
<img
class="sessions-img"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png"
alt=""
/>
<div class="sessions-content">
{{ t('session') }}
<!-- gj场次 -->
<div class="gold-num">{{ PkIDInfodata.pkNumber+t('match') }}</div>
</div>
</div>
</div>
<!-- -->
<div class="Remarks">{{ t('Note')+ArticleDetailsA.remark }}</div>
</div>
<!-- vs -->
<div class="messageVS">
<img class="messageVS-img" src="../../assets/messageVS.png" alt="" />
</div>
<!-- 用户B -->
<div class="userB">
<!-- -->
<div class="Avatar">
<img class="AvatarImg" :src="ArticleDetailsB.anchorIcon" alt="" />
<div class="name">{{ ArticleDetailsB.anchorId }}</div>
</div>
<!-- -->
<div class="genderAndCountry">
<div
class="gender"
:style="{
background: ArticleDetailsB.sex == 1 ? '#59D8DB' : '#F3876F',
}"
>
{{ ArticleDetailsB.sex == 1 ? t("man") : t("woman")}}
<!-- gj男/ -->
</div>
<div class="Country">{{ ArticleDetailsB.country }}</div>
</div>
<!-- -->
<div class="time">
{{ t("PKTime") + TimestamptolocalTime(PkIDInfodata.pkTime * 1000) }}
<!-- gjPK时间 -->
</div>
<!-- -->
<div class="PKinformation">
<div class="gold">
<img
class="gold-img"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png"
alt=""
/>
<div class="sessions-content">
{{t('GoldCoin') }}
<!-- gj金币 -->
<div class="gold-num">{{ ArticleDetailsB.coin }}K</div>
</div>
</div>
<div class="sessions">
<img
class="sessions-img"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png"
alt=""
/>
<div class="sessions-content">
{{ t('session') }}
<!-- gj场次 -->
<div class="gold-num">{{ PkIDInfodata.pkNumber+t('match') }}</div>
</div>
</div>
</div>
<div class="Remarks">{{ t('Note')+ArticleDetailsB.remark }}</div>
<!-- gj备注 -->
</div>
<!-- btn -->
<div
class="btn"
v-if="PkIDInfodata.pkStatus === 0 && ArticleDetailsB.senderId != info.id"
>
<div class="messagebtn-left" @click="agree()">{{ t('agree') }}</div>
<!-- gj同意 -->
<div class="messagebtn-right" @click="refuse()">{{ t('Refuse') }}</div>
<!-- gj拒绝 -->
</div>
<div v-if="PkIDInfodata.pkStatus === 1" class="messageHint">{{ t('HaveAgreedToTheInvitation') }}</div>
<!-- gj已同意 -->
<div v-if="PkIDInfodata.pkStatus === 2" class="messageHint">{{ t('HaveRefusedTheInvitation') }}</div>
<!-- gj已拒绝 -->
<div
v-if="PkIDInfodata.pkStatus === 0 && ArticleDetailsB.senderId == info.id"
class="messageHint"
>
{{ t('WaitForTheOtherPartyResponse') }}
<!-- gj等待对方响应 -->
</div>
</div>
<!-- 弹窗 -->
<!-- 同意邀请提示 -->
<el-dialog v-model="agreedialog" center :title="t('Hint')" width="400" align-center>
<div class="dialog-content">
<div class="dialog-content-text">
<div>{{ t('AfterASuccessfulInvitationThePKCannotBeModifiedOrDeletedPleaseOperateWithCaution') }}</div>
<!-- gj邀请成功后PK不可修改或删除请谨慎操作 -->
</div>
<!-- -->
<div class="myanchor-dialog-btn">
<div class="remindermyAnchorDialogReset" @click="agreedialog = false">{{ t('Cancel') }}</div>
<!-- gj取消 -->
<div class="remindermyAnchorDialogConfirm" @click="agreedialogConfirm"> {{ t('Confirm') }}
<!-- gj确认 --></div>
</div>
</div>
</el-dialog>
<!-- 拒绝邀请提示 -->
<el-dialog v-model="refusedialog" center :title="t('Hint')" width="400" align-center>
<!-- gj提示 -->
<div class="dialog-content">
<!-- -->
<div class="dialog-content-text">
<div> {{ t('AreYouSureYouWantToDeclineThisInvitation') }}
<!-- gj确定拒绝邀请 --></div>
</div>
<div class="myanchor-dialog-btn">
<div class="remindermyAnchorDialogReset" @click="refusedialog = false">{{ t('Cancel') }}</div>
<!-- gj取消 -->
<div class="remindermyAnchorDialogConfirm" @click="refusedialogConfirm"> {{ t('Confirm') }}</div>
<!-- gj确认 -->
</div>
</div>
</el-dialog>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
} from "vue";
import { queryPkRecord, pkArticleDetail, updatePkRecordStatus } from "@/api/account";
import {
setStorage,
getStorage,
getPromiseStorage,
clearStorage,
} from "@/utils/storage.js";
import { TimestamptolocalTime } from "@/utils/timeConversion.js";
import { ElMessage } from "element-plus";
//
import { useI18n } from "vue-i18n";
const { t } = useI18n();
window["$t"] = t;
//
const props = defineProps({
item: {
type: Object,
required: true,
},
});
const info = ref({}); // 用户信息
const PkIDInfodata = ref({}); //pk信息
const ArticleDetailsA = ref({}); //pK文章详情A
const ArticleDetailsB = ref({}); //pk文章详情B
const agreedialog = ref(false); //同意邀请提示
const refusedialog = ref(false); //拒绝邀请提示
const newValitem = ref({});
//确认同意
function agreedialogConfirm() {
updatePkRecordStatus({
id: newValitem.value.payload.customData.id,
pkStatus: 1,
})
.then(() => {
ElMessage.success(t('AgreeToSuccess'));
//gj同意成功
PkIDInfodata.value.pkStatus = 1;
agreedialog.value = false;
})
.catch((err) => {});
}
//确认拒绝
function refusedialogConfirm() {
updatePkRecordStatus({
id: newValitem.value.payload.customData.id,
pkStatus: 2,
})
.then(() => {
ElMessage.success(t('RefuseSuccess'));
//gj拒绝成功
PkIDInfodata.value.pkStatus = 2;
refusedialog.value = false;
})
.catch((err) => {});
}
//同意邀请
const agree = () => {
agreedialog.value = true;
};
//拒绝邀请
const refuse = () => {
refusedialog.value = true;
};
//监听props传值
watch(
() => props.item,
(newVal) => {
console.log(newVal); // 用户可以替换为自己的方法
newValitem.value = newVal;
queryPkRecord({
id: newVal.payload.customData.id,
})
.then((res) => {
PkIDInfodata.value = res;
console.log("PkIDInfodata", res);
})
.catch((err) => {});
pkArticleDetail({
id: newVal.payload.customData.pkIdA,
userId: info.value.id,
from: 2,
})
.then((res) => {
ArticleDetailsA.value = res;
console.log("ArticleDetailsA", res);
})
.catch((err) => {});
pkArticleDetail({
id: newVal.payload.customData.pkIdB,
userId: info.value.id,
from: 2,
})
.then((res) => {
ArticleDetailsB.value = res;
console.log("ArticleDetailsB", res);
})
.catch((err) => {});
},
{ immediate: true }
);
onMounted(() => {
getPromiseStorage("user")
.then((res) => {
info.value = res;
})
.catch((err) => {});
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
const refname = ref("");
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
</script>
<style scoped>
.chat-message-mini-pk {
width: 325px;
height: 820px;
border-radius: 10px;
display: flex;
flex-direction: column;
align-items: center;
background-color: #ffffff;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
.messageVS {
width: 67px;
height: 67px;
margin-top: -33.5px;
margin-bottom: -33.5px;
z-index: 2;
}
.messageVS-img {
width: 67px;
height: 67px;
}
.userA {
width: 90%;
height: 335px;
background-color: #c0e8e8;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.Avatar {
width: 90%;
height: 50px;
margin-top: 15px;
display: flex;
align-items: center;
}
.AvatarImg {
width: 50px;
height: 50px;
border-radius: 50%;
}
.name {
width: calc(100% - 60px);
margin-left: 10px;
font-size: 18px;
font-weight: bold;
color: #000000;
white-space: nowrap; /* 防止换行 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis;
}
.genderAndCountry {
width: 90%;
height: 30px;
margin-top: 15px;
display: flex;
align-items: center;
}
.gender {
font-size: 14px;
padding: 5px 20px 5px 20px;
background-color: #999;
border-radius: 20px;
color: #fff;
}
.Country {
font-size: 14px;
padding: 5px 20px 5px 20px;
background-color: #e4f9f9;
border-radius: 20px;
color: #03aba8;
margin-left: 10px;
margin-right: 10px;
}
.time {
width: 90%;
height: 20px;
margin-top: 10px;
font-size: 14px;
color: #999999;
}
.PKinformation {
width: 90%;
height: 50px;
margin-top: 10px;
display: flex;
justify-content: space-around;
align-items: center;
}
.gold {
display: flex;
align-items: center;
}
.gold-img {
width: 20px;
height: 20px;
}
.sessions-content {
display: flex;
align-items: center;
color: #999;
font-size: 14px;
}
.gold-num {
font-size: 14px;
font-weight: bold;
color: #000000;
margin-left: 5px;
}
.Remarks {
width: 90%;
height: 90px;
font-size: 12px;
color: #999;
margin-top: 10px;
}
.sessions {
display: flex;
align-items: center;
}
.sessions-img {
width: 20px;
height: 20px;
}
.userB {
width: 90%;
height: 315px;
background-color: #f8e4e0;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
padding-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.btn {
margin-top: 20px;
width: 90%;
height: 50px;
display: flex;
justify-content: space-around;
}
.messageHint {
margin-top: 20px;
width: 90%;
height: 50px;
font-size: 20px;
color: #999;
text-align: center;
line-height: 50px;
font-weight: bold;
}
.messagebtn-left {
width: 100px;
height: 50px;
background-color: #f0836c;
border-radius: 10px;
color: #fff;
font-size: 16px;
text-align: center;
line-height: 50px;
transition: all 0.4s ease;
}
.messagebtn-left:hover {
transform: scale(1.05);
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
}
.messagebtn-left:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.messagebtn-right {
width: 100px;
height: 50px;
background-color: #4fcacd;
border-radius: 10px;
color: #fff;
font-size: 16px;
text-align: center;
line-height: 50px;
transition: all 0.4s ease;
}
.messagebtn-right:hover {
transform: scale(1.05);
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
}
.messagebtn-right:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
/* 弹窗 */
.dialog-content {
width: 100%;
height: 300px;
display: flex;
flex-direction: column;
align-items: center;
}
.dialog-content-text {
width: 90%;
height: 200px;
background-color: #c0e8e8;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #03aba8;
border: 1px solid #03aba8;
}
.myanchor-dialog-btn {
width: 100%;
display: flex;
justify-content: space-around;
margin-top: 20px;
}
.remindermyAnchorDialogReset {
width: 150px;
height: 40px;
margin-top: 30px;
text-align: center;
line-height: 40px;
background: linear-gradient(0deg, #e4ffff, #ffffff);
color: #03aba8;
font-size: 18px;
transition: all 0.4s ease;
border-radius: 100px;
border: 1px solid #4fcacd;
}
.remindermyAnchorDialogReset:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
transform: scale(1.1);
opacity: 0.8;
}
.remindermyAnchorDialogReset:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.remindermyAnchorDialogConfirm {
width: 150px;
height: 40px;
margin-top: 30px;
text-align: center;
line-height: 40px;
background: linear-gradient(0deg, #4fcacd, #5fdbde);
color: #ffffff;
font-size: 18px;
transition: all 0.4s ease;
border-radius: 100px;
}
.remindermyAnchorDialogConfirm:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
transform: scale(1.1);
opacity: 0.8;
}
.remindermyAnchorDialogConfirm:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
</style>

View File

@@ -0,0 +1,214 @@
<template>
<!-- <div class="audio-player">
<button @click="togglePlay" :class="{ playing: isPlaying }">
{{ isPlaying ? "暂停" : "播放" }}
</button>
</div> -->
<div class="voice-message">
<!-- 对方消息 -->
<div class="voice-message" v-if="user.id != senderId" @click="togglePlay">
<img
v-if="!isPlaying"
class="voice-icon"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/voice.png"
alt=""
/>
<div class="voice-icon-text" v-if="!isPlaying">'{{floor(size)}}'</div>
<div class="voice-message-text" v-if="isPlaying">
{{ t('Playing') }}
<!-- gj播放中 -->
</div>
</div>
<!-- 自己消息 -->
<div class="voice-message" v-if="user.id == senderId" @click="togglePlay">
<div class="voice-icon-text" v-if="!isPlaying">'{{floor(size)}}'</div>
<img
v-if="!isPlaying"
class="voice-icon"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/voice.png"
alt=""
/>
<div class="voice-message-text" v-if="isPlaying">
{{ t('Playing') }}
<!-- gj播放中 -->
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import { setStorage, getStorage, getPromiseStorage } from "@/utils/storage.js";
//
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
window['$t'] = t
//
const props = defineProps({
item: {
type: String,
required: true,
},
size:{
type: String,
required: true,
},
senderId: {
type: String,
required: true,
},
});
const audioElement = ref(null);
const isPlaying = ref(false);
const currentTime = ref(0);
const duration = ref(0);
const currentProgress = ref(0);
const volume = ref(0.5);
const user = ref({});
function floor(num) {
return Math.floor(num);
}
// 格式化时间
const formatTime = (time) => {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
};
// 切换播放状态
const togglePlay = () => {
isPlaying.value = !isPlaying.value;
isPlaying.value ? audioElement.value.play() : audioElement.value.pause();
};
// 更新进度条
const updateProgress = () => {
currentTime.value = audioElement.value.currentTime;
currentProgress.value = (currentTime.value / duration.value) * 100;
};
// 跳转到指定时间
const seekTo = (e) => {
const newTime = (e.target.value / 100) * duration.value;
audioElement.value.currentTime = newTime;
};
// 更新音量
const updateVolume = () => {
audioElement.value.volume = volume.value;
};
// 监听音频加载完成
watch(
() => props.item,
(newSrc) => {
if (audioElement.value) {
audioElement.value.src = newSrc;
audioElement.value.load();
}
}
);
onMounted(() => {
getPromiseStorage("user")
.then((res) => {
user.value = res;
})
.catch((err) => {
console.log(err);
});
audioElement.value = new Audio(props.item);
audioElement.value.volume = volume.value;
// 监听音频事件
audioElement.value.addEventListener("timeupdate", updateProgress);
audioElement.value.addEventListener("loadedmetadata", () => {
duration.value = audioElement.value.duration;
});
audioElement.value.addEventListener("ended", () => {
isPlaying.value = false;
currentTime.value = 0;
currentProgress.value = 0;
});
// 自动播放策略处理
if ("autoPlayEnabled" in audioElement.value) {
audioElement.value.autoPlayEnabled = false;
}
});
</script>
<style scoped>
.voice-message {
width: 120px;
height: 50px;
display: flex;
align-items: center;
justify-content:space-around;
}
.voice-message-text{
width: 120px;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 14px;
color: #999;
}
.voice-icon {
width: 70px;
height: 50px;
}
.voice-icon-text {
font-size: 14px;
color: #999;
}
.audio-player {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
padding: 2rem;
background: #f5f5f5;
border-radius: 8px;
}
button {
padding: 0.5rem 1rem;
font-size: 1.2rem;
cursor: pointer;
transition: background-color 0.3s;
}
button.playing {
background-color: #4caf50;
color: white;
}
.progress-container {
display: flex;
align-items: center;
gap: 1rem;
}
.progress-bar {
width: 300px;
-webkit-appearance: none;
height: 5px;
background: #ddd;
}
.volume-control {
display: flex;
align-items: center;
gap: 0.5rem;
}
input[type="range"] {
-webkit-appearance: none;
width: 150px;
}
</style>

View File

@@ -0,0 +1,559 @@
<template>
<!-- 主播库 -->
<div class="anchor-library">
<el-splitter>
<el-splitter-panel size="75%">
<!-- 主播列表 -->
<div class="demo-panel">
<div
class="anchor-library-list"
style="overflow: auto"
v-infinite-scroll="load"
v-if="list.length !== 0"
>
<div class="anchor-library-card" v-for="(item, index) in list" :key="index">
<div class="card-content">
<div class="card-avatar">
<img
style="width: 100%; height: 100%; border-radius: 100px"
:src="item.headerIcon"
alt=""
/>
</div>
<div class="personalInformation">
<div class="name">{{ item.anchorId }}</div>
<div class="GenderAndCountry">
<div
class="Gender"
:style="{
background: item.gender == 1 ? '#59D8DB' : '#F3876F',
}"
>
{{ item.gender == 1 ?t('man') : t('woman') }}
<!-- gj男女 -->
</div>
<div class="Country">{{ item.country }}</div>
</div>
</div>
<div class="card-Operation">
<!-- 编辑 -->
<div class="modify" @click="anchormodify(item)">
<img class="modify-icon" src="@/assets/Editor.png" alt="" />
</div>
<!-- 删除 -->
<div class="delete" @click="anchordelete(item.id)">
<img class="delete-icon" src="@/assets/Delete.png" alt="" />
</div>
</div>
</div>
</div>
</div>
<div class="chatNotDeta" v-if="list.length === 0">
<div class="chatNotDeta-text">{{ t('YouDonHaveALiveStreamerYetHurryUpAndAddOne') }}</div>
<!-- gj您还没有主播快去添加吧 -->
</div>
</div>
</el-splitter-panel>
<el-splitter-panel size="25%" :collapsible="true" :resizable="false">
<!-- 添加或修改主播 -->
<div class="demo-panel">
<div class="add-anchor-library">
<div class="title">
<img class="titleimg" src="@/assets/embellish.png" alt="" />
<div v-if="!anchormodifystate">{{ t('AddMyStreamer') }}</div>
<!-- gj添加主播 -->
<div v-if="anchormodifystate">{{ t('ModifyMyStreamer') }}</div>
<!-- gj修改主播 -->
<img class="titleimg" src="@/assets/embellish.png" alt="" />
</div>
<div class="add-anchor-library-content">
<div class="input-name">
<!-- 主播名称 -->
<el-input
@blur="blur()"
v-model="anchorName"
size="large"
:placeholder="t('PleaseEnterTheNameOfTheHost')"
/>
</div>
<div class="country">
<!-- 国家 -->
<el-select-v2
v-model="countryvalue"
filterable
:options="country"
:placeholder="t('PleaseSelectACountry')"
size="large"
style="vertical-align: middle"
class="select"
/>
</div>
<!-- gj选择国家 -->
<div class="gender">
<!-- 性别 -->
<el-select-v2
v-model="gendervalue"
filterable
:options="genderOptions"
size="large"
:placeholder="t('PleaseSelectACountry')"
style="vertical-align: middle"
class="select"
/>
<!-- gj男女 -->
</div>
<div class="Confirm" @click="Confirm()">{{ t('Confirm') }}</div>
<div class="Reset" @click="Reset()">{{ t('Reset') }}</div>
<div class="Reset" v-if="anchormodifystate" @click="cancel()">{{ t('Cancel') }}</div>
</div>
</div>
</div>
</el-splitter-panel>
</el-splitter>
</div>
<!-- 确认删除弹窗 -->
<el-dialog
center
class="center-dialog"
v-model="centerDialogVisible"
:title="t('Hint')"
width="200"
align-center
>
<!-- gj提示 -->
<span>{{ t('ConfirmTheDeletionOfThisStreamer') }}</span>
<!-- gj确认删除此主播 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="centerDialogVisible = false">{{ t('Cancel') }}</el-button>
<!-- gj取消 -->
<el-button type="primary" @click="deleteAnchor()"> {{ t('Confirm') }}</el-button>
<!-- gj确认 -->
</div>
</template>
</el-dialog>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
} from "vue";
import {
getAnchorList,
getAnchorAvatar,
addAnchor,
delAnchor,
editAnchor,
} from "@/api/account";
import { getCountryNamesArray } from "../../utils/countryUtil";
import { ElLoading } from "element-plus";
import { ElMessage } from "element-plus";
import { setStorage, getStorage, getPromiseStorage } from "@/utils/storage.js";
//
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
window['$t'] = t
//
const country = ref([]);
country.value = getCountryNamesArray(); //国家条目
const genderOptions = [
{ value: 1, label: t('man') },
{ value: 2, label: t('woman') },
]; // 性别选项
const gendervalue = ref(null); // 性别值
const countryvalue = ref(null); //国家
const anchorName = ref(null); // 主播名称
const list = ref([]);
const user = ref(null); // 用户信息
const AnchorProfilePicture = ref(null); // 主播头像
const centerDialogVisible = ref(false); // 确认删除弹窗
const anchormodifystate = ref(false); // 编辑
const anchormodifystateId = ref(null); // 主播头像
// 获取主播列表
function AnchorList() {
getAnchorList({ id: user.value.id })
.then((res) => {
list.value = res;
})
.catch((err) => {
console.log(err);
});
}
//编辑主播
function anchormodify(item) {
if (anchormodifystate.value == true) {
cancel();
return;
}
anchormodifystateId.value = item.id;
anchorName.value = item.anchorId;
gendervalue.value = item.gender;
countryvalue.value = item.country;
AnchorProfilePicture.value = item.headerIcon.split("/").pop();
anchormodifystate.value = true;
}
// 取消编辑
function cancel() {
anchormodifystateId.value = null;
anchorName.value = null;
gendervalue.value = null;
countryvalue.value = null;
AnchorProfilePicture.value = null;
anchormodifystate.value = false;
}
// 删除主播
const deleteAnchorID = ref(null);
const deleteAnchor = () => {
delAnchor({ id: deleteAnchorID.value })
.then((res) => {
centerDialogVisible.value = false;
ElMessage.success(t('DeletedSuccessfully'));
// gj删除成功
AnchorList();
cancel();
})
.catch((err) => {});
};
function anchordelete(id) {
deleteAnchorID.value = id;
centerDialogVisible.value = true;
}
// 加载更多
function load() {}
// 确认添加或修改主播
function Confirm() {
if (anchorName.value == null || anchorName.value == "") {
ElMessage.error(t('PleaseEnterTheNameOfTheHost'));
// gj请输入主播名称
return;
}
if (gendervalue.value == null) {
ElMessage.error(t('PleaseSelectGender'));
// gj请选择性别
return;
}
if (countryvalue.value == null) {
ElMessage.error(t('PleaseSelectACountry'));
//gj请选择国家
return;
}
//修改主播还是添加
if (anchormodifystate.value) {
//修改主播
editAnchor({
id: anchormodifystateId.value,
anchorId: anchorName.value,
headerIcon: AnchorProfilePicture.value,
gender: gendervalue.value,
country: countryvalue.value,
createUserId: user.value.id,
})
.then((res) => {
ElMessage.success(t('ModificationSuccessful'));
// gj修改成功
cancel();
AnchorList();
})
.catch((err) => {
console.log(err);
});
} else {
//添加主播
addAnchor({
anchorId: anchorName.value,
headerIcon: AnchorProfilePicture.value,
gender: gendervalue.value,
country: countryvalue.value,
createUserId: user.value.id,
})
.then((res) => {
ElMessage.success(t('AddedSuccessfully'));
AnchorList();
})
.catch((err) => {
console.log(err);
});
}
}
// 重置
function Reset() {
anchorName.value = null;
gendervalue.value = null;
countryvalue.value = null;
}
//输入框失去焦点
function blur() {
if (anchorName.value == null || anchorName.value == "") {
ElMessage.error(t('PleaseEnterTheNameOfTheHost'));
// gj请输入主播名称
return;
}
const loading = ElLoading.service({
lock: true,
text: t('CheckTheStreamerAt'),
// gj正在检查主播
background: "rgba(0, 0, 0, 0.7)",
});
getAnchorAvatar({ name: anchorName.value })
.then((res) => {
loading.close();
AnchorProfilePicture.value = res;
})
.catch((err) => {
loading.close();
console.log(err);
});
}
// 组件挂载完成后执行
onMounted(() => {
getPromiseStorage("user")
.then((res) => {
user.value = res;
AnchorList();
})
.catch((err) => {
console.log(err);
});
});
const refname = ref("");
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped lang="less">
.anchor-library {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.anchor-library-list {
width: 100%;
height: 100%;
}
.chatNotDeta{
width: 100%;
height: 100%;
background-color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
}
.chatNotDeta-text{
font-size: 20px;
color: #03aba8;
font-weight: bold;
}
.anchor-library-card {
width: 100%;
height: 150px;
margin-bottom: 15px;
margin-top: 15px;
display: flex;
justify-content: center;
align-items: center;
}
.card-content {
width: 90%;
height: 100%;
border-radius: 10px;
background-image: url(../../assets/PKbackground.png);
background-size: 100% 100%;
transition: all 0.4s ease;
display: flex;
align-items: center;
}
.card-content:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
transform: scale(1.05);
opacity: 0.8;
}
.card-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
background-color: #ffffff;
margin-left: 20px;
}
.personalInformation {
width: calc(100% - 340px);
height: 100px;
margin-left: 20px;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.name {
font-size: 20px;
font-weight: bold;
color: #333333;
}
.GenderAndCountry {
display: flex;
align-items: center;
}
.Gender {
font-size: 16px;
color: #ffffff;
border-radius: 50px;
padding: 2px 20px 2px 20px;
margin-right: 20px;
}
.Country {
font-size: 16px;
color: #666666;
border-radius: 50px;
background-color: #ffffff;
padding: 2px 20px 2px 20px;
}
.card-Operation {
width: 200px;
height: 100px;
margin-left: 20px;
margin-right: 20px;
display: flex;
align-items: center;
justify-content: space-around;
}
.modify-icon {
width: 30px;
height: 30px;
transition: all 0.4s ease;
}
.modify-icon:hover {
transform: scale(1.2);
}
.modify-icon:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.delete-icon {
width: 30px;
height: 30px;
transition: all 0.4s ease;
}
.delete-icon:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.delete-icon:hover {
transform: scale(1.2);
}
.demo-panel {
width: 100%;
height: 100%;
}
.add-anchor-library {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.title {
width: 80%;
height: 70px;
font-size: 24px;
font-weight: bold;
color: #333333;
text-align: center;
line-height: 70px;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.titleimg {
width: 44px;
height: 30px;
}
.add-anchor-library-content {
width: 100%;
height: calc(100% - 70px);
display: flex;
flex-direction: column;
align-items: center;
}
.input-name {
width: 80%;
height: 50px;
margin-top: 20px;
}
.country {
width: 80%;
height: 50px;
margin-top: 20px;
}
.gender {
width: 80%;
height: 80px;
margin-top: 20px;
}
.select {
width: 100%;
}
.Confirm {
width: 400px;
height: 50px;
margin-top: 200px;
text-align: center;
line-height: 50px;
background-image: linear-gradient(to top, #4fcacd, #5fdbde);
color: #ffffff;
font-size: 22px;
transition: all 0.4s ease;
border-radius: 25px;
}
.Confirm:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
transform: scale(1.1);
opacity: 0.8;
}
.Confirm:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.Reset {
width: 400px;
height: 50px;
margin-top: 30px;
text-align: center;
line-height: 50px;
background-image: linear-gradient(to top, #e4ffff, #ffffff);
border: 1px solid #4fcacd;
color: #03aba8;
font-size: 22px;
transition: all 0.4s ease;
border-radius: 25px;
}
.Reset:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
transform: scale(1.1);
opacity: 0.8;
}
.Reset:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
.center-dialog {
background-color: #03aba8;
}
</style>

View File

@@ -0,0 +1,726 @@
<template>
<!-- 我的PK记录 -->
<div class="pk-record">
<el-splitter>
<el-splitter-panel>
<div class="demo-panel">
<!-- 选项卡 -->
<div class="custom-style">
<div
class="Options"
v-for="time in options"
@click="optionsclick(time.value)"
:style="{
borderBottom: segmentedvalue === time.value ? '5px solid #03ABA8' : '',
}"
>
<img
class="Options-icon"
:src="segmentedvalue === time.value ? time.SelectedIcon : time.icon"
alt=""
/>
<div
class="Options-label"
:style="{ color: segmentedvalue === time.value ? '#03ABA8' : '#636363' }"
>
{{ time.label }}
</div>
</div>
</div>
<!-- list -->
<div class="list" style="overflow: auto" v-infinite-scroll="load" v-if="list.length !== 0">
<div v-for="(item, index) in list" :key="index" class="list-item">
<div
class="list-content"
@click="detail(item)"
:style="{
backgroundImage: item == selectedData ? '' : '',
background: item == selectedData ? '#FFFBFA' : '',
border: item == selectedData ? '1px solid #F4D0C9' : '',
}"
>
<div class="information">
<div class="avatar">
<img class="avatar-img" :src="item.anchorIconA" alt="" />
</div>
<div class="content-left">
<div class="name">{{ item.anchorIdA }}</div>
<div class="time">
{{t('PKTime')+TimestamptolocalTime(item.pkTime * 1000) }}
<!-- gjPK时间 -->
</div>
<div class="gold" v-if="item.userACoins != null">
<img
class="goldimg"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png"
alt=""
/>
{{ t('ActualNumberOfGoldCoins') }}
<!-- gj实际金币数 -->
<div class="gold-num">
{{ goldCoinCalculation(item.userACoins) }}
</div>
</div>
</div>
</div>
<div class="vs">
<img
style="width: 100%; height: 100%"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png"
alt=""
/>
</div>
<div class="information">
<div class="content-right">
<div class="name">{{ item.anchorIdB }}</div>
<div class="time">
{{t('PKTime')+TimestamptolocalTime(item.pkTime * 1000) }}
</div>
<div class="gold" v-if="item.userBCoins != null">
<img
class="goldimg"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png"
alt=""
/>
{{ t('ActualNumberOfGoldCoins') }}
<!-- gj实际金币数 -->
<div class="gold-num">
{{ goldCoinCalculation(item.userBCoins) }}
</div>
</div>
</div>
<div class="avatar">
<img class="avatar-img" :src="item.anchorIconA" alt="" />
</div>
</div>
</div>
</div>
</div>
<div class="chatNotDeta"v-if="list.length === 0">
<div class="chatNotDeta-text">{{ t('YouDonHaveAPKRecordYet') }}</div>
<!-- gj您还没有PK记录 -->
</div>
</div>
</el-splitter-panel>
<el-splitter-panel size="30%" :resizable="false" collapsible>
<div class="demo-panel" v-if="selectedData != null">
<div class="particularsAvatar">
<img
class="particularsAvatar-avatar"
:src="selectedData.anchorIconA"
alt=""
/>
<img
class="particularsAvatar-avatar"
:src="selectedData.anchorIconB"
alt=""
/>
</div>
<!-- -->
<div class="altogether">
<div class="altogethercard">
<div class="altogether-num">
{{t('InTotal')+goldCoinCalculation(selectedData.userACoins) }}
<!-- gj总计 -->
</div>
<img
class="altogether-icon"
src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png"
alt=""
/>
<div class="altogether-num">
{{t('InTotal')+goldCoinCalculation(selectedData.userBCoins) }}
<!-- gj总计 -->
</div>
</div>
</div>
<!-- -->
<div class="goldlist">
<div class="goldlist-card goldlist-card-left">
<div
class="goldlist-list"
v-for="(item, index) in fetchDetailPkDataWithId"
:style="{
background: item.anchorCoinA > item.anchorCoinB ? '#D1F6F7' : '#F9DFD9',
}"
>
{{t('The')+index + 1+t('THInning') }}:
<!-- gj第几局 -->
{{ goldCoinCalculation(item.anchorCoinA) }}
</div>
</div>
<div class="goldlist-card goldlist-card-right">
<div
class="goldlist-list"
v-for="(item, index) in fetchDetailPkDataWithId"
:key="index"
:style="{
background: item.anchorCoinB > item.anchorCoinA ? '#D1F6F7' : '#F9DFD9',
}"
>
{{t('The')+index + 1+t('THInning') }}:
<!-- gj第几局 -->
{{ goldCoinCalculation(item.anchorCoinB) }}
</div>
</div>
</div>
</div>
<div class="notdata" v-if="selectedData == null">
<div class="chatNotDeta-text">{{ t('SelectRecordOnTheRightToViewDetailsImmediately') }}</div>
<!-- gj请先选择记录 -->
</div>
</el-splitter-panel>
</el-splitter>
</div>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
} from "vue";
import { getPkRecord, queryPkDetail } from "@/api/account";
import { ElMessage } from "element-plus";
import { getPromiseStorage } from "@/utils/storage.js";
import { TimestamptolocalTime } from "@/utils/timeConversion.js";
import { goldCoinCalculation } from "@/utils/goldCoinCalculation.js";
//
import { useI18n } from "vue-i18n";
const { t } = useI18n();
window["$t"] = t;
//
const user = ref(null); // 用户信息
const refname = ref("");
const segmentedvalue = ref(1);
const options = [
{
label: t('ThePKIPosted'),
// gj发布的PK
value: 1,
icon: require("@/assets/Publish.png"),
SelectedIcon: require("@/assets/PublishSelected.png"),
},
{
label: t('ThePKIInvited'),
// gj邀请的PK
value: 2,
icon: require("@/assets/Invitation.png"),
SelectedIcon: require("@/assets/InvitationSelected.png"),
},
];
const list = ref([]); // PK记录列表
const page = ref(0); // 页数
const IPKPostedData = ref([]); //我发布的PK
const InvitationData = ref([]); //我邀请的PK
const selectedData = ref(null); //被选中的PK数据
const fetchDetailPkDataWithId = ref([]); // PK详情数据
//选中PK数据
function detail(item) {
selectedData.value = item;
queryPkDetail({
id: item.id,
}).then((res) => {
fetchDetailPkDataWithId.value = res;
}).catch((err) => {});
}
//切换选项卡
function optionsclick(value) {
segmentedvalue.value = value;
selectedData.value = null;
if (value === 1) {
list.value = IPKPostedData.value;
} else {
list.value = InvitationData.value;
}
}
//获取PK记录列表
function PkRecord(type) {
getPkRecord({
type: type,
userId: user.value.id,
page: page.value,
size: 10,
}).then((res) => {
console.log(res);
if (type === 1) {
IPKPostedData.value.push(...res);
if (segmentedvalue.value === type) {
list.value = IPKPostedData.value;
}
} else {
InvitationData.value.push(...res);
if (segmentedvalue.value === type) {
list.value = InvitationData.value;
}
}
}).catch((err) => {});
}
// 加载更多
function load() {}
// 组件挂载完成后执行
onMounted(() => {
getPromiseStorage("user")
.then((res) => {
user.value = res;
PkRecord(1);
PkRecord(2);
})
.catch((err) => {
console.log(err);
});
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped lang="less">
.pk-record {
width: 100%;
height: 100%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.demo-panel {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.notdata {
width: 99%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
border-left: #03aba8 solid 1px;
}
.chatNotDeta-text {
font-size: 20px;
color: #03aba8;
font-weight: bold;
text-align: center;
}
.custom-style {
width: 90%;
height: 150px;
display: flex;
.Options {
&:first-child {
&.active {
animation: slideInFromRight 0.3s ease;
}
}
&:last-child {
&.active {
animation: slideInFromLeft 0.3s ease;
}
}
}
}
@keyframes slideInFromLeft {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0);
}
}
@keyframes slideInFromRight {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
.Options {
width: 178px;
height: 100px;
margin-right: 120px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
&::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 5px;
background-color: #03aba8;
transform: translateX(-100%);
transition: transform 0.3s ease;
}
&.active {
&::after {
transform: translateX(0);
}
}
&.left-to-right {
&::after {
transform: translateX(-100%);
}
&.active::after {
transform: translateX(0);
}
}
&.right-to-left {
&::after {
transform: translateX(100%);
}
&.active::after {
transform: translateX(0);
}
}
display: flex;
}
.Options {
width: 300px;
height: 100px;
margin-right: 120px;
display: flex;
align-items: center;
justify-content: center;
align-items: center;
justify-content: center;
}
.Options-icon {
width: 30px;
height: 30px;
margin-right: 15px;
}
.Options-label {
font-size: 24px;
}
.list {
width: 100%;
height: calc(100% -150px);
}
.chatNotDeta{
width: 100%;
height: calc(100% -150px);
background-color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
}
.chatNotDeta-text{
font-size: 20px;
color: #03aba8;
font-weight: bold;
}
.list-item {
width: 100%;
height: 150px;
margin-bottom: 15px;
margin-top: 15px;
display: flex;
justify-content: center;
align-items: center;
}
.list-content {
width: 90%;
height: 100%;
border-radius: 20px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
background-image: url(../../assets/PKbackground.png);
background-size: 100% 100%;
transition: all 0.4s ease;
display: flex;
align-items: center;
justify-content: space-around;
}
.list-content:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.8);
transform: scale(1.08);
opacity: 0.8;
}
.list-content:active {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);
transform: scale(1.05);
opacity: 0.9;
}
.vs {
width: 50px;
height: 50px;
}
.information {
width: 40%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-around;
}
.avatar {
width: 100px;
height: 100px;
border-radius: 50%;
background-color: #fff;
}
.avatar-img {
width: 100%;
height: 100%;
border-radius: 50%;
}
.content-left {
width: calc(100% - 150px);
height: 80%;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.content-right {
width: calc(100% - 150px);
height: 80%;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-end;
}
.name {
color: @font-color;
font-size: 18px;
font-weight: bold;
white-space: nowrap; /* 防止换行 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis;
}
.time {
color: @Prompt-text-color;
font-size: 14px;
}
.gold {
color: @font-color;
font-size: 14px;
display: flex;
align-items: center;
}
.goldimg {
width: 38px;
height: 38px;
margin-right: 13px;
}
.gold-num {
color: @font-color;
font-size: 18px;
font-weight: bold;
}
.PKbothinfo {
width: 100%;
height: 70px;
display: flex;
}
.PKbothinfo-left {
width: 50%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
border-right: 1px solid @border-color;
}
.PKbothinfo-right {
width: 50%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
border-left: 1px solid @border-color;
}
.PKbothinfo-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #fff;
}
.PKbothinfo-gold-num {
font-size: 12px;
font-weight: bold;
color: @font-color;
}
.PKbothinfo-center {
width: 100%;
height: calc(100% - 70px);
display: flex;
flex-direction: column;
align-items: center;
/* 隐藏滚动条外观 */
scrollbar-width: none; /* Firefox */
scrollbar-color: transparent; /* Firefox */
/* Webkit 浏览器Chrome/Safari */
&::-webkit-scrollbar {
display: none; /* 直接隐藏滚动条 */
/* 或者透明处理 */
/* width: 0; height: 0; */
/* background: transparent; */
}
}
.PKbothinfolist-item {
width: 100%;
height: 100px;
margin-bottom: 8px;
margin-top: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.PKbothinfolist-content {
width: 95%;
height: 100%;
border-radius: 10px;
background-color: rgb(255, 255, 255);
transition: all 0.4s ease;
display: flex;
align-items: center;
}
.PKbothinfolist-content:hover {
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
transform: scale(1.02);
opacity: 0.8;
}
.gold-left {
width: 50%;
height: 100%;
color: @font-color;
font-size: 14px;
font-weight: bold;
text-align: center;
line-height: 100px;
border-right: 2px solid @lose-color;
border-bottom-left-radius: 10px;
border-top-left-radius: 10px;
background-color: @lose-color;
}
.gold-right {
width: 50%;
height: 100%;
color: @font-color;
font-size: 14px;
font-weight: bold;
text-align: center;
line-height: 100px;
border-left: 2px solid @win-color;
border-bottom-right-radius: 10px;
border-top-right-radius: 10px;
background-color: @win-color;
}
.particularsAvatar {
width: 100%;
height: 135px;
display: flex;
align-items: flex-end;
justify-content: space-around;
}
.particularsAvatar-avatar {
width: 85px;
height: 85px;
border-radius: 50px;
background-color: #e9e9e9;
}
.altogether {
width: 100%;
height: 55px;
margin-top: 18px;
display: flex;
align-items: center;
justify-content: center;
}
.altogethercard {
width: 90%;
height: 100%;
border-radius: 50px;
background-image: url(@/assets/InTotal.png);
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: space-around;
}
.altogether-icon {
width: 50px;
height: 40px;
}
.altogether-num {
width: 40%;
text-align: center;
color: #333333;
font-size: 20px;
font-weight: bold;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.goldlist {
width: 90%;
margin-top: 10px;
height: calc(100% - 200px);
display: flex;
justify-content: space-between;
}
.goldlist-card {
width: 48%;
height: 98%;
border-radius: 20px;
display: flex;
flex-direction: column;
align-items: center;
overflow: auto;
scrollbar-width: none; /* Firefox */
}
.goldlist-card-left {
background-color: #dffefc;
border: 1px solid #86e1e3;
}
.goldlist-card-right {
background-color: #fbece9;
border: 1px solid #f4d0c9;
}
.goldlist-list {
width: 85%;
padding-left: 2.5%;
padding-right: 2.5%;
height: 57px;
margin-bottom: 10px;
text-align: center;
line-height: 57px;
font-size: 20px;
font-weight: bold;
color: #03aba8;
border-radius: 10px;
white-space: nowrap;
// overflow-y: hidden;
text-overflow: ellipsis;
}
.goldlist-list:first-child {
margin-top: 20px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,176 @@
<template>
<!-- 积分列表 -->
<div class="points-list-container">
<div class="points-list-title">
<img class="points-icon" src="@/assets/Points.png" mode="scaleToFill" />
<div class="points-text">
{{t('MyPoints')+user.points }}
<!-- gj我的积分 -->
<div class="points-alwaysnum"></div>
</div>
</div>
<div class="points-list" style="overflow: auto" v-if="pointsList.length !== 0">
<div class="points-list-item" v-for="(item, index) in pointsList" :key="index">
<div
class="points-list-content"
:style="{ background: item.status == 1 ? '#DFFEFC' : '#FBECE9' }"
>
<div class="Event">
{{ item.info }}
</div>
<div class="points-num">
{{ item.number }}
</div>
<div class="time">
{{ TimestamptolocalTime(item.time * 1000) }}
</div>
</div>
</div>
<div class="chatNotDeta" v-if="pointsList.length === 0">
<div class="chatNotDeta-text">{{ t('YouDonHaveAnyPointsRecordsYet') }}</div>
<!-- gj您还没有积分记录-->
</div>
</div>
</div>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
} from "vue";
import { getIntegralDetail } from "@/api/account";
import { ElMessage } from "element-plus";
import { getPromiseStorage } from "@/utils/storage.js";
import { TimestamptolocalTime } from "../../utils/timeConversion";
//
import { useI18n } from "vue-i18n";
const { t } = useI18n();
window["$t"] = t;
//
const pointsList = ref([]);
const user = ref({}); //用户信息
const page = ref(0);
//获取积分数据
function getPointsList() {
getIntegralDetail({
page: page.value,
size: 30,
userId: user.value.id,
})
.then((res) => {
console.log(res);
pointsList.value.push(...res);
})
.catch((err) => {});
}
// 组件挂载完成后执行
onMounted(() => {
getPromiseStorage("user")
.then((res) => {
user.value = res;
getPointsList();
})
.catch((err) => {
console.log(err);
});
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
const refname = ref("");
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
</script>
<style scoped lang="less">
.points-list-container {
width: 100%;
height: 100%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.points-list {
width: 100%;
height: calc(100% - 70px);
}
.chatNotDeta {
width: 100%;
height: calc(100% - 70px);
background-color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
}
.chatNotDeta-text {
font-size: 20px;
color: #03aba8;
font-weight: bold;
}
.points-list-title {
width: 100%;
height: 70px;
display: flex;
justify-content: center;
align-items: center;
}
.points-text {
margin-top: 10px;
color: #666666;
font-size: 24px;
display: flex;
align-items: center;
}
.points-alwaysnum {
color: #333333;
font-size: 24px;
font-weight: bold;
}
.points-icon {
width: 52px;
height: 52px;
margin-right: 18px;
}
.points-list-item {
width: 100%;
height: 60px;
margin-top: 10px;
margin-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.points-list-content {
width: 90%;
height: 100%;
border-radius: 10px;
display: flex;
justify-content: space-around;
align-items: center;
}
.Event {
color: @Supplementary-text-color;
font-size: 16px;
}
.points-num {
color: @font-color;
font-size: 18px;
font-weight: bold;
}
.time {
color: @Prompt-text-color;
font-size: 16px;
}
</style>

250
src/i18n/en/country.json Normal file
View File

@@ -0,0 +1,250 @@
{
"AD": "Andorra",
"AE": "United Arab Emirates",
"AF": "Afghanistan",
"AG": "Antigua and Barbuda",
"AI": "Anguilla",
"AL": "Albania",
"AM": "Armenia",
"AO": "Angola",
"AQ": "Antarctica",
"AR": "Argentina",
"AS": "American Samoa",
"AT": "Austria",
"AU": "Australia",
"AW": "Aruba",
"AX": "Åland Islands",
"AZ": "Azerbaijan",
"BA": "Bosnia and Herzegovina",
"BB": "Barbados",
"BD": "Bangladesh",
"BE": "Belgium",
"BF": "Burkina Faso",
"BG": "Bulgaria",
"BH": "Bahrain",
"BI": "Burundi",
"BJ": "Benin",
"BL": "Saint Barthélemy",
"BM": "Bermuda",
"BN": "Brunei Darussalam",
"BO": "Bolivia",
"BQ": "Bonaire, Sint Eustatius and Saba",
"BR": "Brazil",
"BS": "Bahamas",
"BT": "Bhutan",
"BV": "Bouvet Island",
"BW": "Botswana",
"BY": "Belarus",
"BZ": "Belize",
"CA": "Canada",
"CC": "Cocos (Keeling) Islands",
"CD": "Democratic Republic of the Congo",
"CF": "Central African Republic",
"CG": "Congo",
"CH": "Switzerland",
"CI": "Côte d'Ivoire",
"CK": "Cook Islands",
"CL": "Chile",
"CM": "Cameroon",
"CN": "China",
"CO": "Colombia",
"CR": "Costa Rica",
"CU": "Cuba",
"CV": "Cabo Verde",
"CW": "Curaçao",
"CX": "Christmas Island",
"CY": "Cyprus",
"CZ": "Czech Republic",
"DE": "Germany",
"DG": "Diego Garcia",
"DJ": "Djibouti",
"DK": "Denmark",
"DM": "Dominica",
"DO": "Dominican Republic",
"DZ": "Algeria",
"EC": "Ecuador",
"EE": "Estonia",
"EG": "Egypt",
"EH": "Western Sahara",
"ER": "Eritrea",
"ES": "Spain",
"ET": "Ethiopia",
"FI": "Finland",
"FJ": "Fiji",
"FK": "Falkland Islands (Malvinas)",
"FM": "Micronesia",
"FO": "Faroe Islands",
"FR": "France",
"GA": "Gabon",
"GB": "United Kingdom",
"GD": "Grenada",
"GE": "Georgia",
"GF": "French Guiana",
"GG": "Guernsey",
"GH": "Ghana",
"GI": "Gibraltar",
"GL": "Greenland",
"GM": "Gambia",
"GN": "Guinea",
"GP": "Guadeloupe",
"GQ": "Equatorial Guinea",
"GR": "Greece",
"GS": "South Georgia and the South Sandwich Islands",
"GT": "Guatemala",
"GU": "Guam",
"GW": "Guinea-Bissau",
"GY": "Guyana",
"HK": "Hong Kong",
"HM": "Heard Island and McDonald Islands",
"HN": "Honduras",
"HR": "Croatia",
"HT": "Haiti",
"HU": "Hungary",
"ID": "Indonesia",
"IE": "Ireland",
"IL": "Israel",
"IM": "Isle of Man",
"IN": "India",
"IO": "British Indian Ocean Territory",
"IQ": "Iraq",
"IR": "Iran",
"IS": "Iceland",
"IT": "Italy",
"JE": "Jersey",
"JM": "Jamaica",
"JO": "Jordan",
"JP": "Japan",
"KE": "Kenya",
"KG": "Kyrgyzstan",
"KH": "Cambodia",
"KI": "Kiribati",
"KM": "Comoros",
"KN": "Saint Kitts and Nevis",
"KP": "North Korea",
"KR": "South Korea",
"KW": "Kuwait",
"KY": "Cayman Islands",
"KZ": "Kazakhstan",
"LA": "Lao People's Democratic Republic",
"LB": "Lebanon",
"LC": "Saint Lucia",
"LI": "Liechtenstein",
"LK": "Sri Lanka",
"LR": "Liberia",
"LS": "Lesotho",
"LT": "Lithuania",
"LU": "Luxembourg",
"LV": "Latvia",
"LY": "Libya",
"MA": "Morocco",
"MC": "Monaco",
"MD": "Moldova",
"ME": "Montenegro",
"MF": "Saint Martin",
"MG": "Madagascar",
"MH": "Marshall Islands",
"MK": "North Macedonia",
"ML": "Mali",
"MM": "Myanmar",
"MN": "Mongolia",
"MO": "Macao",
"MP": "Northern Mariana Islands",
"MQ": "Martinique",
"MR": "Mauritania",
"MS": "Montserrat",
"MT": "Malta",
"MU": "Mauritius",
"MV": "Maldives",
"MW": "Malawi",
"MX": "Mexico",
"MY": "Malaysia",
"MZ": "Mozambique",
"NA": "Namibia",
"NC": "New Caledonia",
"NE": "Niger",
"NF": "Norfolk Island",
"NG": "Nigeria",
"NI": "Nicaragua",
"NL": "Netherlands",
"NO": "Norway",
"NP": "Nepal",
"NR": "Nauru",
"NU": "Niue",
"NZ": "New Zealand",
"OM": "Oman",
"PA": "Panama",
"PE": "Peru",
"PF": "French Polynesia",
"PG": "Papua New Guinea",
"PH": "Philippines",
"PK": "Pakistan",
"PL": "Poland",
"PM": "Saint Pierre and Miquelon",
"PN": "Pitcairn",
"PR": "Puerto Rico",
"PS": "Palestine",
"PT": "Portugal",
"PW": "Palau",
"PY": "Paraguay",
"QA": "Qatar",
"RE": "Réunion",
"RO": "Romania",
"RS": "Serbia",
"RU": "Russia",
"RW": "Rwanda",
"SA": "Saudi Arabia",
"SB": "Solomon Islands",
"SC": "Seychelles",
"SD": "Sudan",
"SE": "Sweden",
"SG": "Singapore",
"SI": "Slovenia",
"SJ": "Svalbard and Jan Mayen",
"SK": "Slovakia",
"SL": "Sierra Leone",
"SM": "San Marino",
"SN": "Senegal",
"SO": "Somalia",
"SR": "Suriname",
"SS": "South Sudan",
"ST": "Sao Tome and Principe",
"SV": "El Salvador",
"SX": "Sint Maarten",
"SY": "Syria",
"SZ": "Eswatini",
"TC": "Turks and Caicos Islands",
"TD": "Chad",
"TF": "French Southern Territories",
"TG": "Togo",
"TH": "Thailand",
"TJ": "Tajikistan",
"TK": "Tokelau",
"TL": "Timor-Leste",
"TM": "Turkmenistan",
"TN": "Tunisia",
"TO": "Tonga",
"TR": "Turkey",
"TT": "Trinidad and Tobago",
"TV": "Tuvalu",
"TW": "Taiwan",
"TZ": "Tanzania",
"UA": "Ukraine",
"UG": "Uganda",
"UM": "United States Minor Outlying Islands",
"US": "United States",
"UY": "Uruguay",
"UZ": "Uzbekistan",
"VA": "Holy See",
"VC": "Saint Vincent and the Grenadines",
"VE": "Venezuela",
"VG": "Virgin Islands (British)",
"VI": "Virgin Islands (U.S.)",
"VN": "Vietnam",
"VU": "Vanuatu",
"WS": "Samoa",
"YE": "Yemen",
"YT": "Mayotte",
"ZA": "South Africa",
"ZM": "Zambia",
"ZW": "Zimbabwe"
}

11
src/i18n/en/index.js Normal file
View File

@@ -0,0 +1,11 @@
import index from './index.json'
import country from './country.json'
const global = {
logout: 'Logout',
}
export default {
...global,
...index,
...country
}

182
src/i18n/en/index.json Normal file
View File

@@ -0,0 +1,182 @@
{
"login": "Login",
"Login": "Log in.....",
"LoginFailed": "Login failed",
"Welcome_to_login": "Welcome to login",
"PleaseEnterEmailOrUsername": "Please enter email or username",
"PleaseEnterPassword": "Please enter password",
"WechatMiniProgramLogin": "Wechat Mini Program Login",
"LogInByScanningTheQRCodeWithTheWechatMini-program": "Log in by scanning the QR code with the wechat mini-program",
"DontHaveAnAccountYet": "Don't have an account yet?",
"Register": "Register",
"AlreadyHaveAnAccount": "Already have an account?",
"ForgotPassword": "Forgot password?",
"InSiteMessage": "In-site message",
"Mine": "Mine",
"SignIn": "Sign in",
"message": "message",
"Settings": "Settings",
"ContactCustomerService": "Contact customer service (click to copy the email address)",
"Logout": "Log out",
"ReleaseANewPK": "Release a new PK",
"ModifyThePKInformation": "Modify the PK information",
"PleaseEnterTheNameOfTheHost": "Please enter the name of the host",
"ChooseMyStreamer": "Choose mine",
"PleaseSelectACountry": "Please select a country",
"PleaseSelectGender": "Please select gender",
"SelectThePKTime": "Select the PK time",
"NumberOfGoldCoins": "The number of gold coins (in K)",
"Session": "Session",
"PleaseEnterTheRemarks": "Please enter the remarks (optional)",
"Confirm": "Confirm",
"Reset": "Reset",
"Cancel": "Cancel",
"SelectTheStreamersFromMyStreamerLibrary": "Select the streamers from my streamer library",
"man": "man",
"woman": "woman",
"ClickOnTheAvatarAboveToModifyIt": "Click on the avatar above to modify it",
"ClickToEnterAndModifyYourNickname": "Click to enter and modify your nickname",
"ResendTheEmailTo": "Resend the email to",
"ForVerification": "for verification",
"ModifyTheEmailAddress": "Modify the email address",
"ChangePassword": "Change password",
"PleaseEnterTheOldPassword": "Please enter the old password",
"PleaseEnterTheNewPassword": "Please enter the new password",
"PleaseEnterTheConfirmPassword": "Please enter the confirm password",
"PleaseEnterTheVerificationCodeOfYourOldEmailAddress": "Please enter the verification code of your old email address",
"PleaseEnterYourNewEmailAddress": "Please enter your new email address",
"Modify": "Modify",
"PleaseEnterTheEmailVerificationCode": "Please enter the email verification code",
"GetTheVerificationCode": "verification code",
"Resend": "Resend",
"PleaseEnterAValidEmailAddress": "Please enter a valid email address",
"TheModificationIsSuccessfulPleaseVerifyYourEmailInTheNewEmailAddress": "The modification is successful. Please verify your email in the new email address.",
"YouAreTemporarilyUnableToModifyYourEmailAddressPleaseVerifyYourEmailFirstIfItHasBeenVerifiedPleaseRefreshThePage": "You are temporarily unable to modify your email address. Please verify your email first if it has been verified. Please refresh the page.",
"YouAreTemporarilyUnableToChangeYourPasswordPleaseVerifyYourEmailFirstIfYouHaveAlreadyVerifiedItPleaseRefreshThePage": "You are temporarily unable to change your password. Please verify your email first if you have already verified it. Please refresh the page.",
"ThePasswordMustContainBothUpperAndLowerCaseLettersAndNumbersAndBe6To16CharactersLong": "The password must contain both upper and lower case letters and numbers and be 6 to 16 characters long.",
"TheTwoPasswordEntriesAreInconsistent": "The two password entries are inconsistent.",
"ModificationSuccessful": "Modification successful.",
"PleaseUploadImagesInjpgPngFormat": "Please upload images in jpg/png format.",
"TheSizeOfThePictureCannotExceed2M": "The size of the picture cannot exceed 2M.",
"CheckTheStreamerAt": "Check the streamer at...",
"QuerySuccessful": "Query successful.",
"ThePKTimeCannotBeEarlierThanTheCurrentTime": "The PK time cannot be earlier than the current time.",
"PleaseEnterTheNumberOfGoldCoins": "Please enter the number of gold coins",
"PleaseEnterTheSession": "Please enter the session",
"PublishedSuccessfully": "Published successfully.",
"PleaseSelectTheHost": "Please select the host",
"TheEmailAddressHasBeenCopiedToTheClipboard": "The email address has been copied to the clipboard.",
"CopyFailed": "Copy failed.",
"LogoutSuccessful": "Logout successful.",
"CheckInSuccessful": "Check-in successful.",
"YourEmailHasNotBeenVerifiedPleaseGoToTheSettingsToVerifyYourEmailIfItHasBeenVerifiedPleaseRefreshThePage": "Your email has not been verified. Please go to the settings to verify your email if it has been verified. Please refresh the page.",
"NewNewsHasArrived": "New news has arrived.",
"YouHave": "You have",
"unreadMessages": "unread messages",
"AccountInformation": "Account information",
"EmailVerification": "Email verification",
"RetrieveThePassword": "Retrieve the password",
"PleaseEnterTheEmailAddressForWhichYouNeedToRetrieveYourPassword": "Please enter the email address for which you need to retrieve your password",
"Return": "Return",
"NextStep": "Next step",
"AVerificationEmailHasBeenSentToYour": "A verification email has been sent to your ",
"emailAddressPleaseClickOnTheLinkInTheEmailToCompleteTheRetrieval": "email address. Please click on the link in the email to complete the retrieval.",
"emailAddressPleaseClickOnTheLinkInTheEmailToCompleteTheRegistration": "email address. Please click on the link in the email to complete the registration.",
"PreviousStep": "Previous step",
"ResendIn": "Resend in",
"seconds": "seconds",
"ResendTheEmail": "Resend the email",
"ReturnToLogin": "Return to login",
"TheEmailWasSentSuccessfully": "The email was sent successfully.",
"TheEmailFailedToSend": "The email failed to send.",
"PleaseEnterYourEmailAddress": "Please enter your email address",
"PleaseEnterYourUsername": "Please enter your username",
"PleaseEnterThePasswordAgain": "Please enter the password again",
"TheLengthOfAUsernameCannotBeLessThan2CannotExceed16AndCannotBeEmpty": "The length of a username cannot be less than 2, cannot exceed 16, and cannot be empty.",
"TheUsernameAlreadyExists": "The username already exists.",
"TheUsernameIsAvailable": "The username is available.",
"TheEmailHasBeenResold": "The email has been resold.",
"ActivationInProgressPleaseWaitAMoment": "Activation in progress. Please wait a moment.",
"ActivationSuccessfulPleaseLogIn": "Activation successful. Please log in.",
"ActivationFailed": "Activation failed.",
"YourEmailAddressIs": "Your email address is",
"ItHasNotBeenActivatedYetPleaseClickTheActivationLinkInYourEmailToActivateYourAccount": ". It has not been activated yet. Please click the activation link in your email to activate your account.",
"ResendTheActivationEmail": "Resend the activation email",
"TheActivationEmailHasBeenSentPleaseCheckItInTime": "The activation email has been sent. Please check it in time.",
"TheActivationEmailSendingFailedPleaseTryAgainLater": "The activation email sending failed. Please try again later.",
"ResetThePassword": "Reset the password",
"PasswordResetSuccessful": "Password reset successful.",
"VerificationInProgressPleaseWaitAMoment": "Verification in progress. Please wait a moment.",
"VerificationSuccessfulPleaseLogIn": "Verification successful. Please log in.",
"VerificationFailed": "Verification failed.",
"PKHall": "PK Hall",
"TodayPK": "Today PK",
"MinimumNumberOfGoldCoins": "Minimum number of gold coins (in K)",
"MaximumNumberOfGoldCoins": "Maximum number of gold coins (in K)",
"MinimumPKTime": "Minimum PK time",
"MaximumPKTime": "Maximum PK time",
"to": "to",
"Country": "Country",
"Gender": "Gender",
"PKTime": "PK time (local time) :",
"GoldCoin": "Gold coin :",
"session": "Session:",
"match": "Match",
"Send": "Send",
"SelectTheHostOnTheRightToChatImmediately": "Select the host on the right to chat immediately.",
"PKInvitation": "PK invitation",
"ChooseTheOpponentPKStreamer": "Choose the opponent's PK streamer",
"ChooseYourOwnPKStreamer": "Choose your own PK streamer",
"Hint": "Hint",
"AreYouSureYouWantToSendAPKInvitationMessageAfterASuccessfulInvitationThePkCannotBeModifiedOrDeletedPleasePaution": "Are you sure you want to send a PK invitation message? After a successful invitation, the pk cannot be modified or deleted. Please operate with caution!",
"PleaseSelectTheStreamerWhoWillParticipateInThePK": "Please select the streamer who will participate in the PK",
"PleaseSelectOurStreamerToParticipateInThePK": "Please select our streamer to participate in the PK",
"PleaseSelectTheOtherPartyAndYourOwnStreamer": "Please select the other party and your own streamer",
"PleaseEnterTheMaximumNumberOfGoldCoins": "Please enter the maximum number of gold coins",
"PleaseEnterTheMinimumNumberOfGoldCoins": "Please enter the minimum number of gold coins",
"TheStartTimeCannotBeLessThanTheCurrentTime": "The start time cannot be less than the current time",
"AnchorLibrary": "Anchor Library",
"PKInformation": "PK Information",
"MyPKRecord": "My PK record",
"PointsList": "Points list",
"MyPoints": "My points :",
"YouDonHaveAnyPointsRecordsYet": "You don't have any points records yet !",
"ActualNumberOfGoldCoins": "Actual number of gold coins :",
"YouDonHaveAPKRecordYet": "You don't have a PK record yet !",
"InTotal": "In total :",
"The": "The",
"THInning": "TH inning",
"SelectRecordOnTheRightToViewDetailsImmediately": "Select Record on the right to view details immediately",
"ThePKIPosted": "The PK I posted",
"ThePKIInvited": "The PK I invited",
"YoudonHaveAnyPKInformationYetHurryUpAndAddIt": "You don't have any PK information yet. Hurry up and add it!",
"ConfirmTheDeletionOfThisStreamerPKInformation": "Confirm the deletion of this streamer PK information?",
"TopPosition": "Top position",
"TopPromptOnTheHomepage": "After being pinned to the top, the user will be pinned to the top of the home page and enjoy the privilege of being pinned for a specified period of time. If the pinned duration is less than one hour, the system will calculate the points deduction as one hour. Please select the pinned duration:",
"PleaseSelectThePinnedDuration": "Please select the pinned duration",
"UnpinTheTopPrompt": "Are you sure you want to cancel the pinning? After the pinning is cancelled, the user will no longer enjoy the pinning privilege, but can still continue to participatePK challenge. If the pinned duration is less than one hour, the system will calculate the points deduction as one hour. After cancellation, the remaining points will be returned in proportion to the actual usage time. Is this operation confirmed to be carried out?",
"Unpinned": "Unpinned",
"hour": "hour",
"Expired": "Expired",
"TopPlacementSuccessful": "Top placement successful.",
"DeletedSuccessfully": "Deleted successfully.",
"YouDonHaveALiveStreamerYetHurryUpAndAddOne": "You don't have a live-streamer yet. Hurry up and add one!",
"AddMyStreamer": "Add my streamer",
"ModifyMyStreamer": "Modify my streamer",
"ConfirmTheDeletionOfThisStreamer": "Confirm the deletion of this streamer?",
"AddedSuccessfully": "Added successfully.",
"Search": "Search",
"Playing": "Playing...",
"Note": "Note:",
"agree": "agree",
"Refuse": "Refuse",
"HaveAgreedToTheInvitation": "have agreed to the invitation.",
"HaveRefusedTheInvitation": "have refused the invitation.",
"WaitForTheOtherPartyResponse": "Wait for the other party's response.",
"AfterASuccessfulInvitationThePKCannotBeModifiedOrDeletedPleaseOperateWithCaution":"After a successful invitation, the pk cannot be modified or deleted. Please operate with caution!",
"AreYouSureYouWantToDeclineThisInvitation":"Are you sure you want to decline this invitation?",
"AgreeToSuccess":"Agree to success",
"RefuseSuccess":"Refuse success",
"YourAccountHasNotBeenActivatedYetAndMayBeSubjectToRestrictionsPleaseActivateItInTime":"Your account has not been activated yet and may be subject to restrictions. Please activate it in time!",
"Activate":"Activate"
}

27
src/i18n/index.js Normal file
View File

@@ -0,0 +1,27 @@
//语言
// import { lang } from '@/settings/designSetting'
import { createI18n } from 'vue-i18n' //引入vue-i18n组件
import zh from './zh/index'
import en from './en/index'
import { setStorage , getStorage } from '@/utils/storage.js';
const i18n = createI18n({
legacy: false,
globalInjection:true,
locale: getStorage('language') || "ZH", // Default language
fallbackLocale: "EN", // Fallback to English if translation missing
messages: {
ZH: zh,
EN: en
}
})
// Export function to change language
export function setLocale(lang) {
if (['ZH', 'EN'].includes(lang)) {
i18n.global.locale.value = lang
setStorage('language', lang)
}
}
export default i18n

250
src/i18n/zh/country.json Normal file
View File

@@ -0,0 +1,250 @@
{
"AD": "安道尔",
"AE": "阿拉伯联合酋长国",
"AF": "阿富汗",
"AG": "安提瓜和巴布达",
"AI": "安圭拉",
"AL": "阿尔巴尼亚",
"AM": "亚美尼亚",
"AO": "安哥拉",
"AQ": "南极洲",
"AR": "阿根廷",
"AS": "美属萨摩亚",
"AT": "奥地利",
"AU": "澳大利亚",
"AW": "阿鲁巴",
"AX": "奥兰群岛",
"AZ": "阿塞拜疆",
"BA": "波斯尼亚和黑塞哥维那",
"BB": "巴巴多斯",
"BD": "孟加拉国",
"BE": "比利时",
"BF": "布基纳法索",
"BG": "保加利亚",
"BH": "巴林",
"BI": "布隆迪",
"BJ": "贝宁",
"BL": "圣巴泰勒米",
"BM": "百慕大群岛",
"BN": "文莱达鲁萨兰国",
"BO": "玻利维亚",
"BQ": "博奈尔、圣尤斯特歇斯和萨巴",
"BR": "巴西",
"BS": "巴哈马",
"BT": "不丹",
"BV": "布韦岛",
"BW": "博茨瓦纳",
"BY": "白俄罗斯",
"BZ": "伯利兹",
"CA": "加拿大",
"CC": "科科斯(基林)群岛",
"CD": "刚果民主共和国",
"CF": "中非共和国",
"CG": "刚果共和国",
"CH": "瑞士",
"CI": "科特迪瓦",
"CK": "库克群岛",
"CL": "智利",
"CM": "喀麦隆",
"CN": "中国",
"CO": "哥伦比亚",
"CR": "哥斯达黎加",
"CU": "古巴",
"CV": "佛得角",
"CW": "库拉索",
"CX": "圣诞岛",
"CY": "塞浦路斯",
"CZ": "捷克共和国",
"DE": "德国",
"DG": "迪戈加西亚岛",
"DJ": "吉布提",
"DK": "丹麦",
"DM": "多米尼克",
"DO": "多米尼加共和国",
"DZ": "阿尔及利亚",
"EC": "厄瓜多尔",
"EE": "爱沙尼亚",
"EG": "埃及",
"EH": "西撒哈拉",
"ER": "厄立特里亚",
"ES": "西班牙",
"ET": "埃塞俄比亚",
"FI": "芬兰",
"FJ": "斐济",
"FK": "福克兰群岛",
"FM": "密克罗尼西亚",
"FO": "法罗群岛",
"FR": "法国",
"GA": "加蓬",
"GB": "英国",
"GD": "格林纳达",
"GE": "格鲁吉亚",
"GF": "法属圭亚那",
"GG": "根西岛",
"GH": "加纳",
"GI": "直布罗陀",
"GL": "格陵兰",
"GM": "冈比亚",
"GN": "几内亚",
"GP": "瓜德罗普",
"GQ": "赤道几内亚",
"GR": "希腊",
"GS": "南乔治亚和南桑德威奇群岛",
"GT": "危地马拉",
"GU": "关岛",
"GW": "几内亚比绍",
"GY": "圭亚那",
"HK": "中国香港特别行政区",
"HM": "赫德岛和麦克唐纳群岛",
"HN": "洪都拉斯",
"HR": "克罗地亚",
"HT": "海地",
"HU": "匈牙利",
"ID": "印度尼西亚",
"IE": "爱尔兰",
"IL": "以色列",
"IM": "马恩岛",
"IN": "印度",
"IO": "英属印度洋领地",
"IQ": "伊拉克",
"IR": "伊朗",
"IS": "冰岛",
"IT": "意大利",
"JE": "泽西岛",
"JM": "牙买加",
"JO": "约旦",
"JP": "日本",
"KE": "肯尼亚",
"KG": "吉尔吉斯斯坦",
"KH": "柬埔寨",
"KI": "基里巴斯",
"KM": "科摩罗",
"KN": "圣基茨和尼维斯",
"KP": "朝鲜",
"KR": "韩国",
"KW": "科威特",
"KY": "开曼群岛",
"KZ": "哈萨克斯坦",
"LA": "老挝",
"LB": "黎巴嫩",
"LC": "圣卢西亚",
"LI": "列支敦士登",
"LK": "斯里兰卡",
"LR": "利比里亚",
"LS": "莱索托",
"LT": "立陶宛",
"LU": "卢森堡",
"LV": "拉脱维亚",
"LY": "利比亚",
"MA": "摩洛哥",
"MC": "摩纳哥",
"MD": "摩尔多瓦",
"ME": "黑山",
"MF": "圣马丁",
"MG": "马达加斯加",
"MH": "马绍尔群岛",
"MK": "北马其顿",
"ML": "马里",
"MM": "缅甸",
"MN": "蒙古",
"MO": "中国澳门特别行政区",
"MP": "北马里亚纳群岛",
"MQ": "马提尼克",
"MR": "毛里塔尼亚",
"MS": "蒙特塞拉特",
"MT": "马耳他",
"MU": "毛里求斯",
"MV": "马尔代夫",
"MW": "马拉维",
"MX": "墨西哥",
"MY": "马来西亚",
"MZ": "莫桑比克",
"NA": "纳米比亚",
"NC": "新喀里多尼亚",
"NE": "尼日尔",
"NF": "诺福克岛",
"NG": "尼日利亚",
"NI": "尼加拉瓜",
"NL": "荷兰",
"NO": "挪威",
"NP": "尼泊尔",
"NR": "瑙鲁",
"NU": "纽埃",
"NZ": "新西兰",
"OM": "阿曼",
"PA": "巴拿马",
"PE": "秘鲁",
"PF": "法属玻利尼西亚",
"PG": "巴布亚新几内亚",
"PH": "菲律宾",
"PK": "巴基斯坦",
"PL": "波兰",
"PM": "圣皮埃尔和密克隆群岛",
"PN": "皮特凯恩群岛",
"PR": "波多黎各",
"PS": "巴勒斯坦",
"PT": "葡萄牙",
"PW": "帕劳",
"PY": "巴拉圭",
"QA": "卡塔尔",
"RE": "留尼汪",
"RO": "罗马尼亚",
"RS": "塞尔维亚",
"RU": "俄罗斯",
"RW": "卢旺达",
"SA": "沙特阿拉伯",
"SB": "索罗门群岛",
"SC": "塞舌尔",
"SD": "苏丹",
"SE": "瑞典",
"SG": "新加坡",
"SI": "斯洛文尼亚",
"SJ": "斯瓦尔巴和扬马延",
"SK": "斯洛伐克",
"SL": "塞拉利昂",
"SM": "圣马利诺",
"SN": "塞内加尔",
"SO": "索马里",
"SR": "苏里南",
"SS": "南苏丹",
"ST": "圣多美和普林西比",
"SV": "萨尔瓦多",
"SX": "荷属圣马丁",
"SY": "叙利亚",
"SZ": "斯威士兰",
"TC": "特克斯和凯科斯群岛",
"TD": "乍得",
"TF": "法属南部领地",
"TG": "多哥",
"TH": "泰国",
"TJ": "塔吉克斯坦",
"TK": "托克劳群岛",
"TL": "东帝汶",
"TM": "土库曼斯坦",
"TN": "突尼斯",
"TO": "汤加",
"TR": "土耳其",
"TT": "特立尼达和多巴哥",
"TV": "图瓦卢",
"TW": "台湾",
"TZ": "坦桑尼亚",
"UA": "乌克兰",
"UG": "乌干达",
"UM": "美国本土外小岛屿",
"US": "美国",
"UY": "乌拉圭",
"UZ": "乌兹别克斯坦",
"VA": "梵蒂冈",
"VC": "圣文森特",
"VE": "委内瑞拉",
"VG": "英属维尔京群岛",
"VI": "美属维尔京群岛",
"VN": "越南",
"VU": "瓦努阿图",
"WS": "萨摩亚",
"YE": "也门",
"YT": "马约特岛",
"ZA": "南非",
"ZM": "赞比亚",
"ZW": "津巴布韦"
}

11
src/i18n/zh/index.js Normal file
View File

@@ -0,0 +1,11 @@
import index from './index.json'
import country from './country.json'
const global = {
logout: '退出登录',
}
export default {
...global,
...index,
...country
}

182
src/i18n/zh/index.json Normal file
View File

@@ -0,0 +1,182 @@
{
"login": "登录",
"Login": "登录中...",
"LoginFailed": "登录失败",
"Welcome_to_login": "欢迎登录",
"PleaseEnterEmailOrUsername": "请输入邮箱或用户名",
"PleaseEnterPassword": "请输入密码",
"WechatMiniProgramLogin": "微信小程序登录",
"LogInByScanningTheQRCodeWithTheWechatMini-program": "使用微信小程序扫描二维码登录",
"DontHaveAnAccountYet":"还没有账号?",
"Register": "注册",
"AlreadyHaveAnAccount": "已有账号?",
"ForgotPassword": "忘记密码?",
"InSiteMessage":"站内信",
"Mine":"我的",
"SignIn":"签到",
"message":"消息",
"Settings":"设置",
"ContactCustomerService":"联系客服(点击复制邮箱)",
"Logout":"退出登录",
"ReleaseANewPK":"发布新PK",
"ModifyThePKInformation":"修改 PK 信息",
"PleaseEnterTheNameOfTheHost":"请输入主播名称",
"ChooseMyStreamer":"选择我的主播",
"PleaseSelectACountry":"请选择国家",
"PleaseSelectGender":"请选择性别",
"SelectThePKTime":"请选择PK时间",
"NumberOfGoldCoins":"金币数单位为K",
"Session":"场次",
"PleaseEnterTheRemarks":"请输入备注(选填)",
"Confirm":"确认",
"Reset":"重置",
"Cancel":"取消",
"SelectTheStreamersFromMyStreamerLibrary":"选择我的主播库主播",
"man":"男",
"woman":"女",
"ClickOnTheAvatarAboveToModifyIt":"点击头像上方修改头像",
"ClickToEnterAndModifyYourNickname":"点击输入修改昵称",
"ResendTheEmailTo":"向",
"ForVerification":"重发邮箱验证",
"ModifyTheEmailAddress":"修改邮箱",
"ChangePassword":"修改密码",
"PleaseEnterTheOldPassword":"请输入旧密码",
"PleaseEnterTheNewPassword":"请输入新密码",
"PleaseEnterTheConfirmPassword":"请再次输入新密码",
"PleaseEnterTheVerificationCodeOfYourOldEmailAddress":"请输入旧邮箱的验证码",
"PleaseEnterYourNewEmailAddress":"请输入新邮箱地址",
"Modify":"修改",
"PleaseEnterTheEmailVerificationCode":"请输入邮箱验证码",
"GetTheVerificationCode":"获取验证码",
"Resend":"重发",
"PleaseEnterAValidEmailAddress":"请输入有效的邮箱地址",
"TheModificationIsSuccessfulPleaseVerifyYourEmailInTheNewEmailAddress":"修改成功,请至新邮箱中验证邮箱",
"YouAreTemporarilyUnableToModifyYourEmailAddressPleaseVerifyYourEmailFirstIfItHasBeenVerifiedPleaseRefreshThePage":"你暂时无法修改邮箱,请先验证邮箱,如果已经验证,请刷新页面",
"YouAreTemporarilyUnableToChangeYourPasswordPleaseVerifyYourEmailFirstIfYouHaveAlreadyVerifiedItPleaseRefreshThePage":"你暂时无法修改密码,请先验证邮箱,如果已经验证,请刷新页面",
"ThePasswordMustContainBothUpperAndLowerCaseLettersAndNumbersAndBe6To16CharactersLong":"密码必须包含大小写字母和数字,长度为6-16位",
"TheTwoPasswordEntriesAreInconsistent":"两次输入的密码不一致",
"ModificationSuccessful":"修改成功",
"PleaseUploadImagesInjpgPngFormat":"请上传 jpg/png 格式的图片",
"TheSizeOfThePictureCannotExceed2M":"图片大小不能超过 2M",
"CheckTheStreamerAt":"查询主播中...",
"QuerySuccessful":"查询成功",
"ThePKTimeCannotBeEarlierThanTheCurrentTime":"PK 时间不能早于当前时间",
"PleaseEnterTheNumberOfGoldCoins":"请输入金币数",
"PleaseEnterTheSession":"请输入场次",
"PublishedSuccessfully":"发布成功",
"PleaseSelectTheHost":"请选择主播",
"TheEmailAddressHasBeenCopiedToTheClipboard":"邮箱地址已复制到剪切板",
"CopyFailed":"复制失败",
"LogoutSuccessful":"退出登录成功",
"CheckInSuccessful":"签到成功",
"YourEmailHasNotBeenVerifiedPleaseGoToTheSettingsToVerifyYourEmailIfItHasBeenVerifiedPleaseRefreshThePage":"邮箱未验证,请至设置验证邮箱,如果已经验证请刷新页面",
"NewNewsHasArrived":"新消息到来",
"YouHave": "你有",
"unreadMessages":"封未读消息",
"AccountInformation":"账号信息",
"EmailVerification":"邮箱验证",
"RetrieveThePassword":"找回密码",
"PleaseEnterTheEmailAddressForWhichYouNeedToRetrieveYourPassword":"请输入需要找回密码的邮箱地址",
"Return":"返回",
"NextStep":"下一步",
"AVerificationEmailHasBeenSentToYour":"已向您的",
"emailAddressPleaseClickOnTheLinkInTheEmailToCompleteTheRetrieval":"邮箱发送了一封验证邮件,请点击邮件中的链接完成找回密码。",
"emailAddressPleaseClickOnTheLinkInTheEmailToCompleteTheRegistration":"邮箱发送了一封验证邮件,请点击邮件中的链接完成注册。",
"PreviousStep":"上一步",
"ResendIn":"",
"seconds":"秒后重发",
"ResendTheEmail":"重发邮件",
"ReturnToLogin":"返回登录",
"TheEmailWasSentSuccessfully":"邮件发送成功",
"TheEmailFailedToSend":"邮件发送失败",
"PleaseEnterYourEmailAddress":"请输入邮箱地址",
"PleaseEnterYourUsername":"请输入用户名",
"PleaseEnterThePasswordAgain":"请再次输入密码",
"TheLengthOfAUsernameCannotBeLessThan2CannotExceed16AndCannotBeEmpty":"用户名长度不能小于2不能大于16,不能为空",
"TheUsernameAlreadyExists":"用户名已存在",
"TheUsernameIsAvailable":"用户名可用",
"TheEmailHasBeenResold":"邮件已重新发送",
"ActivationInProgressPleaseWaitAMoment":"激活中,请稍候",
"ActivationSuccessfulPleaseLogIn":"激活成功,请登录",
"ActivationFailed":"激活失败",
"YourEmailAddressIs":"您的邮箱",
"ItHasNotBeenActivatedYetPleaseClickTheActivationLinkInYourEmailToActivateYourAccount":",尚未激活,请在您的邮箱中点击激活链接激活您的账",
"ResendTheActivationEmail":"重新发送激活邮件",
"TheActivationEmailHasBeenSentPleaseCheckItInTime":"激活邮件已发送,请注意查收",
"TheActivationEmailSendingFailedPleaseTryAgainLater":"激活邮件发送失败,请稍后再试",
"ResetThePassword":"重置密码",
"PasswordResetSuccessful":"密码重置成功",
"VerificationInProgressPleaseWaitAMoment":"验证中,请稍候",
"VerificationSuccessfulPleaseLogIn":"验证成功,请登录",
"VerificationFailed":"验证失败",
"PKHall":"PK大厅",
"TodayPK":"今日PK",
"MinimumNumberOfGoldCoins":"最小金币数单位为K",
"MaximumNumberOfGoldCoins":"最大金币数单位为K",
"MinimumPKTime":"最小PK时间",
"MaximumPKTime":"最大PK时间",
"to":"至",
"Country":"国家",
"Gender":"性别",
"PKTime":"PK时间本地时间:",
"GoldCoin":"金币:",
"session":"场次:",
"match":"场",
"Send":"发送",
"SelectTheHostOnTheRightToChatImmediately":"右方选择主播立即聊天",
"PKInvitation":"PK邀请",
"ChooseTheOpponentPKStreamer":"选择对方的PK主播",
"ChooseYourOwnPKStreamer": "选择自己的PK主播",
"Hint":"提示",
"AreYouSureYouWantToSendAPKInvitationMessageAfterASuccessfulInvitationThePkCannotBeModifiedOrDeletedPleasePaution":" 您确定要发送PK邀请消息吗邀请成功后的pk不可修改不可删除请谨慎操作",
"PleaseSelectTheStreamerWhoWillParticipateInThePK":"请选择对方参与PK的主播",
"PleaseSelectOurStreamerToParticipateInThePK":"请选择我方参与PK的主播",
"PleaseSelectTheOtherPartyAndYourOwnStreamer":"请选择对方和自己的主播",
"PleaseEnterTheMaximumNumberOfGoldCoins":"请输入最大金币数",
"PleaseEnterTheMinimumNumberOfGoldCoins":"请输入最小金币数",
"TheStartTimeCannotBeLessThanTheCurrentTime":"开始时间不能早于当前时间",
"AnchorLibrary":"主播库",
"PKInformation":"PK信息",
"MyPKRecord":"我的PK记录",
"PointsList":"积分列表",
"MyPoints":"我的积分:",
"YouDonHaveAnyPointsRecordsYet":"您还没有积分记录!",
"ActualNumberOfGoldCoins":"实际金币数:",
"YouDonHaveAPKRecordYet":"您还没有PK记录",
"InTotal":"总共:",
"The":"第",
"THInning":"回",
"SelectRecordOnTheRightToViewDetailsImmediately":"选择右侧的记录,可立即查看详细信息",
"ThePKIPosted":"发布的PK",
"ThePKIInvited":"邀请的PK",
"YoudonHaveAnyPKInformationYetHurryUpAndAddIt":"您还没有PK信息快去添加吧",
"ConfirmTheDeletionOfThisStreamerPKInformation":"确认删除该主播的PK信息",
"TopPosition":"置顶",
"TopPromptOnTheHomepage":"置顶后该用户将在首页置顶并在指定时长内享有置顶特权。置顶时长不足1小时系统将按1小时计算积分扣除。请选择置顶时长:",
"PleaseSelectThePinnedDuration":"请选择置顶时长",
"UnpinTheTopPrompt":" 您确定要取消置顶吗?取消置顶后该用户将不再享有置顶特权但仍可继续参与PK挑战。若置顶时长不足1小时系统将按1小时计算积分扣除。取消后按实际使用时长比例返还剩余积分是否确认执行此操作?",
"Unpinned":"取消置顶",
"hour":"小时",
"Expired":"已过期",
"TopPlacementSuccessful":"置顶成功",
"DeletedSuccessfully":"删除成功",
"YouDonHaveALiveStreamerYetHurryUpAndAddOne":"您还没有主播,快去添加吧!",
"AddMyStreamer":"添加我的主播",
"ModifyMyStreamer":"修改我的主播",
"ConfirmTheDeletionOfThisStreamer":"确认删除该主播?",
"AddedSuccessfully":"添加成功",
"Search":"搜索",
"Playing":"播放中...",
"Note":"备注:",
"agree":"同意",
"Refuse":"拒绝",
"HaveAgreedToTheInvitation":"已同意邀请",
"HaveRefusedTheInvitation":"已拒绝邀请",
"WaitForTheOtherPartyResponse":"等待对方回应(如果已经回应请刷新页面)",
"AfterASuccessfulInvitationThePKCannotBeModifiedOrDeletedPleaseOperateWithCaution":"邀请成功后的pk不可修改不可删除请谨慎操作",
"AreYouSureYouWantToDeclineThisInvitation":" 您确定要拒绝此邀请吗?",
"AgreeToSuccess":"同意成功",
"RefuseSuccess":"拒绝成功",
"YourAccountHasNotBeenActivatedYetAndMayBeSubjectToRestrictionsPleaseActivateItInTime":"您的账号尚未激活,可能受到限制,请及时激活。",
"Activate":"激活"
}

View File

@@ -5,14 +5,29 @@ import store from './store'
import { createPinia } from 'pinia';
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'; // 引入中文语言包
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import GoEasy from 'goeasy'
import i18n from '../src/i18n/index'
// createApp(App).use(store).use(router).mount('#app')
const goeasy = GoEasy.getInstance({
host:"hangzhou.goeasy.io", //若是新加坡区域singapore.goeasy.io
appkey:"PC-a88037e060ed4753bb316ac7239e62d9",
modules:['im']//根据需要传入pubsub或'im或数组方式同时传入
});
const app = createApp(App);
app.config.globalProperties.goeasy = goeasy;
export { goeasy };
app.use(ElementPlus, {
locale: zhCn, // 配置中文
});
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus) // 注册 ElementPlus
app.use(createPinia()); // 注册 Pinia
app.use(store); // 注册 store
app.use(i18n); // 注册 i18n
app.use(router); // 注册 router
app.mount('#app');
app.mount('#app');

View File

@@ -1,29 +1,79 @@
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import emailRegistration from '../views/emailRegistration.vue'
import ActivateEmail from '../views/ActivateEmail.vue'
import activationSuccessful from '../views/activationSuccessful.vue'
import forgetPassword from '../views/forgetPassword.vue'
import resetPassword from '../views/resetPassword.vue'
import verifyAccount from '../views/verifyAccount.vue'
import { setStorage , getStorage } from '@/utils/storage.js';
const routes = [
{
path: '/',
name: 'home',
name: 'login',
component: HomeView
},
{
path: '/emailRegistration',
name: 'emailRegistration',
component: emailRegistration
},
{
path: '/ActivateEmail',
name: 'ActivateEmail',
component: ActivateEmail
},
{
path: '/activationSuccessful/:token',
name: 'activationSuccessful',
component: activationSuccessful,
props: true
},
{
path: '/verifyAccount/:token',
name: 'verifyAccount',
component: verifyAccount,
props: true
},
{
path: '/forgetPassword',
name: 'forgetPassword',
component: forgetPassword
},
{
path: '/resetPassword/:token',
name: 'resetPassword',
component: resetPassword,
props: true
},
{
path: '/nav',
name: 'nav',
// redirect: '/nav/hostsList', // 默认跳转
component: () => import(/* webpackChunkName: "hostsList" */ '../views/nav.vue'),
redirect: '/nav/PK', // 默认跳转
component: () => import('../views/nav.vue'),
children: [
{
path: 'hostsList',
name: 'hostsList',
component: () => import(/* webpackChunkName: "hostsList" */ '../views/hosts/hostsList.vue')
path: 'PK',
name: 'pk',
component: () => import('../views/hosts/pk.vue')
},
{
path: 'workBenches',
name: 'workBenches',
component: () => import(/* webpackChunkName: "hostsList" */ '../views/hosts/workbenches.vue')
},]
path: 'Forum',
name: 'Forum',
component: () => import('../views/hosts/Forum.vue')
},
{
path: 'Message',
name: 'Message',
component: () => import('../views/hosts/Message.vue')
},
{
path: 'Mine',
name: 'Mine',
component: () => import('../views/hosts/Mine.vue')
},
]
}
]
const router = createRouter({
@@ -31,4 +81,23 @@ const router = createRouter({
routes
})
// 添加导航守卫
router.beforeEach((to, from, next) => {
// 假设你有一个方法来检查用户是否已登录
const isAuthenticated = () => {
return localStorage.getItem('token') !== null;
};
// 定义需要登录才能访问的路由
const requiresAuth = ['nav', 'pk', 'Forum', 'Message', 'Mine'];
// 如果需要登录且用户未登录,则重定向到登录页面
if (requiresAuth.includes(to.name) && !isAuthenticated()) {
next({ name: 'login' });
} else {
next();
}
});
export default router

17
src/static/css/app.less Normal file
View File

@@ -0,0 +1,17 @@
@bg-color: #F8F9FA;// 主色
@font-color: #212529; // 字体色
@border-color: #E9ECEF; // 边框色
@bg-Sidebar-color-top:#F8F9FA; // 侧边栏背景色
@bg-Sidebar-color-bottom: #E9ECEF; // 侧边栏底色
@Supplementary-text-color: #495057; // 补充文字色
@Prompt-text-color: #6c757d; // 提示文字色
@Warning-text-color: #FFC107; // 警告文字色
@Error-text-color: #DC3545; // 错误文字色
@Success-text-color: #28A745; // 成功文字色
@lose-color: #ff000010; // 输赢颜色
@win-color: #0099ff10; // 输赢颜色
@bg-night-color: #1A1A1A; // 夜间模式色
@font-color-night: #E9ECEF; // 夜间模式字体色

View File

@@ -9,4 +9,41 @@ export const noticeStore = defineStore('noticeNum', {
this.data.num++;
},
},
});
});
export const tokenStore = defineStore('token', {
state: () => {
return { token: '' }
},
actions: {
setToken(token){
this.token = token
}
},
})
export const UserStore = defineStore('User', {
state: () => {
return { user: {} }
},
// 也可以这样定义
// state: () => ({ count: 0 })
actions: {
setUser(user){
this.user = user
}
},
})
export const IMloginStore = defineStore('IMlogin', {
state: () => {
return { IMstate: false }
},
// 也可以这样定义
// state: () => ({ count: 0 })
actions: {
setIMstate(state){
this.IMstate = state
}
},
})

View File

@@ -4,33 +4,36 @@
*/
import axios from 'axios'
import { getToken, getUser } from '@/utils/storage'
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import router from '@/router'
const router = useRouter();
import { ElMessage } from 'element-plus';
import { ref } from 'vue';
import { defineStore } from 'pinia'
import { setStorage , getStorage } from '@/utils/storage.js';
// 请求地址前缀
let baseURL = ''
if (process.env.NODE_ENV === 'development') {
// 生产环境
baseURL = "http://api.tkpage.vvtiktok.cn"
// baseURL = "http://192.168.0.116:8085/"
baseURL = "https://pk.hanxiaokj.cn/"
} else {
// 开发环境
baseURL = "http://api.tkpage.vvtiktok.cn"
// 测试环境
baseURL = "https://pk.hanxiaokj.cn/"
}
// 请求拦截器
axios.interceptors.request.use((config) => {
// if (getToken()) {
// config.headers['token'] = getToken();
// }
const tokenCache = getStorage('token')
const url = sliceUrl(config.url)
// 请求超时时间 - 毫秒
config.timeout = 60000
config.baseURL = baseURL
// 自定义Content-type
config.headers['Content-type'] = 'application/json'
if (!(config.url == 'getVxQrcode' || config.url == 'getScanResult'||config.url == 'register')) {
config.headers['token'] = tokenCache;
}
return config;
}, (error) => {
return Promise.reject(error)
@@ -38,13 +41,47 @@ axios.interceptors.request.use((config) => {
// 响应拦截器
axios.interceptors.response.use((response) => {
return response
return addPrefixToHeaderIcon(response.data)
}, (error) => {
// 可添加请求失败后的处理逻辑
return Promise.reject(error)
})
import { translateCountryName } from './countryUtil';
// 处理headerIcon的前缀和country的翻译
function addPrefixToHeaderIcon(data) {
// 处理数组:递归处理每个元素
if (Array.isArray(data)) {
data.forEach(item => addPrefixToHeaderIcon(item));
return;
}
// 处理对象:递归处理每个属性
if (typeof data === 'object' && data !== null) {
for (const key in data) {
if (key === 'headerIcon' || key === 'anchorIcon' && data.hasOwnProperty(key)) {
// 在headerIcon值前添加前缀处理各种类型anchorIconA anchorIconB anchorIcon
const value = data[key];
data[key] = "https://vv-1317974657.cos.ap-shanghai.myqcloud.com/headerIcon/" + (
typeof value === 'string' ? value
: value != null ? String(value)
: ""
);
} else if (key === 'country' && data.hasOwnProperty(key)) {
// 翻译country值
const value = data[key];
if (typeof value === 'string') {
data[key] = translateCountryName(value);
}
} else if (typeof data[key] === 'object' && data[key] !== null) {
// 递归处理嵌套对象或数组
addPrefixToHeaderIcon(data[key]);
}
}
}
return data;
}
// axios的get请求
export function getAxios({ url, params }) {
@@ -54,10 +91,17 @@ export function getAxios({ url, params }) {
params
// 请求成功将返回的数据传递给resolve函数
}).then(res => {
resolve(res.data)
// 请求失败将错误信息传递给reject函数
if (res.code == 200) {
resolve(res.data)
} else if (res.code == 40400) {
router.push('/')
ElMessage.error(res.code + '' + res.msg);
reject();
} else {
ElMessage.error(res.code + '' + res.msg);
reject();
}
}).catch(err => {
console.log(err)
reject(err)
})
})
@@ -65,13 +109,7 @@ export function getAxios({ url, params }) {
// axios的post请求
export function postAxios({ url, data }) {
if (url != 'api/account/login') {
throttledCheekalive();
}
console.log("postAxios", url, data);
return new Promise((resolve, reject) => {
axios.post(
url,
@@ -82,91 +120,106 @@ export function postAxios({ url, data }) {
}
}
).then(res => {
resolve(res.data)
}).catch(err => {
if (err.message == "Network Error") {
// alert("网络错误,请检查网络连接")
// ElMessage.error('网络连接错误');
reject('网络连接错误')
if (res.code == 200) {
resolve(res.data)
} else if (res.code == 40400) {
router.push('/')
console.log(res.msg);
ElMessage.error(res.code + '' + res.msg);
reject();
} else {
ElMessage.error(err.message);
reject(err.message)
console.log(res.msg);
ElMessage.error(res.code + '' + res.msg);
reject();
}
// console.log(err)
// reject(err)
}).catch(err => {
reject(err)
})
})
}
export const downFile = async (urlstr, data) => {
// 发送请求,获取文件流
const response = await axios.post(urlstr, data, { responseType: 'blob' });
// export const downFile = async (urlstr, data) => {
// 获取文件名(如果后端设置了 Content-Disposition
const contentDisposition = response.headers['content-disposition'];
let fileName = 'default-file-name'; // 默认文件名
console.log(contentDisposition)
console.log(response)
if (contentDisposition) {
// 从响应头中提取文件名
const fileNameMatch = contentDisposition.match(/filename="(.+)"/);
if (fileNameMatch && fileNameMatch.length > 1) {
fileName = fileNameMatch[1];
}
// // 发送请求,获取文件流
// const response = await axios.post(urlstr, data, { responseType: 'blob' });
// // 获取文件名(如果后端设置了 Content-Disposition
// const contentDisposition = response.headers['content-disposition'];
// let fileName = 'default-file-name'; // 默认文件名
// console.log(contentDisposition)
// console.log(response)
// if (contentDisposition) {
// // 从响应头中提取文件名
// const fileNameMatch = contentDisposition.match(/filename="(.+)"/);
// if (fileNameMatch && fileNameMatch.length > 1) {
// fileName = fileNameMatch[1];
// }
// }
// // 创建一个临时的下载链接
// const blob = new Blob([response.data], { type: response.headers['content-type'] });
// const url = window.URL.createObjectURL(blob);
// const a = document.createElement('a');
// a.href = url;
// a.download = fileName; // 设置下载的文件名
// a.click();
// // 释放 URL 对象
// window.URL.revokeObjectURL(url);
// }
// //请求前验证
// function cheekalive() {
// const userCache = UserStore()
// const tokenCache = tokenStore()
// axios.post('api/account/cheekalive', {
// userId: userCache.user.id,
// currcode: tokenCache.token,
// },
// {
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded'
// }
// }
// ).then(res => {
// console.log(res.data)
// if (res.data) {
// } else {
// alert("账号在其他地方登录!")
// window.location.href = '/';
// }
// })
// }
// //节流函数
// function throttle(func, limit) {
// let inThrottle;
// return function () {
// const args = arguments;
// const context = this;
// if (!inThrottle) {
// func.apply(context, args);
// inThrottle = true;
// setTimeout(() => inThrottle = false, limit);
// }
// }
// }
function sliceUrl(url) {
const lastSlash = url.lastIndexOf('/');
const questionMark = url.indexOf('?');
if (questionMark == -1) {
const result = url.slice(lastSlash + 1, url.length);
return result;
} else {
const result = url.slice(lastSlash + 1, questionMark);
return result;
}
// 创建一个临时的下载链接
const blob = new Blob([response.data], { type: response.headers['content-type'] });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName; // 设置下载的文件名
a.click();
// 释放 URL 对象
window.URL.revokeObjectURL(url);
}
//请求前验证
function cheekalive() {
axios.post('api/account/cheekalive', {
userId: getUser().userId,
currcode: getToken(),
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
).then(res => {
console.log(res.data)
if (res.data) {
} else {
alert("账号在其他地方登录!")
window.location.href = '/';
}
})
}
//节流函数
function throttle(func, limit) {
let inThrottle;
return function () {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
const throttledCheekalive = throttle(cheekalive, 5000);
export default axios

View File

@@ -1,261 +1,28 @@
// country-utils.js
export const CountryCode = {
AD: "安道尔",
AE: "阿拉伯联合酋长国",
AF: "阿富汗",
AG: "安提瓜和巴布达",
AI: "安圭拉",
AL: "阿尔巴尼亚",
AM: "亚美尼亚",
AO: "安哥拉",
AQ: "南极洲",
AR: "阿根廷",
AS: "美属萨摩亚",
AT: "奥地利",
AU: "澳大利亚",
AU1: "澳大利亚",
AW: "阿鲁巴",
AX: "奥兰群岛",
AZ: "阿塞拜疆",
BA: "波斯尼亚和黑塞哥维那",
BB: "巴巴多斯",
BD: "孟加拉国",
BE: "比利时",
BF: "布基纳法索",
BG: "保加利亚",
BH: "巴林",
BI: "布隆迪",
BJ: "贝宁",
BL: "圣巴泰勒米",
BM: "百慕大群岛",
BN: "文莱达鲁萨兰国",
BO: "玻利维亚",
BQ: "博奈尔、圣尤斯特歇斯和萨巴",
BR: "巴西",
BS: "巴哈马",
BT: "不丹",
BV: "布韦岛",
BW: "博茨瓦纳",
BY: "白俄罗斯",
BZ: "伯利兹",
CA: "加拿大",
CA1: "加拿大",
CC: "科科斯(基林)群岛",
CD: "刚果民主共和国",
CF: "中非共和国",
CG: "刚果共和国",
CH: "瑞士",
CI: "科特迪瓦",
CK: "库克群岛",
CL: "智利",
CM: "喀麦隆",
CN: "中国",
CO: "哥伦比亚",
CR: "哥斯达黎加",
CU: "古巴",
CV: "佛得角",
CW: "库拉索",
CX: "圣诞岛",
CY: "塞浦路斯",
CZ: "捷克共和国",
DE: "德国",
DG: "迪戈加西亚岛",
DJ: "吉布提",
DK: "丹麦",
DM: "多米尼克",
DO: "多米尼加共和国",
DZ: "阿尔及利亚",
EC: "厄瓜多尔",
EE: "爱沙尼亚",
EG: "埃及",
EH: "西撒哈拉",
ER: "厄立特里亚",
ES: "西班牙",
ET: "埃塞俄比亚",
FI: "芬兰",
FJ: "斐济",
FK: "福克兰群岛",
FM: "密克罗尼西亚",
FO: "法罗群岛",
FR: "法国",
GA: "加蓬",
GB: "英国",
GD: "格林纳达",
GE: "格鲁吉亚",
GF: "法属圭亚那",
GG: "根西岛",
GH: "加纳",
GI: "直布罗陀",
GL: "格陵兰",
GM: "冈比亚",
GN: "几内亚",
GP: "瓜德罗普",
GQ: "赤道几内亚",
GR: "希腊",
GS: "南乔治亚和南桑德威奇群岛",
GT: "危地马拉",
GU: "关岛",
GW: "几内亚比绍",
GY: "圭亚那",
HK: "中国香港特别行政区",
HM: "赫德岛和麦克唐纳群岛",
HN: "洪都拉斯",
HR: "克罗地亚",
HT: "海地",
HU: "匈牙利",
ID: "印度尼西亚",
IE: "爱尔兰",
IL: "以色列",
IM: "马恩岛",
IN: "印度",
IO: "英属印度洋领地",
IQ: "伊拉克",
IR: "伊朗",
IS: "冰岛",
IT: "意大利",
JE: "泽西岛",
JM: "牙买加",
JO: "约旦",
JP: "日本",
JP1: "日本",
KE: "肯尼亚",
KG: "吉尔吉斯斯坦",
KH: "柬埔寨",
KI: "基里巴斯",
KM: "科摩罗",
KN: "圣基茨和尼维斯",
KP: "朝鲜",
KR: "韩国",
KR1: "韩国",
KR1_UXWAUDIT: "韩国",
KW: "科威特",
KY: "开曼群岛",
KZ: "哈萨克斯坦",
LA: "老挝",
LB: "黎巴嫩",
LC: "圣卢西亚",
LI: "列支敦士登",
LK: "斯里兰卡",
LR: "利比里亚",
LS: "莱索托",
LT: "立陶宛",
LU: "卢森堡",
LV: "拉脱维亚",
LY: "利比亚",
MA: "摩洛哥",
MC: "摩纳哥",
MD: "摩尔多瓦",
ME: "黑山",
MF: "圣马丁",
MG: "马达加斯加",
MH: "马绍尔群岛",
MK: "北马其顿",
ML: "马里",
MM: "缅甸",
MN: "蒙古",
MO: "中国澳门特别行政区",
MP: "北马里亚纳群岛",
MQ: "马提尼克",
MR: "毛里塔尼亚",
MS: "蒙特塞拉特",
MT: "马耳他",
MU: "毛里求斯",
MV: "马尔代夫",
MW: "马拉维",
MX: "墨西哥",
MY: "马来西亚",
MZ: "莫桑比克",
NA: "纳米比亚",
NC: "新喀里多尼亚",
NE: "尼日尔",
NF: "诺福克岛",
NG: "尼日利亚",
NI: "尼加拉瓜",
NL: "荷兰",
NO: "挪威",
NP: "尼泊尔",
NR: "瑙鲁",
NU: "纽埃",
NZ: "新西兰",
OM: "阿曼",
PA: "巴拿马",
PE: "秘鲁",
PF: "法属玻利尼西亚",
PG: "巴布亚新几内亚",
PH: "菲律宾",
PK: "巴基斯坦",
PL: "波兰",
PM: "圣皮埃尔和密克隆群岛",
PN: "皮特凯恩群岛",
PR: "波多黎各",
PS: "巴勒斯坦",
PT: "葡萄牙",
PW: "帕劳",
PY: "巴拉圭",
QA: "卡塔尔",
RE: "留尼汪",
RO: "罗马尼亚",
RS: "塞尔维亚",
RU: "俄罗斯",
RW: "卢旺达",
SA: "沙特阿拉伯",
SB: "索罗门群岛",
SC: "塞舌尔",
SD: "苏丹",
SE: "瑞典",
SG: "新加坡",
SI: "斯洛文尼亚",
SJ: "斯瓦尔巴和扬马延",
SK: "斯洛伐克",
SL: "塞拉利昂",
SM: "圣马利诺",
SN: "塞内加尔",
SO: "索马里",
SR: "苏里南",
SS: "南苏丹",
ST: "圣多美和普林西比",
SV: "萨尔瓦多",
SX: "荷属圣马丁",
SY: "叙利亚",
SZ: "斯威士兰",
TC: "特克斯和凯科斯群岛",
TD: "乍得",
TF: "法属南部领地",
TG: "多哥",
TH: "泰国",
TJ: "塔吉克斯坦",
TK: "托克劳群岛",
TL: "东帝汶",
TM: "土库曼斯坦",
TN: "突尼斯",
TO: "汤加",
TR: "土耳其",
TT: "特立尼达和多巴哥",
TV: "图瓦卢",
TW: "台湾",
TZ: "坦桑尼亚",
UA: "乌克兰",
UG: "乌干达",
UM: "美国本土外小岛屿",
US: "美国",
UY: "乌拉圭",
UZ: "乌兹别克斯坦",
VA: "梵蒂冈",
VC: "圣文森特",
VE: "委内瑞拉",
VG: "英属维尔京群岛",
VI: "美属维尔京群岛",
VN: "越南",
VN1: "越南",
VU: "瓦努阿图",
WS: "萨摩亚",
YE: "也门",
YT: "马约特岛",
ZA: "南非",
ZM: "赞比亚",
ZW: "津巴布韦"
};
import enCountries from '@/i18n/en/country.json';
import zhCountries from '@/i18n/zh/country.json';
export function getCountryName(code) {
return CountryCode[code] || null;
}
// 创建中文名称到国家代码的映射
const zhNameToCode = {};
Object.entries(zhCountries).forEach(([code, zhName]) => {
zhNameToCode[zhName] = code;
});
// 获取国家名称数组value固定为中文名称label根据当前语言变化
export function getCountryNamesArray() {
const currentLanguage = localStorage.getItem('language') || 'ZH';
return Object.entries(zhCountries).map(([code, zhName]) => ({
value: zhName,
label: currentLanguage === 'ZH' ? zhName : enCountries[code]
}));
}
// 根据中文名称获取当前语言环境的翻译
export function translateCountryName(zhName) {
const currentLanguage = localStorage.getItem('language') || 'ZH';
const code = zhNameToCode[zhName];
if (!code) return zhName; // 如果没有找到对应代码,返回原中文名称
return currentLanguage === 'ZH' ? zhName : enCountries[code];
}

175
src/utils/goeasy.js Normal file
View File

@@ -0,0 +1,175 @@
import { goeasy } from '../main'
import { IMloginStore } from '@/stores/notice.js';
import GoEasy from 'goeasy'
//链接IM(登录IM)
export function goEasyLink(data) {
const counter = IMloginStore()
return new Promise((resolve, reject) => {
goeasy.connect({
id:data.id, //im必填最大长度60字符
data:{"avatar":data.avatar,"nickname":data.nickname},
otp:data.key,
onSuccess: function () { //连接成功
console.log("连接成功");
counter.setIMstate(true); //登录IM成功
resolve(true);
},
onFailed: function (error) { //连接失败
console.log("连接失败,错误码:" + error.code + ",错误信息:" + error.content);
},
onProgress:function(attempts) { //连接或自动重连中
console.log("正在重连中...");
}
});
})
}
//断开IM
export function goEasyDisConnect() {
return new Promise((resolve, reject) => {
goeasy.disconnect({
onSuccess: function(){
resolve(true)
},
onFailed: function(error){
console.log("断开失败, code:"+error.code+ ",error:"+error.content);
}
});
});
}
//获取会话列表
export function goEasyGetConversations() {
var im = goeasy.im;
return new Promise((resolve, reject) => {
im.latestConversations({
onSuccess: function (result) {
resolve(result);
},
onFailed: function (error) {
console.log("获取会话列表失败,错误码:" + error.code + " content:" + error.content);
},
});
})
}
//获取指定会话的消息列表
export function goEasyGetMessages(data) {
var im = goeasy.im;
return new Promise((resolve, reject) => {
im.history({
id: data.id,//用户id或者群id
type: GoEasy.IM_SCENE.PRIVATE, //群聊GoEasy.IM_SCENE.GROUP, 客服GoEasy.IM_SCENE.CS,
lastTimestamp: data.timestamp, //上次查询结果里最后一条消息的时间戳首次查询传入null即可
limit: 30, //可选项返回的消息条数默认为10条最多30条
onSuccess: function (result) {
resolve(result.content);
},
onFailed: function (error) {
console.log("获取消息列表失败,错误码:" + error.code + " content:" + error.content);
}
});
})
}
//发送文本消息
export function goEasySendMessage(data) {
var im = goeasy.im;
let textMessage = im.createTextMessage({
text: data.text, //消息内容
to: {
type: GoEasy.IM_SCENE.PRIVATE, //私聊还是群聊群聊为GoEasy.IM_SCENE.GROUP
id: data.id, //接收方用户id
data: {"avatar": data.avatar, "nickname": data.nickname} //接收方用户扩展数据, 任意格式的字符串或者对象用于更新会话列表conversation.data
}
});
return new Promise((resolve, reject) => {
im.sendMessage({
message: textMessage,
onSuccess: function () { //发送成功
resolve(textMessage);
},
onFailed: function (error) { //发送失败
console.log('Failed to send private messagecode:' + error.code + ' ,error ' + error.content);
}
});
})
}
//发送图片消息
export function goEasySendImageMessage(data) {
var im = goeasy.im;
var message = im.createImageMessage({
file: data.imagefile, //H5获得的图片file对象Uniapp和小程序调用chooseImagesuccess时得到的res.tempFiles数组中的元素比如res.tempFiles[0]即为选择的第一张图片
to: {
type: GoEasy.IM_SCENE.PRIVATE, //私聊还是群聊群聊为GoEasy.IM_SCENE.GROUP
id: data.id, //接收方用户id
data: {"avatar": data.avatar, "nickname": data.nickname} //好友扩展数据, 任意格式的字符串或者对象用于更新会话列表conversation.data
},
});
return new Promise((resolve, reject) => {
im.sendMessage({
message: message,
onSuccess: function () { //发送成功
resolve(message);
},
onFailed: function (error) { //发送失败
console.log('Failed to send messagecode:' + error.code + ',error' + error.content);
}
})
})
}
//发送PK消息
export function goEasySendPKMessage(data) {
var im = goeasy.im;
const customData = {
id: data.msgid,
pkIdA: data.pkIdA,
pkIdB: data.pkIdB,
};
let order = {
customData: customData,
link: "https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/pk.png",
text: "PK",
};
var customMessage = im.createCustomMessage({
type: 'pk', //字符串,可以任意自定义类型,比如红包'hongbao', 订单'order处方'chufang'
payload: order,
to: {
type: GoEasy.IM_SCENE.PRIVATE, //私聊还是群聊群聊为GoEasy.IM_SCENE.GROUP
id: data.id, //接收方用户id
data: {"avatar": data.avatar, "nickname": data.nickname} //好友扩展数据, 任意格式的字符串或者对象用于更新会话列表conversation.data
}
});
return new Promise((resolve, reject) => {
im.sendMessage({
message: customMessage,
onSuccess: function () { //发送成功
resolve(customMessage);
},
onFailed: function (error) { //发送失败
console.log('Failed to send messagecode:' + error.code + ',error' + error.content);
}
});
})
}
//消息已读
export function goEasyMessageRead(data) {
var im = goeasy.im;
return new Promise((resolve, reject) => {
im.markMessageAsRead({
id: data.id,
type: GoEasy.IM_SCENE.PRIVATE,
onSuccess: function () {
resolve(true);
},
onFailed: function (error) {
console.log('标记私聊已读失败', error);
},
});
})
}

View File

@@ -0,0 +1,19 @@
export function goldCoinCalculation(goldCoins) {
if (goldCoins === null || goldCoins === undefined) {
return "";
}
if (goldCoins < 1000) {
return String(goldCoins);
}
if (goldCoins >= 1000000) {
return "1M+";
}
const kValue = goldCoins / 1000;
const formattedString = kValue.toFixed(2); // 确保至少保留两位小数
const matchResult = formattedString.match(/^\d+\.\d{0,2}/);
if (matchResult === null) {
return kValue.toFixed(2) + "k"; // 确保至少保留两位小数
}
const formatted = matchResult[0];
return `${formatted}k`;
}

View File

@@ -1,117 +0,0 @@
// pythonBridge.js
import { ref, onMounted } from 'vue';
const bridge = ref(null);
// 初始化 QWebChannel
const initBridge = () => {
if (/localhost/.test(window.location.href)) return
new QWebChannel(qt.webChannelTransport, (channel) => {
bridge.value = channel.objects.bridge;
});
};
export function usePythonBridge() {
// 调用 Python 方法
const fetchDataConfig = (data) => {
return new Promise((resolve, reject) => {
if (bridge.value) {
bridge.value.fetchDataConfig(data, function (result) {
resolve(result);
});
}
});
};
// 查询获取主播的数据
const fetchDataCount = () => {
return new Promise((resolve, reject) => {
if (bridge.value) {
bridge.value.fetchDataCount(function (result) {
resolve(result);
});
}
});
};
// 打开tk后台
const loginTikTok = () => {
if (bridge.value) {
bridge.value.loginTikTok(function (result) {
});
}
};
// 登录tk后台
const loginBackStage = (data) => {
if (bridge.value) {
if (data.index == 0) {
bridge.value.loginBackStage(JSON.stringify(data));
} else if (data.index == 1) {
bridge.value.loginBackStageCopy(JSON.stringify(data));
}
}
};
//跳转到主播页面
const givePyAnchorId = (id) => {
if (bridge.value) {
bridge.value.givePyAnchorId(id, function (result) {
});
}
};
//查询登录状态
const backStageloginStatus = () => {
return new Promise((resolve, reject) => {
if (bridge.value) {
bridge.value.backStageloginStatus(function (result) {
resolve(result);
});
}
});
};
//查询登录状态
const backStageloginStatusCopy = () => {
return new Promise((resolve, reject) => {
if (bridge.value) {
bridge.value.backStageloginStatusCopy(function (result) {
resolve(result);
});
}
});
};
//导出表格
const exportToExcel = (data) => {
if (bridge.value) {
bridge.value.exportToExcel(JSON.stringify(data));
}
};
// 在组件挂载时初始化桥接
onMounted(initBridge);
return {
fetchDataConfig,
fetchDataCount,
loginBackStage,
loginTikTok,
givePyAnchorId,
backStageloginStatus,
backStageloginStatusCopy,
exportToExcel
};
}

View File

@@ -1,45 +1,48 @@
export function setToken(token) {
localStorage.setItem('token', token);
}
export function getToken() {
return localStorage.getItem('token');
//存储localStorage
export function setStorage(key, value) {
const data = typeof value === 'object' ? JSON.stringify(value) : value;
window.localStorage.setItem(key, data);
}
export function removeToken() {
localStorage.removeItem('token');
//获取localStorage
export function getStorage(key) {
const data = window.localStorage.getItem(key);
try {
return JSON.parse(data);
} catch {
return data;
}
}
export function setUser(user) {
localStorage.setItem('user', JSON.stringify(user));
//获取localStorage
export function getPromiseStorage(key) {
return new Promise((resolve, reject) => {
const data = window.localStorage.getItem(key);
try {
resolve(JSON.parse(data));
} catch {
resolve(data);
}
});
}
export function getUser() {
return JSON.parse(localStorage.getItem('user'));
// 删除特定的localStorage项
export function clearStorage(key) {
if (key) {
window.localStorage.removeItem(key);
}
}
export function setNumData(numData) {
localStorage.setItem('num', JSON.stringify(numData));
//存储sessionStorage
export function setSessionStorage(key, value) {
const data = typeof value === 'object' ? JSON.stringify(value) : value;
window.sessionStorage.setItem(key, data);
}
export function getNumData() {
return JSON.parse(localStorage.getItem('num'));
//获取sessionStorage
export function getPromiseSessionStorage(key) {
return new Promise((resolve, reject) => {
const data = window.sessionStorage.getItem(key);
try {
resolve(JSON.parse(data));
} catch {
resolve(data);
}
});
}
// 导出一个函数,用于设置用户密码
export function setUserPass(userdata) {
localStorage.setItem('userPass', JSON.stringify(userdata));
}
// 导出一个函数,用于获取用户密码
export function getUserPass() {
return JSON.parse(localStorage.getItem('userPass'));
}
// 用于设置tk账户密码
export function setTkUser(userdata) {
localStorage.setItem('tkuser', JSON.stringify(userdata));
}
// 用于获取tk账户密码
export function getTkUser() {
return JSON.parse(localStorage.getItem('tkuser'));
}

View File

@@ -0,0 +1,13 @@
// 时间戳转换为本地时间格式为YYYY/MM/DD hh:mm
export function TimestamptolocalTime(date) {
if (!date || isNaN(date)) return '';
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
return `${year}/${month}/${day} ${hours}:${minutes}`;
}

22
src/utils/timeDisplay.js Normal file
View File

@@ -0,0 +1,22 @@
// 记录上次调用时间
let lastTimestamp = null;
/**
* 比较时间戳是否超过5分钟
* @param {number} timestamp - 要比较的时间戳(毫秒)
* @returns {boolean} - 是否超过5分钟或第一次调用
*/
export function timeDisplay(timestamp) {
// 第一次调用直接返回true
if (lastTimestamp === null) {
lastTimestamp = timestamp;
return true;
}
// 计算时间差(毫秒)
const timeDiff = Math.abs(timestamp - lastTimestamp);
lastTimestamp = timestamp;
// 5分钟 = 300,000毫秒
return timeDiff > 300000;
}

134
src/views/ActivateEmail.vue Normal file
View File

@@ -0,0 +1,134 @@
<template>
<div class="activate-email">
<div class="activate-email-content">
{{
t("YourEmailAddressIs") +
user.email +
t(
"ItHasNotBeenActivatedYetPleaseClickTheActivationLinkInYourEmailToActivateYourAccount"
)
}}
<!-- gj您的邮箱是{{user.email}}尚未激活请点击激活链接中的链接激活您的账户 -->
</div>
<div class="activate-email-btn" @click="sendActivateEmail">
{{ t("ResendTheActivationEmail") }}
<!-- gj重新发送激活邮件 -->
</div>
</div>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
onBeforeMount, // 组件挂载前执行
} from "vue";
import { getPromiseStorage } from "@/utils/storage.js";
import { resendEmail } from "@/api/account";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { setLocale } from "@/i18n";
import { setSessionStorage , getStorage } from '@/utils/storage.js';
//
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const language = ref(null); // 语言
window["$t"] = t;
//
const router = useRouter();
const refname = ref("");
function sendActivateEmail() {
resendEmail({
mailAddress: user.value.email,
type: 1,
})
.then((res) => {
ElMessage.success(t("TheActivationEmailHasBeenSentPleaseCheckItInTime"));
// gj激活邮件已发送请注意查收
setSessionStorage("notActivated", true); // 存储用户信息
router.push('/nav'); // 返回上一页
})
.catch((err) => {
ElMessage.error(t("TheActivationEmailSendingFailedPleaseTryAgainLater"));
// gj激活邮件发送失败请稍后再试
});
}
onBeforeMount(() => {
// 语言
getPromiseStorage("language")
.then((res) => {
console.log("获取语言成功", res);
language.value = res;
setLocale(language.value);
})
.catch((err) => {
console.log("获取语言失败", err);
});
});
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
const user = ref({});
onMounted(() => {
getPromiseStorage("user")
.then((res) => {
console.log(res);
user.value = res;
})
.catch((err) => {});
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped>
.activate-email {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
background-image: url(@/assets/bg.png);
background-size: 100% 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.activate-email-content {
font-size: 30px;
color: #4fcacd;
font-weight: bold;
}
.activate-email-btn {
background-color: #4fcacd;
color: #fff;
font-size: 20px;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin-top: 50px;
}
.activate-email-btn:hover {
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.activate-email-btn:active {
transition: all 0.1s ease;
transform: scale(0.95) !important;
}
</style>

View File

@@ -1,295 +1,374 @@
<template>
<div class="main">
<div class="container">
<div class="right">
<img src="../assets/logoBg.png" class="background-video" alt="">
<!-- 设置 -->
<div class="center-align">
<div></div>
<div class="setup">
<div class="setup-item center-justify">
<div></div>
<span>
网络设置
</span>
</div>
<div class="setup-item center-justify">
<div></div>
<span>
简体中文
</span>
</div>
</div>
</div>
<div class="center-line" style="margin-top: 40px;">
<!-- logo -->
<div class="logo">
<div class="center-justify" style="height: 80px; width: 300px;">
<img style="margin-right: 20px;" src="@/assets/logo.png">
<img src="@/assets/logotext.png">
</div>
</div>
<!-- From -->
<div class="from">
<div class="from-title center-justify">
<div>账号登陆</div>
</div>
<div class="from-input">
<el-form label-position="left" label-width="100px" :model="formData">
<div class="from-input-item1">
<img src="@/assets/username.png" alt="">
<el-input style="height: 25px;" v-model="formData.userId" placeholder="账号" clearable
@keyup.enter="onSubmit" />
</div>
<div class="from-input-item1">
<img src="@/assets/password.png" alt="">
<el-input style="height: 25px; " v-model="formData.password" type="password"
placeholder="密码" show-password @keyup.enter="onSubmit" />
</div>
<div class="from-input-item">
<el-button class="loginButton" color="#8f7ee7" type="primary"
@click="onSubmit">登录</el-button>
</div>
</el-form>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="HomeView">
<LanguageSwitcher />
<div class="login">
<!-- 登录切换 -->
<div class="Switch">
<div class="Switch-content" @click="fuSWitch">
<img
v-if="refSwitch"
class="Switchimg"
src="../assets/switchEmail.png"
alt=""
/>
<img v-if="!refSwitch" class="Switchimg" src="../assets/switchvx.png" alt="" />
</div>
</div>
<!-- 标题 -->
<div v-if="!refSwitch" class="container">
<!-- gj欢迎登录 -->
<div class="title">{{ t('Welcome_to_login') }}</div>
<div class="striping"></div>
<div class="input-Email">
<img class="Emailimg" src="../assets/Email.png" alt="" />
<div class="vertical"></div>
<el-input
type="Email"
size="large"
class="input-text"
v-model="refEmail"
:placeholder="t('PleaseEnterEmailOrUsername')"
@keydown.enter.native="EmailLogin"
/>
<!-- gj请输入邮箱或用户名 -->
</div>
<div class="input-Password">
<img class="Passwordimg" src="../assets/Password.png" alt="" />
<div class="vertical"></div>
<el-input
type="Password"
size="large"
class="input-text"
v-model="refpassword"
show-password
:placeholder="t('PleaseEnterPassword')"
@keydown.enter.native="EmailLogin"
/>
<!-- gj请输入密码 -->
</div>
<div class="login-btn" @click="EmailLogin">{{ t('login') }}</div>
<!-- gj登录 -->
</div>
<!-- 微信登录 -->
<div v-if="refSwitch" class="container">
<!-- gj微信登录 -->
<div class="title">{{ t('WechatMiniProgramLogin') }}</div>
<div class="striping"></div>
<img class="qrcode" :src="Qrcode.qrcode" alt="" />
<!-- gj通过微信小程序扫描二维码登录 -->
<div class="qrcode-text">{{ t('LogInByScanningTheQRCodeWithTheWechatMini-program') }}</div>
</div>
</div>
<div class="register" v-if="!refSwitch">
<div class="register-content">
<!-- gj还没有账号 -->
<div class="register-text">{{ t('DontHaveAnAccountYet') }}</div>
<!-- gj注册 -->
<div class="register-btn" @click="register">{{ t('Register') }}</div>
</div>
<div class="register-content">
<!-- gj已有账号 -->
<div class="register-text">{{ t('AlreadyHaveAnAccount') }}</div>
<!-- gj忘记密码 -->
<div class="register-btn" @click="forgetPassword">{{ t('ForgotPassword') }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { login } from '@/api/account';
import { getToken, setToken, setUser, setUserPass, getUserPass } from '@/utils/storage';
import { ElLoading } from 'element-plus';
import { ref, watch, onMounted, onUnmounted } from "vue";
import { getVxQrcode, getScanResult, login, getOtp} from "@/api/account"; // 导入登录接口
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { tokenStore, UserStore } from '@/stores/notice'
import { setStorage , getStorage } from '@/utils/storage.js';
import { goEasyLink } from '@/utils/goeasy.js';
import { ElLoading } from "element-plus";
import LanguageSwitcher from '@/components/LanguageSwitcher.vue';
//
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
window['$t'] = t
//
const router = useRouter();
const refname = ref("");
const refEmail = ref(""); // 邮箱
const refpassword = ref(""); // 密码
const refSwitch = ref(false); // 登录切换/true:微信登录/false:邮箱登录
const Qrcode = ref(""); // 二维码
const token = tokenStore()
const user = UserStore()
const info = ref({})
// 登录
function EmailLogin() {
// 密码验证
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,16}$/;
if (!passwordRegex.test(refpassword.value)) {
ElMessage.error(t('ThePasswordMustContainBothUpperAndLowerCaseLettersAndNumbersAndBe6To16CharactersLong'));
// gj密码必须包含大小写字母和数字长度为6-16位
return;
}
const loading = ElLoading.service({
lock: true,
text:t('Login'),
background: "rgba(0, 0, 0, 0.7)",
});
const formData = ref({
userId: getUserPass() == null ? '' : getUserPass().userId,
password: getUserPass() == null ? '' : getUserPass().password,
login({
userNameOrEmail: refEmail.value,
// email: refEmail.value,
password: refpassword.value,
}).then((res) => {
token.setToken(res.token);
setStorage("token", res.token);
setStorage("email", refEmail.value);
setStorage("password", refpassword.value);
user.setUser(res);
setStorage("user", res);
info.value = res
getOtp().then((res) => {
const data = {
id: String(info.value.id),
avatar: info.value.headerIcon,
nickname: info.value.nickName,
key: res,
}
goEasyLink(data).then(() => {
loading.close();
router.push("/nav");
}).catch((err) => {
ElMessage.error(t('LoginFailed')+err.content);
loading.close();
});
}).catch((err) => {
loading.close();
});
}).catch((err) => {
loading.close();
});
}
// 登录切换
let vxloginstatus;
function fuSWitch() {
refSwitch.value = !refSwitch.value;
if (refSwitch.value) {
vxloginstatus = setInterval(checkLogin, 2000);
} else {
clearInterval(vxloginstatus);
}
}
function register() {
router.push("/emailRegistration");
}
function forgetPassword() {
router.push("/forgetPassword");
}
// 获取二维码
function fetchQrcode() {
getVxQrcode().then((res) => {
Qrcode.value = res;
}).catch((err) => {});
}
//查询微信扫码是否成功
function checkLogin() {
getScanResult(Qrcode.value.uuid).then((res) => {
if (res.token) {
token.setToken(res.token);
setStorage("token", res.token);
user.setUser(res);
setStorage("user", res);
pollstop();
router.push("/nav");
}
}).catch((err) => {});
}
function pollstop() {
clearInterval(vxloginstatus);
}
// 轮询查询登录状态
let pollInterval;
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
onMounted(() => {
refEmail.value = getStorage("email");
refpassword.value = getStorage("password");
// 初始化获取二维码
fetchQrcode();
// 设置每两分钟轮询一次
pollInterval = setInterval(fetchQrcode, 90000);
});
const onSubmit = () => {
const loading = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
});
setUserPass(formData.value);
login({
userId: formData.value.userId,
password: formData.value.password,
}).then((res) => {
loading.close();
console.log(res)
if (res) {
if (res.activeYn == 'Y') {
setToken(res.currcode);
setUser(res);
router.push('/nav');
} else {
alert('账号未启用');
}
} else {
alert('账号或密码错误');
}
}).catch((err) => {
loading.close();
});
};
onUnmounted(() => {
// 清除轮询
clearInterval(pollInterval);
clearInterval(vxloginstatus);
});
</script>
<style lang="less">
.main {
width: 1600px;
height: 900px;
overflow: hidden;
box-sizing: border-box;
/* 页面无法选中 */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.container {
display: flex;
box-sizing: border-box;
width: 1600px;
height: 900px;
.right {
box-sizing: border-box;
position: relative;
width: 1600px;
height: 900px;
padding: 20px 40px 20px 50px;
border-left: 3px solid #23516e;
position: relative;
/* 添加 position: relative */
overflow: hidden;
/* 防止内容溢出 */
.background-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
/* 确保视频在内容之下 */
}
.setup {
display: flex;
color: #fff;
.setup-item {
padding: 10px 6px;
display: flex;
div {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgb(255, 255, 255);
margin-right: 5px;
}
}
}
.logo {
padding: 20px 0;
}
.from {
width: 420px;
height: 320px;
color: #107A4E;
background-color: #ffffff44;
border-radius: 20px;
border: 1px solid #fff;
padding: 32px;
box-sizing: border-box;
.from-title {
font-family: Source Han Sans SC;
font-weight: 500;
font-size: 24px;
color: #107A4E;
line-height: 37px;
div {
font-size: 20px;
font-weight: 600;
// border-bottom: 4px solid #1db97d;
}
}
.from-input {
width: 100%;
padding: 15px 0;
.from-input-item {
display: flex;
padding: 8px 0;
.from-input-item-title {
color: #107A4E;
font-size: 18px;
font-weight: 500;
width: 80px;
height: 50px;
}
.loginButton {
width: 359px;
height: 50px;
background: #FFFFFF;
border-radius: 24px;
border: 1px solid #FFFFFF;
font-family: Source Han Sans SC;
font-weight: 500;
font-size: 18px;
color: #107A4E;
line-height: 37px;
}
}
.from-input-item1 {
display: flex;
width: 359px;
height: 50px;
background: rgba(147, 174, 158, 0.37);
border-radius: 24px;
border: 1px solid #FFFFFF;
padding: 12px 25px 13px 25px;
box-sizing: border-box;
margin-bottom: 16px;
}
}
}
}
}
<style scoped>
.HomeView {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
background-image: url(@/assets/bg.png);
background-size: 100% 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.login {
width: 627px;
height: 514px;
background-image: linear-gradient(180deg, #dbf0f1, #ffffff);
border-radius: 16px;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid #4fcacd;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
}
.register {
width: 730px;
height: 40px;
display: flex;
justify-content:space-around;
}
.register-text {
font-size: 18px;
color: #333333;
font-weight: bold;
margin-right: 10px;
}
.register-content{
display: flex;
}
.register-btn {
font-size: 18px;
color: #5fdbde;
font-weight: bold;
margin-right: 10px;
cursor: pointer;
}
.center-line {
display: flex;
flex-direction: column;
align-items: center;
.Switch {
width: 100%;
height: 100px;
display: flex;
justify-content: end;
}
.center-justify {
display: flex;
justify-content: space-around;
align-items: center;
.Switch-content {
width: 110px;
height: 110px;
margin-top: 7px;
margin-right: 8px;
}
.center-align {
display: flex;
justify-content: space-between;
.Switchimg {
width: 100%;
height: 100%;
}
.center-flex {
display: flex;
justify-content: center;
align-items: center;
.title {
font-size: 36px;
color: #333333;
font-weight: bold;
margin-top: -31px;
}
.el-input__wrapper {
--el-input-focus-border-color: rgba(255, 255, 0, 0);
--el-menu-hover-bg-color: rgba(255, 255, 0, 0);
.striping {
width: 68px;
height: 3px;
background-color: #0aaeab;
margin-top: 10px;
}
.qrcode {
width: 200px;
height: 200px;
margin-top: 50px;
}
.qrcode-text {
color: #333333;
font-size: 18px;
margin-top: 40px;
}
.input-Email {
width: 488px;
height: 71px;
border-radius: 4px;
background-color: #f4f4f4;
border: 1px solid #e4e4e4;
margin-top: 43px;
display: flex;
align-items: center;
}
.Emailimg {
width: 28.7px;
height: 22.2px;
margin-left: 25px;
margin-right: 25px;
}
.vertical {
width: 1px;
height: 38px;
background-color: #d9d9d9;
margin-right: 25px;
}
.input-text {
width: 360px;
height: 38px;
border: none;
background-color: #0aaeab00;
font-size: 20px;
color: #999999;
}
:deep(.el-input__wrapper) {
box-shadow: none !important;
background-color: transparent !important;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: none !important;
}
.input-text:focus {
outline: none; /* 移除默认蓝色边框 */
}
.input-Password {
width: 488px;
height: 71px;
border-radius: 4px;
background-color: #f4f4f4;
border: 1px solid #e4e4e4;
margin-top: 33px;
display: flex;
align-items: center;
}
.Passwordimg {
width: 26.4px;
height: 29.3px;
margin-left: 25px;
margin-right: 25px;
}
.login-btn {
width: 487px;
height: 70px;
border-radius: 4px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
margin-top: 38px;
color: #ffffff;
text-align: center;
line-height: 70px;
font-size: 30px;
font-weight: Medium;
box-shadow: 0px 5px 15px rgba(15, 101, 98, 0.43);
}
</style>
<style scoped>
::v-deep(.el-input__wrapper) {
background-color: rgba(255, 0, 0, 0);
box-shadow: none;
}
::v-deep(.el-input__inner) {
color: #107A4E;
}
::v-deep(.el-input__inner::placeholder) {
color: #107A4E;
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<div class="email-registration">
<!-- 完成注册 -->
<div class="form">
<div class="title">
{{ Hinttext }}
</div>
</div>
</div>
</template>
<script setup>
import { useRoute, useRouter } from "vue-router";
import { ref, watch, onMounted, onUpdated, onUnmounted, onBeforeMount } from "vue";
import { activeEmail } from "@/api/account";
import { setLocale } from "@/i18n";
import { setStorage , getStorage,getPromiseStorage } from '@/utils/storage.js';
//
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const language = ref(null); // 语言
window["$t"] = t;
//
const route = useRoute();
const router = useRouter();
const token = ref("");
import { ElMessage } from "element-plus";
const Hinttext = ref(t('ActivationInProgressPleaseWaitAMoment'));
//gj激活中请稍候
//确认
function nextStep() {
activeEmail(token.value)
.then(() => {
Hinttext.value = t('ActivationSuccessfulPleaseLogIn');
//gj激活成功请登录
setTimeout(() => {
router.push("/");
}, 3000);
})
.catch(() => {
Hinttext.value = t('ActivationFailed');
//gj激活失败
});
}
onMounted(() => {
token.value = route.params.token;
nextStep();
});
onUpdated(() => {
// 组件更新后执行
});
onBeforeMount(() => {
// 语言
getPromiseStorage("language")
.then((res) => {
console.log("获取语言成功", res);
language.value = res;
setLocale(language.value);
})
.catch((err) => {
console.log("获取语言失败", err);
});
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped>
.email-registration {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
background-image: url(../assets/bg.png);
background-size: 100% 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.form {
margin-top: 30px;
width: 80%;
height: 80%;
background-image: linear-gradient(180deg, #dbf0f1, #ffffff);
border-radius: 10px;
border: 1px solid #4fcacd;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.title {
font-size: 100px;
color: #03aba8;
font-weight: bold;
transition: all 0.5s ease-in-out;
}
</style>

View File

@@ -0,0 +1,427 @@
<template>
<div class="email-registration">
<div class="stepBar">
<el-steps
style="background-color: #f5f5f500"
:active="active"
finish-status="success"
simple
>
<el-step :title="t('AccountInformation')" />
<!-- gj账号信息 -->
<el-step :title="t('EmailVerification')" />
<!-- gj邮箱验证 -->
</el-steps>
</div>
<!-- 账号信息 -->
<div class="form" v-if="active === 0">
<div class="title">{{ t('Register') }}</div>
<!-- gj注册 -->
<div class="Email">
<el-input type="text" size="large" class="input-item" v-model="Email" :placeholder="t('PleaseEnterYourEmailAddress')" />
<!-- gj输入邮箱 -->
</div>
<div class="Email">
<el-input type="text" size="large" class="input-item" v-model="userName" :placeholder="t('PleaseEnterYourUsername')" @blur="userNameBlur"/>
<!-- gj输入用户名 -->
</div>
<div class="Password">
<el-input type="Password" size="large" class="input-item" v-model="Password" show-password :placeholder="t('PleaseEnterPassword')" />
<!-- gj输入密码 -->
<text class="password-tip">{{t('ThePasswordMustContainBothUpperAndLowerCaseLettersAndNumbersAndBe6To16CharactersLong')}}</text>
<!-- gj密码规则 -->
</div>
<div class="ConfirmPassword">
<el-input type="Password" size="large" class="input-item" v-model="ConfirmPassword" show-password :placeholder="t('PleaseEnterThePasswordAgain')" />
<!-- gj确认密码 -->
</div>
<div class="btn">
<div class="Return" @click="Return">{{ t('Return') }}</div>
<!-- gj返回 -->
<div class="nextStep" @click="nextStep" >{{ t('NextStep') }}</div>
<!-- gj下一步 -->
</div>
</div>
<!-- 完成注册 -->
<div class="form" v-if="active === 1">
<div class="title">{{ t('EmailVerification') }}</div>
<!-- gj邮箱验证 -->
<div class="Hint">
{{t('AVerificationEmailHasBeenSentToYour')+ Email+ t('emailAddressPleaseClickOnTheLinkInTheEmailToCompleteTheRegistration')}}
</div>
<div
class="ResendEmail"
@click="ResendEmail"
:style="isCounting ? 'background-image: linear-gradient(0deg, #cccccc, #dddddd); cursor: not-allowed;' : ''"
>
{{ isCounting ? t('ResendIn')+`${countdown}`+t('seconds') : t('ResendTheEmail') }}
<!-- gjxxx秒后重发邮件/重发邮件 -->
</div>
</div>
</div>
</template>
<script setup>
import { register,checkStatus,resendEmail,checkUsername} from "@/api/account"; // 导入登录接口
import { tokenStore, UserStore } from '@/stores/notice'
import { setStorage , getStorage,getPromiseStorage } from '@/utils/storage.js';
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
onBeforeMount, // 组件挂载前执行
} from "vue";
import { ElMessage } from "element-plus";
import { useRouter } from 'vue-router'
import { setLocale } from '@/i18n'
//
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
window['$t'] = t
//
const router = useRouter()
const active = ref(0);// 当前步骤
const Email = ref("");// 邮箱
const userName = ref("");// 用户名
const Password = ref("");// 密码
const ConfirmPassword = ref("");// 确认密码
const token = tokenStore()
const user = UserStore()
const userNamebtn = ref(false)
const countdown = ref(0); // 倒计时秒数
const isCounting = ref(false); // 是否正在倒计时
let timer = null; // 定时器
const language = ref(null); // 语言
// 倒计时
function startCountdown() {
countdown.value = 60;
isCounting.value = true;
timer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(timer);
isCounting.value = false;
}
}, 1000);
}
// 用户名失去焦点
function userNameBlur() {
if (userName.value.length < 2 || userName.value.length > 16 || userName.value == '' || userName.value == null) {
ElMessage.error(t('TheLengthOfAUsernameCannotBeLessThan2CannotExceed16AndCannotBeEmpty'));
// gj用户名长度不能小于2不能大于16,不能为空
return;
}
checkUsername({ userName: userName.value }).then(res => {
if (res == false) {
ElMessage.error(t('TheUsernameAlreadyExists'));
// gj用户名已存在
userNamebtn.value = false
return;
} else {
ElMessage.success(t('TheUsernameIsAvailable'));
// gj用户名可用
userNamebtn.value = true
}
}).catch((err) => {});
}
//下一步
function nextStep() {
// 邮箱验证
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(Email.value)) {
ElMessage.error(t('PleaseEnterAValidEmailAddress'));
// gj邮箱格式不正确
return;
}
// 密码验证
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,16}$/;
if (!passwordRegex.test(Password.value)) {
ElMessage.error(t('ThePasswordMustContainBothUpperAndLowerCaseLettersAndNumbersAndBe6To16CharactersLong'));
// gj密码规则
return;
}
//用户名验证
if (userName.value.length < 2 || userName.value.length > 16 || userName.value == '' || userName.value == null) {
ElMessage.error(t('TheLengthOfAUsernameCannotBeLessThan2CannotExceed16AndCannotBeEmpty'));
// gj用户名长度不能小于2不能大于16,不能为空
return;
}
// 确认密码验证
if (Password.value !== ConfirmPassword.value) {
ElMessage.error(t('TheTwoPasswordEntriesAreInconsistent'));
// gj两次密码不一致
return;
}
register({ userName: userName.value,password: Password.value, email: Email.value, nickName: 'User' + Math.floor(Math.random() * 1000000),headerIcon:'181754562968_.pic_hd.jpg'}).then(res => {
active.value = active.value + 1;
check(res.id);
token.setToken(res.token);
setStorage('token', res.token)
startCountdown();
userNamebtn.value = true;
}).catch(err => {});
}
//检查验证
let poll;
function check(id) {
poll = setInterval(() => {
checkStatus({id: id}).then(res => {
if (res.status === 0) {
user.setUser(res)
setStorage('user', res)
pollstop()
router.push('/nav')
}
}).catch((err) => {});
}, 2000);
}
function pollstop() {
clearInterval(poll)
}
onBeforeMount(() => {
// 语言
getPromiseStorage("language")
.then((res) => {
console.log("获取语言成功", res);
language.value = res;
setLocale(language.value);
})
.catch((err) => {
console.log("获取语言失败", err);
});
});
/// 重发邮件
function ResendEmail() {
resendEmail({mailAddress: Email.value,type:1}).then(res => {
ElMessage.success(t('TheEmailHasBeenResold'));
// gj邮件重发成功
startCountdown();
}).catch(err => {});
}
//返回登录页
function Return() {
router.push('/')
}
// watch(refname, async (newQuestion, oldQuestion) => {
// // 变化后执行
// });
onMounted(() => {
// 组件挂载完成后执行
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped>
.email-registration {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
background-image: url(../assets/bg.png);
background-size: 100% 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.stepBar {
width: 900px;
}
.form {
margin-top: 30px;
width: 900px;
height: 600px;
background-image: linear-gradient(180deg, #dbf0f1, #ffffff);
border-radius: 10px;
border: 1px solid #4fcacd;
display: flex;
flex-direction: column;
align-items: center;
}
.title{
font-size: 30px;
color: #4fcacd;
margin-top: 70px;
text-align: center;
font-weight: bold;
}
.Hint{
width: 900px;
height: 400px;
color:#4fcacd;
font-size: 20px;
text-align: center;
line-height: 500px;
margin-top: -50px;
}
.ResendEmail{
width: 200px;
height: 40px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
border-radius: 10px;
color: #ffffff;
font-size: 16px;
text-align: center;
line-height: 40px;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 50px;
}
.ResendEmail:hover{
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.Email{
margin-top: 30px;
width: 90%;
display: flex;
justify-content: center;
align-items: center;
}
.Password{
margin-top: 30px;
width: 90%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.ConfirmPassword{
margin-top: 30px;
width: 90%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.password-tip{
font-size: 12px;
color: #8c939d;
margin-top: 10px;
}
.btn{
width: 70%;
margin-top: 70px;
display: flex;
justify-content: space-between;
align-items: center;
}
.Return{
width: 200px;
height: 40px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
border-radius: 10px;
color: #ffffff;
font-size: 16px;
text-align: center;
line-height: 40px;
cursor: pointer;
transition: all 0.3s ease;
}
.Return:hover{
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.nextStep{
width: 200px;
height: 40px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
border-radius: 10px;
color: #ffffff;
font-size: 16px;
text-align: center;
line-height: 40px;
cursor: pointer;
transition: all 0.3s ease;
}
.nextStep:hover{
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.avatar {
width: 90%;
margin-top: 30px;
height: 178px;
}
.input {
width: 90%;
margin-top: 100px;
height: 50px;
}
.input-item {
border: none;
width: 80%;
height: 100%;
background-color: #ffffff00;
border-bottom: 1px solid #4fcacd;
}
:deep(.el-input__wrapper) {
box-shadow: none !important;
background-color: transparent !important;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: none !important;
}
.avatar-uploader {
width: 178px;
height: 178px;
border: 3px solid #4fcacd;
border-radius: 10px;
}
.avatar-uploader .avatar {
width: 178px;
height: 178px;
display: block;
}
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar-tip {
width: 178px;
text-align: center;
font-size: 12px;
color: #8c939d;
}
</style>

View File

@@ -0,0 +1,369 @@
<template>
<div class="email-registration">
<div class="stepBar">
<el-steps
style="background-color: #f5f5f500"
:active="active"
finish-status="success"
simple
>
<el-step :title="t('AccountInformation')" />
<!-- gj账号信息 -->
<el-step :title="t('EmailVerification')" />
<!-- gj邮箱验证 -->
</el-steps>
</div>
<!-- 账号信息 -->
<div class="form" v-if="active === 0">
<div class="title">{{ t('RetrieveThePassword') }}</div>
<!-- gj找回密码 -->
<div class="Email">
<el-input
type="text"
size="large"
class="input-item"
v-model="Email"
:placeholder="t('PleaseEnterTheEmailAddressForWhichYouNeedToRetrieveYourPassword')"
@keydown.enter.native="nextStep"
/>
<!-- gj请输入需要找回密码的邮箱地址 -->
</div>
<div class="btn">
<div class="Return" @click="Return">{{ t('Return') }}</div>
<!-- gj返回 -->
<div class="nextStep" @click="nextStep">{{ t('NextStep') }}</div>
<!-- gj下一步 -->
</div>
</div>
<!-- 完成注册 -->
<div class="form" v-if="active === 1">
<div class="title">{{ t('EmailVerification') }}</div>
<!-- gj邮箱验证 -->
<div class="Hint">
{{t('AVerificationEmailHasBeenSentToYour')+ Email+ t('emailAddressPleaseClickOnTheLinkInTheEmailToCompleteTheRetrieval')}}
<!-- gj已向您发送验证邮件请点击邮件中的链接完成密码找回 -->
</div>
<div class="ResendEmailbtn">
<div class="Return" @click="previousStep">{{ t('PreviousStep') }}</div>
<!-- gj上一步 -->
<div
class="ResendEmail"
@click="nextStep(1)"
:style="isCounting ? 'background-image: linear-gradient(0deg, #cccccc, #dddddd); cursor: not-allowed;' : ''"
>
{{ isCounting ? t('ResendIn')+`${countdown}`+t('seconds') : t('ResendTheEmail') }}
<!-- gjxxx秒后重发邮件/重发邮件 -->
</div>
<div class="Return" @click="Return">{{ t('ReturnToLogin') }}</div>
<!-- gj返回登录 -->
</div>
</div>
</div>
</template>
<script setup>
import {forgetPassword} from "@/api/account"; // 导入登录接口
import { setStorage, getStorage, getPromiseStorage } from "@/utils/storage.js";
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
onBeforeMount, // 组件挂载前执行
} from "vue";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
import { setLocale } from '@/i18n'
//
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
window['$t'] = t
//
const router = useRouter();
const active = ref(0); // 当前步骤
const Email = ref(""); // 邮箱
const countdown = ref(0); // 倒计时秒数
const isCounting = ref(false); // 是否正在倒计时
let timer = null; // 定时器
const language = ref(null); // 语言
function startCountdown() {
countdown.value = 60;
isCounting.value = true;
timer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(timer);
isCounting.value = false;
}
}, 1000);
}
//下一步
function nextStep(type) {
if (isCounting.value) return;
// 邮箱验证
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(Email.value)) {
ElMessage.error(t('PleaseEnterAValidEmailAddress'));
// gj请输入正确的邮箱地址
return;
}
forgetPassword({
mailAddress: Email.value,
}).then((res) => {
startCountdown();
if (type === 1) {
}else{
active.value = active.value + 1;
}
ElMessage.success(t('TheEmailWasSentSuccessfully'));
// gj邮件发送成功
}).catch((err) => {
ElMessage.error(t('TheEmailFailedToSend'));
// gj邮件发送失败
});
}
function previousStep() {
active.value = active.value - 1;
}
onBeforeMount(() => {
// 语言
getPromiseStorage("language")
.then((res) => {
console.log("获取语言成功", res);
language.value = res;
setLocale(language.value);
})
.catch((err) => {
console.log("获取语言失败", err);
});
});
//返回登录页
function Return() {
router.push("/");
}
// watch(refname, async (newQuestion, oldQuestion) => {
// // 变化后执行
// });
onMounted(() => {
// 组件挂载完成后执行
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
if (timer) clearInterval(timer);
});
</script>
<style scoped>
.email-registration {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
background-image: url(../assets/bg.png);
background-size: 100% 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.stepBar {
width: 900px;
}
.form {
margin-top: 30px;
width: 900px;
height: 600px;
background-image: linear-gradient(180deg, #dbf0f1, #ffffff);
border-radius: 10px;
border: 1px solid #4fcacd;
display: flex;
flex-direction: column;
align-items: center;
}
.title {
font-size: 30px;
color: #4fcacd;
margin-top: 70px;
text-align: center;
font-weight: bold;
}
.Hint {
width: 900px;
height: 400px;
color: #4fcacd;
font-size: 20px;
text-align: center;
line-height: 500px;
margin-top: -50px;
}
.ResendEmailbtn{
width: 100%;
margin-top: 50px;
display: flex;
justify-content:space-around;
align-items: center;
}
.ResendEmail {
width: 200px;
height: 40px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
border-radius: 10px;
color: #ffffff;
font-size: 16px;
text-align: center;
line-height: 40px;
cursor: pointer;
transition: all 0.3s ease;
}
.ResendEmail:hover {
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.Email {
margin-top: 150px;
width: 90%;
display: flex;
justify-content: center;
align-items: center;
}
.Password {
margin-top: 30px;
width: 90%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.ConfirmPassword {
margin-top: 30px;
width: 90%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.password-tip {
font-size: 12px;
color: #8c939d;
margin-top: 10px;
}
.btn {
width: 70%;
margin-top: 150px;
display: flex;
justify-content: space-between;
align-items: center;
}
.Return {
width: 200px;
height: 40px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
border-radius: 10px;
color: #ffffff;
font-size: 16px;
text-align: center;
line-height: 40px;
cursor: pointer;
transition: all 0.3s ease;
}
.Return:hover {
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.nextStep {
width: 200px;
height: 40px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
border-radius: 10px;
color: #ffffff;
font-size: 16px;
text-align: center;
line-height: 40px;
cursor: pointer;
transition: all 0.3s ease;
}
.nextStep:hover {
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.avatar {
width: 90%;
margin-top: 30px;
height: 178px;
}
.input {
width: 90%;
margin-top: 100px;
height: 50px;
}
.input-item {
border: none;
width: 80%;
height: 100%;
background-color: #ffffff00;
border-bottom: 1px solid #4fcacd;
}
:deep(.el-input__wrapper) {
box-shadow: none !important;
background-color: transparent !important;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: none !important;
}
.avatar-uploader {
width: 178px;
height: 178px;
border: 3px solid #4fcacd;
border-radius: 10px;
}
.avatar-uploader .avatar {
width: 178px;
height: 178px;
display: block;
}
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar-tip {
width: 178px;
text-align: center;
font-size: 12px;
color: #8c939d;
}
</style>

98
src/views/hosts/Forum.vue Normal file
View File

@@ -0,0 +1,98 @@
<template>
<!-- 站内信 -->
<div class="forum">
<div v-infinite-scroll="load" class="infinite-list" style="overflow: auto">
<el-card style="width: 90%" class="card" v-for="(item, index) in NoticeList" :key="index">
<template #header>
<div class="card-header">
<span>{{ item.title }}</span>
</div>
</template>
<div class="card-body">
{{ item.content }}
</div>
<template #footer>
<div class="card-footer">
<span>{{ item.time }}</span>
</div>
</template>
</el-card>
</div>
</div>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
} from "vue";
import { getNoticeList } from "@/api/account";
const refname = ref("");
const NoticeList = ref([]);
function load() {
// 加载更多数据
}
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
onMounted(() => {
getNoticeList({page: 0, size: 10}).then((res) => {
NoticeList.value = res;
}).catch((err) => {});
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped lang="less">
.forum {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.infinite-list {
width: 90%;
height: 90%;
display: flex;
flex-direction: column;
align-items: center;
}
.card{
border-radius: 16px;
border: 1px solid #4FCACD;
background-image: linear-gradient(180deg, #E4FFFF, #FFFFFF);
}
.card-header{
font-size: 18px;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
}
.card-footer{
font-size: 14px;
display: flex;
justify-content:flex-end;
align-items: center;
}
.card-body{
color: #333333;
font-size: 16px;
}
</style>

1484
src/views/hosts/Message.vue Normal file

File diff suppressed because it is too large Load Diff

159
src/views/hosts/Mine.vue Normal file
View File

@@ -0,0 +1,159 @@
<template>
<!-- 我的 -->
<div class="mine">
<div class="custom-style">
<div class="Selector" v-for="item in options">
<div class="card" @click="SelectorClick(item.value)" :class="{ 'active': item.value === segmentedvalue }">
<div class="card-icon">
<img class="icon" :class="{ 'pk-info-icon': item.value === 2 , 'PointsList-icon': item.value === 4 }" :src="item.icon" alt=""/>
</div>
<div class="card-content">
<div class="card-title" :class="{'active-text': item.value === segmentedvalue}">{{item.label}}</div>
</div>
</div>
</div>
</div>
<div class="mine-content">
<PointsList v-if="segmentedvalue === 4" />
<PKRecord v-if="segmentedvalue === 3" />
<PKmessage v-if="segmentedvalue === 2" />
<AnchorLibrary v-if="segmentedvalue === 1" />
</div>
</div>
</template>
<script setup>
import {
ref, // 响应式基础
watch, // 侦听器
onMounted, // 组件挂载完成后执行
onUpdated, // 组件更新后执行
onUnmounted, // 组件销毁前执行
} from "vue";
const refname = ref('');
import PointsList from "@/components/mineSubComponent/PointsList";
import PKRecord from "@/components/mineSubComponent/PKRecord";
import PKmessage from "@/components/mineSubComponent/PKmessage";
import AnchorLibrary from "@/components/mineSubComponent/AnchorLibrary";
//
import { useI18n } from "vue-i18n";
const { t } = useI18n();
window["$t"] = t;
//
const options = [
{
label: t('AnchorLibrary'),
// gj主播库
value: 1,
icon: require("@/assets/AnchorLibrary.png"),
},
{
label: t('PKInformation'),
//gjpk信息
value: 2,
icon: require("@/assets/PKInformation.png"),
},
{
label: t('MyPKRecord'),
// gj我的pk记录
value: 3,
icon: require("@/assets/PKRecord.png"),
},
{
label:t('PointsList'),
value: 4,
icon: require("@/assets/PointsList.png"),
},
]
function SelectorClick(value) {
segmentedvalue.value = value;
}
const segmentedvalue = ref(1);
watch(refname, async (newQuestion, oldQuestion) => {
// 变化后执行
});
onMounted(() => {
// 组件挂载完成后执行
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped lang="less">
.mine{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.custom-style{
width: 100%;
height: 100px;
display: flex;
align-items: center;
justify-content:space-between;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.Selector{
width: 420px;
height: 110px;
}
.card{
width: 100%;
height: 90px;
border-radius: 24px;
background-color: #CEF1EB;
border: 2px solid #ffffff;
display: flex;
align-items: center;
justify-content: center;
}
.card-icon{
height: 100%;
display: flex;
align-items: center;
}
.mine-content{
width: 100%;
height: calc(100% - 110px);
background-color: #ffffff;
border-radius: 16px;
}
.icon{
width:65px;
height: 65px;
}
.card-content{
margin-left: 62px;
}
.card-title{
font-size: 24px;
color: #636363;
}
.pk-info-icon {
width:50px;
height:72px;
}
.PointsList-icon{
width: 65px;
height: 69px;
}
.active{
background-image: linear-gradient(90deg, #E4FFFF, #FFFFFF);
border: 1px solid #03ABA8;
}
.active-text{
color: #03ABA8;
}
</style>

View File

@@ -1,579 +0,0 @@
<template>
<div class="hostList">
<div>
<div style="display: flex;">
<el-select v-model="searchForm.country" filterable placeholder="选择国家" size="large" style="width: 240px">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<div></div>
<el-date-picker v-model="searchForm.time" type="date" value-format="YYYYMMDD" placeholder="选择查询时间" size="large"
style="margin-left: 50px;" />
<el-button class="serch-button" style="margin-left: 50px;" type="primary" @click="serch">查询</el-button>
<el-button class="put-button" :disabled="tableData.length == 0" type="primary"
@click="exportList">导出Excel数据</el-button>
<el-button v-if="userInfo.userType == 3" class="put-button" type="primary"
@click="dialogFormVisible = true">分配给指定员工</el-button>
<el-button @click="filterdialogVisible = true" style="width: 50px;" class="put-button" type="primary"><img
style="height: 30px;" src="@/assets/filter.png"></el-button>
</div>
<div class="hostTable center-justify">
<el-table ref="multipleTableRef" :data="tableData" stripe v-loading="loading" height="500"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="35" />
<el-table-column fixed prop="hostId" label="主播id" width="160">
<template #default="scope">
<div class="hostIdText" @click="openHTML(scope.row.hostId)"> {{ scope.row.hostId }}</div>
</template>
</el-table-column>
<!-- <el-table-column prop="hostName" label="主播名字" min-width="160">
<template #default="scope">
{{ scope.row.hostName }}
</template>
</el-table-column> -->
<el-table-column prop="hostlevel" label="等级" width="120">
<template #default="scope">
{{ scope.row.hostlevel }}
</template>
</el-table-column>
<el-table-column v-for="label in labelList" :key="label.paramCode" :prop="label.paramCode"
:label="label.paramCodeMeaning" width="120">
<template v-if="label.paramCode != 'createDt'" #default="scope">
<el-popover v-if="!(label.paramCode == 'hostcoins' || label.paramCode == 'ysthostcoins')"
placement="bottom" :width="600" trigger="hover">
<div style="height: 300px;">
<component :is="EChartsComponent" v-if="isPopoverVisible[`${scope.row.hostId}-${label.paramCode}`]"
:title="label.paramCodeMeaning" :id="scope.row.hostId" :dataType="label.paramCode"
:time="searchForm.time"></component>
</div>
<template #reference>
<span @mouseover="openPopover(scope.row.hostId, label.paramCode)"
@mouseout="closePopover(scope.row.hostId, label.paramCode)">
{{ scope.row[label.paramCode] }}
</span>
</template>
</el-popover>
<el-popover v-else placement="bottom" :width="500" trigger="hover">
<div style="height: 300px;">
<component :is="EChartsComponent" v-if="isPopoverVisible[`${scope.row.hostId}-${label.paramCode}`]"
:title="label.paramCodeMeaning" :id="scope.row.hostId" :dataType="label.paramCode"
:time="searchForm.time">
</component>
</div>
<template #reference>
<!-- 鼠标移入时开启 -->
<span @mouseover="openPopover(scope.row.hostId, label.paramCode)"
@mouseout="closePopover(scope.row.hostId, label.paramCode)">
{{ scope.row[label.paramCode] }}
</span>
</template>
</el-popover>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="120">
<template #default="scope">
<el-popover placement="top-start" :width="200" trigger="hover" :content="scope.row.comment">
<template #reference>
<el-button @click="openComment(scope.row)" link type="primary" size="small">维护情况</el-button>
</template>
</el-popover>
<select @change="handleSelectChange($event, scope.row)" v-model="scope.row.useable" class="m-2"
placeholder="Select" size="small" style="width: 70px">
<option label="未转化" value="N" />
<option label="已转化" value="Y" />
</select>
<el-dialog :modal="false" v-model="commentVisible" title="维护情况" align-center>
<el-input maxlength="80" v-model="commentInfo" placeholder="请输入维护情况" clearable show-word-limit />
<template #footer>
<span class="dialog-footer">
<el-button @click="commentVisible = false">取消</el-button>
<el-button type="primary" @click="uphostcomment()">
提交
</el-button>
</span>
</template>
</el-dialog>
</template>
</el-table-column>
<!-- <el-table-column label="操作">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-button type="primary" @click="getTkhostdetail(scope.row.hostId)">查看</el-button>
</div>
</template>
</el-table-column> -->
</el-table>
</div>
<div class="center-justify" style="margin-top: 30px;">
<el-pagination v-model:current-page="page" v-model:page-size="pageSize" background
layout="sizes, prev, pager, next" :total="total" :page-sizes="[10, 20, 50, 100]"
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
</div>
<el-dialog v-model="dialogFormVisible" title="分配主播" align-center>
<el-select v-model="staffValue" filterable placeholder="Select" style="width: 240px">
<el-option v-for="item in staffOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="allocation">
分配
</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="filterdialogVisible" width="800px">
<el-row :gutter="20">
<el-col :span="8">
<label>选择筛选条件</label>
<el-select v-model="searchForm.dataType" filterable placeholder="筛选条件" style="width: 240px">
<el-option v-for="item in filterOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-col>
<el-col :span="8">
<div v-if="!(searchForm.dataType == 'InvitationType')"><label>最小值</label></div>
<div v-else><label>普票/金票</label></div>
<el-input v-show="!(searchForm.dataType == 'InvitationType')" type="number" v-model="searchForm.dataStart"
placeholder="请输入最小值" />
<el-select v-show="(searchForm.dataType == 'InvitationType')" v-model="searchForm.dataStart" filterable
placeholder="请选择" style="width: 240px">
<el-option v-for="item in [{ label: '普票', value: '1' }, { label: '金票', value: '2' }]" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</el-col>
<el-col :span="8">
<div v-show="!(searchForm.dataType == 'InvitationType')"><label>最大值</label></div>
<el-input v-show="!(searchForm.dataType == 'InvitationType')" type="number" v-model="searchForm.dataEnd"
placeholder="请输入最大值" />
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<!-- <el-button @click="filterdialogVisible = false">取消</el-button> -->
<el-button type="primary" @click="filterdialogVisible = false">
确认
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
// import { getToken, setToken, removeToken } from '@/utils/storage'
import { tkhostdata, dicts, tkhostdetail, downList, getStaffList, managerhosts, upholdinfo, getCountryinfo } from '@/api/account';
import { usePythonBridge, } from '@/utils/pythonBridge'
import { getUser } from '@/utils/storage'
import { ref, reactive, onMounted } from 'vue';
import EChartsComponent from '@/components/EChartsComponent.vue';
import { ElMessage, ElMessageBox } from 'element-plus'
const loading = ref(false)
//py方法
const { givePyAnchorId, exportToExcel } = usePythonBridge();
let num = ref(0)
//账号信息
const userInfo = ref(getUser())
//主播列表DOM
const multipleTableRef = ref(null)
let labelList = ref([])
const tableData = ref([])
//选择国家
const searchForm = ref({
country: '',
time: new Date().toISOString().split('T')[0].replace(/-/g, ''),
dataType: '',
dataStart: '',
dataEnd: '',
})
//员工选择列表
let staffOptions = ref([])
//筛选条件选择列表
let filterOptions = ref([])
//选择的员工
let staffValue = ref('')
//选择的主播列表
let selectHostList = ref([])
//分配弹窗是否弹出
let dialogFormVisible = ref(false)
//备注弹窗是否弹出
let commentVisible = ref(false)
//筛选弹窗是否弹出
let filterdialogVisible = ref(false)
//备注信息
let commentInfo = ref('')
//备注信息主播
let commentHost = ref('')
//分页
let pageSize = ref(10)
let page = ref(1)
let total = ref(0)
//是否渲染
const isPopoverVisible = reactive({})
let options = ref([])
onMounted(() => {
//获取字典
getdictionary()
//获取下级员工
getStaff();
//获取国家
getCountry();
getlist();//获取主播列表
})
function serch() {
getlist();
}
function exportList() {
if (searchForm.value.dataType == 'InvitationType') {
searchForm.value.dataEnd = searchForm.value.dataStart
}
exportToExcel({
searchTime: searchForm.value.time == '' ? null : searchForm.value.time,
region: searchForm.value.country == '' ? null : searchForm.value.country,
dataType: searchForm.value.dataType == '' ? null : searchForm.value.dataType,
dataStart: searchForm.value.dataStart == '' ? null : searchForm.value.dataStart,
dataEnd: searchForm.value.dataEnd == '' ? null : searchForm.value.dataEnd,
pageSize: pageSize.value,
page: page.value,
userId: userInfo.value.userId,
userType: userInfo.value.userType
})
// //浏览器导出方法
// downList('export/hostsinfo',
// {
// searchTime: searchForm.value.time,
// region: searchForm.value.country,
// pageSize: pageSize.value,
// page: page.value,
// userId: userInfo.value.userId,
// userType: userInfo.value.userType
// }
// );
}
//分页每页条数
function handleSizeChange(val) {
console.log(`${val} items per page`)
getlist();
}
//分页页数
function handleCurrentChange(val) {
console.log(`current page: ${val}`)
getlist();
}
//选择行
function handleSelectionChange(data) {
console.log(data)
data.forEach(item => {
selectHostList.value.push(item.hostId)
})
// multipleTableRef.value = data
// console.log(multipleTableRef.value)
}
//获取主播列表
const getlist = () => {
if (searchForm.value.dataType == 'InvitationType') {
searchForm.value.dataEnd = searchForm.value.dataStart
}
loading.value = true
console.log(searchForm.value)
tkhostdata({
searchTime: searchForm.value.time == '' ? null : searchForm.value.time,
region: searchForm.value.country == '' ? null : searchForm.value.country,
dataType: searchForm.value.dataType == '' ? null : searchForm.value.dataType,
dataStart: searchForm.value.dataStart == '' ? null : searchForm.value.dataStart,
dataEnd: searchForm.value.dataEnd == '' ? null : searchForm.value.dataEnd,
pageSize: pageSize.value,
page: page.value,
userId: userInfo.value.userId,
userType: userInfo.value.userType
}).then(res => {
loading.value = false
total.value = res.total
tableData.value = []
res.records.forEach(item => {
if (item.infoMap.InvitationType == '1') {
item.infoMap.InvitationType = '普票'
} else if (item.infoMap.InvitationType == '2') {
item.infoMap.InvitationType = '金票'
}
console.log(item.infoMap)
item = { ...item, ...item.infoMap }
tableData.value.push(item)
})
})
}
function openComment(data) {
console.log(data)
commentInfo.value = data.comment
commentHost.value = data.hostId
commentVisible.value = true
}
//修改主播维护状态
function handleSelectChange(event, data) {
upholdinfo({
"hostId": data.hostId,
"userId": userInfo.value.userId,
"tenantId": userInfo.value.tenantId,
// "comment": "我已经尽力维护,但是失败了",
"useable": event.target.value
}).then(res => {
console.log(res)
})
}
//更改主播维护备注
function uphostcomment() {
upholdinfo({
"hostId": commentHost.value,
"userId": userInfo.value.userId,
"tenantId": userInfo.value.tenantId,
"comment": commentInfo.value,
}).then(res => {
console.log(res)
serch()
commentVisible.value = false
})
}
//获取字典
const getdictionary = () => {
dicts({
paramType: "hostsdata",
// page: 1,
offset: 1,
pageSize: 100
}).then(res => {
// labelList.value = res.records
labelList.value = res
console.log(labelList.value)
labelList.value.forEach(item => {
filterOptions.value.push({
value: item.paramCode,
label: item.paramCodeMeaning
})
})
})
}
//获取国家
function getCountry() {
getCountryinfo({}).then(res => {
console.log(res)
res.forEach(item => {
options.value.push({ value: item.countryGroupName, label: item.countryGroupName })
})
console.log(options.value)
})
}
//获取下级员工
const getStaff = () => {
getStaffList({
userId: userInfo.value.userId,
userType: userInfo.value.userType,
activeYn: userInfo.value.activeYn,
tenantId: userInfo.value.tenantId,
}).then(res => {
console.log(res)
res.forEach(item => {
staffOptions.value.push({
value: item.userId,
label: item.userName
})
})
})
}
//分配主播给员工
function allocation() {
managerhosts({
"manaId": userInfo.value.userId,
"userId": staffValue.value,
"tenantId": userInfo.value.tenantId,
"hostIds": selectHostList.value
}).then(res => {
if (res) {
dialogFormVisible.value = false
}
})
}
//获取主播信息
const getTkhostdetail = (id) => {
tkhostdetail({
hostId: id,
// page: 1,
searchTimeStart: '20250401',
searchTimeEnd: '20250403'
}).then(res => {
console.log(labelList.value)
})
}
function openPopover(hostId, paramCode) {
isPopoverVisible[`${hostId}-${paramCode}`] = true;
}
function closePopover(hostId, paramCode) {
// isPopoverVisible[`${hostId}-${paramCode}`] = false;
}
function openHTML(id) {
givePyAnchorId(id)
}
</script>
<style lang="less">
.hostList {
box-sizing: border-box;
// height: 100vh;
width: 100%;
padding: 40px;
/* 页面无法选中 */
// -webkit-user-select: none;
// -moz-user-select: none;
// -ms-user-select: none;
// user-select: none;
.hostTable {
width: 100%;
padding: 40px 0;
.hostIdText {
text-decoration: underline;
cursor: pointer;
color: #0f0092;
}
}
.serch-button {
width: 80px;
height: 47px;
background: #E7CA92;
border-radius: 10px;
border: none;
}
.put-button {
width: 132px;
height: 47px;
background: #E7CA92;
border-radius: 10px;
border: none;
}
}
.el-dialog {
--el-dialog-font-line-height: 50px;
--el-dialog-width: 600px;
--el-dialog-border-radius: 8px;
}
.center-line {
display: flex;
flex-direction: column;
align-items: center;
// justify-content: center;
}
.center-justify {
display: flex;
justify-content: space-around;
align-items: center;
}
.center-align {
display: flex;
justify-content: space-between;
}
.center-flex {
display: flex;
justify-content: center;
align-items: center;
}
</style>
<style scoped>
::v-deep(.el-input__wrapper) {
background-color: #F2FAF9;
border: 1px solid #B7CEC5;
height: 44px;
}
::v-deep(.el-select__wrapper) {
background-color: #F2FAF9;
border: 1px solid #B7CEC5;
height: 48px;
}
::v-deep(.el-pagination.is-background .el-pager li.is-active) {
background-color: #338F6A;
}
</style>

2196
src/views/hosts/pk.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,610 +0,0 @@
<template>
<div class="center-line workbenches">
<div class="center-align" style="width: 100%; margin: 0 20px;">
<div class="box-card-num1 center-line">
<div>总数量: <span>{{ hostData.totalCount }}</span></div>
<div>有效主播: <span>{{ hostData.validAnchorsCount }}</span></div>
<div> 已查询: <span>{{ hostData.checkedDataCount }}</span></div>
<div>可邀请: <span>{{ hostData.canInvitationCount }}</span></div>
</div>
<div class="center-line" style="padding-top: 15vh;">
<el-button class="open-login" type="primary" @click="openTK">开启tk</el-button>
</div>
<div>
<el-card class="box-card-num" v-for="(item, index) in 2" :key="index">
<div class="center-justify">
<div class="from-input-item">
<div class="from-input-item-title center-justify">
公会账号
</div>
<el-input :disabled="!(tkData[index].code == 0 && !isLogin[index])"
v-model="tkData[index].account" placeholder="请输入登录账号" clearable />
</div>
<div class="from-input-item">
<div class="from-input-item-title center-justify">
公会密码
</div>
<el-input :disabled="!(tkData[index].code == 0 && !isLogin[index])"
v-model="tkData[index].password" type="password" placeholder="请输入登录密码" show-password />
</div>
<el-button class="open-login" style="margin-left: 60px;"
:disabled="!(tkData[index].code == 0 && !isLogin[index])" type="primary"
@click="loginTK(index)">登录后台</el-button>
<div v-if="tkData[index].code == 0" class="loginState"></div>
<div v-if="tkData[index].code == 1" class="loginState" style="background-color: green;"></div>
</div>
<div class="todayCount">今日已查询次数{{ tkData[index].num }}</div>
</el-card>
</div>
</div>
<div class="container ">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span class="center-justify"><img src="@/assets/worklogo.png">工作台 </span>
<div style="margin-right: 120px;">当前网络:{{ countryData }}
<!-- <el-button class="reset-button" @click="reset">重置数据</el-button> -->
</div>
</div>
</template>n
<el-row :gutter="20">
<el-col :span="8">
<div class="input-group">
<label>设置金币数量</label>
<el-input type='number' v-model="pyData.gold.min" :min="0" :max="pyData.gold.max - 1"
placeholder="最小值" style="width: 100%" :disabled="!pyData.isStart">
<template #prepend>最小金币数</template>
</el-input>
<el-input type='number' v-model="pyData.gold.max" :min="pyData.gold.min + 1" :max="100"
placeholder="最大值" style="width: 100%; margin-top: 10px" :disabled="!pyData.isStart">
<template #prepend>最大金币数</template>
</el-input>
</div>
</el-col>
<el-col :span="8">
<div class="input-group">
<label>设置粉丝数量</label>
<el-input type='number' v-model="pyData.fans.min" :min="0" :max="pyData.fans.max - 1"
placeholder="最小值" style="width: 100%" :disabled="!pyData.isStart">
<template #prepend>最小粉丝数</template>
</el-input>
<el-input type='number' v-model="pyData.fans.max" :min="pyData.fans.min + 1" :max="100"
placeholder="最大值" style="width: 100%; margin-top: 10px" :disabled="!pyData.isStart">
<template #prepend>最大粉丝数</template>
</el-input>
</div>
</el-col>
<el-col :span="8">
<div class="input-group">
<label>后台查询频率</label>
<el-input type='number' v-model="pyData.frequency.hour" :min="0"
:max="pyData.frequency.day - 1" placeholder="次/小时" style="width: 100%"
:disabled="!pyData.isStart">
<template #append>/小时</template>
</el-input>
<el-input type='number' v-model="pyData.frequency.day" :min="pyData.frequency.hour + 1"
:max="100" placeholder="次/24小时" style="width: 100%; margin-top: 10px"
:disabled="!pyData.isStart">
<template #append>/24小时</template>
</el-input>
</div>
</el-col>
</el-row>
<div style="margin-top: 20px; text-align: center">
<el-button class="submit-button" :disabled="submitting" v-show="pyData.isStart" type="primary"
@click="submit">开始获取数据</el-button>
<el-button v-show="!pyData.isStart" type="danger" @click="unsubmit">停止获取</el-button>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { usePythonBridge, } from '@/utils/pythonBridge'
import { setNumData, getNumData, getUser, setTkUser, getTkUser } from '@/utils/storage'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getCountryName } from '@/utils/countryUtil'
import { tkaccountuseinfo } from '@/api/account'
//导入python交互方法
const { fetchDataConfig, fetchDataCount, loginBackStage, loginTikTok, backStageloginStatus, backStageloginStatusCopy } = usePythonBridge();
//ip国家
let countryData = ref('');
//获取主播数量的定时器
let getHostTimer = ref(null);
//获取的主播信息
let hostData = ref({
totalCount: 0,
validAnchorsCount: 0,
canInvitationCount: 0,
checkedDataCount: 0,
});
//是否开启tk
// let isTk = ref(true);
//账号是否登陆中
let isLogin = ref([false, false]);
//设置状态轮询定时器
let statusTimer = ref(null);
let statusTimerCopy = ref(null);
//tk账号信息
let tkData = ref([
{
account: '',
password: '',
index: 1,
code: 0,
num: 0
},
{
account: '',
password: '',
index: 2,
code: 0,
num: 0
},
]);
//python需要的数据
let pyData = ref({
gold: { min: 0, max: 0 },
fans: { min: 0, max: 0 },
frequency: { hour: 0, day: 0 },
isStart: true,
country: countryData.value,
test: '123',
test1: { test: 123, test12: 123 },
tenantId: getUser().tenantId,
userId: getUser().userId,
});
//按钮提交状态
let submitting = ref(true);
onMounted(() => {
//从缓存获取数据
if (getNumData()) {
pyData.value = getNumData();
}
if (getTkUser()) {
tkData.value = getTkUser();
tkData.value[0].code = 0;
tkData.value[1].code = 0;
}
tkaccountuse(tkData.value[0].account, 0)
tkaccountuse(tkData.value[1].account, 1)
getIpInfo()
//查询次数查询
})
const getIpInfo = async () => {
try {
const response = await fetch('https://ipapi.co/json/');
if (!response.ok) {
throw new Error('请求失败');
}
const data = await response.json();
console.log('IP信息:', data.country);
countryData.value = getCountryName(data.country);
} catch (error) {
console.error('请求出错:', error);
}
};
//提交数据到py
const submit = () => {
pyData.value.country = countryData.value;
console.log('提交的区间值:', pyData.value);
// if (tkData.value[0].account == '' && tkData.value[1].account == '') {
// ElMessage.error('请输入账号密码');
// return;
// }
// if (tkData.value[0].password == '' && tkData.value[1].password == '') {
// ElMessage.error('请输入账号密码');
// return;
// }
if (((Number(pyData.value.gold.min) > Number(pyData.value.gold.max)) || (Number(pyData.value.fans.min) > Number(pyData.value.fans.max)))) {
ElMessage.error('请输入正确的区间值');
return;
}
if ((Number(pyData.value.gold.max) <= 0 || Number(pyData.value.fans.max <= 0)) || pyData.value.gold.max == '' || pyData.value.fans.max == '') {
ElMessage.error('请输入正确的区间值');
return;
}
if (Number(pyData.value.frequency.hour) <= 0 || Number(pyData.value.frequency.day <= 0) || pyData.value.frequency.hour == '' || pyData.value.frequency.day == '') {
ElMessage.error('请输入正确的频率区间值');
return;
}
ElMessageBox.confirm(
'确认开始爬取数据?',
'开始',
{
confirmButtonText: '开始',
cancelButtonText: '取消',
type: 'success',
}
)
.then(() => {
// console.log('提交的区间值:', pyData.value.gold, pyData.value.fans, pyData.value.frequency);
//开始按钮的状态 改为禁用
submitting.value = true;
setNumData(pyData.value);
console.error('提交的区间值:', JSON.stringify(pyData.value));
fetchDataConfig(JSON.stringify({
gold: pyData.value.gold,
fans: pyData.value.fans,
frequency: pyData.value.frequency,
isStart: true,
country: countryData.value,
tenantId: getUser().tenantId,
userId: getUser().userId,
})).then((res) => {
getHostTimer.value = setInterval(() => {
fetchDataCount().then((res) => {
hostData.value = JSON.parse(res);
tkaccountuse(tkData.value[0].account, 0)
tkaccountuse(tkData.value[1].account, 1)
})
}, 1000);
}).finally(() => {
setTimeout(() => {
pyData.value.isStart = false;
submitting.value = false;
}, 2000)
})
})
.catch(() => {
})
};
//停止
const unsubmit = () => {
fetchDataConfig(JSON.stringify({
gold: pyData.value.gold,
fans: pyData.value.fans,
frequency: pyData.value.frequency,
isStart: false,
country: countryData.value,
tenantId: getUser().tenantId,
userId: getUser().userId,
})).then((res) => {
pyData.value.isStart = true;
clearInterval(getHostTimer.value);
getHostTimer.value = null;
// ElMessage.sussec('已停止')
}).catch((err) => {
// ElMessage.error('停止失败')
})
};
//重置
const reset = () => {
pyData.value.gold = { min: 0, max: 0 };
pyData.value.fans = { min: 0, max: 0 };
pyData.value.frequency = { hour: 0, day: 0 };
};
const loginTK = (index) => {
setTkUser(tkData.value)
loginBackStage({
account: tkData.value[index].account,
password: tkData.value[index].password,
index: index
})
if (index == 0) {
isLogin.value[1] = true;
statusTimer = setInterval(() => {
getloginStatus();
}, 2000)
} else if (index == 1) {
isLogin.value[0] = true;
statusTimerCopy = setInterval(() => {
getloginStatusCopy();
}, 2000)
}
}
const openTK = () => {
// isTk.value = true;
// console.log(isTk.value)
loginTikTok();
}
function getloginStatus() {
backStageloginStatus().then((res) => {
const data = JSON.parse(res);
tkData.value[data.index].code = data.code
if (data.code == 1) {
clearInterval(statusTimer);
statusTimer = null;
submitting.value = false
isLogin.value[1] = false;
}
})
}
function getloginStatusCopy() {
backStageloginStatusCopy().then((res) => {
const data = JSON.parse(res);
tkData.value[data.index].code = data.code
if (data.code == 1) {
clearInterval(statusTimer);
statusTimer = null;
submitting.value = false
isLogin.value[0] = false;
}
})
}
function tkaccountuse(id, index) {
let num = 0;
tkaccountuseinfo({ userId: id }).then((res) => {
num = res
tkData.value[index].num = num
console.log('账号使用次数', tkData.value[index].num)
})
}
</script>
<style scoped lang="less">
.container {
margin: 0 auto;
}
.workbenches {
padding: 45px 29px 22px 27px;
/* 页面无法选中 */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.box-card {
// width: 1240px;
height: 436px;
background: #FFFFFF;
box-shadow: 0px 0px 21px 0px rgba(183, 183, 183, 0.33);
border-radius: 24px;
}
.box-card-num1 {
width: 197px;
height: 321px;
background: #FFFFFF;
box-shadow: 0px 0px 21px 0px rgba(183, 183, 183, 0.33);
border-radius: 24px;
padding-top: 60px;
box-sizing: border-box;
div {
height: 20%;
display: flex;
justify-content: space-around;
align-items: center;
color: #8D8E8E;
span {
color: #000;
padding-left: 10px;
}
}
}
.box-card-num {
width: 897px;
height: 145px;
background: #FFFFFF;
box-shadow: 0px 0px 21px 0px rgba(183, 183, 183, 0.33);
border-radius: 24px;
margin-bottom: 30px;
padding-top: 18px;
box-sizing: border-box;
.todayCount {
padding: 15px 21px;
font-size: 14px;
}
}
.from-input-item {
display: flex;
.from-input-item-title {
color: #000000;
font-size: 14px;
font-weight: 500;
width: 100px;
height: 50px;
}
.loginButton {
width: 100%;
height: 40px;
color: #ffffff;
font-size: 16px;
}
}
.loginState {
width: 15px;
height: 15px;
border-radius: 50%;
background-color: #b90000;
margin-left: 15px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
span {
font-family: Source Han Sans SC;
font-weight: 400;
font-size: 20px;
color: #2D2727;
line-height: 37px;
img {
margin-right: 16px;
}
}
}
.input-group {
margin-bottom: 20px;
.el-input {
margin: 22px 0;
}
}
label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #606266;
}
.open-login {
width: 100px;
height: 47px;
background: #E7CA92;
border-radius: 10px;
border: none;
}
.reset-button {
width: 132px;
height: 47px;
background: #E7CA92;
border-radius: 10px;
font-family: Source Han Sans SC;
font-weight: 400;
font-size: 18px;
color: #FFFFFF;
margin-left: 50px;
}
.submit-button {
width: 160px;
height: 47px;
background: #338F6A;
border-radius: 10px;
}
.center-line {
display: flex;
flex-direction: column;
align-items: center;
// justify-content: center;
}
.center-justify {
display: flex;
justify-content: space-around;
align-items: center;
}
.center-align {
display: flex;
justify-content: space-between;
}
.center-flex {
display: flex;
justify-content: center;
align-items: center;
}
</style>
<style scoped>
::v-deep(.el-input-group__prepend) {
background: #84CEB2;
border-radius: 10px 0px 0px 10px;
border: 1px solid #B7CEC5;
font-family: Source Han Sans SC;
font-weight: 400;
font-size: 18px;
color: #FFFFFF;
line-height: 37px;
}
::v-deep(.el-input-group__append) {
background: #84CEB2;
border-radius: 0px 10px 10px 0px;
border: 1px solid #B7CEC5;
font-family: Source Han Sans SC;
font-weight: 400;
font-size: 18px;
color: #FFFFFF;
line-height: 37px;
}
::v-deep(.el-input__wrapper) {
width: 218px;
height: 44px;
}
.el-input {
width: 200px;
height: 48px;
background: #FFFFFF;
border-radius: 10px;
border: 1px solid #B7CEC5;
}
</style>

View File

@@ -1,89 +1,102 @@
<template>
<div class="app-container">
<Sidebar class="noneText" @activeIndex="activeIndexFn" />
<div class="content ">
<div v-show="activeIndex == 1">
<workbenches v-if="openWerk" />
</div>
<div v-show="activeIndex == 2">
<hostsList v-if="openList" />
</div>
</div>
<div class="common-layout">
<el-container>
<!-- 左侧导航栏 -->
<el-aside class="nav-aside">
<Appaside></Appaside>
</el-aside>
<!-- 右侧主体内容 -->
<el-main class="nav-main">
<router-view />
</el-main>
</el-container>
</div>
</template>
<script setup>
import Sidebar from '../components/Sidebar.vue';
import { RouterLink, RouterView } from 'vue-router'
import hostsList from '@/views/hosts/hostsList.vue'
import workbenches from '@/views/hosts/workbenches.vue'
import { ref } from 'vue'
import { getUser } from '@/utils/storage'
let userType = ref(getUser().userType)
let activeIndex = ref(userType.value == 3 ? 1 : 2)
let openWerk = ref(userType.value == 3 ? true : false)
let openList = ref(userType.value == 3 ? false : true)
console.log("用户等级", getUser().userType)
function activeIndexFn(data) {
activeIndex.value = data
openWerk.value = true
openList.value = true
console.log(data)
import Appaside from "/src/components/Appaside.vue";
import { ref, watch, onMounted, onUnmounted,onBeforeMount } from "vue";
import { getOtp } from "@/api/account";
const user = ref(null); // 用户信息
import { goEasyLink } from "@/utils/goeasy.js";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import {getPromiseStorage } from "@/utils/storage.js";
import { setLocale } from '@/i18n'
const router = useRouter();
const language = ref(null); // 语言
//自动链接IM
function autoLinkIM() {
getOtp()
.then((res) => {
const data = {
id: String(user.value.id),
avatar: user.value.headerIcon,
nickname: user.value.nickName,
key: res,
};
goEasyLink(data)
.then(() => {
console.log("IM链接成功");
})
.catch((err) => {
ElMessage.error("聊天系统链接失败");
router.push("/");
});
})
.catch((err) => {
ElMessage.error("聊天系统获取失败");
router.push("/");
});
}
onBeforeMount(() => {
// 语言
getPromiseStorage("language")
.then((res) => {
console.log("获取语言成功", res);
language.value = res;
setLocale(language.value);
})
.catch((err) => {
console.log("获取语言失败", err);
});
});
onMounted(() => {
getPromiseStorage("user")
.then((res) => {
user.value = res;
autoLinkIM() //自动链接IM
})
.catch((err) => {
console.log(err);
});
});
</script>
<style>
<style lang="less">
body,
html {
margin: 0;
padding: 0;
height: 100%;
height: 100vh;
width: 100vw;
}
.app-container {
display: flex;
width: 1600px;
height: 900px;
background-color: #338F6A;
.common-layout {
width: 100vw;
height: 100vh;
background-image: url(../assets/bg.png);
background-size: 100% 100%;
}
.noneText {
/* 页面无法选中 */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.nav-main {
width: 95vw;
height: 100vh;
}
.sidebar {
width: 200px;
background-color: #338F6A;
padding: 20px;
/* box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); */
.nav-aside {
width: 5vw;
height: 100vh;
// background-image: linear-gradient(0deg, @bg-Sidebar-color-top, @bg-color);
// border: 2px solid @border-color;
}
.content {
margin-left: 280px;
width: 1304px;
height: 868px;
background: #FFFFFF;
border-radius: 36px;
margin-top: 16px;
}
.center-justify {
display: flex;
justify-content: space-around;
align-items: center;
}
</style>
</style>

323
src/views/resetPassword.vue Normal file
View File

@@ -0,0 +1,323 @@
<template>
<div class="email-registration">
<!-- 账号信息 -->
<div class="form" v-if="active === 0">
<div class="title">{{ t('ResetThePassword') }}</div>
<!-- gj重置密码 -->
<div class="Password">
<el-input
type="Password"
size="large"
class="input-item"
v-model="Password"
show-password
:placeholder="t('PleaseEnterTheNewPassword')"
/>
<!-- gj请输入新密码 -->
<text class="password-tip">{{ t('ThePasswordMustContainBothUpperAndLowerCaseLettersAndNumbersAndBe6To16CharactersLong') }}</text>
<!-- gj密码须包含大小写字母和数字长度6-16 -->
</div>
<div class="ConfirmPassword">
<el-input
type="Password"
size="large"
class="input-item"
v-model="ConfirmPassword"
show-password
:placeholder="t('PleaseEnterThePasswordAgain')"
/>
<!-- gj请再次输入密码 -->
</div>
<div class="btn">
<div class="nextStep" @click="nextStep">{{ t('Confirm') }}</div>
<!-- gj确认 -->
</div>
</div>
<!-- 完成注册 -->
<div class="forms" v-if="active === 1">
<div class="title">{{ t('PasswordResetSuccessful') }}</div>
<!-- gj密码重置成功 -->
</div>
</div>
</template>
<script setup>
import { useRoute } from "vue-router";
import { ref, watch, onMounted, onUpdated, onUnmounted,onBeforeMount } from "vue";
import { resetPassword } from "@/api/account";
const active = ref(0);// 当前步骤
const route = useRoute();
const token = ref("");
const Password = ref("");// 密码
const ConfirmPassword = ref("");// 确认密码
import { ElMessage } from "element-plus";
import { setLocale } from "@/i18n";
//
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const language = ref(null); // 语言
window["$t"] = t;
//
//确认
function nextStep() {
// 密码验证
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,16}$/;
if (!passwordRegex.test(Password.value)) {
ElMessage.error(t('ThePasswordMustContainBothUpperAndLowerCaseLettersAndNumbersAndBe6To16CharactersLong'));
return;
}
// 确认密码验证
if (Password.value !== ConfirmPassword.value) {
ElMessage.error(t('TheTwoPasswordEntriesAreInconsistent'));
return;
}
resetPassword({
token: token.value,
password: Password.value,
confirmPassword: ConfirmPassword.value
}).then(() => {
active.value = 1;
}).catch((err) => {});
}
onMounted(() => {
token.value = route.params.token;
});
onBeforeMount(() => {
// 语言
getPromiseStorage("language")
.then((res) => {
console.log("获取语言成功", res);
language.value = res;
setLocale(language.value);
})
.catch((err) => {
console.log("获取语言失败", err);
});
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped>
.email-registration {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
background-image: url(../assets/bg.png);
background-size: 100% 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.stepBar {
width: 80%;
}
.form {
margin-top: 30px;
width: 80%;
height: 80%;
background-image: linear-gradient(180deg, #dbf0f1, #ffffff);
border-radius: 10px;
border: 1px solid #4fcacd;
display: flex;
flex-direction: column;
align-items: center;
}
.forms{
margin-top: 30px;
width: 80%;
height: 80%;
background-image: linear-gradient(180deg, #dbf0f1, #ffffff);
border-radius: 10px;
border: 1px solid #4fcacd;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.title{
font-size: 70px;
color: #4fcacd;
margin-top: 70px;
text-align: center;
font-weight: bold;
}
.Hint{
width: 900px;
height: 400px;
color:#4fcacd;
font-size: 20px;
text-align: center;
line-height: 500px;
margin-top: -50px;
}
.ResendEmail{
width: 200px;
height: 40px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
border-radius: 10px;
color: #ffffff;
font-size: 16px;
text-align: center;
line-height: 40px;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 50px;
}
.ResendEmail:hover{
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.Email{
margin-top: 30px;
width: 90%;
display: flex;
justify-content: center;
align-items: center;
}
.Password{
margin-top: 30px;
width: 90%;
height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.ConfirmPassword{
margin-top: 50px;
width: 90%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.password-tip{
font-size: 40px;
color: #8c939d;
margin-top: 10px;
}
.btn{
width: 70%;
margin-top: 70px;
display: flex;
justify-content: space-between;
align-items: center;
}
.Return{
width: 200px;
height: 40px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
border-radius: 10px;
color: #ffffff;
font-size: 16px;
text-align: center;
line-height: 40px;
cursor: pointer;
transition: all 0.3s ease;
}
.Return:hover{
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.nextStep{
width: 100%;
height: 100px;
background-image: linear-gradient(0deg, #4fcacd, #5fdbde);
border-radius: 10px;
color: #ffffff;
font-size: 50px;
text-align: center;
line-height: 100px;
cursor: pointer;
transition: all 0.3s ease;
}
.nextStep:hover{
box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.3);
transform: scale(1.08);
opacity: 0.8;
}
.avatar {
width: 90%;
margin-top: 30px;
height: 178px;
}
.input {
width: 90%;
margin-top: 100px;
height: 200px;
}
.input-item {
border: none;
width: 80%;
height: 100px;
background-color: #ffffff00;
border-bottom: 1px solid #4fcacd;
font-size: 40px;
}
:deep(.el-input__wrapper) {
height: 50px;
box-shadow: none !important;
background-color: transparent !important;
}
:deep(.el-input__icon) {
width: 50px;
height: 50px;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: none !important;
}
.avatar-uploader {
width: 178px;
height: 178px;
border: 3px solid #4fcacd;
border-radius: 10px;
}
.avatar-uploader .avatar {
width: 178px;
height: 178px;
display: block;
}
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
.avatar-tip {
width: 178px;
text-align: center;
font-size: 12px;
color: #8c939d;
}
</style>

105
src/views/verifyAccount.vue Normal file
View File

@@ -0,0 +1,105 @@
<template>
<div class="email-registration">
<!-- 完成注册 -->
<div class="form">
<div class="title">
{{ Hinttext }}
</div>
</div>
</div>
</template>
<script setup>
import { useRoute, useRouter } from "vue-router";
import { ref, watch, onMounted, onUpdated, onUnmounted,onBeforeMount} from "vue";
import { checkAccount } from "@/api/account";
const route = useRoute();
const router = useRouter();
const token = ref("");
import { ElMessage } from "element-plus";
import { setLocale } from "@/i18n";
//
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const language = ref(null); // 语言
window["$t"] = t;
//
const Hinttext = ref(t('VerificationInProgressPleaseWaitAMoment'));
// gj验证中请稍候
//确认
function nextStep() {
checkAccount(token.value,
)
.then(() => {
Hinttext.value = t('VerificationSuccessfulPleaseLogIn');
// gj登录成功跳转到首页
setTimeout(() => {
router.push("/");
}, 3000);
})
.catch(() => {
Hinttext.value = t('VerificationFailed');
// gj验证失败
});
}
onBeforeMount(() => {
// 语言
getPromiseStorage("language")
.then((res) => {
console.log("获取语言成功", res);
language.value = res;
setLocale(language.value);
})
.catch((err) => {
console.log("获取语言失败", err);
});
});
onMounted(() => {
token.value = route.params.token;
nextStep();
});
onUpdated(() => {
// 组件更新后执行
});
onUnmounted(() => {
// 组件销毁前执行
});
</script>
<style scoped>
.email-registration {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
background-image: url(../assets/bg.png);
background-size: 100% 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.form {
margin-top: 30px;
width: 80%;
height: 80%;
background-image: linear-gradient(180deg, #dbf0f1, #ffffff);
border-radius: 10px;
border: 1px solid #4fcacd;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.title {
font-size: 100px;
color: #03aba8;
font-weight: bold;
transition: all 0.5s ease-in-out;
}
</style>

View File

@@ -8,8 +8,8 @@ module.exports = defineConfig({
postcssOptions: {
plugins: [
require('postcss-px-to-viewport')({
viewportWidth: 1600, // 视窗的宽度,对应设计稿宽度
viewportHeight: 900, // 视窗的高度,对应设计稿高度
viewportWidth: 1920, // 视窗的宽度,对应设计稿宽度
viewportHeight: 1080, // 视窗的高度,对应设计稿高度
unitPrecision: 3, // 指定 px 转换为视窗单位值的小数位数
viewportUnit: 'vw', // 指定需要转换成的视窗单位vw 或者 vh
selectorBlackList: ['.ignore', '.hairlines'], // 指定不需要转换的类
@@ -18,6 +18,9 @@ module.exports = defineConfig({
})
]
}
},
less: {
additionalData: `@import "@/static/css/app.less";` // 注入全局变量文件
}
}
}