12
.babelrc
|
@ -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"]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
|
@ -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
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
/build/
|
||||
/config/
|
||||
/dist/
|
||||
/*.js
|
46
.eslintrc.js
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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": {}
|
||||
}
|
||||
}
|
10
README.md
|
@ -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/)。
|
||||
|
||||
这是构建的一个例子:
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
|
@ -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'
|
||||
))
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
}
|
BIN
build/logo.png
Before Width: | Height: | Size: 6.7 KiB |
101
build/utils.js
|
@ -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')
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
})
|
|
@ -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
|
|
@ -1,7 +0,0 @@
|
|||
'use strict'
|
||||
const merge = require('webpack-merge')
|
||||
const prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"'
|
||||
})
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
'use strict'
|
||||
module.exports = {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
129
package.json
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 799 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 4.2 KiB |
|
@ -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 |
|
@ -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>
|
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow:
|
21
src/App.vue
|
@ -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: '主页' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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('&', '&')
|
||||
// XSS 过滤
|
||||
text = Xss(text)
|
||||
// text = Xss(text)
|
||||
// 返回
|
||||
return text
|
||||
}
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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('&')
|
||||
|
|
|
@ -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) })
|
||||
})
|
||||
}
|
||||
// 处理消息文本
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
106
src/main.js
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
// 则意味着这次加载历史记录的同时需要跳转到指定的消息
|
|
@ -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 {
|
|
@ -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 () {
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
})
|
|
@ -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
|
||||
|
|
|
@ -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: ''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
// 别问我为什么微软是紫色的
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true
|
||||
})
|