Skip to content

Commit

Permalink
大意了,没有闪
Browse files Browse the repository at this point in the history
✨ 支持官方端发送的 `com.tencent.map` 地图定位显示,使用高德地图的 js API,在 Apple 设备上点击将唤起系统的地图。API key 是我自己的,使用免费额度,如果你打算自行构建使用,建议更换 env 中的 key。
🎨 卡片组件现在由单独的组件负责,便于扩展
🐛 #61 #62 连接协议判断失效
:wheelchair: 部分程序参数移到了 env 里
:green_heart: [build-electron]
  • Loading branch information
Stapxs committed May 23, 2023
1 parent bbaef71 commit d612f53
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 305 deletions.
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
VUE_APP_GA=G-ZQ88GPJRGH
VUE_APP_AMAP_KEY=99b8849d7b67f05e9f834bde4108f0f9
VUE_APP_AMAP_SECRET=e9ac5aac621defdd6bb111b55fb75d73
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stapxs-qq-lite",
"version": "2.5.5",
"version": "2.5.7",
"private": false,
"author": "Stapx Steve [林槐]",
"description": "一个兼容 oicq-http 的非官方网页版 QQ 客户端,使用 Vue 重制的全新版本。",
Expand Down Expand Up @@ -33,7 +33,6 @@
"register-service-worker": "^1.7.2",
"semver-compare": "^1.0.0",
"spacingjs": "^1.0.7",
"svg-sprite-loader": "^6.0.11",
"ts-loader": "~8.2.0",
"ts-md5": "^1.3.1",
"uuidv4": "^6.2.13",
Expand Down
2 changes: 2 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@
<div id="app"></div>
</body>
<script src="Border-Card-UI/js/main.js"></script>
<script src="https://webapi.amap.com/loader.js"></script>
<script src="js/amap.js"></script>
</html>
20 changes: 20 additions & 0 deletions public/js/amap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
window.createMap = function (key, msgId, point) {
if (key == undefined || point == undefined) return

AMapLoader.load({
key: key,
version: "2.0",
}).then((AMap) => {
const map = new AMap.Map('map-' + msgId, {
viewMode: '2D',
zoom: 15,
center: [point.lng, point.lat]
});
const marker = new AMap.Marker({
position: [point.lng, point.lat]
})
map.add(marker)
}).catch((e) => {
throw e
})
}
5 changes: 4 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -478,12 +478,15 @@ export default defineComponent({
const { property } = useState()
if (property) {
property.value = {
id: 'G-ZQ88GPJRGH'
id: process.env.VUE_APP_GA
} as DomainConfig
}
} else if (process.env.NODE_ENV == 'development') {
logger.debug(this.$t('log_GA_auto_closed'))
}
// AMAP:初始化高德地图
window._AMapSecurityConfig =
process.env.VUE_APP_AMAP_SECRET
// 检查版本
const appVersion = appInfo.version
const cacheVersion = localStorage.getItem('version')
Expand Down
7 changes: 4 additions & 3 deletions src/components/MsgBody.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,8 @@
<video v-if="item.url" controls><source :src="item.url" type="video/mp4"></video>
<div v-else-if="!getVideo" :class="getVideoUrl(item, data.message_id)"></div>
</div>
<div v-else-if="item.type == 'xml'" v-html="View.buildXML(item.data, item.id, data.message_id)" @click="View.cardClick('xml-' + data.message_id)"></div>
<div v-else-if="item.type == 'json'" v-html="View.buildJSON(item.data, data.message_id)" @click="View.cardClick('json-' + data.message_id)">
</div>
<CardMessage
v-else-if="item.type == 'xml' || item.type == 'json'" :item="item" :id="data.message_id"></CardMessage>

<span v-else-if="item.type == 'forward'" class="msg-unknown" @click="View.getForwardMsg(item.id)">{{ $t('chat_show_forward') }}</span>
<div :data-seq="getSeq(item.id).toString()" @click="scrollToMsg(getSeq(item.id).toString())" v-else-if="item.type == 'reply'" :class="isMe ? (type == 'merge' ? 'msg-replay' : 'msg-replay me') : 'msg-replay'">
Expand Down Expand Up @@ -115,6 +114,7 @@
import Util from '@/function/util'
import Option from '@/function/option'
import CardMessage from './msg-component/CardMessage.vue'
import { MsgBodyFuns as ViewFuns } from '@/function/model/msg-body'
import { defineComponent } from 'vue'
Expand All @@ -127,6 +127,7 @@ import { StringifyOptions } from 'querystring'
export default defineComponent({
name: 'MsgBody',
props: ['data', 'type'],
components: { CardMessage },
data () {
return {
getSizeFromBytes: Util.getSizeFromBytes,
Expand Down
118 changes: 118 additions & 0 deletions src/components/msg-component/CardMessage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<!--
* @FileDescription: 卡片消息消息组件
* @Author: Stapxs
* @Date: 2023/05/23
* @Version: 1.0 - 初始版本
* @Description: 卡片消息的单独组件,由于卡片消息的类型过于复杂越写越乱,所以单独写一个组件
同时也是为了优化消息刷新机制的性能,可以对不同的卡片类型设置 v-once。
-->

<!--
附加补充:
这儿主要针对更复杂的 json 卡片消息 …… xml 类型的卡片消息因为自定义性比 json 低
其实已经被官方放弃了,除了比较旧的一些卡片消息,现在基本上都是 json 类型的卡片消息。
-->

<template>
<div>
<div v-if="item.type == 'xml'" v-html="View.buildXML(item.data, item.id, id)" @click="View.cardClick('xml-' + id)"></div>
<div v-else>
<div v-if="info.type == 'default'" v-html="buildJSON(info, id)" @click="View.cardClick('json-' + id)"></div>
<div v-once v-else-if="info.type == 'tencent.map'" class="msg-comp-map" @click="View.cardClick('map-' + id)">
<p>{{ info.app.title }}</p>
<span>{{ info.app.desc }}</span>
<div
class="map"
:data-url="createMap()"
data-urlOpenType="_self"
:id="'map-' + id"></div>
</div>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { MsgBodyFuns as ViewFuns } from '@/function/model/msg-body'
export default defineComponent({
name: 'CardMessage',
props: [ 'item', 'id' ],
components: {},
data() {
return {
View: ViewFuns,
info: ViewFuns.getJSONType(this.item.data),
}
},
methods: {
/**
* 构建基础 JSON 消息
* @param info 卡片信息
* @param id 消息 ID
*/
buildJSON(data: any, id: string) {
const info = data.app
const div = document.createElement('div')
// 构建 HTML
const html = '<p>' + info.title + '</p>' +
'<span>' + info.desc + '</span>' +
'<img style="' + (info.preview === undefined ? 'display:none' : '') + '" src="' + info.preview + '">' +
(info.name ? '<div><img src="' + info.icon + '"><span>' + info.name + '</span></div>' : '')
div.className = 'msg-json'
div.id = 'json-' + id
div.dataset.url = info.url
div.dataset.urlOpenType = info.urlOpenType
div.innerHTML = html
// 附加信息
if(Object.keys(data.append).length > 0) {
// 将 append 里的信息附加到 div 上
for(const key in data.append) {
div.dataset[key] = data.append[key]
}
}
// 返回
return div.outerHTML
},
/**
* 创建高德地图模块
*/
createMap() {
const json = JSON.parse(this.item.data)
window.createMap(
process.env.VUE_APP_AMAP_KEY,
this.id,
{
lat: json.meta['Location.Search'].lat,
lng: json.meta['Location.Search'].lng
}
)
return this.info.app.url
}
}
})
</script>

<style scoped>
.msg-comp-map {
cursor: pointer;
}
.msg-comp-map > p {
font-weight: bold;
margin-bottom: 0;
}
.msg-comp-map > span {
font-size: 0.9rem;
opacity: 0.7;
}
.msg-comp-map > div.map {
height: 200px;
border-radius: 7px;
margin-top: 10px;
width: 400px;
max-width: calc(100vw - 150px);
pointer-events: none;
}
</style>
16 changes: 13 additions & 3 deletions src/function/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,31 +81,41 @@ export class Connector {
parse(e.data)
}
websocket.onclose = (e) => {
login.status = false
websocket = undefined

switch(e.code) {
case 1000: break; // 正常关闭
case 1006: { // 非正常关闭,尝试重连
this.create(address, token, undefined)
if(login.status) {
this.create(address, token, undefined)
} else {
// PS:由于创建连接失败也会触发此事件,所以需要判断是否已经登录
// 尝试使用 ws 连接
this.create(address, token, false)
}
break;
}
case 1015: { // TSL 错误,尝试使用 ws 连接
this.create(address, token, false)
break;
}
default: {
logger.error($t('pop_log_con_fail') + ': ' + e.code)
popInfo.add(PopType.ERR, $t('pop_log_con_fail') + ': ' + e.code, false)
console.log(e)
}
}
logger.error($t('pop_log_con_fail') + ': ' + e.code)
login.status = false

// 除了 1006 意外断开(可能要保留数据重连),其他情况都会清空
if(e.code != 1006) {
resetRimtime()
}
}
websocket.onerror = (e) => {
popInfo.add(PopType.ERR, $t('pop_log_con_fail') + ': ' + e.type, false)
return
}
}

/**
Expand Down
105 changes: 62 additions & 43 deletions src/function/model/msg-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,64 +116,77 @@ export class MsgBodyFuns {
}

/**
* 尝试渲染 JSON 消息
* @param data JSON 消息字符串
* @param msgId 消息 ID
* @returns
* 获取 JSON 消息的有效信息(通用)
* @param data JSON 消息(已解析)
* @returns appInfo
*/
static buildJSON(data: string, msgId: string) {
static getJSON(json: any) {
// 解析 JSON
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
const app = {} as {[key: string]: any}

let title = body.title
let desc = body.desc
app.name = body.tag === undefined ? body.title : body.tag
app.icon = body.icon === undefined ? body.source_icon : body.icon

let preview = body.preview
if (preview !== undefined && preview.indexOf('http') === -1) preview = '//' + preview
app.title = body.title
app.desc = body.desc

const div = document.createElement('div')
app.preview = body.preview
if (app.preview !== undefined && app.preview.indexOf('http') === -1) app.preview = '//' + app.preview

app.url = body.qqdocurl === undefined ? body.jumpUrl : body.qqdocurl

return app
}

// 一些特殊判定
/**
* 获取具体的 JSON 消息类型用于特殊处理
* @param data JSON 消息
* @returns { type: string, app: any }
*/
static getJSONType(data: string) {
const json = JSON.parse(data)
const info = this.getJSON(json)
let type = 'default'
const append = {} as {[key: string]: any}

// 下面就是一大堆特殊判定
if (json.desc === '群公告') {
title = json.desc
desc = json.prompt
preview = undefined
icon = ''
name = json.desc
info.title = json.desc
info.desc = json.prompt
info.preview = undefined
info.icon = ''
info.name = json.desc
}
if(json.desc.indexOf('聊天记录') >= 1) {
title = json.meta.detail.source
desc = '<div style="padding: 15px 20px 5px 20px">'
info.title = json.meta.detail.source
info.desc = '<div style="padding: 15px 20px 5px 20px">'
json.meta.detail.news.forEach((item: any) => {
desc += '<span>' + item.text + '</span><br>'
info.desc += '<span>' + item.text + '</span><br>'
})
desc += '</div>'
icon = ''
name = json.meta.detail.summary
info.desc += '</div>'
info.icon = ''
info.name = json.meta.detail.summary

div.dataset.type = 'forward'
div.dataset.id = json.meta.detail.resid
div.style.cursor = 'pointer'
append.type = 'forward'
append.id = json.meta.detail.resid
}
if(json.app == 'com.tencent.map') {
info.title = json.meta['Location.Search'].name
append.urlOpenType = '_self'
const deviceType = util.getDeviceType()
if(deviceType == 'Android') {
info.url = 'geo:' + json.meta['Location.Search'].lat + ',' + json.meta['Location.Search'].lng
} else if(deviceType == 'iOS' || deviceType == 'MacOS') {
info.url = 'http://maps.apple.com/?ll=' + json.meta['Location.Search'].lat + ',' + json.meta['Location.Search'].lng +
'&q=' + json.meta['Location.Search'].name
}
info.desc = json.meta['Location.Search'].address
type = 'tencent.map'
}

const url = body.qqdocurl === undefined ? body.jumpUrl : body.qqdocurl
// 构建 HTML
const html = '<p>' + title + '</p>' +
'<span>' + desc + '</span>' +
'<img style="' + (preview === undefined ? 'display:none' : '') + '" src="' + preview + '">' +
'<div><img src="' + icon + '"><span>' + name + '</span></div>'

div.className = 'msg-json'
div.id = 'json-' + msgId
div.dataset.url = url
div.innerHTML = html

// 返回
return div.outerHTML
return { type, app: info, append }
}

/**
Expand All @@ -186,7 +199,13 @@ export class MsgBodyFuns {
const type = sender.dataset.type
// 如果存在 url 项,优先打开 url
if (sender.dataset.url !== undefined && sender.dataset.url !== 'undefined' && sender.dataset.url !== '') {
util.openLink(sender.dataset.url)
const openType = sender.dataset.urlOpenType || sender.dataset.urlopentype
if(openType == '_self') {
window.open(sender.dataset.url, '_self')
} else {
// 默认都以 _blank 打开
util.openLink(sender.dataset.url)
}
return
}
// 接下来按类型处理
Expand Down
Loading

0 comments on commit d612f53

Please sign in to comment.