四九年入国军(此版本不可用)

 升级至 Vue 3.0,迁移 bug 待检查
This commit is contained in:
stapxs 2022-12-06 16:53:29 +08:00
parent 173d1146dc
commit a65bac67c7
67 changed files with 7494 additions and 31310 deletions

View File

@ -1,12 +0,0 @@
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"]
}

4
.browserslistrc Normal file
View File

@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

View File

@ -1,9 +1,5 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1,4 +0,0 @@
/build/
/config/
/dist/
/*.js

View File

@ -1,29 +1,17 @@
// https://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint'
},
env: {
browser: true,
},
extends: [
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
'plugin:vue/essential',
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'standard'
],
// required to lint *.vue files
plugins: [
'vue'
],
// add your custom rules here
rules: {
// allow async-await
'generator-star-spacing': 'off',
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/vue3-essential',
'@vue/standard'
],
parserOptions: {
parser: '@babel/eslint-parser'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

40
.gitignore vendored
View File

@ -1,17 +1,23 @@
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
# Other files
cache_update.txt
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1,10 +0,0 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
"postcss-import": {},
"postcss-url": {},
// to edit target browsers: use "browserslist" field in package.json
"autoprefixer": {}
}
}

View File

@ -91,14 +91,16 @@ Stapxs QQ Lite 2.0 是一个基于 Vue 的单页应用,这意味着如果你
``` bash
# 安装依赖
npm install
yarn install
# 运行热重载开发模式在本机 8080 端口
npm run dev
yarn serve
# 构建应用
npm run build
yarn build
# 代码检查
yarn lint
```
\* 你可以查看这些文档了解构建流程详情 [guide](http://vuejs-templates.github.io/webpack/) 、 [vue-loader 文档](http://vuejs.github.io/vue-loader)。
\* 你可以查看文档了解构建流程详情 [vue-cli 文档](https://cli.vuejs.org/config/)。
这是构建的一个例子:

5
babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@ -1,41 +0,0 @@
'use strict'
require('./check-versions')()
process.env.NODE_ENV = 'production'
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')
const spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
chunks: false,
chunkModules: false
}) + '\n\n')
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})

View File

@ -1,54 +0,0 @@
'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
const versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,101 +0,0 @@
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const packageConfig = require('../package.json')
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
const postcssLoader = {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
const output = []
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
icon: path.join(__dirname, 'logo.png')
})
}
}

View File

@ -1,22 +0,0 @@
'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'
const sourceMapEnabled = isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap
module.exports = {
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled,
extract: isProduction
}),
cssSourceMap: sourceMapEnabled,
cacheBusting: config.dev.cacheBusting,
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}

View File

@ -1,92 +0,0 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
const createLintingRule = () => ({
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter'),
emitWarning: !config.dev.showEslintErrorsInOverlay
}
})
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
module: {
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}

View File

@ -1,95 +0,0 @@
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})

View File

@ -1,145 +0,0 @@
'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const env = require('../config/prod.env')
const webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
usePostCSS: true
})
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true,
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),
// enable scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig

View File

@ -1,7 +0,0 @@
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

View File

@ -1,76 +0,0 @@
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
// Use Eslint Loader?
// If true, your code will be linted during bundling and
// linting errors and warnings will be shown in the console.
useEslint: true,
// If true, eslint errors and warnings will also be shown in the error overlay
// in the browser.
showEslintErrorsInOverlay: false,
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'cheap-module-eval-source-map',
// If you have problems debugging vue-files in devtools,
// set this to false - it *may* help
// https://vue-loader.vuejs.org/en/options.html#cachebusting
cacheBusting: true,
cssSourceMap: true
},
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: 'https://stapxs.github.io/Stapxs-QQ-Lite-2.0/',
/**
* Source Maps
*/
productionSourceMap: true,
// https://webpack.js.org/configuration/devtool/#production
devtool: '#source-map',
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
}
}

View File

@ -1,4 +0,0 @@
'use strict'
module.exports = {
NODE_ENV: '"production"'
}

19
jsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

30294
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +1,42 @@
{
"name": "stapxs-qq-lite",
"version": "2.0.1",
"description": "一个兼容 oicq-http 的非官方网页版 QQ 客户端",
"author": "Stapx Steve [林槐]",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"lint": "eslint --ext .js,.vue src",
"build": "node build/build.js"
},
"dependencies": {
"date-format": "^4.0.13",
"detect-browser": "^5.3.0",
"jquery": "^3.6.0",
"js-file-downloader": "^1.1.24",
"js-md5": "^0.7.3",
"layui": "^2.8.0-beta.2",
"semver-compare": "^1.0.0",
"uuidv4": "^6.2.13",
"v-viewer": "^1.6.4",
"vue": "^2.5.2",
"vue-clipboard2": "^0.3.3",
"vue-cookies": "^1.8.1",
"vue-gtag": "^1.16.1",
"vue-i18n": "^8.27.2",
"vue-infinite-scroll": "^2.0.2",
"vue-xss": "^1.0.4"
},
"devDependencies": {
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-eslint": "^8.2.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^7.1.1",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"chalk": "^2.0.1",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"eslint": "^4.15.0",
"eslint-config-standard": "^10.2.1",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.2.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^3.0.1",
"eslint-plugin-vue": "^4.0.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",
"node-notifier": "^5.1.2",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.6.0",
"webpack-bundle-analyzer": "^2.9.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
{
"name": "stapxs-qq-lite",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.8.3",
"date-format": "^4.0.13",
"detect-browser": "^5.3.0",
"jquery": "^3.6.0",
"js-file-downloader": "^1.1.24",
"js-md5": "^0.7.3",
"layui": "^2.8.0-beta.2",
"register-service-worker": "^1.7.2",
"semver-compare": "^1.0.0",
"uuidv4": "^6.2.13",
"v-viewer": "^3.0.11",
"vue": "^3.2.13",
"vue-clipboard2": "^0.3.3",
"vue-cookies": "^1.8.1",
"vue-i18n": "9",
"vue-infinite-scroll": "^2.0.2"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-pwa": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-standard": "^6.1.0",
"eslint": "^7.32.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^8.0.3"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 215 B

26
public/index.html Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="referrer" content="no-referrer" id="referrer">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<!-- Border Card UI -->
<link rel="stylesheet" href="https://stapxs.github.io/Border-Card-UI/css/style.css">
<link rel="stylesheet" href="https://stapxs.github.io/Border-Card-UI/css/color-light.css">
<script>window.is_auto_dark = false</script>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
<script src="https://stapxs.github.io/Border-Card-UI/js/main.js"></script>
<script src="https://stapxs.github.io/Border-Card-UI/js/auto-theme.js"></script>
</html>

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

View File

@ -162,11 +162,10 @@ import Vue from 'vue'
import Util from './assets/js/util'
import Option from './assets/js/options'
import Friends from './pages/Friends.vue'
import Options from './pages/Options.vue'
import Messages from './pages/Messages.vue'
import Friends from './pages/friends.vue'
import Options from './pages/options.vue'
import Messages from './pages/messages.vue'
import { component as Viewer } from 'v-viewer'
import { bootstrap } from 'vue-gtag'
import { logger, popInfo, popList } from './assets/js/base'
import { connect as connector, login } from './assets/js/connect'
@ -191,7 +190,7 @@ export default {
inList: [],
//
imgView: {
options: {inline: false, button: false, title: false, toolbar: {prev: true, rotateLeft: true, reset: true, rotateRight: true, next: true}},
options: { inline: false, button: false, title: false, toolbar: { prev: true, rotateLeft: true, reset: true, rotateRight: true, next: true } },
srcList: []
},
//
@ -241,10 +240,10 @@ export default {
Object.assign(this.$data.imgView, this.$options.data().imgView)
if (data.type === 'group') {
//
connector.send('get_group_member_info', {group_id: data.id, user_id: this.runtimeData.loginInfo.uin}, 'getUserInfoInGroup')
connector.send('get_group_member_info', { group_id: data.id, user_id: this.runtimeData.loginInfo.uin }, 'getUserInfoInGroup')
//
// PS
connector.send('get_group_member_list', {group_id: data.id}, 'getGroupMemberList')
connector.send('get_group_member_list', { group_id: data.id }, 'getGroupMemberList')
}
},
@ -343,14 +342,14 @@ export default {
logger.debug(this.$t('log_runtime') + ': ' + process.env.NODE_ENV)
//
if (Option.get('close_ga') !== true && process.env.NODE_ENV === 'production') {
bootstrap().then(() => {
logger.debug(this.$t('log_GA_loaded'))
})
// bootstrap().then(() => {
// logger.debug(this.$t('log_GA_loaded'))
// })
} else if (process.env.NODE_ENV === 'development') {
logger.debug(this.$t('log_GA_auto_closed'))
}
// GA
this.$gtag.pageview({page_path: '/Home', page_title: '主页'})
this.$gtag.pageview({ page_path: '/Home', page_title: '主页' })
}
}
}

View File

@ -52,6 +52,7 @@ class Logger {
error (args) {
this.add(this.logMode.err, args)
}
debug (args) {
this.add(this.logMode.debug, args)
}
@ -94,4 +95,4 @@ class PopInfo {
export const logger = new Logger()
export const popInfo = new PopInfo()
export let popList = []
export const popList = []

View File

@ -54,7 +54,7 @@ export class connect {
static send (name, value, echo) {
// 构建 JSON
let obj = {}
const obj = {}
obj.action = name
obj.params = (value === null || value === undefined) ? {} : value
obj.echo = (echo === null || echo === undefined) ? name : echo

View File

@ -6,8 +6,6 @@
* @Description: 此模块抽离出了本来在 MsgBody.vue 中的一些较为通用的方法便于进行多 Bot 适配
*/
import Xss from 'xss'
import Util from './util'
import { popInfo } from './base'
@ -33,6 +31,7 @@ export class MsgBodyFuns {
case 'xml': return false
}
}
/**
* 尝试渲染 xml 消息
* @param { string } xml xml 消息内容
@ -76,7 +75,7 @@ export class MsgBodyFuns {
msgHeader = msgHeader.replace('msg', 'div')
msgHeader = msgHeader.replace('m_resid=', 'data-resid=')
msgHeader = msgHeader.replace('url=', 'data-url=')
let header = document.createElement('div')
const header = document.createElement('div')
header.innerHTML = msgHeader
// 处理特殊的出处
let sourceBody = ''
@ -106,6 +105,7 @@ export class MsgBodyFuns {
}
return div.outerHTML
}
/**
* 尝试渲染 json 消息
* @param {object } data json 消息内容
@ -114,8 +114,8 @@ export class MsgBodyFuns {
*/
static buildJSON (data, msgId) {
// 解析 JSON
let json = JSON.parse(data)
let body = json.meta[Object.keys(json.meta)[0]]
const json = JSON.parse(data)
const body = json.meta[Object.keys(json.meta)[0]]
// App 信息
let name = body.tag === undefined ? body.title : body.tag
let icon = body.icon === undefined ? body.source_icon : body.icon
@ -135,9 +135,9 @@ export class MsgBodyFuns {
name = json.desc
}
let url = body.qqdocurl === undefined ? body.jumpUrl : body.qqdocurl
const url = body.qqdocurl === undefined ? body.jumpUrl : body.qqdocurl
// 构建 HTML
let html = '<div class="msg-json" id="json-' + msgId + '" data-url="' + url + '">' +
const html = '<div class="msg-json" id="json-' + msgId + '" data-url="' + url + '">' +
'<p>' + title + '</p>' +
'<span>' + desc + '</span>' +
'<img style="' + (preview === undefined ? 'display:none' : '') + '" src="' + preview + '">' +
@ -146,6 +146,7 @@ export class MsgBodyFuns {
// 返回
return html
}
/**
* xml, json 消息的点击事件
* @param { string } bodyId 用于寻找 DOM id
@ -162,12 +163,13 @@ export class MsgBodyFuns {
if (type === 'forward') {
// 解析合并转发消息
if (sender.dataset.id !== 'undefined') {
connecter.send('get_forward_msg', { 'resid': sender.dataset.id }, 'getForwardMsg')
connecter.send('get_forward_msg', { resid: sender.dataset.id }, 'getForwardMsg')
} else {
popInfo.add(popInfo.appMsgType.err, this.$t('pop_chat_forward_toooomany'))
}
}
}
/**
* 处理纯文本消息处理换行转义字符并进行 xss 过滤便于高亮链接
* @param { string } text 文本
@ -179,7 +181,7 @@ export class MsgBodyFuns {
// 防止意外渲染转义字符串
text = text.replaceAll('&', '&amp;')
// XSS 过滤
text = Xss(text)
// text = Xss(text)
// 返回
return text
}

View File

@ -84,7 +84,7 @@ function saveUser (list) {
const back = Util.mergeList(runtimeData.userList, list)
Vue.set(runtimeData, 'userList', back)
// 刷新置顶列表
let info = Vue.$cookies.get('top')
const info = Vue.$cookies.get('top')
if (info !== null) {
Vue.set(runtimeData, 'topInfo', info)
const topList = info[runtimeData.loginInfo.uin]
@ -107,12 +107,10 @@ function saveLoginInfo (data) {
Vue.set(runtimeData, 'loginInfo', data)
Vue.set(login, 'status', true)
// 获取更详细的信息
let url = 'https://find.qq.com/proxy/domain/cgi.find.qq.com/qqfind/find_v11?backver=2'
let info = `bnum=15&pagesize=15&id=0&sid=0&page=0&pageindex=0&ext=&guagua=1&gnum=12&guaguan=2&type=2&ver=4903&longitude=116.405285&latitude=39.904989&lbs_addr_country=%E4%B8%AD%E5%9B%BD&lbs_addr_province=%E5%8C%97%E4%BA%AC&lbs_addr_city=%E5%8C%97%E4%BA%AC%E5%B8%82&keyword=${data.uin}&nf=0&of=0&ldw=${data.bkn}`
const url = 'https://find.qq.com/proxy/domain/cgi.find.qq.com/qqfind/find_v11?backver=2'
const info = `bnum=15&pagesize=15&id=0&sid=0&page=0&pageindex=0&ext=&guagua=1&gnum=12&guaguan=2&type=2&ver=4903&longitude=116.405285&latitude=39.904989&lbs_addr_country=%E4%B8%AD%E5%9B%BD&lbs_addr_province=%E5%8C%97%E4%BA%AC&lbs_addr_city=%E5%8C%97%E4%BA%AC%E5%B8%82&keyword=${data.uin}&nf=0&of=0&ldw=${data.bkn}`
connecter.send(
'http_proxy',
{ 'url': url, 'method': 'post', 'data': info },
'getMoreLoginInfo'
'http_proxy', { url: url, method: 'post', data: info }, 'getMoreLoginInfo'
)
// GA将 QQ 号 MD5 编码后用于用户识别码
if (Option.get('open_ga_user') === true) {
@ -127,7 +125,7 @@ function saveFileList (data) {
if (data.ec !== 0) {
popInfo.add(
popInfo.appMsgType.err,
Util.$t('pop_chat_chat_info_load_file_err', {code: data.ec})
Util.$t('pop_chat_chat_info_load_file_err', { code: data.ec })
)
} else {
saveInfo(runtimeData.onChat.info, 'group_files', data)
@ -161,7 +159,7 @@ function backTestInfo (data) {
}
function saveMsgFist (msg) {
if (msg.error !== undefined || msg.status === 'failed') {
popInfo.add(popInfo.appMsgType.err, Util.$t('pop_chat_load_msg_err', {code: msg.error}))
popInfo.add(popInfo.appMsgType.err, Util.$t('pop_chat_load_msg_err', { code: msg.error }))
Vue.set(runtimeData, 'messageList', [])
} else {
// TODO: 对 CQCode 消息进行转换
@ -173,7 +171,7 @@ function saveMsgFist (msg) {
}
function saveMsg (msg) {
if (msg.error !== undefined) {
popInfo.add(popInfo.appMsgType.err, this.$t('pop_chat_load_msg_err', {code: msg.error}))
popInfo.add(popInfo.appMsgType.err, this.$t('pop_chat_load_msg_err', { code: msg.error }))
} else {
const items = msg.data
items.pop() // 去除最后一条重复的消息,获取历史消息会返回当前消息 **以及** 之前的 N-1 条
@ -187,13 +185,13 @@ function saveMsg (msg) {
}
function showSendedMsg (msg) {
if (msg.error !== undefined) {
popInfo.add(popInfo.appMsgType.err, Util.$t('pop_chat_send_msg_err', {code: msg.error}))
popInfo.add(popInfo.appMsgType.err, Util.$t('pop_chat_send_msg_err', { code: msg.error }))
} else {
if (msg.message_id !== undefined && Option.get('send_reget') !== true) {
// 请求消息内容
connecter.send(
'get_msg',
{ 'message_id': msg.message_id },
{ message_id: msg.message_id },
'getSendMsg_' + msg.message_id + '_0'
)
}
@ -208,7 +206,7 @@ function saveSendedMsg (echoList, msg) {
setTimeout(() => {
connecter.send(
'get_msg',
{ 'message_id': echoList[1] },
{ message_id: echoList[1] },
'getSendMsg_' + echoList[1] + '_' + (Number(echoList[2]) + 1)
)
}, 5000)
@ -358,9 +356,9 @@ function newMsg (data) {
}
}
// 重新排序列表
let newList = []
const newList = []
let topNum = 1
runtimeData.onMsg.filter((item) => {
runtimeData.onMsg.forEach((item) => {
if (item.always_top === true) {
newList.unshift(item)
topNum++
@ -379,7 +377,7 @@ function sendNotice (msg) {
raw = raw === '' ? msg.raw_message : raw
// 构建通知
let notificationTile = ''
let notificationBody = {}
const notificationBody = {}
if (msg.message_type === 'group') {
notificationTile = msg.group_name
notificationBody.body = msg.sender.nickname + ':' + raw
@ -398,7 +396,7 @@ function sendNotice (msg) {
}
})
// 发起通知
let notification = new Notification(notificationTile, notificationBody)
const notification = new Notification(notificationTile, notificationBody)
notificationList[msg.message_id] = notification
notification.onclick = function () {
const userId = event.target.tag.split('/')[0]
@ -409,7 +407,7 @@ function sendNotice (msg) {
// 跳转到这条消息的发送者页面
window.focus()
let body = document.getElementById('user-' + userId)
const body = document.getElementById('user-' + userId)
if (body === null) {
// 从缓存列表里寻找这个 ID
for (var i = 0; i < runtimeData.userList.length; i++) {
@ -444,7 +442,7 @@ function saveBotInfo (data) {
// GA提交统计信息主要在意的是 bot 类型
if (Option.get('open_ga_bot') !== false) {
if (data.app_name !== undefined) {
Vue.$gtag.event('login', {method: data.app_name})
Vue.$gtag.event('login', { method: data.app_name })
} else {
Vue.$gtag.event('login')
}
@ -520,17 +518,17 @@ function revokeMsg (msg) {
// }
}
let notificationList = {}
const notificationList = {}
// 运行时数据,用于在全程序内共享使用
export let runtimeData = {
export const runtimeData = {
onChat: { type: '', id: '', name: '', avatar: '', info: {} },
onMsg: [],
messageList: [],
botInfo: {},
loginInfo: {},
pageView: {
chatView: () => import('../../pages/Chat.vue'),
chatView: () => import('../../pages/chat.vue'),
msgView: () => import('../../components/msg/MsgBody.vue')
},
tags: {},

View File

@ -40,7 +40,7 @@ function changeChatView (name) {
if (name !== '') {
Vue.set(runtimeData.pageView, 'chatView', () => import(`../../pages/chat-view/${name}.vue`))
} else {
Vue.set(runtimeData.pageView, 'chatView', () => import('../../pages/Chat.vue'))
Vue.set(runtimeData.pageView, 'chatView', () => import('../../pages/chat.vue'))
}
}
@ -71,7 +71,7 @@ function changeTheme (id) {
// 读取存储在 cookie 的设置项
function load () {
let options = {}
const options = {}
const str = Vue.$cookies.get('options')
if (str != null) {
const list = str.split('&')

View File

@ -39,11 +39,11 @@ export default {
// ========================================
function parseMsgToJSON (msg, cache) {
let back = []
const back = []
// 如果消息发送框功能是启用的,则先将 cache 的图片插入到返回列表的最前面
if (Vue.cacheImg != null) {
Vue.cacheImg.forEach((item) => {
back.push({type: 'image', file: 'base64://' + item.substring(item.indexOf('base64,') + 7, item.length)})
back.push({ type: 'image', file: 'base64://' + item.substring(item.indexOf('base64,') + 7, item.length) })
})
}
// 处理消息文本

View File

@ -51,14 +51,14 @@ export function isExternal (path) {
}
export function waveAnimation (wave) {
let waves = wave.children[1].children
let min = 20
let max = 195
let add = 1
let timer = setInterval(() => {
const waves = wave.children[1].children
const min = 20
const max = 195
const add = 1
const timer = setInterval(() => {
// 遍历波浪体
for (var i = 0; i < waves.length; i++) {
let now = waves[i].getAttribute('x')
const now = waves[i].getAttribute('x')
if (Number(now) + add > max) {
waves[i].setAttribute('x', min)
} else {
@ -105,7 +105,7 @@ export function loadHistoryMessage (id, type) {
// 发送请求
connector.send(
'get_chat_history',
{ 'message_id': msgid },
{ message_id: msgid },
'getChatHistoryFist'
)
return true
@ -163,9 +163,9 @@ export function htmlDecodeByRegExp (str) {
}
export function getRandom (num, maxA, minlA, fqy) {
let arr = []
let arr1 = []
let arr2 = []
const arr = []
const arr1 = []
const arr2 = []
if (num) {
for (let m = 0; m <= 9; m++) {
arr.push(m)
@ -184,9 +184,9 @@ export function getRandom (num, maxA, minlA, fqy) {
if (!fqy) {
console.log('生成位数必传')
}
let mergeArr = arr.concat(arr1)
let mergeArr1 = mergeArr.concat(arr2)
let _length = mergeArr1.length
const mergeArr = arr.concat(arr1)
const mergeArr1 = mergeArr.concat(arr2)
const _length = mergeArr1.length
let text = ''
for (let m = 0; m < fqy; m++) {
let text1 = ''
@ -196,7 +196,7 @@ export function getRandom (num, maxA, minlA, fqy) {
max = _length
min = 0
}
let random = parseInt(Math.random() * (max - min)) + min
const random = parseInt(Math.random() * (max - min)) + min
if ((mergeArr1[random]) <= 9) {
text1 = mergeArr1[random]
} else if ((mergeArr1[random]) > 9) {

View File

@ -5,7 +5,8 @@
<span>{{ data.msg.title }}</span>
</header>
<div :class="'body' + (!showAll ? '' : ' all')">
<span v-html="Xss(data.msg.text_face).replaceAll('\r', '\n').replaceAll('\n\n', '\n')"></span>
<!-- need Xss -->
<span v-html="data.msg.text_face.replaceAll('\r', '\n').replaceAll('\n\n', '\n')"></span>
</div>
<div class="info">
<img :src="'https://q1.qlogo.cn/g?b=qq&s=0&nk=' + data.u">
@ -21,7 +22,6 @@
</template>
<script>
import Xss from 'xss'
import { runtimeData } from '../../assets/js/msg'
export default {
@ -29,7 +29,6 @@ export default {
props: ['data'],
data () {
return {
Xss: Xss,
runtimeData: runtimeData,
showAll: false
}

View File

@ -16,7 +16,7 @@
<div
v-for="index in baseFaceMax"
:key="'base-face-' + index"
v-if="baseFacePass.indexOf(index) == -1"
v-show="baseFacePass.indexOf(index) == -1"
@click="addBaseFace(index)">
<img loading="lazy"
:src="require('./../../assets/src/qq-face/' + index + '.gif')">
@ -71,10 +71,10 @@ export default {
)
},
addBaseFace: function (id) {
this.addSpecialMsg({type: 'face', id: id}, true)
this.addSpecialMsg({ type: 'face', id: id }, true)
},
addImgFace: function (url) {
this.addSpecialMsg({type: 'image', file: url, cache: true, asface: true}, true)
this.addSpecialMsg({ type: 'image', file: url, cache: true, asface: true }, true)
}
},
mounted () {

View File

@ -65,10 +65,10 @@ export default {
getFile: function (item) {
const url = `https://pan.qun.qq.com/cgi-bin/group_share_get_downurl?uin=${Vue.loginInfo.uin}&groupid=${this.chat.id}&pa=/${item.bus_id}${item.id}&r=${getRandom(true, false, false, 16)}&charset=utf-8&g_tk=${runtimeData.loginInfo.bkn}`
if (this.parent === undefined) {
connecter.send('http_proxy', { 'url': url }, 'downloadGroupFile_' + item.id)
connecter.send('http_proxy', { url: url }, 'downloadGroupFile_' + item.id)
} else {
//
connecter.send('http_proxy', { 'url': url }, 'downloadGroupFile_' + this.parent + '_' + item.id)
connecter.send('http_proxy', { url: url }, 'downloadGroupFile_' + this.parent + '_' + item.id)
}
},
/**
@ -78,7 +78,7 @@ export default {
if (type === 2 && this.item.sub_list === undefined) {
//
const url = `https://pan.qun.qq.com/cgi-bin/group_file/get_file_list?gc=${this.chat.id}&bkn=${runtimeData.loginInfo.bkn}&start_index=0&cnt=30&filter_code=0&folder_id=${id}&show_onlinedoc_folder=0`
connecter.send('http_proxy', { 'url': url }, 'getGroupDirFiles_' + id)
connecter.send('http_proxy', { url: url }, 'getGroupDirFiles_' + id)
}
}
}

View File

@ -154,9 +154,9 @@ export default {
const reg1 = /\/\/(.*?)\//g
const getDom = fistLink.match(reg1)
if (getDom !== null) {
Vue.$gtag.event('link_view', {domain: RegExp.$1})
// Vue.$gtag.event('link_view', {domain: RegExp.$1})
} else {
Vue.$gtag.event('link_view')
// Vue.$gtag.event('link_view')
}
//
fetch('https://api.stapxs.cn/Page-Info?address=' + fistLink)
@ -210,7 +210,7 @@ export default {
const pointY = pointEvent.clientY
// TODO:
//
connecter.send('getGroupMemberInfo', {group_id: group, user_id: id},
connecter.send('getGroupMemberInfo', { group_id: group, user_id: id },
'getGroupMemberInfo_' + pointX + '_' + pointY)
},
hiddenUserInfo: function () {

View File

@ -1,107 +1,39 @@
import Vue from 'vue'
import App from './App'
import 'layui/dist/css/layui.css'
import 'layui/dist/layui.js'
import 'viewerjs/dist/viewer.css'
import InfScroll from 'vue-infinite-scroll'
import VueCookies from 'vue-cookies'
import VueViewer from 'v-viewer'
import VueClipboard from 'vue-clipboard2'
import zh from '../src/assets/src/l10n/zh-CN.json'
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
import 'layui/dist/css/layui.css'
import 'viewerjs/dist/viewer.css'
import '../src/assets/css/view.css'
import '../src/assets/css/chat.css'
import '../src/assets/css/msg.css'
import '../src/assets/css/options.css'
import infScroll from 'vue-infinite-scroll'
import VueCookies from 'vue-cookies'
import VueViewer from 'v-viewer'
import VueXss from 'vue-xss'
import VueI18n from 'vue-i18n'
import VueClipboard from 'vue-clipboard2'
import VueGtag from 'vue-gtag'
Vue.use(infScroll)
Vue.use(VueCookies)
Vue.use(VueViewer)
Vue.use(VueXss, {whiteList: {}, stripIgnoreTag: true})
Vue.use(VueI18n)
Vue.use(VueClipboard)
Vue.use(VueGtag, {config: { id: 'G-35G5JQ4T9Q' }, bootstrap: false})
/* eslint-disable */
Vue.config.productionTip = false
// 日志组件
Vue.logMode = {
ws: ["7abb7e", "fff", "WS"],
ui: ["b573f7", "fff", "UI"],
err: ["ff5370", "fff", "ERR"],
ss: ["99b3db", "fff", "SS"],
debug: ["677480", "fff", "DEBUG"],
}
Vue.log = function(mode, args) {
console.log("%c" + mode[2] + "%c " + args,
"background:#" + mode[0] + ";color:#" + mode[1] +
";border-radius:7px 0 0 7px;display:inline-block;padding:2px 4px 2px 7px;", "")
}
// 消息组件
Vue.appMsgType = {
info: ['<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM232 152C232 138.8 242.8 128 256 128s24 10.75 24 24v128c0 13.25-10.75 24-24 24S232 293.3 232 280V152zM256 400c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 385.9 273.4 400 256 400z"/></svg>'],
err: ['<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/></svg>']
}
import 'layui/dist/layui.js'
// 载入 l10n
import zh from '../src/assets/src/l10n/zh-CN.json'
const messages = {
'zh-CN': zh
}
// 载入时间格式化设置
// date: {year:'numeric',month:"short",day:"numeric"},
// time: {hour:"numeric",minute:"numeric",second:"numeric"}
// 初始化
const i18n = new VueI18n({
const i18n = createI18n({
locale: 'zh-CN',
fallbackLocale: 'zh-CN',
silentFallbackWarn: true,
messages
})
Vue.i18n = i18n
const app = new Vue({
i18n,
el: '#app',
components: { App },
template: '<App/>'
})
// 构建请求结构
Vue.createAPI = function(action, params, echo) {
let apiObj = {}
apiObj.action = action
if(params == null) {
apiObj.params = {}
} else {
apiObj.params = params
}
if(echo == null) {
apiObj.echo = action
} else {
apiObj.echo = echo
}
return JSON.stringify(apiObj)
}
// 发送 WS 请求
Vue.sendWs = function(str) {
Vue.ws.send(str)
logger.add(logger.logMode.ws, 'PUT' + str)
}
// 构建消息 ID
Vue.buildMsgIdInfo = function(buffer) {
var binary = ''
var bytes = new Uint8Array(buffer)
var len = bytes.byteLength
for (var i=0; i<len; i++) {
binary += String.fromCharCode(bytes[i])
}
return window.btoa(binary)
}
const app = createApp(App)
app.use(InfScroll)
app.use(VueCookies)
app.use(VueViewer)
app.use(VueClipboard)
app.use(i18n)
app.mount('#app')

View File

@ -32,7 +32,7 @@
:data="msg"
@scrollToMsg="scrollToMsg"
@viewImg="viewImg"
@contextmenu.native.prevent="showMsgMeun($event, msg)"></component>
@contextmenu.prevent="showMsgMeun($event, msg)"></component>
<NoticeBody
v-if="msg.post_type === 'notice'"
:key="'notice-' + index"
@ -197,7 +197,7 @@ import { connect as connecter } from '../assets/js/connect'
import { runtimeData } from '../assets/js/msg'
export default {
name: 'Chat',
name: 'chat_view',
props: ['chat', 'list', 'mergeList', 'mumberInfo', 'imgView'],
components: { MsgBody, InfoBody, FacePan, NoticeBody },
data () {
@ -260,7 +260,7 @@ export default {
//
connecter.send(
'get_chat_history',
{ 'message_id': firstMsgId },
{ message_id: firstMsgId },
'getChatHistory'
)
}
@ -444,7 +444,7 @@ export default {
const msgId = msg.message_id
//
// PS
this.addSpecialMsg({msgObj: {type: 'reply', id: msgId}, addText: false, addTop: true})
this.addSpecialMsg({ msgObj: { type: 'reply', id: msgId }, addText: false, addTop: true })
//
this.tags.isReply = true
//
@ -477,7 +477,7 @@ export default {
const msg = this.selectedMsg
if (this.selectedMsg !== null) {
const msgId = msg.message_id
connecter.send('delete_msg', {'message_id': msgId})
connecter.send('delete_msg', { message_id: msgId })
//
this.closeMsgMenu()
}
@ -513,7 +513,7 @@ export default {
const url = `https://qinfo.clt.qq.com/cgi-bin/qun_info/get_group_info_all?gc=${this.chat.id}&bkn=${runtimeData.loginInfo.bkn}`
connecter.send(
'http_proxy',
{'url': url},
{ url: url },
'getMoreGroupInfo'
)
} else if (this.chat.type === 'user' && this.chat.info.user.uin !== this.chat.id) {
@ -521,7 +521,7 @@ export default {
const info = `bnum=15&pagesize=15&id=0&sid=0&page=0&pageindex=0&ext=&guagua=1&gnum=12&guaguan=2&type=2&ver=4903&longitude=116.405285&latitude=39.904989&lbs_addr_country=%E4%B8%AD%E5%9B%BD&lbs_addr_province=%E5%8C%97%E4%BA%AC&lbs_addr_city=%E5%8C%97%E4%BA%AC%E5%B8%82&keyword=${this.chat.id}&nf=0&of=0&ldw=${runtimeData.loginInfo.bkn}`
connecter.send(
'http_proxy',
{ 'url': url, 'method': 'post', 'data': info },
{ url: url, method: 'post', data: info },
'getMoreUserInfo'
)
}
@ -530,7 +530,7 @@ export default {
(Object.keys(this.chat.info.group_members).length === 0 || this.chat.info.group_members.length <= 0 || this.chat.info.group_members[0].group_id !== this.chat.id)) {
connecter.send(
'get_group_member_list',
{'group_id': this.chat.id},
{ group_id: this.chat.id },
'getGroupMemberList'
)
}
@ -539,7 +539,7 @@ export default {
const url = `https://web.qun.qq.com/cgi-bin/announce/get_t_list?bkn=${runtimeData.loginInfo.bkn}&qid=${this.chat.id}&ft=23&s=-1&n=20`
connecter.send(
'http_proxy',
{ 'url': url },
{ url: url },
'getGroupNotices'
)
}
@ -548,7 +548,7 @@ export default {
const url = `https://pan.qun.qq.com/cgi-bin/group_file/get_file_list?gc=${this.chat.id}&bkn=${runtimeData.loginInfo.bkn}&start_index=0&cnt=30&filter_code=0&folder_id=%2F&show_onlinedoc_folder=0`
connecter.send(
'http_proxy',
{ 'url': url },
{ url: url },
'getGroupFiles'
)
}
@ -564,7 +564,7 @@ export default {
const url = `https://pan.qun.qq.com/cgi-bin/group_file/get_file_list?gc=${this.chat.id}&bkn=${runtimeData.loginInfo.bkn}&start_index=${this.chat.info.group_files.next_index}&cnt=30&filter_code=0&folder_id=%2F&show_onlinedoc_folder=0`
connecter.send(
'http_proxy',
{ 'url': url },
{ url: url },
'getMoreGroupFiles'
)
}
@ -616,9 +616,9 @@ export default {
return
}
for (let i = 0, len = e.clipboardData.items.length; i < len; i++) {
let item = e.clipboardData.items[i]
const item = e.clipboardData.items[i]
if (item.kind === 'file') {
let blob = item.getAsFile()
const blob = item.getAsFile()
if (blob.type.indexOf('image/') >= 0 && blob.size !== 0) {
popInfo.add(popInfo.appMsgType.info, this.$t('pop_chat_image_processing'))
if (blob.size < 3145728) {
@ -666,11 +666,11 @@ export default {
// const sendCache = [{type:"face",id:1},{type:"at",qq:1007028430}]
// ^^^^^^ 0 ^^^^^^ ^^^^^^^^^^ 1 ^^^^^^^^^^
//
let msg = SendUtil.parseMsg(this.msg, this.sendCache)
const msg = SendUtil.parseMsg(this.msg, this.sendCache)
if (msg !== null && msg.length > 0) {
switch (this.chat.type) {
case 'group': connecter.send('send_group_msg', {'group_id': this.chat.id, 'message': msg}, 'sendMsgBack'); break
case 'user': connecter.send('send_private_msg', {'user_id': this.chat.id, 'message': msg}, 'sendMsgBack'); break
case 'group': connecter.send('send_group_msg', { group_id: this.chat.id, message: msg }, 'sendMsgBack'); break
case 'user': connecter.send('send_private_msg', { user_id: this.chat.id, message: msg }, 'sendMsgBack'); break
}
}
//
@ -689,9 +689,10 @@ export default {
this.NewMsgNum += this.list.length - this.listSize
}
// 100 shift
if (this.list.length > 100 && !this.tags.nowGetHistroy) {
this.list.shift()
}
// TODO:
// if (this.list.length > 100 && !this.tags.nowGetHistroy) {
// this.list.shift()
// }
//
this.listSize = this.list.length
@ -719,7 +720,7 @@ export default {
this.scrollBottom(true)
}
//
let getImgList = []
const getImgList = []
this.list.forEach((item) => {
if (item.message !== undefined) {
item.message.forEach((msg) => {
@ -731,7 +732,8 @@ export default {
})
}
})
this.imgView.srcList = getImgList
// TODO:
// this.imgView.srcList = getImgList
//
// onChat jump undefined
//

View File

@ -32,7 +32,7 @@
v-for="item in runtimeData.showData"
:key="'fb-' + (item.user_id ? item.user_id : item.group_id)"
:data="item"
@click.native="userClick(item, $event)"></FriendBody>
@click="userClick(item, $event)"></FriendBody>
</div>
</div>
<div>
@ -53,7 +53,7 @@ import { connect as connecter } from '../assets/js/connect'
import { runtimeData } from '../assets/js/msg'
export default {
name: 'Friends',
name: 'friends_list',
props: ['list'],
data () {
return {

View File

@ -18,7 +18,7 @@
:key="'in' + item.user_id ? item.user_id : item.group_id"
:select="item.user_id ? item.user_id === chat.id : item.group_id === chat.id"
:data="item"
@click.native="userClick(item)"></FriendBody>
@click="userClick(item)"></FriendBody>
</div>
</div>
<div>
@ -40,7 +40,7 @@ import FriendBody from '../components/FriendBody.vue'
import { runtimeData } from '../assets/js/msg'
export default {
name: 'Messages',
name: 'messages_list',
props: ['chat'],
components: { FriendBody },
data () {

View File

@ -175,7 +175,7 @@ import OptFunction from './options/OptFunction.vue'
const { detect } = require('detect-browser')
export default {
name: 'Options',
name: 'options_view',
props: ['config', 'login', 'uinfo'],
components: { OptView, OptDev, OptAccount, OptFunction },
data () {
@ -194,10 +194,10 @@ export default {
.then(data => {
for (let i = 0; i < data.length; i++) {
this.contributors.push({
url: data[i]['avatar_url'],
link: data[i]['html_url'],
title: data[i]['login'],
isMe: data[i]['login'] === 'Stapxs'
url: data[i].avatar_url,
link: data[i].html_url,
title: data[i].login,
isMe: data[i].login === 'Stapxs'
})
}
})

View File

@ -78,7 +78,7 @@ export default {
}
return Intl.DateTimeFormat(
Util.getTrueLang(),
{year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric'}).format(new Date(value))
{ year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' }).format(new Date(value))
}
} catch (ex) {
return value

View File

@ -16,7 +16,7 @@
<span>{{ $t('option_dev_log_level_tip') }}</span>
</div>
<label>
<select @change="save" name="log_level" v-model="config.log_level">
<select @change="save" name="log_level" v-model="configIn.log_level">
<option value="err">{{ $t('option_dev_log_level_err') }}</option>
<option value="debug">{{ $t('option_dev_log_level_debug') }}</option>
<option value="info">{{ $t('option_dev_log_level_info') }}</option>
@ -31,7 +31,7 @@
<span>{{ $t('option_dev_msg_menu_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="msg_menu" v-model="config.msg_menu">
<input type="checkbox" @change="save" name="msg_menu" v-model="configIn.msg_menu">
<div>
<div></div>
</div>
@ -44,7 +44,7 @@
<span><a style="cursor:pointer;" @click="sendAbab">{{ $t('option_dev_debug_msg_tip') }}</a></span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="debug_msg" v-model="config.debug_msg">
<input type="checkbox" @change="save" name="debug_msg" v-model="configIn.debug_msg">
<div>
<div></div>
</div>
@ -84,7 +84,7 @@
<span>{{ $t('option_dev_ui_test_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="ui_test" v-model="config.ui_test">
<input type="checkbox" @change="save" name="ui_test" v-model="configIn.ui_test">
<div>
<div></div>
</div>
@ -103,7 +103,7 @@
<span>{{ $t('option_dev_connect_beat_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="connect_beat" v-model="config.connect_beat">
<input type="checkbox" @change="save" name="connect_beat" v-model="configIn.connect_beat">
<div>
<div></div>
</div>
@ -129,7 +129,7 @@
<span>{{ $t('option_dev_chatview_name') }}</span>
<span>{{ $t('option_dev_chatview_name_tip') }}</span>
</div>
<input class="ss-input" style="width:150px" type="text" v-model="config.chatview_name" @keyup="saveWName($event, 'chatview_name')">
<input class="ss-input" style="width:150px" type="text" v-model="configIn.chatview_name" @keyup="saveWName($event, 'chatview_name')">
</div>
</div>
</div>
@ -146,6 +146,7 @@ export default {
props: ['config'],
data () {
return {
configIn: this.config,
save: save,
ws_text: '',
appmsg_text: ''

View File

@ -16,7 +16,7 @@
<span>{{ $t('option_dev_notice_close_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="close_notice" v-model="config.close_notice">
<input type="checkbox" @change="save" name="close_notice" v-model="configIn.close_notice">
<div>
<div></div>
</div>
@ -29,7 +29,7 @@
<span>{{ $t('option_dev_notice_all_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="notice_all" v-model="config.notice_all">
<input type="checkbox" @change="save" name="notice_all" v-model="configIn.notice_all">
<div>
<div></div>
</div>
@ -45,7 +45,7 @@
<span>{{ $t('option_dev_send_reget_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="send_reget" v-model="config.send_reget">
<input type="checkbox" @change="save" name="send_reget" v-model="configIn.send_reget">
<div>
<div></div>
</div>
@ -71,7 +71,7 @@
<span>{{ $t('option_dev_chat_pic_pan_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="close_chat_pic_pan" v-model="config.close_chat_pic_pan">
<input type="checkbox" @change="save" name="close_chat_pic_pan" v-model="configIn.close_chat_pic_pan">
<div>
<div></div>
</div>
@ -87,7 +87,7 @@
<span>{{ $t('option_fun_ga_turn_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="close_ga" v-model="config.close_ga">
<input type="checkbox" @change="save" name="close_ga" v-model="configIn.close_ga">
<div>
<div></div>
</div>
@ -103,7 +103,7 @@
<span>{{ $t('option_fun_ga_user_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="open_ga_user" v-model="config.open_ga_user">
<input type="checkbox" @change="save" name="open_ga_user" v-model="configIn.open_ga_user">
<div>
<div></div>
</div>
@ -116,7 +116,7 @@
<span>{{ $t('option_fun_ga_bot_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="open_ga_bot" v-model="config.open_ga_bot">
<input type="checkbox" @change="save" name="open_ga_bot" v-model="configIn.open_ga_bot">
<div>
<div></div>
</div>
@ -134,6 +134,7 @@ export default {
props: ['config'],
data () {
return {
configIn: this.config,
save: save,
ndt: 0,
ndv: false

View File

@ -24,7 +24,7 @@
<span>{{ $t('option_view_language_tip') }}</span>
</div>
<label>
<select @change="save" name="language" v-model="config.language">
<select @change="save" name="language" v-model="configIn.language">
<option v-for="item in languages" :value="item.value" :key="item.value">{{ item.name }}</option>
</select>
</label>
@ -39,7 +39,7 @@
<span>{{ $t('option_view_dark_mode_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="opt_dark" v-model="config.opt_dark">
<input type="checkbox" @change="save" name="opt_dark" v-model="configIn.opt_dark">
<div><div></div></div>
</label>
</div>
@ -50,7 +50,7 @@
<span>{{ $t('option_view_auto_dark_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="opt_auto_dark" v-model="config.opt_auto_dark">
<input type="checkbox" @change="save" name="opt_auto_dark" v-model="configIn.opt_auto_dark">
<div><div></div></div>
</label>
</div>
@ -84,6 +84,7 @@ export default {
props: ['config'],
data () {
return {
configIn: this.config,
save: save,
languages: languages,
//

View File

@ -0,0 +1,32 @@
/* eslint-disable no-console */
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
console.log(
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
)
},
registered () {
console.log('Service worker has been registered.')
},
cached () {
console.log('Content has been cached for offline use.')
},
updatefound () {
console.log('New content is downloading.')
},
updated () {
console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (error) {
console.error('Error during service worker registration:', error)
}
})
}

View File

4
vue.config.js Normal file
View File

@ -0,0 +1,4 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})

7163
yarn.lock Normal file

File diff suppressed because it is too large Load Diff