4 Commits

Author SHA1 Message Date
c6d814f343 登录页图片压缩 2026-03-05 13:29:26 +08:00
73842a4012 fusion 2026-02-12 19:43:44 +08:00
38f8fe9bd0 ui样式回滚修复 2026-01-23 19:53:38 +08:00
21eec1085a 开启tk 状态灯 2026-01-23 16:12:37 +08:00
10 changed files with 829 additions and 407 deletions

461
package-lock.json generated
View File

@@ -24,12 +24,15 @@
"@vue/cli-plugin-router": "~5.0.0", "@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0", "@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"autoprefixer": "^10.4.24",
"less": "^4.2.2", "less": "^4.2.2",
"less-loader": "^12.2.0", "less-loader": "^12.2.0",
"postcss": "^8.5.6",
"postcss-preset-env": "^10.1.5", "postcss-preset-env": "^10.1.5",
"postcss-px-to-viewport": "^1.1.1", "postcss-px-to-viewport": "^1.1.1",
"postcss-px-viewport": "^0.0.4", "postcss-px-viewport": "^0.0.4",
"postcss-viewport-units": "^0.1.6" "postcss-viewport-units": "^0.1.6",
"tailwindcss": "^3.4.17"
} }
}, },
"node_modules/@achrinza/node-ipc": { "node_modules/@achrinza/node-ipc": {
@@ -47,6 +50,19 @@
"node": "8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21 || 22" "node": "8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21 || 22"
} }
}, },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -4582,6 +4598,13 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true,
"license": "MIT"
},
"node_modules/array-flatten": { "node_modules/array-flatten": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -4629,9 +4652,9 @@
} }
}, },
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.4.21", "version": "10.4.24",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.24.tgz",
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -4649,10 +4672,9 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"browserslist": "^4.24.4", "browserslist": "^4.28.1",
"caniuse-lite": "^1.0.30001702", "caniuse-lite": "^1.0.30001766",
"fraction.js": "^4.3.7", "fraction.js": "^5.3.4",
"normalize-range": "^0.1.2",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0" "postcss-value-parser": "^4.2.0"
}, },
@@ -4792,6 +4814,16 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/baseline-browser-mapping": {
"version": "2.9.19",
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
"integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/batch": { "node_modules/batch": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
@@ -4935,9 +4967,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.24.4", "version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz",
"integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -4955,10 +4987,11 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001688", "baseline-browser-mapping": "^2.9.0",
"electron-to-chromium": "^1.5.73", "caniuse-lite": "^1.0.30001759",
"node-releases": "^2.0.19", "electron-to-chromium": "^1.5.263",
"update-browserslist-db": "^1.1.1" "node-releases": "^2.0.27",
"update-browserslist-db": "^1.2.0"
}, },
"bin": { "bin": {
"browserslist": "cli.js" "browserslist": "cli.js"
@@ -5089,6 +5122,16 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/caniuse-api": { "node_modules/caniuse-api": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -5103,9 +5146,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001707", "version": "1.0.30001767",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz",
"integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==", "integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -6385,6 +6428,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/dir-glob": { "node_modules/dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -6398,6 +6448,13 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true,
"license": "MIT"
},
"node_modules/dns-packet": { "node_modules/dns-packet": {
"version": "5.6.1", "version": "5.6.1",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
@@ -6573,9 +6630,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.129", "version": "1.5.283",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.129.tgz", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz",
"integrity": "sha512-JlXUemX4s0+9f8mLqib/bHH8gOHf5elKS6KeWG3sk3xozb/JTq/RLXIv8OKUWiK4Ah00Wm88EFj5PYkFr4RUPA==", "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@@ -7197,16 +7254,16 @@
} }
}, },
"node_modules/fraction.js": { "node_modules/fraction.js": {
"version": "4.3.7", "version": "5.3.4",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz",
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "*" "node": "*"
}, },
"funding": { "funding": {
"type": "patreon", "type": "github",
"url": "https://github.com/sponsors/rawify" "url": "https://github.com/sponsors/rawify"
} }
}, },
@@ -8198,6 +8255,16 @@
"url": "https://github.com/chalk/supports-color?sponsor=1" "url": "https://github.com/chalk/supports-color?sponsor=1"
} }
}, },
"node_modules/jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
}
},
"node_modules/joi": { "node_modules/joi": {
"version": "17.13.3", "version": "17.13.3",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
@@ -9226,9 +9293,9 @@
} }
}, },
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.19", "version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -9265,16 +9332,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/normalize-range": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/normalize-url": { "node_modules/normalize-url": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
@@ -9330,6 +9387,16 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/object-hash": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -9780,6 +9847,16 @@
"@vue/devtools-kit": "^7.7.2" "@vue/devtools-kit": "^7.7.2"
} }
}, },
"node_modules/pirates": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/pkg-dir": { "node_modules/pkg-dir": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@@ -9808,9 +9885,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.3", "version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -9827,7 +9904,7 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.8", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
}, },
@@ -10387,6 +10464,50 @@
"postcss": "^8.4" "postcss": "^8.4"
} }
}, },
"node_modules/postcss-import": {
"version": "15.1.0",
"resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0",
"resolve": "^1.1.7"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"postcss": "^8.0.0"
}
},
"node_modules/postcss-js": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.1.0.tgz",
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"camelcase-css": "^2.0.1"
},
"engines": {
"node": "^12 || ^14 || >= 16"
},
"peerDependencies": {
"postcss": "^8.4.21"
}
},
"node_modules/postcss-lab-function": { "node_modules/postcss-lab-function": {
"version": "7.0.8", "version": "7.0.8",
"resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.8.tgz", "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.8.tgz",
@@ -10417,6 +10538,71 @@
"postcss": "^8.4" "postcss": "^8.4"
} }
}, },
"node_modules/postcss-load-config": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"lilconfig": "^3.0.0",
"yaml": "^2.3.4"
},
"engines": {
"node": ">= 14"
},
"peerDependencies": {
"postcss": ">=8.0.9",
"ts-node": ">=9.0.0"
},
"peerDependenciesMeta": {
"postcss": {
"optional": true
},
"ts-node": {
"optional": true
}
}
},
"node_modules/postcss-load-config/node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/postcss-load-config/node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/postcss-loader": { "node_modules/postcss-loader": {
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz",
@@ -10674,6 +10860,32 @@
"postcss": "^8.1.0" "postcss": "^8.1.0"
} }
}, },
"node_modules/postcss-nested": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "^6.1.1"
},
"engines": {
"node": ">=12.0"
},
"peerDependencies": {
"postcss": "^8.2.14"
}
},
"node_modules/postcss-nesting": { "node_modules/postcss-nesting": {
"version": "13.0.1", "version": "13.0.1",
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz",
@@ -11788,6 +12000,26 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pify": "^2.3.0"
}
},
"node_modules/read-cache/node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/read-pkg": { "node_modules/read-pkg": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@@ -12776,6 +13008,39 @@
"postcss": "^8.2.15" "postcss": "^8.2.15"
} }
}, },
"node_modules/sucrase": {
"version": "3.35.1",
"resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz",
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
"commander": "^4.0.0",
"lines-and-columns": "^1.1.6",
"mz": "^2.7.0",
"pirates": "^4.0.1",
"tinyglobby": "^0.2.11",
"ts-interface-checker": "^0.1.9"
},
"bin": {
"sucrase": "bin/sucrase",
"sucrase-node": "bin/sucrase-node"
},
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/sucrase/node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/superjson": { "node_modules/superjson": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
@@ -12852,6 +13117,57 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.6.0",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.3.2",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.21.6",
"lilconfig": "^3.1.3",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.1.1",
"postcss": "^8.4.47",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.2",
"postcss-nested": "^6.2.0",
"postcss-selector-parser": "^6.1.2",
"resolve": "^1.22.8",
"sucrase": "^3.35.0"
},
"bin": {
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tailwindcss/node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@@ -13068,6 +13384,54 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -13108,6 +13472,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -13211,9 +13582,9 @@
} }
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.1.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {

View File

@@ -23,12 +23,15 @@
"@vue/cli-plugin-router": "~5.0.0", "@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0", "@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"autoprefixer": "^10.4.24",
"less": "^4.2.2", "less": "^4.2.2",
"less-loader": "^12.2.0", "less-loader": "^12.2.0",
"postcss": "^8.5.6",
"postcss-preset-env": "^10.1.5", "postcss-preset-env": "^10.1.5",
"postcss-px-to-viewport": "^1.1.1", "postcss-px-to-viewport": "^1.1.1",
"postcss-px-viewport": "^0.0.4", "postcss-px-viewport": "^0.0.4",
"postcss-viewport-units": "^0.1.6" "postcss-viewport-units": "^0.1.6",
"tailwindcss": "^3.4.17"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@@ -1,13 +0,0 @@
module.exports = {
plugins: {
'postcss-px-to-viewport': {
viewportWidth: 1600, // 视窗的宽度,对应设计稿宽度
viewportHeight: 900, // 视窗的高度,对应设计稿高度
unitPrecision: 3, // 指定 px 转换为视窗单位值的小数位数
viewportUnit: 'vw', // 指定需要转换成的视窗单位vw 或者 vh
selectorBlackList: ['.ignore', '.hairlines'], // 指定不需要转换的类
minPixelValue: 1, // 小于或等于 1 px 不转换为视窗单位
mediaQuery: false // 允许在媒体查询中转换 px
}
}
};

BIN
src/assets/logoBg2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -78,7 +78,7 @@
sure: '确定', sure: '确定',
invitationType: '邀请类型', invitationType: '邀请类型',
invitationType1: '普票', invitationType1: '普票',
invitationType2: '票', invitationType2: '进阶票',
liveSessions: '直播场次', liveSessions: '直播场次',
viewSessions: '查看场次', viewSessions: '查看场次',
liveRevenue: '直播收益', liveRevenue: '直播收益',

View File

@@ -5,143 +5,154 @@ const bridge = ref(null);
// 统一安全调用,确保 Qt 响应有回调可执行 // 统一安全调用,确保 Qt 响应有回调可执行
const callBridge = (method, ...args) => { const callBridge = (method, ...args) => {
if (!bridge.value || typeof bridge.value[method] !== 'function') return; if (!bridge.value || typeof bridge.value[method] !== 'function') return;
const last = args[args.length - 1]; const last = args[args.length - 1];
const hasCallback = typeof last === 'function'; const hasCallback = typeof last === 'function';
const callback = hasCallback ? args.pop() : () => { }; const callback = hasCallback ? args.pop() : () => { };
bridge.value[method](...args, callback); bridge.value[method](...args, callback);
}; };
// 防御:若 Qt 返回了未知 id忽略以免 execCallbacks 报错 // 防御:若 Qt 返回了未知 id忽略以免 execCallbacks 报错
const patchQWebChannel = () => { const patchQWebChannel = () => {
if (!window.QWebChannel || QWebChannel.__patchedIgnoreMissing) return; if (!window.QWebChannel || QWebChannel.__patchedIgnoreMissing) return;
const originalHandleResponse = QWebChannel.prototype.handleResponse; const originalHandleResponse = QWebChannel.prototype.handleResponse;
QWebChannel.__patchedIgnoreMissing = true; QWebChannel.__patchedIgnoreMissing = true;
QWebChannel.prototype.handleResponse = function (message) { QWebChannel.prototype.handleResponse = function (message) {
const cb = this.execCallbacks && this.execCallbacks[message.id]; const cb = this.execCallbacks && this.execCallbacks[message.id];
if (message.id && typeof cb !== 'function') { if (message.id && typeof cb !== 'function') {
console.warn('忽略未知的 WebChannel 响应', message); console.warn('忽略未知的 WebChannel 响应', message);
return; return;
} }
return originalHandleResponse.call(this, message); return originalHandleResponse.call(this, message);
}; };
}; };
// 初始化 QWebChannel // 初始化 QWebChannel
const initBridge = () => { const initBridge = () => {
if (/localhost/.test(window.location.href)) return; if (/localhost/.test(window.location.href)) return;
patchQWebChannel(); patchQWebChannel();
new QWebChannel(qt.webChannelTransport, (channel) => { new QWebChannel(qt.webChannelTransport, (channel) => {
// 兜底:任何缺失的回调都返回空函数,避免 execCallbacks 报错 // 兜底:任何缺失的回调都返回空函数,避免 execCallbacks 报错
channel.execCallbacks = new Proxy(channel.execCallbacks || {}, { channel.execCallbacks = new Proxy(channel.execCallbacks || {}, {
get(target, prop) { get(target, prop) {
const val = target[prop]; const val = target[prop];
if (typeof val === 'function') return val; if (typeof val === 'function') return val;
// 返回空函数,确保 handleResponse 可调用 // 返回空函数,确保 handleResponse 可调用
return () => {}; return () => { };
}, },
set(target, prop, value) { set(target, prop, value) {
target[prop] = value; target[prop] = value;
return true; return true;
}, },
}); });
bridge.value = channel.objects.bridge; bridge.value = channel.objects.bridge;
}); });
}; };
export function usePythonBridge() { export function usePythonBridge() {
// 调用 Python 方法 // 调用 Python 方法
const fetchDataConfig = (data) => { const fetchDataConfig = (data) => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!bridge.value) return resolve(null); if (!bridge.value) return resolve(null);
callBridge('fetchDataConfig', data, (result) => { callBridge('fetchDataConfig', data, (result) => {
resolve(result); resolve(result);
}); });
}); });
}; };
// 查询获取主播的数据 // 查询获取主播的数据
const fetchDataCount = () => { const fetchDataCount = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!bridge.value) return resolve(null); if (!bridge.value) return resolve(null);
callBridge('fetchDataCount', (result) => { callBridge('fetchDataCount', (result) => {
resolve(result); resolve(result);
}); });
}); });
}; };
// 打开 tk 后台 // 打开 tk 后台
const loginTikTok = () => { const loginTikTok = () => {
callBridge('loginTikTok'); callBridge('loginTikTok');
}; };
// 登录 tk 后台 // 登录 tk 后台
const loginBackStage = (data) => { const loginBackStage = (data) => {
if (data.index == 0) { if (data.index == 0) {
callBridge('loginBackStage', JSON.stringify(data)); callBridge('loginBackStage', JSON.stringify(data));
} else if (data.index == 1) { } else if (data.index == 1) {
callBridge('loginBackStageCopy', JSON.stringify(data)); callBridge('loginBackStageCopy', JSON.stringify(data));
} }
}; };
// 跳转到主播页面 // 跳转到主播页面
const givePyAnchorId = (id) => { const givePyAnchorId = (id) => {
callBridge('givePyAnchorId', id); callBridge('givePyAnchorId', id);
}; };
// 查询登录状态 // 查询登录状态
const backStageloginStatus = () => { const backStageloginStatus = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!bridge.value) return resolve(null); if (!bridge.value) return resolve(null);
callBridge('backStageloginStatus', (result) => { callBridge('backStageloginStatus', (result) => {
resolve(result); resolve(result);
}); });
}); });
}; };
// 查询登录状态(副账号) // 查询登录状态(副账号)
const backStageloginStatusCopy = () => { const backStageloginStatusCopy = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!bridge.value) return resolve(null); if (!bridge.value) return resolve(null);
callBridge('backStageloginStatusCopy', (result) => { callBridge('backStageloginStatusCopy', (result) => {
resolve(result); resolve(result);
}); });
}); });
}; };
// 导出表格 // 导出表格
const exportToExcel = (data) => { const exportToExcel = (data) => {
callBridge('exportToExcel', JSON.stringify(data)); callBridge('exportToExcel', JSON.stringify(data));
}; };
const stopScript = () => { const stopScript = () => {
callBridge('stopScript'); callBridge('stopScript');
}; };
// 获取版本号 // 获取版本号
const getVersion = () => { const getVersion = () => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!bridge.value) return resolve(null); if (!bridge.value) return resolve(null);
callBridge('currentVersion', (result) => { callBridge('currentVersion', (result) => {
resolve(result); resolve(result);
}); });
}); });
}; };
// 在组件挂载时初始化桥接 // 查询TK登录状态
onMounted(initBridge); const getTkLoginStatus = () => {
return new Promise((resolve) => {
if (!bridge.value) return resolve(false);
callBridge('getTkLoginStatus', (result) => {
resolve(result);
});
});
};
return { // 在组件挂载时初始化桥接
fetchDataConfig, onMounted(initBridge);
fetchDataCount,
loginBackStage, return {
loginTikTok, fetchDataConfig,
givePyAnchorId, fetchDataCount,
backStageloginStatus, loginBackStage,
backStageloginStatusCopy, loginTikTok,
exportToExcel, givePyAnchorId,
stopScript, backStageloginStatus,
getVersion, backStageloginStatusCopy,
}; exportToExcel,
stopScript,
getVersion,
getTkLoginStatus,
};
} }

View File

@@ -4,56 +4,56 @@ import { usePythonBridge } from '@/utils/pythonBridge'
export function setToken(token) { export function setToken(token) {
localStorage.setItem('token', token); localStorage.setItem('token', token);
} }
export function getToken() { export function getToken() {
return localStorage.getItem('token'); return localStorage.getItem('token');
} }
export function removeToken() { export function removeToken() {
localStorage.removeItem('token'); localStorage.removeItem('token');
} }
export function setUser(user) { export function setUser(user) {
localStorage.setItem('user', JSON.stringify(user)); localStorage.setItem('user', JSON.stringify(user));
} }
export function getUser() { export function getUser() {
return JSON.parse(localStorage.getItem('user')); return JSON.parse(localStorage.getItem('user'));
} }
export function setNumData(numData) { export function setNumData(numData) {
localStorage.setItem('num', JSON.stringify(numData)); localStorage.setItem('num', JSON.stringify(numData));
} }
export function getNumData() { export function getNumData() {
return JSON.parse(localStorage.getItem('num')); return JSON.parse(localStorage.getItem('num'));
} }
// 导出一个函数,用于设置用户密码 // 导出一个函数,用于设置用户密码
export function setUserPass(userdata) { export function setUserPass(userdata) {
localStorage.setItem('userPass', JSON.stringify(userdata)); localStorage.setItem('userPass', JSON.stringify(userdata));
} }
// 导出一个函数,用于获取用户密码 // 导出一个函数,用于获取用户密码
export function getUserPass() { export function getUserPass() {
return JSON.parse(localStorage.getItem('userPass')); return JSON.parse(localStorage.getItem('userPass'));
} }
// 用于设置tk账户密码 // 用于设置tk账户密码
export function setTkUser(userdata) { export function setTkUser(userdata) {
localStorage.setItem('tkuser', JSON.stringify(userdata)); localStorage.setItem('tkuser', JSON.stringify(userdata));
} }
// 用于获取tk账户密码 // 用于获取tk账户密码
export function getTkUser() { export function getTkUser() {
return JSON.parse(localStorage.getItem('tkuser')); return JSON.parse(localStorage.getItem('tkuser'));
} }
// 用于列表筛选条件 // 用于列表筛选条件
export function setSerch(data) { export function setSerch(data) {
localStorage.setItem('Serch', JSON.stringify(data)); localStorage.setItem('Serch', JSON.stringify(data));
} }
// 用于获取列表筛选条件 // 用于获取列表筛选条件
export function getSerch() { export function getSerch() {
return JSON.parse(localStorage.getItem('Serch')); return JSON.parse(localStorage.getItem('Serch'));
} }

View File

@@ -67,7 +67,7 @@
<div class="version">{{ $t('login.version') }}:{{ version }}</div> <div class="version">{{ $t('login.version') }}:{{ version }}</div>
</div> </div>
<div class="auth-right"> <div class="auth-right">
<img src="@/assets/logoBg2.png" class="illustration" alt=""> <img src="@/assets/logoBg2.webp" class="illustration" alt="">
</div> </div>
</div> </div>
</div> </div>

View File

@@ -4,47 +4,36 @@
<header class="px-8 py-6 border-b border-slate-100 dark:border-slate-200/60 bg-white"> <header class="px-8 py-6 border-b border-slate-100 dark:border-slate-200/60 bg-white">
<div class="flex flex-wrap items-center gap-4"> <div class="flex flex-wrap items-center gap-4">
<div class="relative flex-1 min-w-[200px]"> <div class="relative flex-1 min-w-[200px]">
<select <select v-model="searchForm.country"
v-model="searchForm.country" 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 outline-none">
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 outline-none"
>
<option value="">{{ $t('hostList.selectAll') }}</option> <option value="">{{ $t('hostList.selectAll') }}</option>
<option v-for="item in options" :key="item.value" :value="item.value">{{ item.label }}</option> <option v-for="item in options" :key="item.value" :value="item.value">{{ item.label }}</option>
</select> </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> <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>
<div class="relative flex-1 min-w-[200px]"> <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 pointer-events-none">calendar_today</span> <span
<input class="material-icons-round absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">calendar_today</span>
ref="dateInput" <input ref="dateInput" v-model="searchForm.createTime" type="date" @click="openDatePicker"
v-model="searchForm.createTime" 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 outline-none cursor-pointer" />
type="date"
@click="openDatePicker"
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 outline-none cursor-pointer"
/>
</div> </div>
<div class="relative flex-[1.5] min-w-[240px]"> <div class="relative flex-[1.5] min-w-[240px]">
<input <input v-model="searchForm.hostsId"
v-model="searchForm.hostsId" 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 outline-none"
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 outline-none" :placeholder="$t('hostList.placeHostId')" />
:placeholder="$t('hostList.placeHostId')"
/>
</div> </div>
<div class="flex items-center gap-2 ml-auto"> <div class="flex items-center gap-2 ml-auto">
<button <button @click="serch"
@click="serch" 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">
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> <span class="material-icons-round text-sm">search</span>
{{ $t('hostList.query') }} {{ $t('hostList.query') }}
</button> </button>
<button <button @click="filterdialogVisible = true"
@click="filterdialogVisible = true" class="bg-slate-100 hover:bg-slate-200 text-slate-600 p-3 rounded-xl transition-all">
class="bg-slate-100 hover:bg-slate-200 text-slate-600 p-3 rounded-xl transition-all"
>
<span class="material-icons-round">tune</span> <span class="material-icons-round">tune</span>
</button> </button>
</div> </div>
@@ -54,9 +43,9 @@
<!-- Table --> <!-- Table -->
<div class="flex-1 flex flex-col overflow-hidden relative" style="padding-left: 20px;"> <div class="flex-1 flex flex-col overflow-hidden relative" style="padding-left: 20px;">
<div v-if="loading" class="absolute inset-0 bg-white/50 z-30 flex items-center justify-center"> <div v-if="loading" class="absolute inset-0 bg-white/50 z-30 flex items-center justify-center">
<span class="material-icons-round animate-spin text-primary text-4xl">sync</span> <span class="material-icons-round animate-spin text-primary text-4xl">sync</span>
</div> </div>
<!-- Table with Fixed Layout --> <!-- Table with Fixed Layout -->
<div class="flex-1 overflow-auto"> <div class="flex-1 overflow-auto">
<table class="w-full text-left" style="table-layout: fixed; min-width: 1000px;"> <table class="w-full text-left" style="table-layout: fixed; min-width: 1000px;">
@@ -64,7 +53,7 @@
<col style="width: 12%;"> <col style="width: 12%;">
<col style="width: 5%;"> <col style="width: 5%;">
<col style="width: 8%;"> <col style="width: 8%;">
<col style="width: 15%;"> <col style="width: 8%;">
<col style="width: 6%;"> <col style="width: 6%;">
<col style="width: 10%;"> <col style="width: 10%;">
<col style="width: 8%;"> <col style="width: 8%;">
@@ -79,7 +68,8 @@
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.hostId') }}</th> <th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.hostId') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.grade') }}</th> <th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.grade') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.invitationType') }}</th> <th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.invitationType') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.liveSessions') }}/{{ $t('hostList.liveRevenue') }}</th> <!-- <th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.liveSessions') }}/{{ $t('hostList.liveRevenue') }}</th> -->
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.liveSessions') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.country') }}</th> <th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.country') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.creationTime') }}</th> <th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.creationTime') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.anchorcoins') }}</th> <th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.anchorcoins') }}</th>
@@ -90,40 +80,36 @@
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.anchorType') }}</th> <th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.anchorType') }}</th>
</tr> </tr>
</thead> </thead>
<tbody class="text-sm text-slate-600"> <tbody class="text-sm text-slate-600">
<tr v-if="tableData.length === 0" class="text-center"> <tr v-if="tableData.length === 0" class="text-center">
<td colspan="14" class="py-10 text-slate-400">暂无数据</td> <td colspan="14" class="py-10 text-slate-400">暂无数据</td>
</tr> </tr>
<tr v-for="row in tableData" :key="row.hostId" class="group hover:bg-slate-50/80 transition-colors"> <tr v-for="row in tableData" :key="row.hostId" class="group hover:bg-slate-50/80 transition-colors">
<!-- Host ID --> <!-- Host ID -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none"> <td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<span <span @click="openHTML(row.hostId)"
@click="openHTML(row.hostId)" class="font-medium text-slate-900 border-b border-transparent group-hover:border-primary/30 transition-all cursor-pointer hover:text-primary">
class="font-medium text-slate-900 border-b border-transparent group-hover:border-primary/30 transition-all cursor-pointer hover:text-primary" {{ row.hostId }}
> </span>
{{ row.hostId }} </td>
</span>
</td> <!-- Level -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<!-- Level --> {{ row.hostlevel }}
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none"> </td>
{{ row.hostlevel }}
</td> <!-- Invitation Type -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<!-- Invitation Type --> <span class="px-3 py-1 text-[10px] font-bold uppercase rounded-full"
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none"> :class="row.invitationType == 1 ? 'bg-green-50 text-green-600' : 'bg-amber-50 text-amber-600'">
<span {{ row.invitationType == 1 ? $t('hostList.invitationType1') : $t('hostList.invitationType2') }}
class="px-3 py-1 text-[10px] font-bold uppercase rounded-full" </span>
:class="row.invitationType == 1 ? 'bg-green-50 text-green-600' : 'bg-amber-50 text-amber-600'" </td>
>
{{ row.invitationType == 1 ? $t('hostList.invitationType1') : $t('hostList.invitationType2') }} <!-- Data Buttons -->
</span> <td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
</td>
<!-- Data Buttons -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
@click="getliveHost(row.hostId)" @click="getliveHost(row.hostId)"
@@ -131,111 +117,100 @@
> >
{{ $t('hostList.viewSessions') }} {{ $t('hostList.viewSessions') }}
</button> </button>
<button <!-- <button
@click="getRevenueStats(row.hostId)" @click="getRevenueStats(row.hostId)"
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" 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"
> >
{{ $t('hostList.viewRevenue') }} {{ $t('hostList.viewRevenue') }}
</button> </button> -->
</div> </div>
</td> </td>
<!-- Country -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.country }}
</td>
<!-- Time -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<div class="flex flex-col">
<span>{{ formatTimeOnlyDate(row.createTime) }}</span>
<span class="text-[10px] text-slate-400">{{ formatTimeOnlyTime(row.createTime) }}</span>
</div>
</td>
<!-- Coins -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none font-semibold text-slate-900">
{{ row.hostsCoins }}
</td>
<!-- Yesterday Coins -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.yesterdayCoins }}
</td>
<!-- Fans -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.fans }}
</td>
<!-- Followers -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.fllowernum }}
</td>
<!-- Online Fans -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.onlineFans }}
</td>
<!-- Host Kind -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.hostsKind }}
</td>
</tr> <!-- Country -->
</tbody> <td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
</table> {{ row.country }}
</td>
<!-- Time -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<div class="flex flex-col">
<span>{{ formatTimeOnlyDate(row.createTime) }}</span>
<span class="text-[10px] text-slate-400">{{ formatTimeOnlyTime(row.createTime) }}</span>
</div>
</td>
<!-- Coins -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none font-semibold text-slate-900">
{{ row.hostsCoins }}
</td>
<!-- Yesterday Coins -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.yesterdayCoins }}
</td>
<!-- Fans -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.fans }}
</td>
<!-- Followers -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.fllowernum }}
</td>
<!-- Online Fans -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.onlineFans }}
</td>
<!-- Host Kind -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.hostsKind }}
</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
<!-- Footer / Pagination --> <!-- Footer / Pagination -->
<footer class="px-8 py-6 border-t border-slate-100 dark:border-slate-200/60 bg-white flex flex-wrap items-center justify-between gap-4"> <footer
class="px-8 py-6 border-t border-slate-100 dark:border-slate-200/60 bg-white flex flex-wrap items-center justify-between gap-4">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<div class="relative"> <div class="relative">
<select <select v-model="pageSize" @change="handleSizeChange"
v-model="pageSize" 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 outline-none">
@change="handleSizeChange"
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 outline-none"
>
<option :value="10">10/page</option> <option :value="10">10/page</option>
<option :value="20">20/page</option> <option :value="20">20/page</option>
<option :value="50">50/page</option> <option :value="50">50/page</option>
<option :value="100">100/page</option> <option :value="100">100/page</option>
</select> </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> <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> </div>
<span class="text-xs text-slate-400 font-medium">总条数: <span class="text-primary">{{ total }}</span></span> <span class="text-xs text-slate-400 font-medium">总条数: <span class="text-primary">{{ total }}</span></span>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<button <button @click="changePage(page - 1)" :disabled="page <= 1"
@click="changePage(page - 1)" class="p-2 text-slate-400 hover:bg-slate-100 rounded-lg transition-colors disabled:opacity-50">
:disabled="page <= 1"
class="p-2 text-slate-400 hover:bg-slate-100 rounded-lg transition-colors disabled:opacity-50"
>
<span class="material-icons-round text-lg">chevron_left</span> <span class="material-icons-round text-lg">chevron_left</span>
</button> </button>
<!-- Page Numbers --> <!-- Page Numbers -->
<template v-for="(p, index) in paginationPages" :key="index"> <template v-for="(p, index) in paginationPages" :key="index">
<span v-if="p === '...'" class="w-8 h-8 flex items-center justify-center text-slate-400 text-sm">...</span> <span v-if="p === '...'" class="w-8 h-8 flex items-center justify-center text-slate-400 text-sm">...</span>
<button <button v-else @click="changePage(p)" class="w-8 h-8 rounded-lg text-xs font-bold transition-all"
v-else :class="p === page ? 'bg-slate-900 text-white shadow-md' : 'text-slate-600 hover:bg-slate-100'">
@click="changePage(p)"
class="w-8 h-8 rounded-lg text-xs font-bold transition-all"
:class="p === page ? 'bg-slate-900 text-white shadow-md' : 'text-slate-600 hover:bg-slate-100'"
>
{{ p }} {{ p }}
</button> </button>
</template> </template>
<button <button @click="changePage(page + 1)" :disabled="page >= totalPages"
@click="changePage(page + 1)" class="p-2 text-slate-400 hover:bg-slate-100 rounded-lg transition-colors disabled:opacity-50">
:disabled="page >= totalPages"
class="p-2 text-slate-400 hover:bg-slate-100 rounded-lg transition-colors disabled:opacity-50"
>
<span class="material-icons-round text-lg">chevron_right</span> <span class="material-icons-round text-lg">chevron_right</span>
</button> </button>
</div> </div>
@@ -275,7 +250,9 @@
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="10">
<div><label class="text-sm text-slate-500 mb-1 block">{{ $t('hostList.ascending') }}/{{ $t('hostList.descending') }}</label></div> <div><label class="text-sm text-slate-500 mb-1 block">{{ $t('hostList.ascending') }}/{{
$t('hostList.descending')
}}</label></div>
<el-select v-model="sortData.sortForm" filterable :placeholder="$t('hostList.selectPlaceholder')" <el-select v-model="sortData.sortForm" filterable :placeholder="$t('hostList.selectPlaceholder')"
class="w-full"> class="w-full">
<el-option <el-option
@@ -298,7 +275,8 @@
</el-dialog> </el-dialog>
<!-- Dialogs --> <!-- Dialogs -->
<LiveRecordDialog v-model:modelValue="liveDetailDialogVisible" :rows="liveDetailRecords" @select="handleLiveSelect" /> <LiveRecordDialog v-model:modelValue="liveDetailDialogVisible" :rows="liveDetailRecords"
@select="handleLiveSelect" />
<el-dialog v-model="revenueDialogVisible" :title="$t('hostList.liveRevenue')" width="80vw" top="6vh" <el-dialog v-model="revenueDialogVisible" :title="$t('hostList.liveRevenue')" width="80vw" top="6vh"
:close-on-click-modal="false" destroy-on-close> :close-on-click-modal="false" destroy-on-close>
@@ -448,7 +426,7 @@ const paginationPages = computed(() => {
const current = page.value const current = page.value
const totalP = totalPages.value const totalP = totalPages.value
const pages = [] const pages = []
if (totalP <= 7) { if (totalP <= 7) {
// 总页数小于等于7显示全部 // 总页数小于等于7显示全部
for (let i = 1; i <= totalP; i++) { for (let i = 1; i <= totalP; i++) {
@@ -457,15 +435,15 @@ const paginationPages = computed(() => {
} else { } else {
// 总是显示第一页 // 总是显示第一页
pages.push(1) pages.push(1)
if (current > 4) { if (current > 4) {
pages.push('...') pages.push('...')
} }
// 计算中间页码范围 // 计算中间页码范围
let start = Math.max(2, current - 2) let start = Math.max(2, current - 2)
let end = Math.min(totalP - 1, current + 2) let end = Math.min(totalP - 1, current + 2)
// 调整范围确保显示足够的页码 // 调整范围确保显示足够的页码
if (current <= 4) { if (current <= 4) {
end = Math.min(5, totalP - 1) end = Math.min(5, totalP - 1)
@@ -473,21 +451,21 @@ const paginationPages = computed(() => {
if (current >= totalP - 3) { if (current >= totalP - 3) {
start = Math.max(totalP - 4, 2) start = Math.max(totalP - 4, 2)
} }
for (let i = start; i <= end; i++) { for (let i = start; i <= end; i++) {
pages.push(i) pages.push(i)
} }
if (current < totalP - 3) { if (current < totalP - 3) {
pages.push('...') pages.push('...')
} }
// 总是显示最后一页 // 总是显示最后一页
if (totalP > 1) { if (totalP > 1) {
pages.push(totalP) pages.push(totalP)
} }
} }
return pages return pages
}) })
@@ -549,9 +527,9 @@ function handleClose(done) {
function getliveHost(hostId) { function getliveHost(hostId) {
liveHostDetail({ liveHostDetail({
"hostsId": hostId, "hostsId": hostId,
"tenantId": userInfo.value.tenantId "tenantId": userInfo.value.tenantId
}).then(res => { }).then(res => {
const detailList = Array.isArray(res) ? res : (res?.records || []) const detailList = Array.isArray(res) ? res : (res?.records || [])
liveDetailRecords.value = detailList liveDetailRecords.value = detailList
liveDetailDialogVisible.value = true liveDetailDialogVisible.value = true
@@ -588,13 +566,13 @@ function getCountry() {
} }
function formatTimeOnlyDate(val) { function formatTimeOnlyDate(val) {
if(!val) return '' if (!val) return ''
return val.split(' ')[0] || val return val.split(' ')[0] || val
} }
function formatTimeOnlyTime(val) { function formatTimeOnlyTime(val) {
if(!val) return '' if (!val) return ''
return val.split(' ')[1] || '' return val.split(' ')[1] || ''
} }
// History parsing helpers (preserved from original) // History parsing helpers (preserved from original)
@@ -606,9 +584,9 @@ function parseHistoryItems(history) {
} }
if (!Array.isArray(arr)) return [] if (!Array.isArray(arr)) return []
return arr.map((item, index) => { return arr.map((item, index) => {
if (typeof item === 'number') return { date: `Day ${index + 1}`, value: item } if (typeof item === 'number') return { date: `Day ${index + 1}`, value: item }
if (item && typeof item === 'object') return { date: item.date ? String(item.date) : '', value: Number(item.value ?? 0) } if (item && typeof item === 'object') return { date: item.date ? String(item.date) : '', value: Number(item.value ?? 0) }
return null return null
}).filter(Boolean) }).filter(Boolean)
} }
function hasHistory(history) { return parseHistoryItems(history).length > 0 } function hasHistory(history) { return parseHistoryItems(history).length > 0 }
@@ -663,6 +641,7 @@ function formatTimestamp(value) {
padding: 4px; padding: 4px;
width: 190px; width: 190px;
} }
.history-sparkline-top, .history-sparkline-top,
.history-sparkline-bottom { .history-sparkline-bottom {
display: flex; display: flex;
@@ -671,6 +650,7 @@ function formatTimestamp(value) {
color: #64748b; color: #64748b;
margin-bottom: 2px; margin-bottom: 2px;
} }
.history-sparkline { .history-sparkline {
background-color: #fff; background-color: #fff;
border-radius: 2px; border-radius: 2px;

View File

@@ -1,41 +1,64 @@
<template> <template>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4 mb-4"> <div class="grid grid-cols-1 lg:grid-cols-12 gap-4 mb-4">
<!-- Stat Cards --> <!-- Stat Cards -->
<div class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800"> <!-- 总数量 (较小) -->
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-slate-500">{{ $t('workbenches.totalnumber') }}</span>
<span class="material-icons-round text-primary/40">analytics</span>
</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white">{{ hostData.totalCount }}</div>
</div>
<div class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-slate-500">{{ $t('workbenches.createHost') }}</span>
<span class="material-icons-round text-secondary/40">person_add</span>
</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white">{{ hostData.validAnchorsCount }}</div>
</div>
<div class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-slate-500">{{ $t('workbenches.query') }} / {{ $t('workbenches.invite')
}}</span>
<span class="material-icons-round text-slate-400">compare_arrows</span>
</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white">{{ hostData.checkedDataCount }} <span
class="text-slate-300 text-lg mx-2">/</span> {{ hostData.canInvitationCount }}</div>
</div>
<div <div
class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800 flex flex-col justify-center"> class="lg:col-span-2 bg-white dark:bg-slate-900 p-4 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-slate-500">{{ $t('workbenches.totalnumber') }}</span>
<span class="material-icons-round text-primary/40 text-lg">analytics</span>
</div>
<div class="text-xl font-bold text-slate-900 dark:text-white">{{ hostData.totalCount }}</div>
</div>
<!-- 新建主播 (较小) -->
<div
class="lg:col-span-2 bg-white dark:bg-slate-900 p-4 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-slate-500">{{ $t('workbenches.createHost') }}</span>
<span class="material-icons-round text-secondary/40 text-lg">person_add</span>
</div>
<div class="text-xl font-bold text-slate-900 dark:text-white">{{ hostData.validAnchorsCount }}</div>
</div>
<!-- 查询 (较小) -->
<div
class="lg:col-span-2 bg-white dark:bg-slate-900 p-4 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-slate-500">{{ $t('workbenches.query') }}</span>
<span class="material-icons-round text-amber-400/60 text-lg">search</span>
</div>
<div class="text-xl font-bold text-slate-900 dark:text-white">{{ hostData.checkedDataCount }}</div>
</div>
<!-- 邀请 (较大突出显示) -->
<div
class="lg:col-span-3 bg-gradient-to-br from-primary to-blue-600 p-5 rounded-xl shadow-lg shadow-primary/20 text-white relative overflow-hidden">
<div class="absolute top-0 right-0 w-24 h-24 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/2"></div>
<div class="flex items-center justify-between mb-2 relative z-10">
<span class="text-sm font-medium text-white/80">{{ $t('workbenches.invite') }}</span>
<span class="material-icons-round text-white/60">mail_outline</span>
</div>
<div class="text-3xl font-bold text-white relative z-10">{{ hostData.canInvitationCount }}</div>
<div class="text-xs text-white/60 mt-1">可邀请主播</div>
</div>
<!-- 运行时间 (较大) -->
<div
class="lg:col-span-3 bg-white dark:bg-slate-900 p-5 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800 flex flex-col justify-center">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<span class="text-xs font-semibold text-slate-400 uppercase tracking-wider block mb-1">{{ <span class="text-xs font-semibold text-slate-400 uppercase tracking-wider block mb-1">{{
$t('workbenches.runTime') }}</span> $t('workbenches.runTime') }}</span>
<div class="text-2xl font-mono font-bold text-primary">{{ formattedTime }}</div> <div class="text-2xl font-mono font-bold text-primary">{{ formattedTime }}</div>
</div> </div>
<button @click="openTK" <div class="flex items-center gap-2">
class="bg-primary hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-semibold transition-all shadow-lg shadow-primary/25"> <span class="w-4 h-4 rounded-full" :class="isTkLoggedIn ? 'bg-emerald-500' : 'bg-red-500'"></span>
{{ $t('workbenches.openTK') }} <button @click="openTK"
</button> class="bg-primary hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-semibold shadow-lg shadow-primary/25">
{{ $t('workbenches.openTK') }}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -45,29 +68,25 @@
<div v-for="(item, index) in 2" :key="index" class="bg-white border border-slate-100 p-5 rounded-xl shadow-sm"> <div v-for="(item, index) in 2" :key="index" class="bg-white border border-slate-100 p-5 rounded-xl shadow-sm">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="w-2 h-2 rounded-full" :class="tkData[index].code == 1 ? 'bg-emerald-500' : 'bg-red-500'"></span> <span class="w-4 h-4 rounded-full" :class="tkData[index].code == 1 ? 'bg-emerald-500' : 'bg-red-500'"></span>
<h3 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenches.guildAccount') }} {{ index === 0 ? 'A' <h3 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenches.guildAccount') }} {{ index === 0 ? 'A'
: 'B' }}</h3> : 'B' }}</h3>
</div> </div>
<span class="text-xs text-slate-500">{{ $t('workbenches.queriedNum') }}: {{ tkData[index].num }}</span> <span class=" text-slate-500" style="font-size: 17px;">{{ $t('workbenches.queriedNum') }}: {{
tkData[index].num }}</span>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>
<label class="text-xs font-semibold text-slate-500 mb-1 block">{{ $t('workbenches.guildAccount') }}</label> <label class="text-xs font-semibold text-slate-500 mb-1 block">{{ $t('workbenches.guildAccount') }}</label>
<input <el-input v-model="tkData[index].account" :placeholder="$t('workbenches.guildAccountPlace')"
class="w-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-primary/20 outline-none transition-all disabled:opacity-50" :disabled="!(tkData[index].code == 0 && !isLogin[index])" class="el-input-custom" />
type="text" v-model="tkData[index].account" :placeholder="$t('workbenches.guildAccountPlace')"
:disabled="!(tkData[index].code == 0 && !isLogin[index])" />
</div> </div>
<div> <div>
<label class="text-xs font-semibold text-slate-500 mb-1 block">{{ $t('workbenches.guildPass') }}</label> <label class="text-xs font-semibold text-slate-500 mb-1 block">{{ $t('workbenches.guildPass') }}</label>
<div class="relative"> <el-input v-model="tkData[index].password" type="password" show-password
<input :placeholder="$t('workbenches.guildPassPlace')" :disabled="!(tkData[index].code == 0 && !isLogin[index])"
class="w-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-primary/20 outline-none transition-all disabled:opacity-50" class="el-input-custom" />
type="password" v-model="tkData[index].password" :placeholder="$t('workbenches.guildPassPlace')"
:disabled="!(tkData[index].code == 0 && !isLogin[index])" />
</div>
</div> </div>
</div> </div>
<button @click="loginTK(index)" :disabled="!(tkData[index].code == 0 && !isLogin[index])" <button @click="loginTK(index)" :disabled="!(tkData[index].code == 0 && !isLogin[index])"
@@ -219,30 +238,24 @@
class="flex flex-col lg:flex-row items-center justify-between gap-6 pt-4 border-t border-slate-100 dark:border-slate-800"> class="flex flex-col lg:flex-row items-center justify-between gap-6 pt-4 border-t border-slate-100 dark:border-slate-800">
<div class="flex items-center gap-6"> <div class="flex items-center gap-6">
<div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('filterGame')"> <div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('filterGame')">
<span <span class="w-4 h-4 rounded border-2 flex items-center justify-center transition-all"
class="w-4 h-4 rounded border-2 flex items-center justify-center transition-all" :class="pyData.filterGame ? 'bg-primary border-primary' : 'bg-white border-slate-300'">
:class="pyData.filterGame ? 'bg-primary border-primary' : 'bg-white border-slate-300'"
>
<span v-if="pyData.filterGame" class="material-icons-round text-white text-xs">check</span> <span v-if="pyData.filterGame" class="material-icons-round text-white text-xs">check</span>
</span> </span>
<span <span
class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤游戏主播</span> class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤游戏主播</span>
</div> </div>
<div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('filterSelling')"> <div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('filterSelling')">
<span <span class="w-4 h-4 rounded border-2 flex items-center justify-center transition-all"
class="w-4 h-4 rounded border-2 flex items-center justify-center transition-all" :class="pyData.filterSelling ? 'bg-primary border-primary' : 'bg-white border-slate-300'">
:class="pyData.filterSelling ? 'bg-primary border-primary' : 'bg-white border-slate-300'"
>
<span v-if="pyData.filterSelling" class="material-icons-round text-white text-xs">check</span> <span v-if="pyData.filterSelling" class="material-icons-round text-white text-xs">check</span>
</span> </span>
<span <span
class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤带货主播</span> class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤带货主播</span>
</div> </div>
<div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('rankingList')"> <div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('rankingList')">
<span <span class="w-4 h-4 rounded border-2 flex items-center justify-center transition-all"
class="w-4 h-4 rounded border-2 flex items-center justify-center transition-all" :class="pyData.rankingList ? 'bg-primary border-primary' : 'bg-white border-slate-300'">
:class="pyData.rankingList ? 'bg-primary border-primary' : 'bg-white border-slate-300'"
>
<span v-if="pyData.rankingList" class="material-icons-round text-white text-xs">check</span> <span v-if="pyData.rankingList" class="material-icons-round text-white text-xs">check</span>
</span> </span>
<span <span
@@ -280,7 +293,7 @@ import { tkaccountuseinfo, getExpiredTime } from '@/api/account'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { locale } = useI18n() const { locale } = useI18n()
//导入python交互方法 //导入python交互方法
const { fetchDataConfig, fetchDataCount, loginBackStage, loginTikTok, backStageloginStatus, backStageloginStatusCopy } = usePythonBridge(); const { fetchDataConfig, fetchDataCount, loginBackStage, loginTikTok, backStageloginStatus, backStageloginStatusCopy, getTkLoginStatus } = usePythonBridge();
//ip国家 //ip国家
@@ -305,6 +318,10 @@ let hostData = ref({
//账号是否登陆中 //账号是否登陆中
let isLogin = ref([false, false]); let isLogin = ref([false, false]);
//TK登录状态
let isTkLoggedIn = ref(false);
//TK状态轮询定时器
let tkStatusTimer = ref(null);
//设置状态轮询定时器 //设置状态轮询定时器
let statusTimer = ref(null); let statusTimer = ref(null);
let statusTimerCopy = ref(null); let statusTimerCopy = ref(null);
@@ -610,6 +627,22 @@ const openTK = () => {
// console.log(isTk.value) // console.log(isTk.value)
loginTikTok(); loginTikTok();
// 开始轮询TK登录状态
if (tkStatusTimer.value) {
clearInterval(tkStatusTimer.value);
}
tkStatusTimer.value = setInterval(() => {
checkTkLoginStatus();
}, 3000);
}
// 检查TK登录状态
const checkTkLoginStatus = () => {
getTkLoginStatus().then((res) => {
isTkLoggedIn.value = res === true || res === 'true';
}).catch(() => {
isTkLoggedIn.value = false;
});
} }
function getloginStatus() { function getloginStatus() {
@@ -656,7 +689,7 @@ function tkaccountuse(id, index) {
}).catch((err) => { }).catch((err) => {
console.log('账号使用次数', err) console.log('账号使用次数', err)
}) })
} }
const isRunning = ref(false); const isRunning = ref(false);
@@ -804,4 +837,41 @@ const checkVPN = async () => {
Most styles are replaced by Tailwind utility classes. Most styles are replaced by Tailwind utility classes.
We can keep specific overrides or custom animations here if needed. We can keep specific overrides or custom animations here if needed.
*/ */
/* Element Plus 输入框统一样式 */
.el-input-custom :deep(.el-input__wrapper) {
background-color: white;
border: 1px solid rgb(226, 232, 240);
border-radius: 0.5rem;
padding: 0.5rem 1rem;
box-shadow: none;
transition: all 0.15s ease;
}
.el-input-custom :deep(.el-input__wrapper:hover) {
border-color: rgb(203, 213, 225);
}
.el-input-custom :deep(.el-input__wrapper.is-focus) {
border-color: var(--el-color-primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
.el-input-custom :deep(.el-input__inner) {
font-size: 0.875rem;
}
.el-input-custom :deep(.el-input__wrapper.is-disabled) {
opacity: 0.5;
}
/* 暗色模式支持 */
.dark .el-input-custom :deep(.el-input__wrapper) {
background-color: rgb(30, 41, 59);
border-color: rgb(51, 65, 85);
}
.dark .el-input-custom :deep(.el-input__wrapper:hover) {
border-color: rgb(71, 85, 105);
}
</style> </style>