逃不掉的自绘标题栏

 electron:无边框模式!更漂亮的自绘窗口现在可以使用了!
 现在设置项存储在 LocalStronge 里了
 移出群聊加了个二次确认
 优化了复制选中文本的操作体验
🐛 chrome 下滑条无法交互
 新增依赖 electron-store
 移除依赖 vue3-cookies
🚀 补了一堆构建信息
This commit is contained in:
stapxs 2023-03-02 15:09:39 +08:00
parent 466e5b0927
commit cd77da5480
19 changed files with 404 additions and 86 deletions

View File

@ -1,7 +1,7 @@
{
"name": "stapxs-qq-lite",
"version": "2.3.0",
"private": true,
"private": false,
"author": "Stapx Steve [林槐]",
"description": "一个兼容 oicq-http 的非官方网页版 QQ 客户端,使用 Vue 重制的全新版本。",
"scripts": {
@ -22,6 +22,7 @@
"core-js": "^3.8.3",
"date-format": "^4.0.13",
"detect-browser": "^5.3.0",
"electron-store": "^8.1.0",
"electron-window-state": "^5.0.3",
"jquery": "^3.6.0",
"js-file-downloader": "^1.1.24",
@ -40,7 +41,6 @@
"vue-gtag-next": "^1.14.0",
"vue-i18n": "^9.2.2",
"vue-infinite-scroll": "^2.0.2",
"vue3-cookies": "^1.0.6",
"vue3-infinite-scroll-better": "^2.2.0",
"xss": "^1.0.14"
},

View File

@ -1,5 +1,14 @@
<template>
<div id="app">
<div class="top-bar" v-if="runtimeData.sysConfig.opt_no_window">
<img src="img/icons/icon.svg">
<span>Stapxs QQ Lite</span>
<div class="space"></div>
<div class="controller">
<div @click="controllWin('minimize')" class="min"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M416 256c0 17.7-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l352 0c17.7 0 32 14.3 32 32z"/></svg></div>
<div @click="controllWin('close')" class="close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"/></svg></div>
</div>
</div>
<div id="base-app">
<div class="layui-tab layui-tab-brief main-body">
<ul class="layui-tab-title">
<li @click="changeTab('主页', 'Home', true)" :class="loginInfo.status ? 'hiden-home' : 'layui-this'">
@ -239,6 +248,17 @@ export default defineComponent({
}
},
methods: {
/**
* electron 窗口操作
*/
controllWin (name: string) {
const electron = (process.env.IS_ELECTRON as any) === true ? window.require('electron') : null
const reader = electron ? electron.ipcRenderer : null
if (reader) {
reader.send('win:' + name)
}
},
/**
* 发起连接
*/
@ -392,24 +412,21 @@ export default defineComponent({
//
window.onload = () => {
app.config.globalProperties.$viewer = this.viewerBody
const $cookies = app.config.globalProperties.$cookies
//
runtimeData.tags.loginWaveTimer = this.waveAnimation(document.getElementById('login-wave'))
// cookie
// TODO:
if ($cookies.isKey('address')) {
this.loginInfo.address = $cookies.get('address')
}
//
runtimeData.sysConfig = Option.load()
runtimeData.sysConfig.top_info = $cookies.get('top')
// PS
//
// PS
Option.runAS('opt_dark', Option.get('opt_dark'))
Option.runAS('opt_auto_dark', Option.get('opt_auto_dark'))
Option.runAS('theme_color', Option.get('theme_color'))
Option.runAS('opt_auto_win_color', Option.get('opt_auto_win_color'))
if(Option.get('opt_no_window') == true) {
const app = document.getElementById('base-app')
if(app) app.classList.add('withBar')
}
//
loginInfo.address = runtimeData.sysConfig.address
if(runtimeData.sysConfig.save_password && runtimeData.sysConfig.save_password != true) {
loginInfo.token = runtimeData.sysConfig.save_password
this.tags.savePassword = true
@ -440,10 +457,10 @@ export default defineComponent({
}
//
const appVersion = appInfo.version
const cacheVersion = app.config.globalProperties.$cookies.get('version')
if (!app.config.globalProperties.$cookies.isKey('version') || cmp(appVersion, cacheVersion) == 1) {
const cacheVersion = localStorage.getItem('version')
if (!cacheVersion || cmp(appVersion, cacheVersion) == 1) {
// cookie
app.config.globalProperties.$cookies.set('version', appVersion, '1m')
localStorage.setItem('version', appVersion)
logger.debug(this.$t('version_updated') + ': ' + cacheVersion + ' -> ' + appVersion)
// Github
const url = 'https://api.github.com/repos/stapxs/stapxs-qq-lite-2.0/commits'
@ -534,9 +551,10 @@ export default defineComponent({
})
}
//
if ($cookies.isKey('times')) {
const getTimes = Number($cookies.get('times')) + 1
$cookies.set('times', getTimes, '1m')
const times = localStorage.getItem('times')
if (times != null) {
const getTimes = Number(times) + 1
localStorage.setItem('times', getTimes.toString())
if (getTimes % 50 == 0) {
// HTML
let html = '<div style="display:flex;flex-direction:column;padding:10px 5%;align-items:center;">'
@ -562,7 +580,7 @@ export default defineComponent({
runtimeData.popBoxList.push(popInfo)
}
} else {
$cookies.set('times', 1, '1m')
localStorage.setItem('times', '1')
//
const popInfo = {
template: WelPan,
@ -586,8 +604,9 @@ export default defineComponent({
.then(data => {
// ID
let noticeShow = [] as number[]
if ($cookies.isKey('notice_show')) {
noticeShow = $cookies.get('notice_show').split(',')
const showId = localStorage.getItem('notice_show')
if (showId) {
noticeShow = showId.split(',').map((id: string) => parseInt(id))
}
//
data.forEach((notice: any) => {
@ -621,7 +640,7 @@ export default defineComponent({
if (noticeShow.indexOf(notice.id) < 0) {
noticeShow.push(notice.id)
}
$cookies.set('notice_show', noticeShow, '7d')
localStorage.setItem('notice_show', noticeShow.toString())
//
runtimeData.popBoxList.shift()
}

View File

@ -19,6 +19,10 @@ textarea {
right: 0;
top: 0;
}
.chat-pan.withBar {
height: calc(100% - 40px);
top: 40px;
}
.chat-pan.full {
height: 100%;
z-index: 20;
@ -1270,6 +1274,10 @@ textarea {
height: calc(100% - 75px);
left: 80px;
}
.chat-pan.withBar {
height: calc(100% - 115px);
top: 40px;
}
.chat-pan.open {
transform: translateX(220px);
}

View File

@ -307,7 +307,7 @@
opacity: 0;
}
.opt-item div.ss-range input::-webkit-slider-runnable-track {
display: none;
opacity: 0;
}
.l10n-info {

View File

@ -5,10 +5,69 @@ html, body {
margin: 0;
}
#app {
.top-bar {
-webkit-app-region: drag;
box-shadow: 0 0 10px 0 rgb(0 0 0 / 28%);
background: var(--color-card-2);
margin-bottom: 10px;
flex-direction: row;
position: absolute;
display: flex;
height: 40px;
width: 100%;
z-index: 99;
}
.top-bar > img {
margin: 10px 15px;
height: 20px;
}
.top-bar > span {
color: var(--color-font);
font-weight: bold;
font-size: 0.9rem;
line-height: 40px;
}
.top-bar > div.space {
flex: 1;
}
.top-bar > div.controller {
-webkit-app-region: no-drag;
display: flex;
}
.top-bar > div.controller > div {
transition: background .2s;
justify-content: center;
border-radius: 7px;
display: flex;
margin: 4px 1px;
padding: 9px;
width: 26px;
}
.top-bar > div.controller > div.min > svg {
transform: scale(0.9);
}
.top-bar > div.controller > div.close {
margin-right: 4px;
}
.top-bar > div.controller > div:hover {
background: var(--color-card-1);
}
.top-bar > div.controller > div.close:hover {
background: #f20000dd;
}
.top-bar > div.controller > div > svg {
fill: var(--color-font);
height: 16px;
}
#app, #base-app {
background-color: var(--color-bg);
height: 100%;
}
#base-app.withBar {
height: calc(100% - 40px);
padding-top: 40px;
}
.main-body {
flex-direction: row;
@ -383,6 +442,9 @@ html, body {
padding: 10px;
width: 20%;
}
.menu.topOut {
transition: transform .1s, margin-top .1s;
}
.menu.show {
transform: scaleY(1);
}
@ -425,8 +487,6 @@ html, body {
display: flex !important;
}
.ss-range input {
-webkit-appearance: none;
background-image: linear-gradient(var(--color-main), var(--color-main));
background-color: var(--color-card-1);
background-repeat: no-repeat;
@ -434,6 +494,8 @@ html, body {
background-size: 0% 100%;
border-radius: 7px;
min-width: 100%;
-webkit-appearance: none;
}
.ss-range input::-webkit-slider-thumb,
.ss-range input::-moz-range-thumb {
@ -754,6 +816,7 @@ html, body {
.hiden-home {
margin-top: calc(-100% + 5px);
transform: translateY(-100%);
}
.qq-err-card {
@ -882,6 +945,7 @@ html, body {
}
.hiden-home {
margin-top: calc(100% + 5px);
transform: translateY(0);
margin-left: -70px;
}
/* 二级侧栏 */

View File

@ -273,5 +273,10 @@
"option_view_background_blur": "背景模糊",
"option_view_background_blur_tip": "什么都看不见了(恼",
"pop_chat_image_compression_fail": "压缩图片失败",
"pop_chat_image_compression": "正在压缩图片 ……"
"pop_chat_image_compression": "正在压缩图片 ……",
"chat_msg_menu_remove_tip": "真的要将 {user} 移出群聊吗",
"option_view_no_window": "去除窗口框架",
"option_view_no_window_tip": "沉浸式没圆角体验(无端",
"option_dev_restart": "重启应用",
"option_dev_restart_tip": "99% 的特性都能通过重启解决!"
}

View File

@ -1,13 +1,15 @@
'use strict'
import Store from 'electron-store'
import windowStateKeeper from 'electron-window-state'
import regIpcListener from './function/electron/ipc'
import path from 'path'
import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer'
import { Menu } from 'electron'
import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
@ -18,6 +20,7 @@ protocol.registerSchemesAsPrivileged([
export let win = undefined as BrowserWindow | undefined
async function createWindow() {
console.log('开始创建窗口 ……')
// 窗口创建前事务
Menu.setApplicationMenu(null)
regIpcListener()
@ -26,13 +29,16 @@ async function createWindow() {
defaultWidth: 1200,
defaultHeight: 800
})
const store = new Store()
const noWindow = await store.get('opt_no_window')
console.log('窗口框架状态:' + noWindow)
win = new BrowserWindow({
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
icon: path.join(__dirname,'./public/img/icons/icon.png'),
// frame: false,
frame: noWindow === true ? false : true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
@ -40,6 +46,7 @@ async function createWindow() {
})
win.once('focus', () => {if(win)win.flashFrame(false)})
mainWindowState.manage(win) // 窗口状态管理器
console.log('窗口创建完成')
// 加载应用
if (process.env.WEBPACK_DEV_SERVER_URL) {
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string)
@ -48,6 +55,7 @@ async function createWindow() {
createProtocol('app')
win.loadURL('app://./index.html')
}
console.log('应用加载完成')
}
app.on('window-all-closed', () => {

View File

@ -196,6 +196,7 @@ export default defineComponent({
let style = 'msg-img'
//
// if(isFace) { style += ' face' }
if(isFace) { style += ' ' }
if(length === 1) { return style += ' alone' }
if(at === 0) { return style += ' top' }
if(at === length - 1) { return style += ' button' }

View File

@ -28,7 +28,6 @@ export class Connector {
*/
static create(address: string, token?: string) {
const $t = app.config.globalProperties.$t
const $cookies = app.config.globalProperties.$cookies
logger.debug($t('log_ws_log_debug'))
logger.add(LogType.WS, $t('log_we_log_all'))
@ -44,7 +43,7 @@ export class Connector {
websocket.onopen = () => {
logger.add(LogType.WS, $t('log_con_success'))
// 保存登录信息(一个月)
$cookies.set('address', address, '1m')
Option.save('address', address)
// 保存密钥
if(runtimeData.sysConfig.save_password == true) {
Option.save('save_password', token)

View File

@ -1,13 +1,33 @@
import Store from 'electron-store'
import path from 'path'
import os from 'os'
import { dialog, DownloadItem, ipcMain, shell, systemPreferences } from "electron"
import { ipcMain, shell, systemPreferences, app } from "electron"
import { GtkTheme, GtkData } from '@jakejarrett/gtk-theme'
import { Exception } from 'vue-gtag-next'
import { queryKeys } from './util'
import { win } from '@/background'
export default function regIpcListener() {
// 关闭窗口
ipcMain.on('win:close', () => {
if(win) win.close()
})
// 最小化
ipcMain.on('win:minimize', () => {
if(win) win.minimize()
})
// 重启应用
ipcMain.on('win:relaunch', () => {
app.relaunch()
app.exit()
})
// 单独用于保存窗口框架是否显示的设置
// PS因为改变窗口框架需要在窗口创建前设置所以单独保存设置便于获取
ipcMain.on('opt:saveNoWindow', (event, arg) => {
const store = new Store()
store.set('opt_no_window', Boolean(arg))
})
// 获取补充的调试信息
ipcMain.handle('opt:getSystemInfo', () => {
const systemInfo = {} as { [key: string]: any }

View File

@ -791,7 +791,8 @@ function readMemberMessage(data: any) {
* @param msg
*/
function livePackage(msg: any) {
//
// TODO: 还没写这个功能
msg
}
/**

View File

@ -44,7 +44,16 @@ const configFunction: { [key: string]: (value: any) => void } = {
initial_scale: changeInitialScale,
msg_type: setMsgType,
opt_auto_gtk: updateGTKColor,
opt_auto_win_color: updateWinColorOpt
opt_auto_win_color: updateWinColorOpt,
opt_no_window: changeNoWindow
}
function changeNoWindow(value: boolean) {
const electron = (process.env.IS_ELECTRON as any) === true ? window.require('electron') : null
const reader = electron ? electron.ipcRenderer : null
if(reader) {
reader.send('opt:saveNoWindow', value)
}
}
function updateWinColorOpt(value: boolean) {
@ -269,17 +278,24 @@ function changeChatView(name: string | undefined) {
*/
export function load(): { [key: string]: any } {
const options: { [key: string]: any } = {}
// 解析拆分 cookie 并执行各个设置项的初始化方法
const str: string = app.config.globalProperties.$cookies.get('options')
const str = localStorage.getItem('options')
if (str != null) {
const list = str.split('&')
for (let i = 0; i <= list.length; i++) {
if (list[i] !== undefined) {
const opt: string[] = list[i].split(':')
if (opt.length === 2) {
// 特殊处理被字符串化的布尔值
if (opt[1] === 'true' || opt[1] === 'false') {
// 特殊处理被字符串化的布尔值
options[opt[0]] = (opt[1] === 'true')
} else if(opt[0] == 'top_info') {
// 特殊处理 top_info
try {
options[opt[0]] = JSON.parse(decodeURIComponent(opt[1]))
} catch (e) {
// 无法解析的数据,初始化为空对象
options[opt[0]] = {}
}
} else {
options[opt[0]] = decodeURIComponent(opt[1])
}
@ -335,7 +351,7 @@ export function get(name: string): any {
*/
export function getRaw(name: string) {
// 解析拆分 cookie 并执行各个设置项的初始化方法
const str: string = app.config.globalProperties.$cookies.get('options')
const str = localStorage.getItem('options')
if (str != null) {
const list = str.split('&')
for (let i = 0; i <= list.length; i++) {
@ -366,10 +382,12 @@ export function saveAll(config = {} as {[key: string]: any}) {
}
let str = ''
Object.keys(config).forEach(key => {
str += key + ':' + encodeURIComponent(config[key]) + '&'
const isObject = typeof config[key] == 'object'
str += key + ':' +
encodeURIComponent(isObject ? JSON.stringify(config[key]) : config[key]) + '&'
})
str = str.substring(0, str.length - 1)
app.config.globalProperties.$cookies.set('options', str, '1m')
localStorage.setItem('options', str)
}
/**

View File

@ -1,14 +1,13 @@
import VueCookies from 'vue3-cookies'
import VueViewer from 'v-viewer'
import VueClipboard from 'vue-clipboard2'
import InfiniteScroll from 'vue3-infinite-scroll-better'
import VueGtag from 'vue-gtag-next'
import packageInfo from '../package.json'
import App from './App.vue'
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
import { version } from '../package.json'
import './registerServiceWorker'
@ -38,7 +37,6 @@ export const i18n = createI18n({
// 创建 App
const app = createApp(App)
app.use(i18n)
app.use(VueCookies)
app.use(VueViewer)
app.use(VueClipboard)
app.use(InfiniteScroll)
@ -51,6 +49,6 @@ const strList = ['VERSION', 'WELCOME', 'HELLO']
const colorList = ['50534f', 'f9a633', '8076a3', 'f0a1a8', '92aa8a', '606E7A', '7abb7e', 'b573f7', 'ff5370', '99b3db', '677480']
const color = colorList[Math.floor(Math.random() * colorList.length)]
const str = strList[Math.floor(Math.random() * strList.length)]
console.log(`%c${str}%c Stapxs QQ Lite - ${version} ( ${process.env.NODE_ENV} ) `, `font-weight:bold;background:#${color};color:#fff;border-radius:7px 0 0 7px;display:inline-block;padding:7px 14px;margin:7px 0 7px 7px;`, 'background:#fff;color:#000;border-radius:0 7px 7px 0;display:inline-block;padding:7px 14px;margin:7px 7px 7px 0;');
console.log(`%c${str}%c Stapxs QQ Lite - ${packageInfo.version} ( ${process.env.NODE_ENV} ) `, `font-weight:bold;background:#${color};color:#fff;border-radius:7px 0 0 7px;display:inline-block;padding:7px 14px;margin:7px 0 7px 7px;`, 'background:#fff;color:#000;border-radius:0 7px 7px 0;display:inline-block;padding:7px 14px;margin:7px 7px 7px 0;');
console.log(app)

View File

@ -12,7 +12,7 @@
<template>
<div
:style="`background-image: url(${runtimeData.sysConfig.chat_background})`"
:class="'chat-pan' + (runtimeData.tags.openSideBar ? ' open': '')"
:class="'chat-pan' + (runtimeData.tags.openSideBar ? ' open': '') + (runtimeData.sysConfig.opt_no_window ? ' withBar': '')"
id="chat-pan">
<!-- 聊天基本信息 -->
<div class="info">
@ -300,11 +300,11 @@
<div><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z"/></svg></div>
<a>{{ $t('chat_msg_menu_withdraw') }}</a>
</div>
<div @click="(selectedMsg ? addSpecialMsg({ msgObj: { type: 'at', qq: selectedMsg.sender.user_id }, addText: true }) : '');closeMsgMenu();" v-show="tags.menuDisplay.at">
<div @click="(selectedMsg ? addSpecialMsg({ msgObj: { type: 'at', qq: selectedMsg.sender.user_id }, addText: true }) : '');toMainInput();closeMsgMenu();" v-show="tags.menuDisplay.at">
<div><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 64C150 64 64 150 64 256s86 192 192 192c17.7 0 32 14.3 32 32s-14.3 32-32 32C114.6 512 0 397.4 0 256S114.6 0 256 0S512 114.6 512 256v32c0 53-43 96-96 96c-29.3 0-55.6-13.2-73.2-33.9C320 371.1 289.5 384 256 384c-70.7 0-128-57.3-128-128s57.3-128 128-128c27.9 0 53.7 8.9 74.7 24.1c5.7-5 13.1-8.1 21.3-8.1c17.7 0 32 14.3 32 32v80 32c0 17.7 14.3 32 32 32s32-14.3 32-32V256c0-106-86-192-192-192zm64 192c0-35.3-28.7-64-64-64s-64 28.7-64 64s28.7 64 64 64s64-28.7 64-64z"/></svg></div>
<a>{{ $t('chat_msg_menu_at') }}</a>
</div>
<div @click="(selectedMsg ? Connector.send('set_group_kick', {group_id: runtimeData.chatInfo.show.id, user_id: selectedMsg.sender.user_id}, 'setGroupKick') : '');closeMsgMenu();" v-show="tags.menuDisplay.remove">
<div @click="removeUser" v-show="tags.menuDisplay.remove">
<div><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L353.3 251.6C407.9 237 448 187.2 448 128C448 57.3 390.7 0 320 0C250.2 0 193.5 55.8 192 125.2L38.8 5.1zM264.3 304.3C170.5 309.4 96 387.2 96 482.3c0 16.4 13.3 29.7 29.7 29.7H514.3c3.9 0 7.6-.7 11-2.1l-261-205.6z"/></svg></div>
<a>{{ $t('chat_msg_menu_remove') }}</a>
</div>
@ -621,7 +621,7 @@ export default defineComponent({
// at
this.addSpecialMsg({ msgObj: { type: 'at', qq: id }, addText: true })
}
document.getElementById('main-input')?.focus()
this.toMainInput()
this.tags.onAtFind = false
this.atFindList = null
},
@ -711,11 +711,30 @@ export default defineComponent({
}
const selection = document.getSelection()
const textBody = selection?.anchorNode?.parentElement
let textMsg = null as HTMLElement | null
// message class chat class
let msgParent = textBody
if(msgParent) {
while(msgParent.className != 'chat') {
if(msgParent.className.startsWith('message') &&
msgParent.className.indexOf('-') < 0) {
textMsg = msgParent
break
}
msgParent = msgParent.parentElement as HTMLDivElement
if(!msgParent) {
break
}
}
}
if(textBody && textBody.className.indexOf('msg-text') > -1 &&
selection.focusNode == selection.anchorNode) {
selection.focusNode == selection.anchorNode &&
textMsg && textMsg.id == msg.id) {
// msg-text Node
this.tags.menuDisplay.copySelect = true
this.selectCache = selection.toString()
if(this.selectCache.length > 0) {
this.tags.menuDisplay.copySelect = true
}
}
const nList = ['xml', 'json']
data.message.forEach((item: any) => {
@ -727,24 +746,30 @@ export default defineComponent({
}
//
const pointEvent = event as PointerEvent || window.event as PointerEvent
const pointX = pointEvent.offsetX
const pointX = pointEvent.clientX - msg.getBoundingClientRect().left + 20
const pointY = pointEvent.clientY
//
menu.style.marginLeft = pointX + 'px'
menu.style.marginTop = pointY + 'px'
//
const menuWidth = menu.clientWidth
const menuHeight = menu.clientHeight
const msgWidth = msg.offsetWidth
const bodyHeight = document.body.clientHeight
if (pointX + menuWidth > msgWidth + 27) {
menu.style.marginLeft = (msgWidth + 27 - menuWidth) + 'px'
}
if (pointY + menuHeight > bodyHeight - 10) {
menu.style.marginTop = (bodyHeight - menuHeight - 10) + 'px'
}
//
this.tags.showMsgMenu = true
// PS
setTimeout(() => {
//
const menuHeight = menu.clientHeight
const bodyHeight = document.body.clientHeight
if (pointY + menuHeight > bodyHeight + 10) {
menu.classList.add('topOut')
menu.style.marginTop = (bodyHeight - menuHeight - 10) + 'px'
// menu.classList.remove('topOut')
}
}, 90)
//
this.tags.openedMenuMsg = msg
msg.style.background = '#00000008'
@ -780,10 +805,7 @@ export default defineComponent({
//
this.tags.isReply = true
//
const mainInput = document.getElementById('main-input')
if(mainInput !== null) {
mainInput.focus()
}
this.toMainInput()
//
if(closeMenu) {
this.closeMsgMenu()
@ -925,6 +947,41 @@ export default defineComponent({
}
},
/**
* 移出群聊
*/
removeUser() {
const msg = this.selectedMsg
if (msg !== null) {
const popInfo = {
title: this.$t('popbox_tip'),
html: `<span>${this.$t('chat_msg_menu_remove_tip', { user: msg.sender.nickname })}</span>`,
button: [
{
text: app.config.globalProperties.$t('btn_yes'),
fun: () => {
if(msg) {
Connector.send('set_group_kick',
{
group_id: runtimeData.chatInfo.show.id,
user_id: msg.sender.user_id
}, 'setGroupKick')
this.closeMsgMenu()
runtimeData.popBoxList.shift()
}
}
},
{
text: app.config.globalProperties.$t('btn_no'),
master: true,
fun: () => { runtimeData.popBoxList.shift() }
}
]
}
runtimeData.popBoxList.push(popInfo)
}
},
/**
* 获取悬浮窗显示位置
*/

View File

@ -170,6 +170,18 @@
</div>
</label>
</div>
<template v-if="runtimeData.tags.isElectron">
<div class="opt-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V256c0 17.7 14.3 32 32 32s32-14.3 32-32V32zM143.5 120.6c13.6-11.3 15.4-31.5 4.1-45.1s-31.5-15.4-45.1-4.1C49.7 115.4 16 181.8 16 256c0 132.5 107.5 240 240 240s240-107.5 240-240c0-74.2-33.8-140.6-86.6-184.6c-13.6-11.3-33.8-9.4-45.1 4.1s-9.4 33.8 4.1 45.1c38.9 32.3 63.5 81 63.5 135.4c0 97.2-78.8 176-176 176s-176-78.8-176-176c0-54.4 24.7-103.1 63.5-135.4z"/></svg>
<div>
<span>{{ $t('option_dev_restart') }}</span>
<span>{{ $t('option_dev_restart_tip') }}</span>
</div>
<button style="width:100px;font-size:0.8rem;" class="ss-button" @click="restartapp">{{
$t('option_dev_runtime_run')
}}</button>
</div>
</template>
</div>
<div class="ss-card">
<header>{{ $t('option_dev_backup') }}</header>
@ -350,9 +362,7 @@ export default defineComponent({
try {
const json = JSON.parse(input.value)
runtimeData.sysConfig = json
console.log(json)
saveAll(json)
app.config.globalProperties.$cookies.set('top', JSON.stringify(json.top_info), '1m')
location.reload()
} catch (e) {
new PopInfo().add(PopType.ERR, app.config.globalProperties.$t('import_config_fail'))
@ -390,6 +400,13 @@ export default defineComponent({
]
}
runtimeData.popBoxList.push(popInfo)
},
restartapp() {
const electron = (process.env.IS_ELECTRON as any) === true ? window.require('electron') : null
const reader = electron ? electron.ipcRenderer : null
if (reader) {
reader.send('win:relaunch')
}
}
},
mounted() {

View File

@ -57,6 +57,7 @@
<script lang="ts">
import app from '@/main'
import Option from '@/function/option'
import { defineComponent } from 'vue'
import { runtimeData } from '@/function/msg'
@ -141,9 +142,7 @@ export default defineComponent({
// cookie
if (topList) {
topInfo[id] = topList
runtimeData.sysConfig.top_info = topInfo
console.log(topInfo)
app.config.globalProperties.$cookies.set('top', JSON.stringify(topInfo), '1m')
Option.save('top_info', topInfo)
}
//
for (let i = 0; i < runtimeData.onMsgList.length; i++) {

View File

@ -95,10 +95,7 @@
</template>
<template v-if="runtimeData.tags.isElectron && browser.os == 'Linux'">
<div class="opt-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path
d="M192 64C86 64 0 150 0 256S86 448 192 448H384c106 0 192-86 192-192s-86-192-192-192H192zM384 352c-53 0-96-43-96-96s43-96 96-96s96 43 96 96s-43 96-96 96z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M84.6 9.4C72.1-3.1 51.9-3.1 39.4 9.4s-12.5 32.8 0 45.3L120.7 136 28.6 228.1c-37.5 37.5-37.5 98.3 0 135.8L146.1 481.4c37.5 37.5 98.3 37.5 135.8 0L472.3 290.9c28.1-28.1 28.1-73.7 0-101.8L320.9 37.7c-28.1-28.1-73.7-28.1-101.8 0L166 90.7 84.6 9.4zM166 181.3l49.4 49.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L211.3 136l53.1-53.1c3.1-3.1 8.2-3.1 11.3 0L427.1 234.3c3.1 3.1 3.1 8.2 0 11.3L384.7 288H65.5c1.4-5.4 4.2-10.4 8.4-14.6L166 181.3z"/></svg>
<div>
<span>{{ $t('option_view_auto_gtk') }}</span>
<span>{{ $t('option_view_auto_gtk_tip') }}</span>
@ -168,6 +165,22 @@
<span :style="`color: var(--color-font${initialScaleShow / 0.05 > 50 ? '-r' : ''})`">{{ initialScaleShow }}</span>
</div>
</div>
<template v-if="runtimeData.tags.isElectron">
<div class="opt-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M384 32C419.3 32 448 60.65 448 96V416C448 451.3 419.3 480 384 480H64C28.65 480 0 451.3 0 416V96C0 60.65 28.65 32 64 32H384zM384 80H64C55.16 80 48 87.16 48 96V416C48 424.8 55.16 432 64 432H384C392.8 432 400 424.8 400 416V96C400 87.16 392.8 80 384 80z"/></svg>
<div>
<span>{{ $t('option_view_no_window') }}</span>
<span>{{ $t('option_view_no_window_tip') }}</span>
</div>
<label class="ss-switch">
<input type="checkbox" @change="save" name="opt_no_window"
v-model="runtimeData.sysConfig.opt_no_window">
<div>
<div></div>
</div>
</label>
</div>
</template>
</div>
</div>
</template>

View File

@ -61,18 +61,41 @@ module.exports = {
}
},
pluginOptions: {
/**
* Electron Builder 设置
* @type {import('vue-cli-plugin-electron-builder').PluginOptions}
*/
electronBuilder: {
builderOptions: {
nsis: {
allowToChangeInstallationDirectory: true,
oneClick: false,
installerIcon: "./public/favicon.ico",
installerHeaderIcon: "./public/favicon.ico"
appId: 'com.stapxs.qq-web',
productName: 'Stapxs QQ Lite',
copyright: 'Copyright © 2022-2023 Stapx Steve [林槐]',
linux: {
target: ['AppImage', 'pacman', 'tar.gz'],
maintainer: 'Stapx Steve [林槐]',
vendor: 'Stapxs Steve Team',
// TODO: 需要完善更完整的尺寸的图标
icon: 'public/img/icons/icon.png',
synopsis: '一个兼容 oicq-http 的非官方网页版 QQ 客户端。',
category: 'Network',
// TODO: 将来可能需要占用 QQ 自己的 MIME 类型
mimeTypes: ['application/x-stapxs-qq-lite'],
desktop: {
Type: 'Application',
Name: 'Stapxs QQ Lite',
GenericName: 'Stapxs QQ Lite Electron 客户端',
Comment: '一个兼容 oicq-http 的非官方网页版 QQ 客户端。',
Terminal: 'false',
Category: 'Network'
}
},
win: {
icon: './public/favicon.ico'
},
productName: 'Stapxs QQ Lite'
target: 'portable',
icon: 'public/img/icons/icon.png',
legalTrademarks: 'Copyright © 2022-2023 Stapx Steve [林槐]',
}
}
}
}

View File

@ -2513,6 +2513,16 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.6.0, ajv@^8.8.0:
require-from-string "^2.0.2"
uri-js "^4.2.2"
ajv@^8.6.3:
version "8.12.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
ansi-align@^3.0.0:
version "3.0.1"
resolved "https://registry.npmmirror.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
@ -2777,6 +2787,11 @@ atob@^2.1.2:
resolved "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
atomically@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe"
integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==
autoprefixer@^10.2.4:
version "10.4.13"
resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.13.tgz#b5136b59930209a321e9fa3dca2e7c4d223e83a8"
@ -3734,6 +3749,22 @@ concat-stream@^1.5.0:
readable-stream "^2.2.2"
typedarray "^0.0.6"
conf@^10.2.0:
version "10.2.0"
resolved "https://registry.yarnpkg.com/conf/-/conf-10.2.0.tgz#838e757be963f1a2386dfe048a98f8f69f7b55d6"
integrity sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==
dependencies:
ajv "^8.6.3"
ajv-formats "^2.1.1"
atomically "^1.7.0"
debounce-fn "^4.0.0"
dot-prop "^6.0.1"
env-paths "^2.2.1"
json-schema-typed "^7.0.3"
onetime "^5.1.2"
pkg-up "^3.1.0"
semver "^7.3.5"
config-chain@^1.1.11:
version "1.1.13"
resolved "https://registry.npmmirror.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
@ -4117,6 +4148,13 @@ date-format@^4.0.13:
resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400"
integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==
debounce-fn@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7"
integrity sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==
dependencies:
mimic-fn "^3.0.0"
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8:
version "2.6.9"
resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -4440,6 +4478,13 @@ dot-prop@^5.2.0:
dependencies:
is-obj "^2.0.0"
dot-prop@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083"
integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==
dependencies:
is-obj "^2.0.0"
dotenv-expand@^5.1.0:
version "5.1.0"
resolved "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
@ -4563,6 +4608,14 @@ electron-publish@22.14.13:
lazy-val "^1.0.5"
mime "^2.5.2"
electron-store@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-8.1.0.tgz#46a398f2bd9aa83c4a9daaae28380e2b3b9c7597"
integrity sha512-2clHg/juMjOH0GT9cQ6qtmIvK183B39ZXR0bUoPwKwYHJsEF3quqyDzMFUAu+0OP8ijmN2CbPRAelhNbWUbzwA==
dependencies:
conf "^10.2.0"
type-fest "^2.17.0"
electron-to-chromium@^1.4.251:
version "1.4.284"
resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
@ -4654,7 +4707,7 @@ entities@^2.0.0:
resolved "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
env-paths@^2.2.0:
env-paths@^2.2.0, env-paths@^2.2.1:
version "2.2.1"
resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
@ -6648,6 +6701,11 @@ json-schema-traverse@^1.0.0:
resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json-schema-typed@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9"
integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==
json-schema@0.4.0, json-schema@^0.4.0:
version "0.4.0"
resolved "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
@ -7173,6 +7231,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-fn@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74"
integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==
mimic-response@^1.0.0, mimic-response@^1.0.1:
version "1.0.1"
resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
@ -7958,6 +8021,13 @@ pkg-dir@^4.1.0:
dependencies:
find-up "^4.0.0"
pkg-up@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
dependencies:
find-up "^3.0.0"
plist@^3.0.1, plist@^3.0.4:
version "3.0.6"
resolved "https://registry.npmmirror.com/plist/-/plist-3.0.6.tgz#7cfb68a856a7834bca6dbfe3218eb9c7740145d3"
@ -10137,6 +10207,11 @@ type-fest@^0.8.1:
resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type-fest@^2.17.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
@ -10548,19 +10623,12 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.npmmirror.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue3-cookies@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/vue3-cookies/-/vue3-cookies-1.0.6.tgz#c1da41f1f3d6bbea8e75157d49d09dfc2a18ca8a"
integrity sha512-a1UvVD0qIgxyOqjlSOwnLnqAnz8ASltugEv8yX+96i/WGZAN9fEDci7xO4HIWZE1uToUnRq9JnFhvfDCSo45OA==
dependencies:
vue "^3.0.0"
vue3-infinite-scroll-better@^2.2.0:
version "2.2.0"
resolved "https://registry.npmmirror.com/vue3-infinite-scroll-better/-/vue3-infinite-scroll-better-2.2.0.tgz#73a74df93a893fc66e44da2dabd97dbc1139054e"
integrity sha512-tIqY7Ab0Hu9j7SiLLNn9jUR2te2THvfVrDvdOfilx/Qk84mc3YS8rjxMNYM5OAlv7Wk92u2HD3rRqx892JafdA==
vue@^3.0.0, vue@^3.2.13:
vue@^3.2.13:
version "3.2.45"
resolved "https://registry.npmmirror.com/vue/-/vue-3.2.45.tgz#94a116784447eb7dbd892167784619fef379b3c8"
integrity sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==