Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1606f9a3e3 | |||
| 2ce7cd6344 | |||
| 1b07facf49 | |||
| 1576cadf8c | |||
| 1adb587edb | |||
| 14abd6c7c0 | |||
| afaacbd2fa | |||
| 32f9c48c91 | |||
| 74bf170490 |
@@ -9,7 +9,68 @@
|
||||
<title>
|
||||
<%= webpackConfig.name %>
|
||||
</title>
|
||||
<!-- pywebview API 自动注入,无需手动引入脚本 -->
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet" />
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,typography"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#0066FF", // Cyber Blue
|
||||
secondary: "#6366F1", // Electric Indigo
|
||||
"background-light": "#F8FAFC",
|
||||
"background-dark": "#0F172A",
|
||||
},
|
||||
fontFamily: {
|
||||
display: ["Inter", "sans-serif"],
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: "12px",
|
||||
'xl': '20px',
|
||||
},
|
||||
boxShadow: {
|
||||
'premium': '0 4px 20px -2px rgba(0, 0, 0, 0.05), 0 2px 10px -2px rgba(0, 0, 0, 0.02)',
|
||||
'glass': 'inset 0 0 0 1px rgba(255, 255, 255, 0.4)',
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Global Styles -->
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.sidebar-active {
|
||||
background: linear-gradient(90deg, #F0F7FF 0%, #FFFFFF 100%);
|
||||
border-right: 3px solid #0066FF;
|
||||
box-shadow: 4px 0 15px -5px rgba(0, 102, 255, 0.15);
|
||||
}
|
||||
|
||||
.subtle-grid {
|
||||
background-image: radial-gradient(#E2E8F0 0.5px, transparent 0.5px);
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
/* QT WebChannel */
|
||||
</style>
|
||||
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -21,20 +82,12 @@
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
/* width: 1600px;
|
||||
height: 900px; */
|
||||
}
|
||||
</style>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -76,3 +76,7 @@ export function accountName(str) {
|
||||
export function liveHostDetail(data) {
|
||||
return postAxios({ url: 'api/save_data/live_host_detail', data })
|
||||
}
|
||||
|
||||
export function revenueStats(hostId) {
|
||||
return getAxios({ url: 'api/save_data/revenue_stats?displayId=' + hostId })
|
||||
}
|
||||
|
||||
BIN
src/assets/logo2.png
Normal file
BIN
src/assets/logo2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
BIN
src/assets/logoBg2.png
Normal file
BIN
src/assets/logoBg2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 363 KiB |
@@ -1,148 +1,63 @@
|
||||
<template>
|
||||
<div class="sidebar">
|
||||
<div class="logo">
|
||||
<!-- <img style="margin-right: 10px;" src="@/assets/logo.png"> -->
|
||||
<img src="@/assets/logotext.png">
|
||||
<aside class="w-64 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 flex flex-col z-20 h-full">
|
||||
<div class="p-8 flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-primary rounded-xl flex items-center justify-center shadow-lg shadow-primary/20">
|
||||
<span class="text-white font-bold text-xl">TK
|
||||
</span>
|
||||
</div>
|
||||
<h1 class="text-xl font-bold tracking-tight text-slate-900 dark:text-white">Yolo <span class="text-primary">助手</span>
|
||||
</h1>
|
||||
</div>
|
||||
<ul>
|
||||
<li @click="updateActiveIndex(1)">
|
||||
<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">{{
|
||||
$t('menu.workbenches')
|
||||
}}</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">{{
|
||||
$t('menu.hostList')
|
||||
}}</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;">
|
||||
{{
|
||||
$t('menu.logout')
|
||||
}}
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 px-4 space-y-4">
|
||||
<a
|
||||
class="flex items-center gap-3 px-4 py-4 rounded-lg font-medium cursor-pointer transition-colors"
|
||||
:class="activeIndex == 1 ? 'sidebar-active text-primary' : 'text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800'"
|
||||
@click="updateActiveIndex(1)"
|
||||
>
|
||||
<span class="material-icons-round">grid_view</span>
|
||||
{{ $t('menu.workbenches') }}
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="flex items-center gap-3 px-4 py-4 rounded-lg font-medium cursor-pointer transition-colors"
|
||||
:class="activeIndex == 2 ? 'sidebar-active text-primary' : 'text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800'"
|
||||
@click="updateActiveIndex(2)"
|
||||
>
|
||||
<span class="material-icons-round">person_search</span>
|
||||
{{ $t('menu.hostList') }}
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="p-6 border-t border-slate-100 dark:border-slate-800">
|
||||
<button
|
||||
class="flex items-center gap-3 text-slate-500 hover:text-red-500 transition-colors w-full px-4 py-2"
|
||||
@click="$router.push('/')"
|
||||
>
|
||||
<span class="material-icons-round text-lg">logout</span>
|
||||
<span class="font-medium">{{ $t('menu.logout') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { getUser } from '@/utils/storage'
|
||||
import { defineEmits } from 'vue';
|
||||
|
||||
|
||||
|
||||
const userInfo = ref(getUser())
|
||||
|
||||
let activeIndex = ref(1);
|
||||
|
||||
const emit = defineEmits(['activeIndex']);
|
||||
|
||||
|
||||
const updateActiveIndex = (index) => {
|
||||
activeIndex.value = index;
|
||||
emit('activeIndex', index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 900px;
|
||||
width: 280px;
|
||||
background-color: @bg-color;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.logo {
|
||||
border-bottom: 1px solid #fff;
|
||||
padding-top: 20px;
|
||||
|
||||
img:nth-of-type(1) {
|
||||
height: 66px;
|
||||
}
|
||||
|
||||
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 scoped>
|
||||
/* Scoped styles are minimal now as we use Tailwind */
|
||||
</style>
|
||||
@@ -79,5 +79,24 @@ export default {
|
||||
invitationType: 'invitationType',
|
||||
invitationType1: 'Regular',
|
||||
invitationType2: 'Golden',
|
||||
liveSessions: 'Live Sessions',
|
||||
viewSessions: 'View Sessions',
|
||||
liveRevenue: 'Live Revenue',
|
||||
viewRevenue: 'View Revenue',
|
||||
revenueHost: 'Host',
|
||||
todayRevenueUsd: 'Today Revenue (USD)',
|
||||
totalRevenueUsd: 'Total Revenue (USD)',
|
||||
liveDays: 'Live Days',
|
||||
historyRevenueUsd: 'History Revenue (USD)',
|
||||
revenueHigh: 'High',
|
||||
revenueLow: 'Low',
|
||||
revenueTime: 'Time',
|
||||
close: 'Close',
|
||||
selectPlaceholder: 'Please select',
|
||||
countryEng: 'Country (EN)',
|
||||
videoCount: 'Video Count',
|
||||
heart: 'Likes',
|
||||
signature: 'Bio Signature',
|
||||
bioLink: 'Bio Link',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export default {
|
||||
export default {
|
||||
login: {
|
||||
title: '账号登陆',
|
||||
title: '账号登录',
|
||||
version: '版本号',
|
||||
login: '登录',
|
||||
tenantName: '租户名称',
|
||||
account: '账户',
|
||||
account: '账号',
|
||||
password: '密码',
|
||||
Language: '语言设置',
|
||||
network: '网络设置',
|
||||
@@ -27,6 +27,7 @@ export default {
|
||||
guildPassPlace: '请输入登录密码',
|
||||
queriedNum: '今日已查询次数',
|
||||
loginBackend: '登录后台',
|
||||
workbenches: '工作台',
|
||||
},
|
||||
workbenchesSetup: {
|
||||
workbenches: '工作台',
|
||||
@@ -34,7 +35,7 @@ export default {
|
||||
setCoinsNum: '设置金币数量',
|
||||
setFansNum: '设置粉丝数量',
|
||||
setQuery: '后台查询频率',
|
||||
setNum: ' 期望可邀请主播数量',
|
||||
setNum: '期望可邀请主播数量',
|
||||
minCoinsNum: '最小金币数量',
|
||||
maxCoinsNum: '最大金币数量',
|
||||
minFansNum: '最小粉丝数量',
|
||||
@@ -44,11 +45,9 @@ export default {
|
||||
num: '个',
|
||||
start: '开始获取数据',
|
||||
stop: '停止',
|
||||
prompt: '到达数量后停止爬取',
|
||||
prompt: '达到数量后停止爬取',
|
||||
setHostNum: '设置爬取数量',
|
||||
unlimitedQuantity: '不限爬取数量',
|
||||
|
||||
|
||||
},
|
||||
hostList: {
|
||||
placeCountry: '选择国家',
|
||||
@@ -80,5 +79,24 @@ export default {
|
||||
invitationType: '邀请类型',
|
||||
invitationType1: '普票',
|
||||
invitationType2: '金票',
|
||||
liveSessions: '直播场次',
|
||||
viewSessions: '查看场次',
|
||||
liveRevenue: '直播收益',
|
||||
viewRevenue: '查看收益',
|
||||
revenueHost: '主播',
|
||||
todayRevenueUsd: '今日收益(美元)',
|
||||
totalRevenueUsd: '总收益(美元)',
|
||||
liveDays: '直播天数',
|
||||
historyRevenueUsd: '历史收益(美元)',
|
||||
revenueHigh: '高',
|
||||
revenueLow: '低',
|
||||
revenueTime: '时间',
|
||||
close: '关闭',
|
||||
selectPlaceholder: '请选择',
|
||||
countryEng: '国家(英文)',
|
||||
videoCount: '视频数量',
|
||||
heart: '点赞数',
|
||||
signature: '主页签名',
|
||||
bioLink: '主页链接',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +1,74 @@
|
||||
// pythonBridge.js
|
||||
// 适配 pywebview API (替代原有的 QWebChannel)
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const bridge = ref(null);
|
||||
const isReady = ref(false);
|
||||
|
||||
/**
|
||||
* 等待 pywebview API 准备就绪
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const waitForPywebview = () => {
|
||||
return new Promise((resolve) => {
|
||||
// 如果已经存在,直接返回
|
||||
if (window.pywebview && window.pywebview.api) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
// 监听 pywebviewready 事件
|
||||
window.addEventListener('pywebviewready', () => {
|
||||
resolve();
|
||||
}, { once: true });
|
||||
});
|
||||
// 统一安全调用,确保 Qt 响应有回调可执行
|
||||
const callBridge = (method, ...args) => {
|
||||
if (!bridge.value || typeof bridge.value[method] !== 'function') return;
|
||||
const last = args[args.length - 1];
|
||||
const hasCallback = typeof last === 'function';
|
||||
const callback = hasCallback ? args.pop() : () => { };
|
||||
bridge.value[method](...args, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* 统一安全调用 pywebview API
|
||||
* pywebview 的方法调用会返回 Promise
|
||||
*/
|
||||
const callBridge = async (method, ...args) => {
|
||||
if (!bridge.value || typeof bridge.value[method] !== 'function') {
|
||||
console.warn(`[pythonBridge] 方法不存在: ${method}`);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const result = await bridge.value[method](...args);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`[pythonBridge] 调用 ${method} 失败:`, error);
|
||||
return null;
|
||||
}
|
||||
// 防御:若 Qt 返回了未知 id,忽略以免 execCallbacks 报错
|
||||
const patchQWebChannel = () => {
|
||||
if (!window.QWebChannel || QWebChannel.__patchedIgnoreMissing) return;
|
||||
const originalHandleResponse = QWebChannel.prototype.handleResponse;
|
||||
QWebChannel.__patchedIgnoreMissing = true;
|
||||
QWebChannel.prototype.handleResponse = function (message) {
|
||||
const cb = this.execCallbacks && this.execCallbacks[message.id];
|
||||
if (message.id && typeof cb !== 'function') {
|
||||
console.warn('忽略未知的 WebChannel 响应', message);
|
||||
return;
|
||||
}
|
||||
return originalHandleResponse.call(this, message);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化 pywebview 桥接
|
||||
*/
|
||||
const initBridge = async () => {
|
||||
// 监听 DevTools 快捷键 (Ctrl + Shift + P)
|
||||
window.addEventListener('keydown', (e) => {
|
||||
// 1. 自定义快捷键: Ctrl + Shift + P -> 调用后端
|
||||
if (e.ctrlKey && e.shiftKey && (e.key === 'p' || e.key === 'P')) {
|
||||
e.preventDefault();
|
||||
callBridge('openDevTools');
|
||||
return;
|
||||
}
|
||||
// 初始化 QWebChannel
|
||||
const initBridge = () => {
|
||||
if (/localhost/.test(window.location.href)) return;
|
||||
patchQWebChannel();
|
||||
new QWebChannel(qt.webChannelTransport, (channel) => {
|
||||
// 兜底:任何缺失的回调都返回空函数,避免 execCallbacks 报错
|
||||
channel.execCallbacks = new Proxy(channel.execCallbacks || {}, {
|
||||
get(target, prop) {
|
||||
const val = target[prop];
|
||||
if (typeof val === 'function') return val;
|
||||
// 返回空函数,确保 handleResponse 可调用
|
||||
return () => { };
|
||||
},
|
||||
set(target, prop, value) {
|
||||
target[prop] = value;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
// 2. 屏蔽 F12 (防止用户按下 F12 打开)
|
||||
if (e.key === 'F12') {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
bridge.value = channel.objects.bridge;
|
||||
});
|
||||
|
||||
// 开发环境 (localhost) 不初始化
|
||||
if (/localhost/.test(window.location.href)) {
|
||||
console.log('[pythonBridge] 开发环境,跳过初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
await waitForPywebview();
|
||||
|
||||
if (window.pywebview && window.pywebview.api) {
|
||||
bridge.value = window.pywebview.api;
|
||||
isReady.value = true;
|
||||
console.log('[pythonBridge] 初始化成功');
|
||||
} else {
|
||||
console.error('[pythonBridge] pywebview API 初始化失败');
|
||||
}
|
||||
};
|
||||
|
||||
export function usePythonBridge() {
|
||||
// 调用 Python 方法
|
||||
const fetchDataConfig = async (data) => {
|
||||
if (!bridge.value) return null;
|
||||
return await callBridge('fetchDataConfig', data);
|
||||
const fetchDataConfig = (data) => {
|
||||
return new Promise((resolve) => {
|
||||
if (!bridge.value) return resolve(null);
|
||||
callBridge('fetchDataConfig', data, (result) => {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 查询获取主播的数据
|
||||
const fetchDataCount = async () => {
|
||||
if (!bridge.value) return null;
|
||||
const result = await callBridge('fetchDataCount');
|
||||
// pywebview 返回的是字符串,需要解析
|
||||
try {
|
||||
return typeof result === 'string' ? JSON.parse(result) : result;
|
||||
} catch {
|
||||
return result;
|
||||
}
|
||||
const fetchDataCount = () => {
|
||||
return new Promise((resolve) => {
|
||||
if (!bridge.value) return resolve(null);
|
||||
callBridge('fetchDataCount', (result) => {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 打开 tk 后台
|
||||
@@ -117,25 +91,23 @@ export function usePythonBridge() {
|
||||
};
|
||||
|
||||
// 查询登录状态
|
||||
const backStageloginStatus = async () => {
|
||||
if (!bridge.value) return null;
|
||||
const result = await callBridge('backStageloginStatus');
|
||||
try {
|
||||
return typeof result === 'string' ? JSON.parse(result) : result;
|
||||
} catch {
|
||||
return result;
|
||||
}
|
||||
const backStageloginStatus = () => {
|
||||
return new Promise((resolve) => {
|
||||
if (!bridge.value) return resolve(null);
|
||||
callBridge('backStageloginStatus', (result) => {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 查询登录状态(副账号)
|
||||
const backStageloginStatusCopy = async () => {
|
||||
if (!bridge.value) return null;
|
||||
const result = await callBridge('backStageloginStatusCopy');
|
||||
try {
|
||||
return typeof result === 'string' ? JSON.parse(result) : result;
|
||||
} catch {
|
||||
return result;
|
||||
}
|
||||
const backStageloginStatusCopy = () => {
|
||||
return new Promise((resolve) => {
|
||||
if (!bridge.value) return resolve(null);
|
||||
callBridge('backStageloginStatusCopy', (result) => {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 导出表格
|
||||
@@ -148,33 +120,29 @@ export function usePythonBridge() {
|
||||
};
|
||||
|
||||
// 获取版本号
|
||||
const getVersion = async () => {
|
||||
if (!bridge.value) return null;
|
||||
return await callBridge('currentVersion');
|
||||
const getVersion = () => {
|
||||
return new Promise((resolve) => {
|
||||
if (!bridge.value) return resolve(null);
|
||||
callBridge('currentVersion', (result) => {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 存储账号信息
|
||||
const storageAccountInfo = async (key, data) => {
|
||||
if (!bridge.value) return false;
|
||||
return await callBridge('storageAccountInfo', key, JSON.stringify(data));
|
||||
};
|
||||
|
||||
// 读取账号信息
|
||||
const readAccountInfo = async (key) => {
|
||||
if (!bridge.value) return null;
|
||||
const result = await callBridge('readAccountInfo', key);
|
||||
try {
|
||||
return typeof result === 'string' ? JSON.parse(result) : result;
|
||||
} catch {
|
||||
return result;
|
||||
}
|
||||
// 查询主播列表
|
||||
const getHosts = (data) => {
|
||||
return new Promise((resolve) => {
|
||||
if (!bridge.value) return resolve(null);
|
||||
callBridge('searchHosts', JSON.stringify(data), (result) => {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 在组件挂载时初始化桥接
|
||||
onMounted(initBridge);
|
||||
|
||||
return {
|
||||
isReady,
|
||||
fetchDataConfig,
|
||||
fetchDataCount,
|
||||
loginBackStage,
|
||||
@@ -185,7 +153,6 @@ export function usePythonBridge() {
|
||||
exportToExcel,
|
||||
stopScript,
|
||||
getVersion,
|
||||
storageAccountInfo,
|
||||
readAccountInfo,
|
||||
getHosts,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,80 +1,55 @@
|
||||
<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>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link">
|
||||
<span style="font-size:20px; color: #fff;">
|
||||
{{ $t('login.network') }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div class="setup-item center-justify">
|
||||
<div></div>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link">
|
||||
<span style="font-size:20px; color: #fff;">
|
||||
{{ $t('login.Language') }}
|
||||
</span>
|
||||
<!-- <el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</el-icon> -->
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="switchLanguage('zh')">
|
||||
中文
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="switchLanguage('en')">
|
||||
English
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- <el-radio-group v-model="localeData" size="large">
|
||||
<el-radio-button @click="switchLanguage('zh')" label="中文" />
|
||||
<el-radio-button @click="switchLanguage('en')" label="English" />
|
||||
</el-radio-group> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="auth-shell">
|
||||
<div class="top-actions ">
|
||||
<div class="setup-item center-justify">
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link ">
|
||||
<span class="setup-text">{{ $t('login.network') }}</span>
|
||||
</span>
|
||||
</el-dropdown>
|
||||
</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;height: 100%;" src="@/assets/logo.png"> -->
|
||||
<img style="height: 100%;" src="@/assets/logotext.png">
|
||||
</div>
|
||||
<div class="setup-item center-justify">
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link">
|
||||
<span class="setup-text">{{ $t('login.Language') }}</span>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="switchLanguage('zh')">
|
||||
中文
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="switchLanguage('en')">
|
||||
English
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="auth-card">
|
||||
<div class="auth-left">
|
||||
<div class="logo ">
|
||||
<img class="logo-image" src="@/assets/logo2.png" alt="">
|
||||
<span class="logo-title">TK主播数据助手(定制版)</span>
|
||||
</div>
|
||||
|
||||
<!-- From -->
|
||||
<div class="welcome">{{ $t('login.title') }}</div>
|
||||
<div class="from">
|
||||
<div class="from-title center-justify">
|
||||
<div>{{ $t('login.title') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="from-input">
|
||||
<el-form label-position="left" label-width="100px" :model="formData">
|
||||
<div class="field-label">{{ $t('login.tenantName') }}</div>
|
||||
<div class="from-input-item1">
|
||||
<img src="@/assets/username.png" alt="">
|
||||
<el-input style="height: 25px;" v-model="formData.tenantName"
|
||||
:placeholder="$t('login.tenantName')" clearable @keyup.enter="onSubmit" />
|
||||
</div>
|
||||
<div class="field-label">{{ $t('login.account') }}</div>
|
||||
<div class="from-input-item1">
|
||||
<img src="@/assets/username.png" alt="">
|
||||
<el-input style="height: 25px;" v-model="formData.userId"
|
||||
:placeholder="$t('login.account')" clearable @keyup.enter="onSubmit" />
|
||||
</div>
|
||||
<div class="field-label">{{ $t('login.password') }}</div>
|
||||
<div class="from-input-item1">
|
||||
<img src="@/assets/password.png" alt="">
|
||||
<el-input style="height: 25px; " v-model="formData.password" type="password"
|
||||
@@ -82,18 +57,19 @@
|
||||
</div>
|
||||
|
||||
<div class="from-input-item">
|
||||
<el-button class="loginButton" color="#8f7ee7" type="primary" @click="onSubmit">{{
|
||||
$t('login.login') }}</el-button>
|
||||
|
||||
<el-button class="loginButton" style="height: 3vw;" color="#4f6ef7" type="primary"
|
||||
:loading="isSubmitting" :disabled="isSubmitting" @click="onSubmit">{{
|
||||
$t('login.login') }}</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="version">{{ $t('login.version') }}:{{ version }}</div>
|
||||
</div>
|
||||
<div class="auth-right">
|
||||
<img src="@/assets/logoBg2.png" class="illustration" alt="">
|
||||
</div>
|
||||
<div class="version center-justify ">{{ $t('login.version') }}:{{ version }}</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -103,7 +79,6 @@ import { ref, reactive, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { login, getIdByName } from '@/api/account';
|
||||
import { getToken, setToken, setUser, setUserPass, getUserPass } from '@/utils/storage';
|
||||
import { ElLoading } from 'element-plus';
|
||||
import { usePythonBridge } from '@/utils/pythonBridge'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -117,7 +92,7 @@ function switchLanguage(lang) {
|
||||
}
|
||||
|
||||
const { getVersion, stopScript } = usePythonBridge();
|
||||
let version = ref('5.1.0');
|
||||
let version = ref('5.9.2');
|
||||
onMounted(() => {
|
||||
|
||||
stopScript();
|
||||
@@ -132,6 +107,7 @@ onMounted(() => {
|
||||
|
||||
|
||||
const router = useRouter();
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
const formData = ref({
|
||||
tenantName: getUserPass() == null ? '' : getUserPass().tenantName,
|
||||
@@ -142,11 +118,10 @@ const formData = ref({
|
||||
|
||||
|
||||
const onSubmit = () => {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: 'Loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
});
|
||||
if (isSubmitting.value) {
|
||||
return;
|
||||
}
|
||||
isSubmitting.value = true;
|
||||
setUserPass(formData.value);
|
||||
getIdByName(formData.value.tenantName).then((tenantId) => {
|
||||
console.log(tenantId)
|
||||
@@ -155,14 +130,16 @@ const onSubmit = () => {
|
||||
username: formData.value.userId,
|
||||
password: formData.value.password,
|
||||
}).then((res) => {
|
||||
loading.close();
|
||||
isSubmitting.value = false;
|
||||
// console.log(res)
|
||||
setToken(res.tokenValue);
|
||||
setUser(res);
|
||||
router.push('/nav');
|
||||
}).catch((err) => {
|
||||
loading.close();
|
||||
isSubmitting.value = false;
|
||||
});
|
||||
}).catch(() => {
|
||||
isSubmitting.value = false;
|
||||
})
|
||||
|
||||
|
||||
@@ -171,157 +148,27 @@ const onSubmit = () => {
|
||||
|
||||
<style lang="less">
|
||||
.main {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
width: 1600px;
|
||||
height: 900px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(180deg, #f3f5f9 0%, #eef1f5 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: "Source Han Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
|
||||
/* 页面无法选中 */
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.right {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px 40px 20px 50px;
|
||||
border-left: 3px solid #23516e;
|
||||
position: relative;
|
||||
/* 添加 position: relative */
|
||||
overflow: hidden;
|
||||
/* 防止内容溢出 */
|
||||
|
||||
.version {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
font-size: 20px;
|
||||
bottom: 20px;
|
||||
left: calc(50% - 50px);
|
||||
// box-sizing: border-box;
|
||||
// width: 1600px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.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:nth-child(1) {
|
||||
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: @bg-color;
|
||||
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: @bg-color;
|
||||
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: @bg-color;
|
||||
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: @bg-color;
|
||||
line-height: 37px;
|
||||
}
|
||||
}
|
||||
|
||||
.from-input-item1 {
|
||||
display: flex;
|
||||
width: 359px;
|
||||
height: 50px;
|
||||
background: @bg-color-light-light;
|
||||
border-radius: 24px;
|
||||
border: 1px solid #FFFFFF;
|
||||
padding: 12px 25px 13px 25px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center-line {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
// justify-content: center;
|
||||
}
|
||||
|
||||
.center-justify {
|
||||
@@ -330,37 +177,246 @@ const onSubmit = () => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.center-align {
|
||||
.auth-shell {
|
||||
width: 1320px;
|
||||
height: 760px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.center-flex {
|
||||
.auth-shell::before,
|
||||
.auth-shell::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(0);
|
||||
}
|
||||
|
||||
.auth-shell::before {
|
||||
width: 420px;
|
||||
height: 420px;
|
||||
background: radial-gradient(circle, rgba(214, 226, 255, 0.7), rgba(214, 226, 255, 0));
|
||||
left: -80px;
|
||||
top: -40px;
|
||||
}
|
||||
|
||||
.auth-shell::after {
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
background: radial-gradient(circle, rgba(255, 236, 214, 0.6), rgba(255, 236, 214, 0));
|
||||
right: -40px;
|
||||
bottom: -60px;
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 80px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.setup-item {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid #e1e6f2;
|
||||
border-radius: 16px;
|
||||
padding: 8px 12px 8px 14px;
|
||||
box-shadow: 0 8px 18px rgba(40, 57, 108, 0.12);
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.setup-text {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #2b3347;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.setup-item::after {
|
||||
content: "";
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-right: 2px solid #9aa1b3;
|
||||
border-bottom: 2px solid #9aa1b3;
|
||||
transform: rotate(45deg);
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.setup-item:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: #c9d2f4;
|
||||
box-shadow: 0 12px 24px rgba(40, 57, 108, 0.16);
|
||||
}
|
||||
|
||||
.setup-item:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 1200px;
|
||||
height: 680px;
|
||||
display: flex;
|
||||
background: #ffffff;
|
||||
border-radius: 28px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 24px 60px rgba(40, 57, 108, 0.16);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.auth-left {
|
||||
width: 42%;
|
||||
padding: 48px 56px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
// margin-bottom: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo-image {
|
||||
height: 200px;
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
|
||||
.logo-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #2b3347;
|
||||
letter-spacing: 2px;
|
||||
position: relative;
|
||||
margin-top: 30px;
|
||||
padding: 10px 0px 10px 14px;
|
||||
}
|
||||
|
||||
.logo-title::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 4px;
|
||||
border-radius: 2px;
|
||||
background: linear-gradient(180deg, #4f6ef7, #5b7bff);
|
||||
}
|
||||
|
||||
.welcome {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #2b3347;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.from {
|
||||
width: 100%;
|
||||
color: #2b3347;
|
||||
background-color: transparent;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.from-input {
|
||||
width: 100%;
|
||||
padding: 8px 0 0 0;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin: 12px 0 6px;
|
||||
}
|
||||
|
||||
.from-input-item {
|
||||
display: flex;
|
||||
padding: 16px 0 0 0;
|
||||
}
|
||||
|
||||
.loginButton {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background: linear-gradient(135deg, #4f6ef7, #5b7bff);
|
||||
border-radius: 14px;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 12px 24px rgba(79, 110, 247, 0.28);
|
||||
}
|
||||
|
||||
.from-input-item1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background: #f6f8fc;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e6eaf2;
|
||||
padding: 10px 14px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 4px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.from-input-item1 img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.version {
|
||||
margin-top: 24px;
|
||||
font-size: 12px;
|
||||
color: #9aa1b3;
|
||||
}
|
||||
|
||||
.auth-right {
|
||||
width: 58%;
|
||||
background: linear-gradient(160deg, #f8f9fc 0%, #f0f3f9 100%);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.illustration {
|
||||
width: 90%;
|
||||
height: auto;
|
||||
max-height: 88%;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 30px 60px rgba(40, 57, 108, 0.25));
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
--el-input-focus-border-color: rgba(255, 255, 0, 0);
|
||||
--el-menu-hover-bg-color: rgba(255, 255, 0, 0);
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="less">
|
||||
::v-deep(.el-input__wrapper) {
|
||||
background-color: rgba(255, 0, 0, 0);
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
}
|
||||
|
||||
::v-deep(.el-input__inner) {
|
||||
color: #fff;
|
||||
|
||||
color: #2b3347;
|
||||
}
|
||||
|
||||
::v-deep(.el-input__inner::placeholder) {
|
||||
color: @bg-color;
|
||||
color: #9aa1b3;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
440
src/views/hosttest.html
Normal file
440
src/views/hosttest.html
Normal file
@@ -0,0 +1,440 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>Yolo Assistant - Modern Light-Tech Dashboard</title>
|
||||
<link href="https://fonts.googleapis.com" rel="preconnect" />
|
||||
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet" />
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,typography"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#0052CC",
|
||||
"background-light": "#F4F7FA",
|
||||
"background-dark": "#0B1120",
|
||||
"tech-blue": "#E3F2FD",
|
||||
"tech-green": "#E8F5E9",
|
||||
"tech-orange": "#FFF3E0",
|
||||
},
|
||||
fontFamily: {
|
||||
display: ["Inter", "system-ui", "sans-serif"],
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: "12px",
|
||||
'xl': '20px',
|
||||
},
|
||||
boxShadow: {
|
||||
'soft-inner': 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.05)',
|
||||
'premium': '0 10px 25px -5px rgba(0, 0, 0, 0.04), 0 8px 10px -6px rgba(0, 0, 0, 0.04)',
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.sidebar-active {
|
||||
background: white;
|
||||
color: #0052CC !important;
|
||||
border-radius: 40px 0 0 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar-active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 20px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.sidebar-active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -40px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 20px 20px 0 0 white;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.sidebar-active-footer::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -40px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 20px -20px 0 0 white;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #CBD5E1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #94A3B8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-slate-900 font-display text-slate-900 antialiased overflow-hidden">
|
||||
<div class="flex h-screen">
|
||||
<aside class="w-64 flex flex-col pt-8 pb-10 bg-slate-900 shrink-0">
|
||||
<div class="px-8 mb-12">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-3xl font-bold tracking-tighter text-white">yolo</span>
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs font-semibold tracking-wider text-slate-900 bg-white rounded uppercase">助手</span>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="flex-1 space-y-2 pl-4">
|
||||
<a class="flex items-center gap-4 px-6 py-4 text-slate-400 hover:text-slate-200 transition-all duration-200 group"
|
||||
href="#">
|
||||
<span class="material-icons-round text-2xl">grid_view</span>
|
||||
<span class="font-medium">工作台</span>
|
||||
</a>
|
||||
<div class="relative">
|
||||
<a class="sidebar-active flex items-center gap-4 px-6 py-4 text-primary transition-all duration-200"
|
||||
href="#">
|
||||
<span class="material-icons-round text-2xl">person_search</span>
|
||||
<span class="font-medium">主播列表</span>
|
||||
</a>
|
||||
<div class="sidebar-active-footer"></div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="px-8 mt-auto">
|
||||
<button
|
||||
class="flex items-center gap-3 text-slate-400 hover:text-red-400 transition-colors duration-200 group">
|
||||
<span class="material-icons-round text-xl group-hover:rotate-12 transition-transform">logout</span>
|
||||
<span class="font-medium">退出登录</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="flex-1 bg-white dark:bg-slate-50 m-2 rounded-3xl shadow-2xl flex flex-col overflow-hidden">
|
||||
<header class="px-8 py-6 border-b border-slate-100 dark:border-slate-200/60 bg-white/50 backdrop-blur-md">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<div class="relative flex-1 min-w-[200px]">
|
||||
<select
|
||||
class="w-full bg-slate-50 border-none rounded-xl py-3 pl-4 pr-10 text-sm text-slate-600 appearance-none focus:ring-2 focus:ring-primary/20 shadow-soft-inner">
|
||||
<option>选择国家</option>
|
||||
<option>哈萨克斯坦</option>
|
||||
<option>土库曼斯坦</option>
|
||||
<option>摩尔多瓦</option>
|
||||
</select>
|
||||
<span
|
||||
class="material-icons-round absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none">expand_more</span>
|
||||
</div>
|
||||
<div class="relative flex-1 min-w-[200px]">
|
||||
<span
|
||||
class="material-icons-round absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">calendar_today</span>
|
||||
<input
|
||||
class="w-full bg-slate-50 border-none rounded-xl py-3 pl-10 pr-4 text-sm text-slate-600 focus:ring-2 focus:ring-primary/20 shadow-soft-inner"
|
||||
placeholder="选择查询时间" type="text" />
|
||||
</div>
|
||||
<div class="relative flex-[1.5] min-w-[240px]">
|
||||
<input
|
||||
class="w-full bg-slate-50 border-none rounded-xl py-3 px-4 text-sm text-slate-600 focus:ring-2 focus:ring-primary/20 shadow-soft-inner"
|
||||
placeholder="请输入主播id" type="text" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-auto">
|
||||
<button
|
||||
class="bg-primary hover:bg-blue-700 text-white px-8 py-3 rounded-xl font-medium text-sm transition-all shadow-lg shadow-primary/20 flex items-center gap-2">
|
||||
<span class="material-icons-round text-sm">search</span>
|
||||
查询
|
||||
</button>
|
||||
<button class="bg-slate-100 hover:bg-slate-200 text-slate-600 p-3 rounded-xl transition-all">
|
||||
<span class="material-icons-round">tune</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex-1 overflow-auto p-8">
|
||||
<table class="w-full text-left border-separate border-spacing-0">
|
||||
<thead>
|
||||
<tr class="text-slate-400 text-xs font-semibold uppercase tracking-wider">
|
||||
<th class="pb-4 pl-4 font-normal"><input
|
||||
class="rounded border-slate-300 text-primary focus:ring-primary/20"
|
||||
type="checkbox" /></th>
|
||||
<th class="pb-4 font-semibold">主播ID</th>
|
||||
<th class="pb-4 font-semibold">等级</th>
|
||||
<th class="pb-4 font-semibold">邀请类型</th>
|
||||
<th class="pb-4 font-semibold">数据查看</th>
|
||||
<th class="pb-4 font-semibold">国家</th>
|
||||
<th class="pb-4 font-semibold">创建时间</th>
|
||||
<th class="pb-4 font-semibold">主播金币</th>
|
||||
<th class="pb-4 font-semibold text-right pr-4">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-sm text-slate-600">
|
||||
<tr class="group hover:bg-slate-50/80 transition-colors">
|
||||
<td class="py-5 pl-4 border-b border-slate-50 group-last:border-none">
|
||||
<input class="rounded border-slate-300 text-primary focus:ring-primary/20"
|
||||
type="checkbox" />
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="font-medium text-slate-900 border-b border-transparent group-hover:border-primary/30 transition-all cursor-pointer">kisimisi1990</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">D5</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="px-3 py-1 bg-green-50 text-green-600 text-[10px] font-bold uppercase rounded-full">普票</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看场次</button>
|
||||
<button
|
||||
class="px-3 py-1.5 bg-sky-50 text-sky-600 hover:bg-sky-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看收益</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">哈萨克斯坦</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex flex-col">
|
||||
<span>2026-01-13</span>
|
||||
<span class="text-[10px] text-slate-400">20:23:52</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="py-5 border-b border-slate-50 group-last:border-none font-semibold text-slate-900">
|
||||
1260</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none text-right pr-4">
|
||||
<button class="p-1 hover:bg-slate-200 rounded transition-colors text-slate-400">
|
||||
<span class="material-icons-round text-lg">more_horiz</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="group hover:bg-slate-50/80 transition-colors">
|
||||
<td class="py-5 pl-4 border-b border-slate-50 group-last:border-none">
|
||||
<input class="rounded border-slate-300 text-primary focus:ring-primary/20"
|
||||
type="checkbox" />
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="font-medium text-slate-900 border-b border-transparent group-hover:border-primary/30 transition-all cursor-pointer">maga44458</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">D5</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="px-3 py-1 bg-green-50 text-green-600 text-[10px] font-bold uppercase rounded-full">普票</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看场次</button>
|
||||
<button
|
||||
class="px-3 py-1.5 bg-sky-50 text-sky-600 hover:bg-sky-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看收益</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">土库曼斯坦</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex flex-col">
|
||||
<span>2026-01-13</span>
|
||||
<span class="text-[10px] text-slate-400">20:23:52</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="py-5 border-b border-slate-50 group-last:border-none font-semibold text-slate-900">
|
||||
3006</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none text-right pr-4">
|
||||
<button class="p-1 hover:bg-slate-200 rounded transition-colors text-slate-400">
|
||||
<span class="material-icons-round text-lg">more_horiz</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="group hover:bg-slate-50/80 transition-colors">
|
||||
<td class="py-5 pl-4 border-b border-slate-50 group-last:border-none">
|
||||
<input class="rounded border-slate-300 text-primary focus:ring-primary/20"
|
||||
type="checkbox" />
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="font-medium text-slate-900 border-b border-transparent group-hover:border-primary/30 transition-all cursor-pointer">erke_700</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">B5</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="px-3 py-1 bg-amber-50 text-amber-600 text-[10px] font-bold uppercase rounded-full">金票</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看场次</button>
|
||||
<button
|
||||
class="px-3 py-1.5 bg-sky-50 text-sky-600 hover:bg-sky-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看收益</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">哈萨克斯坦</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex flex-col">
|
||||
<span>2026-01-13</span>
|
||||
<span class="text-[10px] text-slate-400">20:23:49</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="py-5 border-b border-slate-50 group-last:border-none font-semibold text-slate-900">
|
||||
5572</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none text-right pr-4">
|
||||
<button class="p-1 hover:bg-slate-200 rounded transition-colors text-slate-400">
|
||||
<span class="material-icons-round text-lg">more_horiz</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="group hover:bg-slate-50/80 transition-colors">
|
||||
<td class="py-5 pl-4 border-b border-slate-50 group-last:border-none">
|
||||
<input class="rounded border-slate-300 text-primary focus:ring-primary/20"
|
||||
type="checkbox" />
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="font-medium text-slate-900 border-b border-transparent group-hover:border-primary/30 transition-all cursor-pointer">luckystar1458</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">D1</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="px-3 py-1 bg-green-50 text-green-600 text-[10px] font-bold uppercase rounded-full">普票</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看场次</button>
|
||||
<button
|
||||
class="px-3 py-1.5 bg-sky-50 text-sky-600 hover:bg-sky-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看收益</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">摩尔多瓦</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex flex-col">
|
||||
<span>2026-01-13</span>
|
||||
<span class="text-[10px] text-slate-400">20:23:50</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="py-5 border-b border-slate-50 group-last:border-none font-semibold text-slate-900">
|
||||
15107</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none text-right pr-4">
|
||||
<button class="p-1 hover:bg-slate-200 rounded transition-colors text-slate-400">
|
||||
<span class="material-icons-round text-lg">more_horiz</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="group hover:bg-slate-50/80 transition-colors">
|
||||
<td class="py-5 pl-4 border-b border-slate-50 group-last:border-none">
|
||||
<input class="rounded border-slate-300 text-primary focus:ring-primary/20"
|
||||
type="checkbox" />
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="font-medium text-slate-900 border-b border-transparent group-hover:border-primary/30 transition-all cursor-pointer">boss.667b1</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">C5</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<span
|
||||
class="px-3 py-1 bg-amber-50 text-amber-600 text-[10px] font-bold uppercase rounded-full">金票</span>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看场次</button>
|
||||
<button
|
||||
class="px-3 py-1.5 bg-sky-50 text-sky-600 hover:bg-sky-600 hover:text-white rounded-lg text-xs font-medium transition-all">查看收益</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">哈萨克斯坦</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none">
|
||||
<div class="flex flex-col">
|
||||
<span>2026-01-13</span>
|
||||
<span class="text-[10px] text-slate-400">20:23:49</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
class="py-5 border-b border-slate-50 group-last:border-none font-semibold text-slate-900">
|
||||
4815</td>
|
||||
<td class="py-5 border-b border-slate-50 group-last:border-none text-right pr-4">
|
||||
<button class="p-1 hover:bg-slate-200 rounded transition-colors text-slate-400">
|
||||
<span class="material-icons-round text-lg">more_horiz</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<footer
|
||||
class="px-8 py-6 border-t border-slate-100 dark:border-slate-200/60 bg-white/50 backdrop-blur-md flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative">
|
||||
<select
|
||||
class="bg-slate-50 border-none rounded-lg py-2 pl-4 pr-10 text-sm text-slate-600 appearance-none focus:ring-2 focus:ring-primary/20 shadow-soft-inner">
|
||||
<option>10/page</option>
|
||||
<option>20/page</option>
|
||||
<option>50/page</option>
|
||||
</select>
|
||||
<span
|
||||
class="material-icons-round absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">expand_more</span>
|
||||
</div>
|
||||
<span class="text-xs text-slate-400 font-medium">到期时间: <span class="text-green-600">2036-08-01
|
||||
00:00:00</span></span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<button class="p-2 text-slate-400 hover:bg-slate-100 rounded-lg transition-colors">
|
||||
<span class="material-icons-round text-lg">chevron_left</span>
|
||||
</button>
|
||||
<button
|
||||
class="w-8 h-8 rounded-lg bg-slate-900 text-white text-xs font-bold transition-all shadow-md">1</button>
|
||||
<button
|
||||
class="w-8 h-8 rounded-lg hover:bg-slate-100 text-slate-600 text-xs font-semibold transition-all">2</button>
|
||||
<button
|
||||
class="w-8 h-8 rounded-lg hover:bg-slate-100 text-slate-600 text-xs font-semibold transition-all">3</button>
|
||||
<button
|
||||
class="w-8 h-8 rounded-lg hover:bg-slate-100 text-slate-600 text-xs font-semibold transition-all">4</button>
|
||||
<button
|
||||
class="w-8 h-8 rounded-lg hover:bg-slate-100 text-slate-600 text-xs font-semibold transition-all">5</button>
|
||||
<button
|
||||
class="w-8 h-8 rounded-lg hover:bg-slate-100 text-slate-600 text-xs font-semibold transition-all">6</button>
|
||||
<span class="px-2 text-slate-400 text-xs font-bold">...</span>
|
||||
<button
|
||||
class="w-8 h-8 rounded-lg hover:bg-slate-100 text-slate-600 text-xs font-semibold transition-all">46</button>
|
||||
<button class="p-2 text-slate-400 hover:bg-slate-100 rounded-lg transition-colors">
|
||||
<span class="material-icons-round text-lg">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,89 +1,97 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<Sidebar class="noneText" @activeIndex="activeIndexFn" />
|
||||
<div class="content ">
|
||||
<div v-show="activeIndexA == 1">
|
||||
<workbenches />
|
||||
</div>
|
||||
<div v-show="activeIndexA == 2">
|
||||
<hostsList />
|
||||
</div>
|
||||
<div style="position: absolute; bottom: 0; right: 0;">{{ version }}</div>
|
||||
|
||||
|
||||
<div class="nav-container bg-background-light dark:bg-background-dark text-slate-800 dark:text-slate-200 shadow-2xl overflow-hidden" :style="containerStyle">
|
||||
<div class="flex h-full overflow-hidden">
|
||||
<Sidebar class="flex-none h-full" @activeIndex="activeIndexFn" />
|
||||
<main class="flex-1 overflow-y-auto p-4 relative flex flex-col h-full bg-[#f3f5f9]">
|
||||
<div v-show="activeIndexA == 1" class="h-full">
|
||||
<workbenches />
|
||||
</div>
|
||||
<div v-show="activeIndexA == 2" class="h-full">
|
||||
<hostsList />
|
||||
</div>
|
||||
<!-- Decorative blobs -->
|
||||
<!-- <div class="absolute -top-24 -right-24 w-64 h-64 bg-primary/5 rounded-full blur-3xl pointer-events-none"></div> -->
|
||||
<!-- <div class="absolute -bottom-24 -left-24 w-64 h-64 bg-secondary/5 rounded-full blur-3xl pointer-events-none"></div> -->
|
||||
|
||||
<!-- <div class="absolute bottom-2 right-4 text-xs text-slate-400">{{ version }}</div> -->
|
||||
</main>
|
||||
</div>
|
||||
</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'
|
||||
// import { usePythonBridge } from '@/utils/pythonBridge'
|
||||
|
||||
|
||||
|
||||
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
// 设计稿尺寸
|
||||
const DESIGN_WIDTH = 1600
|
||||
const DESIGN_HEIGHT = 900
|
||||
|
||||
let activeIndexA = ref(1)
|
||||
const version = ref('v0.1.0')
|
||||
|
||||
// 缩放比例
|
||||
const zoomRatio = ref(1)
|
||||
|
||||
// 计算缩放比例
|
||||
const calcZoom = () => {
|
||||
const windowWidth = window.innerWidth
|
||||
const windowHeight = window.innerHeight
|
||||
|
||||
// 根据宽高比例计算缩放值,取较小值保证完整显示
|
||||
const widthRatio = windowWidth / DESIGN_WIDTH
|
||||
const heightRatio = windowHeight / DESIGN_HEIGHT
|
||||
|
||||
zoomRatio.value = Math.min(widthRatio, heightRatio)
|
||||
}
|
||||
|
||||
// 容器样式
|
||||
const containerStyle = computed(() => ({
|
||||
width: `${DESIGN_WIDTH}px`,
|
||||
height: `${DESIGN_HEIGHT}px`,
|
||||
zoom: zoomRatio.value
|
||||
}))
|
||||
|
||||
function activeIndexFn(data) {
|
||||
activeIndexA.value = data
|
||||
console.log(data)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
calcZoom()
|
||||
window.addEventListener('resize', calcZoom)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', calcZoom)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
body,
|
||||
html {
|
||||
<style>
|
||||
/* Global resets if needed, but Tailwind handles most */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background-color: #f3f5f9; /* Match background */
|
||||
display: flex;
|
||||
width: 1600px;
|
||||
height: 900px;
|
||||
background-color: @bg-color;
|
||||
position: relative;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.noneText {
|
||||
/* 页面无法选中 */
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
background-color: @bg-color;
|
||||
padding: 20px;
|
||||
/* box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); */
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 280px;
|
||||
width: 1304px;
|
||||
height: 868px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 36px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.center-justify {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
-webkit-user-select: none; /* Chrome / Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE / Edge */
|
||||
user-select: none; /* 标准 */
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user