fix build_exe有问题,添加了代码。

打包的时候,必须手动添加项目地址
This commit is contained in:
pengGgxp 2025-05-04 00:41:28 +08:00
parent 6bb8b8b6ec
commit 5fc703360d
106 changed files with 241 additions and 10934 deletions

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,35 @@
---
name: Bug report
about: 帮助定位问题所在
title: ''
labels: ''
assignees: ''
---
**问题描述**
请在此处提供对问题的详细描述。
**复现步骤**
请提供重现问题所需的步骤。(执行的命令)
1. 步骤 1
2. 步骤 2
3. 步骤 3
**预期行为**
请清楚地描述您预期的行为。
**实际行为**
请描述实际的行为和问题出现的地方。
**环境信息**
- pywxdump版本
- 操作系统版本:
- python版本
- 微信版本:
**其他信息**
请提供任何与问题相关的其他信息(文字,截图等)。

42
.github/workflows/auto-sync-gitee.yml vendored Normal file
View File

@ -0,0 +1,42 @@
#on:
# push:
# branches: [master]
#name: Mirror GitHub Repos to Gitee
#jobs:
# run:
# name: Sync-GitHub-to-Gitee
# runs-on: ubuntu-latest
# steps:
# - name: Mirror the Github repos to Gitee.
# uses: Yikun/hub-mirror-action@master
# with:
# src: github/xaoyaoo
# dst: gitee/xaoyaoo
# dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
# dst_token: ${{ secrets.GITEE_TOKEN }}
# force_update: true
# src_account_type: org
# dst_account_type: user
# mappings: "dashboard=>dashboards"
# static_list: "trader"
# cache_path: /github/workspace/hub-mirror-cache
name: Hello World Action
on:
push:
branches: [ main ] # 触发条件:当主分支有新的推送时
jobs:
hello-job:
runs-on: ubuntu-latest # 运行环境:最新的 Ubuntu 系统
steps:
- name: Checkout Repository
uses: actions/checkout@v3 # 检出代码
- name: Print Hello Message
run: echo "Hello, world!" # 执行命令,打印消息
- name: Print Date
run: date # 执行命令,打印当前日期

134
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,134 @@
name: Publish
on:
# 当master分支有push时触发action
push:
tags:
- 'v*' # 以 'v' 开头的标签触发工作流程
jobs:
publish:
name: Publish Pypi and Create Release
if: github.repository == 'xaoyaoo/PyWxDump' # 仅在指定仓库的 tag 触发工作流程
# 此作业在 Linux 上运行
runs-on: windows-latest
steps:
- name: Checkout repository # 检出仓库
uses: actions/checkout@v2 # 使用 GitHub 官方的 checkout action
- name: Set git fetch depth # 设置 git fetch 深度
run: |
git fetch --prune --unshallow # 获取完整的 git 历史记录
- name: Set up Python # 设置 Python 环境
uses: actions/setup-python@v4
with:
python-version: '3.8'
cache: 'pip' # caching pip dependencies
- run: |
python -m pip install --upgrade pip
pip install build
python -m pip install --upgrade twine
pip install pyinstaller
pip install -r requirements.txt
- name: Set up Node.js # 设置 Node.js 环境
uses: actions/setup-node@v2
with:
node-version: 20
- name: Build Web UI # 构建 Web UI
run: |
cd ..
git clone https://github.com/xaoyaoo/wxdump_web.git
Compress-Archive -Path wxdump_web -DestinationPath wxdump_web.zip
Compress-Archive -Path PyWxDump -DestinationPath PyWxDump.zip
cd wxdump_web
npm list -g
npm install
npm run build
- name: copy web ui to pywxdump/ui/web
run: |
cd ..
ls -l wxdump_web/dist
cp -r wxdump_web/dist PyWxDump/pywxdump/ui/web
ls -l PyWxDump/pywxdump/ui/web
cd PyWxDump
# - name: Build Export UI # 构建导出的 Web UI
# run: |
# cd ..
# cd wxdump_web
# cp src/main.ts src/t.ts
# cp src/main.ts.export src/main.ts
# npm install
# npm run build
#
# - name: copy Export UI and Export UI to pywxdump/ui/web and pywxdump/ui/export
# run: |
# cd ..
# ls -l wxdump_web/dist
# cp -r wxdump_web/dist PyWxDump/pywxdump/ui/export
# ls -l PyWxDump/pywxdump/ui/export
# cd PyWxDump
- name: Build package # 构建包
run: |
python -m build
pip install -U .
- name: Generate File pywxdump.spec # 生成 pywxdump.spec 文件
run: |
python tests/build_exe.py
ls
ls dist
cat dist/pywxdump.spec
- name: Build Executable
run: |
pyinstaller --clean --distpath=dist dist/pywxdump.spec
- name: test
run: |
ls -l dist
ls -l "${{ github.workspace }}"
- name: Zip Executable
run: |
cd ..
ls
Compress-Archive -Path PyWxDump/dist/*.exe,PyWxDump/dist/*.whl -DestinationPath exe_whl.zip
Compress-Archive -Path PyWxDump.zip,wxdump_web.zip -DestinationPath Source.zip
ls
cp exe_whl.zip PyWxDump/dist/exe_whl.zip
cp Source.zip PyWxDump/dist/Source.zip
ls PyWxDump/dist
cd PyWxDump
- name: Publish package with Twine # 使用 Twine 发布到 PyPI
run: |
twine upload dist/*.whl dist/*.tar.gz
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref.tag }}
body: |
[Auto Release] Update PyWxDump to ${{ github.ref }}
详细更新日志请查看 [CHANGELOG.md](https://github.com/xaoyaoo/PyWxDump/blob/master/doc/CHANGELOG.md)
draft: false
prerelease: false
files: |
dist/*.exe
dist/*.whl
dist/exe_whl.zip
dist/Source.zip

View File

@ -458,11 +458,11 @@ class DeepSeekApi(BaseLLMApi):
if __name__ == "__main__":
deepseek_api = DeepSeekApi("sk-2ed4377a895d4ce18e086258c254fc8e")
response = deepseek_api.send_msg(module=0,message="""""")
print(response)
# if __name__ == "__main__":
# deepseek_api = DeepSeekApi("sk-2ed4377a895d4ce18e086258c254fc8e")
#
# response = deepseek_api.send_msg(module=0,message="""""")
# print(response)

View File

@ -582,7 +582,7 @@ def de_weight(l1:List,l2:List):
列表去重针对特定对象
"""
len1 = min(len(l1), len(l2))
len1 = len1-1 if len1 > 0 else 0
len1 = len1-1 if len1 > 1 else len1
for i in range(len1):
if l1[i]["wxid"] == l2[i]["wxid"] and l1[i]["start_time"] == l2[i]["start_time"] and l1[i]["end_time"] == l2[i][
"end_time"]:

View File

@ -367,7 +367,7 @@ class MainApi(BaseSubMainClass):
def console_run():
# 检查是否需要显示帮助信息
if len(sys.argv) == 1:
sys.argv.append(MainApi.mode)
sys.argv.append(MainUi.mode)
elif len(sys.argv) == 2 and sys.argv[1] not in models.keys():
sys.argv.append('-h')
main_parser.print_help()

View File

@ -1,28 +0,0 @@
name: Build and Deploy Vue App
on:
push:
branches:
- web
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 20
- name: Install dependencies
run: npm install
- name: Build the app
run: npm run build
- name: Cheak dist
run: ls dist

View File

@ -1,34 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
tsconfig.app.tsbuildinfo
tsconfig.node.tsbuildinfo
/public/data/

View File

@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

View File

@ -1 +0,0 @@
## 这是[PyWxDump](https://github.com/xaoyaoo/PyWxDump)的Web版用于在浏览器中查看微信聊天记录。

View File

@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-#
# -------------------------------------------------------------------------------
# Name: __init__.py.py
# Description:
# Author: xaoyaoo
# Date: 2023/12/03
# -------------------------------------------------------------------------------
# from .view_chat import app_show_chat, get_user_list, export
if __name__ == '__main__':
pass

View File

@ -1,6 +0,0 @@
/// <reference types="vite/client" />
declare module 'vue3-markdown-it';
declare module '@/utils/axios.js' {
import http from '@/utils/axios.js';
export default http;
}

View File

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="./favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PyWxDump</title>
<style>
html,
body {
margin: 0;
height: 100%;
}
</style>
<script src="./data.js"></script>
</head>
<body>
<div id="app" style="height: 100%;"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +0,0 @@
{
"name": "wxdump_web",
"version": "2.4.10",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force"
},
"dependencies": {
"@types/axios": "^0.14.0",
"axios": "^1.6.3",
"cors": "^2.8.5",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.4.4",
"markdown-it": "^14.0.0",
"v3-infinite-loading": "^1.3.1",
"vue": "^3.3.11",
"vue-echarts": "^6.6.9",
"vue-router": "^4.2.5",
"vue3-markdown-it": "^1.0.10"
},
"devDependencies": {
"@tsconfig/node18": "^18.2.2",
"@types/markdown-it": "^13.0.7",
"@types/node": "^18.19.3",
"@vitejs/plugin-vue": "^4.5.2",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/tsconfig": "^0.5.0",
"npm-run-all2": "^6.1.1",
"sass": "^1.69.7",
"typescript": "~5.3.0",
"vite": "^5.0.10",
"vue-tsc": "^1.8.25"
}
}

View File

@ -1,5 +0,0 @@
localStorage.setItem('isUseLocalData', 'f') // 't' : 'f'
const local_msg_count = 772
const local_mywxid = ''
const local_user_list = {}
const local_msg_list = []

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

View File

@ -1,228 +0,0 @@
<script setup lang="ts">
import chatIcon from "@/assets/icon/ChatIcon.vue";
import StatisticsIcon from "@/assets/icon/StatisticsIcon.vue";
import CleanupIcon from "@/assets/icon/CleanupIcon.vue";
import ToolsIcon from "@/assets/icon/ToolsIcon.vue";
import AboutIcon from "@/assets/icon/AboutIcon.vue";
import HelpIcon from "@/assets/icon/HelpIcon.vue";
import SettingIcon from "@/assets/icon/SettingIcon.vue";
// import CollapseIcon from "@/assets/icon/CollapseIcon.vue";
import HomeIcon from "@/assets/icon/HomeIcon.vue";
import ContactsIcon from "@/assets/icon/ContactsIcon.vue";
import MomentsIcon from "@/assets/icon/MomentsIcon.vue";
import FavoriteIcon from "@/assets/icon/FavoriteIcon.vue";
import CollapseOpenIcon from "@/assets/icon/CollapseOpenIcon.vue";
import CollapseCloseIcon from "@/assets/icon/CollapseCloseIcon.vue";
import {RouterLink, RouterView} from 'vue-router'
import {ref, onMounted, withCtx, watch} from 'vue'
import router from "@/router";
import {is_db_init, is_use_local_data} from "@/utils/common_utils";
import ChatRecordsMain from "@/components/chat/ChatRecordsMain.vue";
const isCollapse = ref(true);
const is_local_data = ref(true);
onMounted(() => {
// localStorage.setItem('isDbInit', "t");
is_local_data.value = localStorage.getItem('isUseLocalData') === 't';
console.log("is_local_data", is_local_data.value);
if(!is_local_data.value) {
is_db_init();
}
})
// watch(isDbInit, (val) => {
// localStorage.setItem('isDbInit', val);
// })
const handleOpen = (key: string, keyPath: string[]) => {
// console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
// console.log(key, keyPath)
}
</script>
<template>
<div class="export-main" v-if="is_local_data">
<chat-records-main wxid="wxid_test"/>
</div>
<div id="appbg" v-else>
<el-container class="layout-container-demo" style="height: 100%;background:none;">
<el-aside :width="isCollapse ? '64px' : '160px'">
<el-container class="sidebar-container">
<el-menu default-active="1" class="el-menu-vertical-demo" :collapse="isCollapse" :router='true'
:collapse-transition="false" :show-timeout="0" :hide-timeout="0">
<el-radio-group v-model="isCollapse"
style="margin-bottom: 20px;margin-top: 10px;margin-left: 10px;max-height: 30px">
<el-radio-button :label="false" v-if="isCollapse">
<collapse-open-icon></collapse-open-icon>
</el-radio-button>
<el-radio-button :label="true" v-else>
<collapse-close-icon></collapse-close-icon>
</el-radio-button>
</el-radio-group>
<el-menu-item index='/home'>
<home-icon></home-icon>
<template #title>首页</template>
</el-menu-item>
<el-menu-item index='/chat'>
<chat-icon></chat-icon>
<template #title>聊天查看</template>
</el-menu-item>
<!-- <el-menu-item index='/contacts'>-->
<!-- <contacts-icon></contacts-icon>-->
<!-- <template #title>好友管理</template>-->
<!-- </el-menu-item>-->
<!-- <el-menu-item index='/moments'>-->
<!-- <moments-icon></moments-icon>-->
<!-- <template #title>朋友圈</template>-->
<!-- </el-menu-item>-->
<!-- <el-menu-item index='/favorite'>-->
<!-- <favorite-icon></favorite-icon>-->
<!-- <template #title>收藏管理</template>-->
<!-- </el-menu-item>-->
<el-menu-item index='/statistics'>
<statistics-icon></statistics-icon>
<template #title>统计分析</template>
</el-menu-item>
<el-menu-item index='/cleanup'>
<cleanup-icon></cleanup-icon>
<template #title>文件清理</template>
</el-menu-item>
<el-sub-menu index='/tools'>
<template #title>
<tools-icon></tools-icon>
<span>实用工具</span>
</template>
<el-menu-item index='/wxinfo'>账号信息</el-menu-item>
<el-menu-item index='/bias'>基址偏移</el-menu-item>
<el-menu-item index='/decrypt'>解密数据</el-menu-item>
<el-menu-item index='/merge'>数据库合并</el-menu-item>
</el-sub-menu>
<el-menu-item index='/chat2ui_select'>
<statistics-icon></statistics-icon>
<template #title>AI可视化</template>
</el-menu-item>
</el-menu>
<el-menu default-active="1" class="el-menu-vertical-demo" :collapse="isCollapse" @open="handleOpen"
@close="handleClose" :router='true'>
<el-menu-item index='/about'>
<about-icon></about-icon>
<template #title>关于我们</template>
</el-menu-item>
<el-menu-item index='/help'>
<help-icon></help-icon>
<template #title>帮助中心</template>
</el-menu-item>
<el-menu-item index='/setting'>
<setting-icon></setting-icon>
<template #title>更多设置</template>
</el-menu-item>
</el-menu>
</el-container>
</el-aside>
<el-main>
<RouterView/>
</el-main>
</el-container>
</div>
</template>
<style scoped>
.export-main {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#appbg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
header {
line-height: 1.5;
max-height: 100vh;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 160px;
}
.layout-container-demo .el-header {
position: relative;
background-color: var(--el-color-primary-light-7);
color: var(--el-text-color-primary);
}
.layout-container-demo .el-aside {
color: var(--el-text-color-primary);
}
.layout-container-demo .el-menu {
border-right: none;
}
.layout-container-demo .el-main {
padding: 0;
}
.layout-container-demo .toolbar {
display: inline-flex;
align-items: center;
justify-content: center;
height: 100%;
right: 0px;
}
.sidebar-container {
flex-direction: column;
justify-content: space-between;
height: 100%;
}
</style>

View File

@ -1,100 +0,0 @@
import http from "@/utils/axios.js";
// const is_local_data = false;
const is_local_data = localStorage.getItem("isUseLocalData") === "t";
export const apiVersion = () => {
return http
.get("/api/rs/version")
.then((res: any) => {
return res;
})
.catch((err: any) => {
console.log(err);
return "";
});
};
export const api_db_init = async () => {
const t = await http.get("/api/rs/is_init");
console.log("is_db_init", !!t);
return !!t;
};
export const api_img = (url: string) => {
if (is_local_data) {
return `./imgsrc?src=${url}`;
}
return `/api/rs/imgsrc?src=${url}`;
};
export const api_audio = (url: string) => {
if (is_local_data) {
return `./audio?src=${url}`;
}
return `/api/rs/audio?src=${url}`;
};
export const api_video = (url: string) => {
if (is_local_data) {
return `./video?src=${url}`;
}
return `/api/rs/video?src=${url}`;
};
export const api_file = (url: string) => {
if (is_local_data) {
return `./file?src=${url}`;
}
return `/api/rs/file?src=${url}`;
};
// file_info
export const api_file_info = (url: string) => {
if (is_local_data) {
return `./file_info?src=${url}`;
}
return http
.post("/api/rs/file_info", {
file_path: url,
})
.then((res: any) => {
return res;
})
.catch((err: any) => {
console.log(err);
return "";
});
};
// DeepSeek设置部分
export const apiDeepSeekSet = (key: string) => {
return http
.post("/api/rs/deepseek_setting", {
deepseek: {
api_key: key,
},
})
.then((res: any) => {
return res;
})
.catch((err: any) => {
console.log(err);
return "";
});
};
/**
* DeepSeek设置
*/
export const apiDeepSeekGet = () => {
return http
.get("/api/rs/deepseek_setting")
.then((res: any) => {
return res;
})
.catch((err: any) => {
console.log(err);
return "";
});
};

View File

@ -1,161 +0,0 @@
import http from "@/utils/axios.js";
import {ElNotification} from "element-plus";
const is_local_data = localStorage.getItem('isUseLocalData') === 't';
// 编辑器禁用检查
const l_msg_count = local_msg_count
const l_user_list = local_user_list
const l_msg_list = local_msg_list
const l_mywxid = local_mywxid
// user list 部分
export const apiUserList = (word: string = '', wxids: string[] = [], labels: string[] = []) => {
if (is_local_data) {
return l_user_list;
}
return http.post('/api/rs/user_list', {
'word': word,
'wxids': wxids,
'labels': labels
}).then((res: any) => {
return res;
}).catch((err: any) => {
console.log(err);
return '';
})
}
export const apiUserSessionList = () => {
return http.post('/api/rs/user_session_list', {})
.then((res: any) => {
return res;
})
.catch((err: any) => {
console.log(err);
return [];
})
}
export const apiMyWxid = () => {
if (is_local_data) {
return l_mywxid;
}
return http.get('/api/rs/mywxid').then((res: any) => {
return res.my_wxid;
}).catch((err: any) => {
console.log(err);
return '';
})
}
// 消息部分
export const apiRealTime = () => {
return http.post('/api/ls/realtimemsg', {}).then((res: any) => {
console.log(res);
// 滚动消息提醒
ElNotification({
title: 'Success',
message: '获取实时消息成功!',
type: 'success',
})
return true;
}).catch((err: any) => {
console.log(err);
ElNotification({
title: 'Error',
message: '获取实时消息失败!',
type: 'error',
})
return false;
})
}
export const apiMsgCount = (wxids: string[]) => {
return http.post('/api/rs/msg_count', {
"wxids": wxids
}).then((res: any) => {
return res;
}).catch((err: any) => {
console.log(err);
return '';
})
}
export const apiMsgCountSolo = (wxid: string) => {
if (is_local_data) {
return l_msg_count;
}
return apiMsgCount([wxid]).then((res: any) => {
return res[wxid] || 0;
}).catch((err: any) => {
console.log(err);
return 0;
})
}
export const apiMsgs = (wxid: string, start: number, limit: number) => {
if (is_local_data) {
return {
'msg_list': l_msg_list || [],
'user_list': l_user_list || [],
}
}
return http.post('/api/rs/msg_list', {
'start': start,
'limit': limit,
'wxid': wxid,
}).then((res: any) => {
return res;
}).catch((err: any) => {
console.log(err);
return '';
})
}
/**
* ai可视化文件列表
*/
export const apiAiList = () =>{
return http.get('/api/rs/ai_ui_json_list' ).then((res: any) => {
return res;
}).catch((err: any) => {
console.log(err);
return '';
})
}
/**
* ai可视化文件内容
*/
export interface AiUiJson {
wxid: string,
start_time:string,
end_time:string,
}
export const apiAiUiJson = (file_name: AiUiJson) =>{
return http.post('/api/rs/get_ui_json', {file_name}).then((res: any) => {
return res;
}).catch((err: any) => {
console.log(err);
return '';
})
}
export const apiAiUiCreateJson = (file_name: AiUiJson) =>{
return http.post('/api/rs/db_to_ai_json', {file_name}).then((res: any) => {
return res;
}).catch((err: any) => {
console.log(err);
return '';
})
}

View File

@ -1,40 +0,0 @@
import http from "@/utils/axios.js";
const is_local_data = localStorage.getItem('isUseLocalData') === 't';
// user list 部分
export const apiDateCount = (wxid: string = '', start_time: number = 0, end_time: number = 0) => {
return http.post('/api/rs/date_count', {
'wxid': wxid,
'start_time': start_time,
'end_time': end_time,
}).then((res: any) => {
return res;
}).catch((err: any) => {
console.log(err);
return '';
})
}
export const apiTalkerCount = (top: number = 10, start_time: number = 0, end_time: number = 0) => {
return http.post('/api/rs/top_talker_count', {
'top': top,
'start_time': start_time,
'end_time': end_time,
}).then((res: any) => {
return res;
}).catch((err: any) => {
console.log(err);
return '';
})
}
export const apiWordcloud = (target: string = "") => {
return http.post('/api/rs/wordcloud', {
target: target,
}).then((res: any) => {
return res;
}).catch((err: any) => {
console.log(err);
return '';
})
}

View File

@ -1,100 +0,0 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
/* 可视化页面的css*/
:root {
--bg-ui-s-primary: #0f0e17;
--bg-ui-s-secondary: #1a1925;
--bg-ui-s-tertiary: #252336;
--text-ui-s-primary: #fffffe;
--text-ui-s-secondary: #a7a9be;
--accent-ui-s-primary: #ff8906;
--accent-ui-s-secondary: #f25f4c;
--accent-ui-s-tertiary: #e53170;
--accent-ui-s-blue: #3da9fc;
--accent-ui-s-purple: #7209b7;
--accent-ui-s-cyan: #00b4d8;
}
/* @media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
} */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@ -1,10 +0,0 @@
<template>
<el-icon>
<svg t="1704181424728" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="11271" width="200" height="200">
<path
d="M491.776 352c12.864 0 52.48-6.144 65.152-18.304a61.056 61.056 0 0 0 18.88-45.504 61.248 61.248 0 0 0-18.88-45.184 62.464 62.464 0 0 0-45.248-18.56 61.312 61.312 0 0 0-44.48 18.304 61.312 61.312 0 0 0-18.688 45.44c0 18.432 6.336 33.664 18.88 45.76 12.608 12.032 11.904 18.048 24.384 18.048zM512 32C246.912 32 32 246.848 32 512s214.912 480 480 480c265.152 0 480-214.848 480-480S777.152 32 512 32z m0 896a416 416 0 1 1 0-832 416 416 0 0 1 0 832z m39.488-278.528l-14.912 14.848 39.744-210.816c0.256-1.408-0.192-2.816-0.128-4.224v-0.64a30.592 30.592 0 0 0-9.408-22.72l-0.256-0.448c-0.192-0.192-0.448-0.192-0.64-0.384a33.28 33.28 0 0 0-12.992-7.424c-1.664-0.512-3.008-1.472-4.8-1.728-1.216-0.192-2.432 0.128-3.712 0.064l-3.136 0.064a34.24 34.24 0 0 0-29.504 17.216L425.472 519.488a33.152 33.152 0 1 0 47.04 46.976l14.848-14.848-39.68 210.816c-0.256 1.472 0.192 2.816 0.128 4.288v0.64a30.272 30.272 0 0 0 9.408 22.72l0.256 0.384 0.64 0.448a35.456 35.456 0 0 0 12.992 7.36c1.6 0.512 2.944 1.408 4.736 1.728 1.28 0.192 2.496-0.192 3.776-0.064l3.136-0.128a34.048 34.048 0 0 0 29.504-17.152l86.272-86.272a33.152 33.152 0 1 0-47.04-46.912z"
fill="" p-id="11272"></path>
</svg>
</el-icon>
</template>

View File

@ -1,13 +0,0 @@
<template>
<el-icon>
<svg t="1722526818775" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="1411" width="200" height="200">
<path
d="M129.792 887.168c-2.816 0-5.76-0.512-8.576-1.536-10.24-3.584-17.024-13.312-17.024-24.064V727.552H77.696C34.816 727.552 0 688.64 0 640.768V150.784C0 102.912 34.816 64 77.696 64h625.408C745.984 64 780.8 102.912 780.8 150.784v489.856c0 47.872-34.816 86.784-77.696 86.784H601.6c-14.08 0-25.6-11.52-25.6-25.6s11.52-25.6 25.6-25.6h101.504c14.336 0 26.496-16.384 26.496-35.584V150.784C729.6 131.456 717.44 115.2 703.104 115.2H77.696C63.36 115.2 51.2 131.456 51.2 150.784v489.856c0 19.328 12.16 35.584 26.496 35.584h52.096c14.08 0 25.6 11.52 25.6 25.6v87.808l84.864-103.936c4.864-6.016 12.16-9.472 19.84-9.472h163.328c14.08 0 25.6 11.52 25.6 25.6s-11.52 25.6-25.6 25.6H272.256l-122.624 150.144c-4.864 6.272-12.288 9.6-19.84 9.6z"
p-id="1412"></path>
<path
d="M928.896 960c-6.656 0-13.312-2.688-18.176-7.552l-79.36-79.872H546.688c-33.28 0-60.288-27.136-60.288-60.544V532.352c0-30.592 23.808-60.544 48.128-60.544h131.968c14.08 0 25.6 11.52 25.6 25.6s-11.52 25.6-25.6 25.6h-125.44c-1.536 2.176-3.456 5.76-3.456 9.344v279.68c0 5.12 4.096 9.344 9.088 9.344h295.296c6.784 0 13.312 2.688 18.176 7.552l43.136 43.392v-25.344c0-14.08 11.52-25.6 25.6-25.6h34.688c4.992 0 9.088-4.224 9.088-9.344V532.352c0-5.12-4.096-9.344-9.088-9.344h-119.808c-14.08 0-25.6-11.52-25.6-25.6s11.52-25.6 25.6-25.6h119.808c33.28 0 60.288 27.136 60.288 60.544v279.68c0 33.408-27.008 60.544-60.288 60.544h-9.088V934.4a25.6384 25.6384 0 0 1-25.6 25.6z"
p-id="1413"></path>
</svg>
</el-icon>
</template>

View File

@ -1,10 +0,0 @@
<template>
<el-icon>
<svg t="1713444801401" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="4641" width="200" height="200">
<path
d="M426.688 94.144a32 32 0 0 0-32 32v138.688h-288a32 32 0 0 0-32 32v170.688a32 32 0 0 0 32 32h32v353.792a32 32 0 0 0 32 32h682.624a32 32 0 0 0 32-32V499.52h32a32 32 0 0 0 32-32V296.832a32 32 0 0 0-32-32h-288V126.144a32 32 0 0 0-32-32H426.688z m394.624 727.168h-106.624v-97.792a32 32 0 1 0-64 0v97.792H544V723.2a32 32 0 1 0-64 0v98.176H373.312v-97.792a32 32 0 1 0-64 0v97.792H202.688v-320h618.624v320zM458.688 296.832V158.144h106.624v138.688a32 32 0 0 0 32 32h288v106.688H138.688V328.832h288a32 32 0 0 0 32-32z"
fill="#333333" p-id="4642"></path>
</svg>
</el-icon>
</template>

View File

@ -1,10 +0,0 @@
<template>
<el-icon>
<svg t="1722527189048" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="2087" width="200" height="200">
<path
d="M813.056 1024c-10.5472 0-21.1968-3.1744-29.184-9.4208L163.84 534.6304c-16.1792-12.4928-16.1792-32.768 0-45.2608L783.7696 9.4208c16.1792-12.4928 42.2912-12.4928 58.4704 0s16.1792 32.768 0 45.2608L251.4944 512l590.6432 457.4208c16.1792 12.4928 16.1792 32.768 0 45.2608-7.9872 6.144-18.5344 9.3184-29.0816 9.3184z"
p-id="2088"></path>
</svg>
</el-icon>
</template>

View File

@ -1,13 +0,0 @@
<template>
<el-icon>
<svg t="1704182542260" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="16755" width="200" height="200">
<path d="M896 0l128 0 0 128-128 0 0-128Z" fill="#1296db" p-id="16756"></path>
<path d="M0 0l704 0 0 128-704 0 0-128Z" fill="#1296db" p-id="16757"></path>
<path d="M896 448l128 0 0 128-128 0 0-128Z" fill="#1296db" p-id="16758"></path>
<path d="M0 448l704 0 0 128-704 0 0-128Z" fill="#1296db" p-id="16759"></path>
<path d="M896 896l128 0 0 128-128 0 0-128Z" fill="#1296db" p-id="16760"></path>
<path d="M0 896l704 0 0 128-704 0 0-128Z" fill="#1296db" p-id="16761"></path>
</svg>
</el-icon>
</template>

View File

@ -1,10 +0,0 @@
<template>
<el-icon>
<svg t="1722527233092" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="2249" width="200" height="200">
<path
d="M193.1264 1024c10.5472 0 21.1968-3.1744 29.184-9.4208l619.9296-479.9488c16.1792-12.4928 16.1792-32.768 0-45.2608L222.3104 9.4208C206.1312-3.072 180.0192-3.072 163.84 9.4208s-16.1792 32.768 0 45.2608L754.5856 512 163.84 969.4208c-16.1792 12.4928-16.1792 32.768 0 45.2608 8.0896 6.144 18.6368 9.3184 29.2864 9.3184z"
p-id="2250"></path>
</svg>
</el-icon>
</template>

View File

@ -1,20 +0,0 @@
<template>
<el-icon>
<svg t="1722526340718" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="3332" width="200" height="200">
<path
d="M394 108.1c-130.9 0-237 106.1-237 237s106.1 237 237 237 237-106.1 237-237-106.1-237-237-237z m0 391.3c-85.2 0-154.3-69.1-154.3-154.3S308.8 190.8 394 190.8s154.3 69.1 154.3 154.3S479.2 499.4 394 499.4z"
fill="#242424" p-id="3333"></path>
<path
d="M393.5 581c151.1 0 273.6 122.5 273.6 273.6H749c0-196.3-159.2-355.5-355.5-355.5S38 658.3 38 854.6h81.9c0-151.1 122.5-273.6 273.6-273.6z"
fill="#242424" p-id="3334"></path>
<path d="M78.9 855.4m-40.9 0a40.9 40.9 0 1 0 81.8 0 40.9 40.9 0 1 0-81.8 0Z" fill="#242424" p-id="3335"></path>
<path d="M708.1 855.4m-40.9 0a40.9 40.9 0 1 0 81.8 0 40.9 40.9 0 1 0-81.8 0Z" fill="#242424" p-id="3336"></path>
<path d="M663.4 541.2m-40.9 0a40.9 40.9 0 1 0 81.8 0 40.9 40.9 0 1 0-81.8 0Z" fill="#242424" p-id="3337"></path>
<path d="M643.1 149.2m-40.9 0a40.9 40.9 0 1 0 81.8 0 40.9 40.9 0 1 0-81.8 0Z" fill="#242424" p-id="3338"></path>
<path
d="M989 854.6c0-143.7-85.3-267.5-208-323.6 54.8-43.4 90-110.6 90-185.9 0-127.8-101.2-232-227.9-236.8v81.8c81 4.7 145.2 72.9 145.2 155 0 78.8-56.1 146.7-132.3 156.1-5.6 0.7-12.9 7.4-12.9 13v60.6c0-20 3.1-0.6 7.2-0.3l5 7c143.3 8.7 251.8 127.6 251.8 273.1h0.1v0.8c0 22.6 18.3 40.9 40.9 40.9S989 878 989 855.4v-0.8z"
fill="#242424" p-id="3339"></path>
</svg>
</el-icon>
</template>

View File

@ -1,25 +0,0 @@
<template>
<el-icon>
<!-- <svg t="1722527105629" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"-->
<!-- p-id="1917" width="200" height="200">-->
<!-- <path-->
<!-- d="M637.184 998.4h-485.12c-13.312 0-24.064-10.88-24.064-24.32V49.92c0-13.44 10.752-24.32 24.064-24.32s24.064 10.88 24.064 24.32v899.84h461.184c13.312 0 24.064 10.88 24.064 24.32-0.128 13.44-10.88 24.32-24.192 24.32z"-->
<!-- p-id="1918"></path>-->
<!-- <path-->
<!-- d="M872.064 779.52c-13.312 0-24.064-10.88-24.064-24.32V74.24H308.992v105.344h445.568c13.312 0 24.064 10.88 24.064 24.32s-10.752 24.32-24.064 24.32H285.056c-13.312 0-24.064-10.88-24.064-24.32V49.92c0-13.44 10.752-24.32 24.064-24.32h587.008C885.248 25.6 896 36.48 896 49.92V755.2c0 13.44-10.752 24.32-23.936 24.32z"-->
<!-- p-id="1919"></path>-->
<!-- <path-->
<!-- d="M695.936 382.336h-410.88c-13.312 0-24.064-10.88-24.064-24.32 0-13.44 10.752-24.32 24.064-24.32h410.88c13.312 0 24.064 10.88 24.064 24.32-0.128 13.44-10.88 24.32-24.064 24.32zM578.56 536.32H285.056c-13.312 0-24.064-10.88-24.064-24.32s10.752-24.32 24.064-24.32H578.56c13.312 0 24.064 10.88 24.064 24.32s-10.88 24.32-24.064 24.32zM461.184 690.304H285.056c-13.312 0-24.064-10.88-24.064-24.32s10.752-24.32 24.064-24.32h176.128c13.312 0 24.064 10.88 24.064 24.32s-10.88 24.32-24.064 24.32zM638.72 996.48c-3.456 0-6.912-0.768-10.24-2.176a25.9328 25.9328 0 0 1-15.232-23.68V755.2c0-13.44 10.752-24.32 24.064-24.32h232.832c9.984 0 19.456 6.656 23.04 16 3.712 9.728 1.152 20.608-6.4 27.648l-230.656 215.04c-4.992 4.48-11.136 6.912-17.408 6.912z m22.528-216.96v139.136L810.496 779.52H661.248z"-->
<!-- p-id="1920"></path>-->
<!-- </svg>-->
<svg t="1722527337412" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="2411" width="200" height="200">
<path
d="M512 985.6c-7.296 0-14.464-3.072-19.584-9.088l-384-454.272c-92.928-109.952-92.928-289.024 0-398.976C154.752 68.48 216.576 38.4 282.624 38.4s128 30.08 174.208 84.864l146.304 173.184a25.6 25.6 0 0 1-3.072 36.096 25.6 25.6 0 0 1-36.096-3.072L417.792 156.288C381.44 113.28 333.44 89.6 282.624 89.6c-50.688 0-98.688 23.68-135.04 66.688-77.568 91.776-77.568 241.152 0 332.928l384 454.272A25.6 25.6 0 0 1 512 985.6z"
p-id="2412"></path>
<path
d="M512 985.6c-5.888 0-11.648-2.048-16.512-6.016-10.752-9.088-12.16-25.216-3.072-36.096l384-454.272c77.568-91.776 77.568-241.152 0-332.928C840.064 113.28 792.064 89.6 741.376 89.6c-50.816 0-98.688 23.68-135.04 66.688-9.088 10.752-25.216 12.16-36.096 3.072s-12.16-25.216-3.072-36.096C613.376 68.48 675.2 38.4 741.376 38.4c66.048 0 128 30.08 174.208 84.864 92.928 109.952 92.928 289.024 0 398.976l-384 454.272c-5.12 6.016-12.288 9.088-19.584 9.088z"
p-id="2413"></path>
</svg>
</el-icon>
</template>

View File

@ -1,13 +0,0 @@
<template>
<el-icon>
<svg t="1704181514907" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="13165" width="200" height="200">
<path
d="M512 120C296 120 120 296 120 512s176 392 392 392 392-176 392-392S728 120 512 120z m0 736c-190.4 0-344-153.6-344-344S321.6 168 512 168 856 321.6 856 512 702.4 856 512 856z"
p-id="13166"></path>
<path
d="M467.2 656h94.4v91.2h-94.4zM624 313.6c-32-22.4-70.4-33.6-118.4-33.6-35.2 0-65.6 8-91.2 24-38.4 25.6-59.2 67.2-62.4 126.4h91.2c0-17.6 4.8-33.6 14.4-49.6 9.6-16 27.2-24 51.2-24 24 0 41.6 6.4 51.2 19.2 9.6 12.8 14.4 27.2 14.4 43.2 0 14.4-4.8 27.2-12.8 38.4-4.8 6.4-11.2 12.8-17.6 19.2l-22.4 17.6C499.2 512 484.8 528 480 540.8s-8 38.4-9.6 73.6h84.8c0-16 1.6-28.8 4.8-36.8 3.2-12.8 11.2-24 24-33.6l22.4-17.6c22.4-17.6 38.4-32 46.4-43.2 12.8-19.2 20.8-41.6 20.8-68.8-3.2-44.8-17.6-76.8-49.6-100.8z"
p-id="13167"></path>
</svg>
</el-icon>
</template>

View File

@ -1,5 +0,0 @@
<template>
<el-icon>
<svg t="1722526872404" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1580" width="200" height="200"><path d="M852.096 1024H612.224c-16.128 0-29.312-14.336-29.312-32s13.056-32 29.312-32h210.56V471.296c0-17.664 13.056-32 29.312-32h62.592L518.912 73.6 111.104 439.296h60.8c16.128 0 29.312 14.336 29.312 32V960H409.6c16.128 0 29.312 14.336 29.312 32S425.856 1024 409.6 1024H171.904c-16.128 0-29.312-14.336-29.312-32V503.296H29.312c-12.416 0-23.424-8.448-27.52-21.248-4.096-12.672-0.512-27.008 8.96-35.584L500.608 7.296c10.88-9.728 26.624-9.6 37.376 0.256l475.52 439.296c9.344 8.704 12.8 22.784 8.576 35.456-4.224 12.672-15.232 20.992-27.52 20.992h-113.408V992c0.256 17.664-12.928 32-29.056 32z" p-id="1581"></path><path d="M612.224 1024h-0.256c-16.128-0.128-29.184-14.592-29.056-32.256l1.92-277.888H453.504c-16.128 0-29.312-14.336-29.312-32s13.056-32 29.312-32H614.4c7.808 0 15.232 3.456 20.736 9.472 5.504 6.016 8.576 14.208 8.448 22.784l-2.176 310.144c0 17.536-13.056 31.744-29.184 31.744z" p-id="1582"></path></svg>
</el-icon>
</template>

View File

@ -1,19 +0,0 @@
<template>
<el-icon>
<svg t="1722526765465" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="6678" width="200" height="200">
<path
d="M512 1024C229.6832 1024 0 794.3168 0 512S229.6832 0 512 0s512 229.6832 512 512-229.6832 512-512 512z m0-958.5664C263.168 65.4336 60.7232 265.728 60.7232 512S263.168 958.5664 512 958.5664 963.2768 758.272 963.2768 512 760.832 65.4336 512 65.4336z"
p-id="6679"></path>
<path
d="M512 704.512c-105.2672 0-190.8736-86.3232-190.8736-192.512 0-106.0864 85.6064-192.4096 190.8736-192.4096S702.8736 405.9136 702.8736 512 617.2672 704.512 512 704.512z m0-319.5904c-71.7824 0-130.2528 57.0368-130.2528 127.0784S440.1152 639.0784 512 639.0784c71.7824 0 130.2528-57.0368 130.2528-127.0784S583.7824 384.9216 512 384.9216z"
p-id="6680"></path>
<path
d="M961.536 384.9216H512c-16.7936 0-30.3104-14.6432-30.3104-32.6656s13.6192-32.6656 30.3104-32.6656h449.536c16.7936 0 30.3104 14.6432 30.3104 32.6656s-13.5168 32.6656-30.3104 32.6656zM672.5632 992.0512c-16.7936 0-30.3104-14.6432-30.3104-32.6656V512c0-18.0224 13.6192-32.6656 30.3104-32.6656 16.7936 0 30.3104 14.6432 30.3104 32.6656v447.3856c0 18.0224-13.5168 32.6656-30.3104 32.6656zM512 704.512H78.5408c-16.7936 0-30.3104-14.6432-30.3104-32.6656s13.6192-32.6656 30.3104-32.6656H512c16.7936 0 30.3104 14.6432 30.3104 32.6656S528.7936 704.512 512 704.512zM351.4368 544.6656c-16.7936 0-30.3104-14.6432-30.3104-32.6656V80.5888c0-18.0224 13.6192-32.6656 30.3104-32.6656 16.7936 0 30.3104 14.6432 30.3104 32.6656V512c0 18.0224-13.5168 32.6656-30.3104 32.6656z"
p-id="6681"></path>
<path
d="M385.024 445.1328c-8.192 0-16.2816-3.584-22.3232-10.5472-11.3664-13.312-10.6496-33.9968 1.7408-46.1824L682.2912 72.0896c12.288-12.1856 31.5392-11.3664 42.9056 1.8432 11.3664 13.312 10.6496 33.9968-1.7408 46.1824L405.6064 436.4288c-5.8368 5.8368-13.2096 8.704-20.5824 8.704zM945.4592 745.984c-7.3728 0-14.7456-2.8672-20.5824-8.704L607.0272 420.9664c-12.288-12.288-13.1072-32.9728-1.7408-46.1824 11.3664-13.312 30.5152-14.1312 42.9056-1.8432l317.8496 316.3136c12.288 12.288 13.1072 32.9728 1.7408 46.1824-5.9392 6.9632-14.1312 10.5472-22.3232 10.5472zM305.5616 976.0768c-8.192 0-16.2816-3.4816-22.3232-10.5472-11.3664-13.312-10.6496-33.9968 1.7408-46.1824l306.4832-305.0496c12.288-12.1856 31.5392-11.4688 42.9056 1.8432 11.3664 13.312 10.6496 33.9968-1.7408 46.1824L326.144 967.3728c-5.8368 5.8368-13.2096 8.704-20.5824 8.704zM385.024 671.0272c-7.3728 0-14.7456-2.8672-20.5824-8.704L57.9584 357.376c-12.288-12.288-13.1072-32.9728-1.7408-46.1824 11.3664-13.312 30.5152-14.1312 42.9056-1.8432L405.6064 614.4c12.288 12.288 13.1072 32.9728 1.7408 46.1824-6.0416 6.9632-14.1312 10.4448-22.3232 10.4448z"
p-id="6682"></path>
</svg>
</el-icon>
</template>

View File

@ -1,13 +0,0 @@
<template>
<el-icon>
<svg t="1704181557524" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="14155" width="200" height="200">
<path
d="M913.94086 431.828053c-4.878101-25.783223-20.693298-42.455951-40.947598-42.455951l-3.511987 0c-54.731532 0-99.248422-44.515866-99.248422-99.262748 0-17.294898 8.299013-37.015032 8.619308-37.798884 10.097986-22.722514 2.349511-50.567699-18.078751-64.859193l-102.701057-57.183374-1.509377-0.738827c-20.545942-8.909927-48.667419-3.207042-63.987337 12.753465-11.086499 11.434423-49.306986 44.037982-78.471213 44.037982-29.543873 0-67.849294-33.257451-78.992075-44.908816-15.293311-16.077164-43.12417-22.112624-63.902402-13.218046l-106.35733 58.272171-1.596358 1.016143c-20.430308 14.234189-28.207435 42.078351-18.165732 64.713884 0.346901 0.827855 8.676613 20.387329 8.676613 37.914518 0 54.746882-44.51689 99.262748-99.247398 99.262748l-4.149507 0c-19.617803 0-35.434024 16.671705-40.309054 42.455951-0.363274 1.814322-8.590656 45.446052-8.590656 80.429821 0 34.938744 8.227382 78.555124 8.590656 80.355119 4.875031 25.799596 20.691251 42.48665 40.946574 42.48665l3.511987 0c54.730509 0 99.247398 44.51689 99.247398 99.247398 0 17.411555-8.328689 37.058011-8.647961 37.812187-10.069333 22.766516-2.349511 50.567699 18.021445 64.787562l100.756775 56.531528 1.538029 0.696872c20.836561 9.17087 49.01432 3.191692 64.250326-13.464663 14.07353-15.207353 52.207036-46.782489 80.20981-46.782489 30.354332 0 69.445652 35.347043 80.706113 47.76691 10.387581 11.376095 26.349111 18.22713 42.687218 18.22713 7.631818 0 14.857383-1.511423 21.474081-4.354168l104.4724-57.574277 1.538029-0.989537c20.428262-14.276145 28.206412-42.077328 18.138102-64.727187-0.348947-0.842181-8.677637-20.402679-8.677637-37.928844 0-54.730509 44.51689-99.247398 99.248422-99.247398l4.093225 0c19.644409 0 35.488259-16.687054 40.365336-42.48665 0.347924-1.799996 8.588609-45.416376 8.588609-80.355119C922.529469 477.274104 914.288784 433.642374 913.94086 431.828053M862.982258 512.257873c0 22.605857-4.498454 51.655474-6.559393 63.785745-82.09781 6.732331-145.738245 75.335802-145.738245 158.303422 0 23.419386 7.430226 45.851281 11.377118 56.169277l-89.12076 49.216935c-4.38282-4.584412-17.325597-17.644869-34.939767-30.762631-30.93557-22.925129-60.595077-34.635845-88.106664-34.635845-27.278273 0-56.703443 11.420097-87.493703 34.05563-17.528212 12.768815-30.296003 25.479301-34.765805 30.18037l-85.724407-47.997154c4.179183-10.839883 11.405771-32.982182 11.405771-56.226582 0-82.96762-63.640436-151.571091-145.70857-158.303422-2.089591-12.130272-6.588045-41.178865-6.588045-63.785745 0-22.650883 4.498454-51.713802 6.588045-63.844074 82.068134-6.718005 145.70857-75.335802 145.70857-158.303422 0-23.288402-7.429203-45.792952-11.376095-56.095599l91.325985-50.190099c3.975545 3.976568 17.005302 16.730033 34.82311 29.411867 30.355355 21.663392 59.260685 32.633235 86.016049 32.633235 26.494421 0 55.19509-10.766205 85.346807-32.009018 17.963117-12.623505 30.964222-25.203008 34.736129-28.757974l87.900979 48.825009c-3.975545 10.244318-11.405771 32.676214-11.405771 56.18258 0 82.96762 63.640436 151.585417 145.738245 158.303422C858.483804 460.5717 862.982258 489.7523 862.982258 512.257873"
fill="#231F20" p-id="14156"></path>
<path
d="M510.215866 365.633445c-80.530105 0-146.056494 65.527412-146.056494 146.057517 0 80.543408 65.527412 146.043191 146.056494 146.043191 80.530105 0 146.057517-65.499783 146.057517-146.043191C656.273383 431.160857 590.74597 365.633445 510.215866 365.633445M596.725148 511.690962c0 47.693232-38.799678 86.491887-86.509283 86.491887-47.708582 0-86.479607-38.798655-86.479607-86.491887 0-47.665603 38.771025-86.479607 86.479607-86.479607C557.925471 425.212378 596.725148 464.025359 596.725148 511.690962"
fill="#231F20" p-id="14157"></path>
</svg>
</el-icon>
</template>

View File

@ -1,13 +0,0 @@
<template>
<el-icon>
<svg t="1704181237272" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="10274" width="200" height="200">
<path
d="M833.6 867.2H192.4c-18.5 0-33.4-15-33.4-33.4V192.6c0-18.5 15-33.4 33.4-33.4s33.4 15 33.4 33.4v607.7h607.7c18.5 0 33.4 15 33.4 33.4 0.1 18.5-14.9 33.5-33.3 33.5z"
fill="#202A35" p-id="10275"></path>
<path
d="M281.6 711.1c-8.6 0-17.1-3.3-23.6-9.8-13.1-13.1-13.1-34.2 0-47.3l164.5-164.5c13-13 34-13 47-0.3l98.3 96.1 171.8-181.6c12.7-13.4 33.9-14 47.3-1.3 13.4 12.7 14 33.9 1.3 47.3L593 656c-6.2 6.5-14.7 10.3-23.6 10.5-8.8 0-17.6-3.3-24-9.5l-99-96.8-141.1 141.1c-6.5 6.5-15.1 9.8-23.7 9.8z"
fill="#202A35" p-id="10276"></path>
</svg>
</el-icon>
</template>

View File

@ -1,10 +0,0 @@
<template>
<el-icon>
<svg t="1704181120854" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="9265" width="200" height="200">
<path
d="M166.2976 995.1232c-36.2496 0-70.2464-14.1312-95.8464-39.7312-52.8384-52.8384-52.8384-139.0592 0-191.8976l242.4832-242.4832c6.7584-6.7584 15.7696-10.4448 25.3952-10.4448s18.6368 3.6864 25.3952 10.4448c2.048 2.048 4.7104 3.072 7.3728 3.072 1.6384 0 4.5056-0.4096 6.9632-2.8672 2.048-2.048 3.2768-4.7104 3.2768-7.3728 0-1.4336-0.4096-4.5056-2.8672-6.9632-6.9632-6.9632-10.6496-15.7696-10.6496-25.3952s3.6864-18.6368 10.4448-25.3952l75.9808-75.9808c13.9264-13.9264 36.6592-13.9264 50.5856 0l28.8768 28.8768L804.6592 137.6256l12.4928-46.2848c4.7104-17.408 17.8176-30.72 35.4304-35.84l74.5472-21.0944c13.1072-4.3008 27.0336-1.024 36.864 8.6016l18.6368 18.6368c9.8304 9.8304 13.1072 23.9616 8.3968 37.0688l-21.0944 74.1376c-5.12 17.408-18.0224 30.3104-35.4304 35.4304h-0.4096l-46.2848 12.4928-270.9504 271.1552 28.8768 28.8768c6.7584 6.7584 10.4448 15.7696 10.4448 25.3952s-3.6864 18.6368-10.4448 25.3952l-75.9808 75.9808c-6.7584 6.7584-15.7696 10.4448-25.3952 10.4448s-18.6368-3.6864-25.3952-10.4448c-2.048-2.048-4.5056-3.072-7.3728-3.072-1.6384 0-4.5056 0.4096-6.9632 2.8672l-0.2048 0.2048c-2.4576 2.4576-2.8672 5.5296-2.8672 7.168s0.4096 4.5056 2.8672 7.168c14.1312 14.1312 14.1312 36.864 0.2048 50.7904L262.3488 955.392c-25.8048 25.6-59.8016 39.7312-96.0512 39.7312z m172.2368-441.7536L99.5328 792.3712c-36.864 36.864-36.864 97.0752 0 134.144 17.8176 17.8176 41.5744 27.8528 66.7648 27.8528h0.2048c25.1904 0 49.152-9.8304 66.9696-27.8528l239.0016-239.2064c-7.5776-9.0112-11.6736-20.2752-11.6736-32.3584 0-13.7216 5.3248-26.624 15.1552-36.2496 9.6256-9.6256 22.3232-14.9504 35.84-15.1552h0.2048c11.8784 0 23.3472 4.096 32.3584 11.6736l68.8128-68.8128-54.0672-54.4768L866.5088 183.9104l56.7296-15.36c3.8912-1.2288 6.3488-3.4816 7.3728-7.168l20.8896-73.1136-14.1312-14.1312-73.3184 20.8896c-3.6864 1.024-6.144 3.4816-7.168 7.168l-15.36 56.9344-307.8144 307.4048-54.272-54.272-68.8128 69.0176c7.3728 9.0112 11.4688 20.2752 11.6736 32.1536 0 13.7216-5.3248 26.624-15.1552 36.4544-9.4208 9.6256-22.3232 14.9504-35.84 15.1552h-0.2048c-12.0832-0.2048-23.3472-4.3008-32.5632-11.6736zM826.7776 994.9184c-42.5984 0-85.1968-16.1792-117.76-48.7424-35.6352-35.6352-53.0432-84.7872-47.9232-134.9632 1.4336-12.9024-2.6624-23.9616-11.6736-33.1776l-71.0656-71.68 126.5664-126.5664 71.4752 71.4752c9.216 9.216 20.2752 13.1072 33.1776 11.6736 49.9712-5.3248 99.328 12.0832 134.9632 47.9232 31.3344 31.3344 48.7424 73.1136 48.7424 117.76 0 44.4416-17.2032 86.2208-48.7424 117.76-32.5632 32.3584-75.1616 48.5376-117.76 48.5376zM636.1088 706.56l42.3936 42.5984c17.8176 17.8176 26.2144 41.3696 23.3472 66.3552-3.8912 37.888 9.216 74.9568 36.0448 101.7856 48.9472 48.9472 128.6144 48.9472 177.5616 0 23.7568-23.7568 36.6592-55.0912 36.6592-88.6784s-13.1072-65.1264-36.6592-88.6784c-27.0336-27.0336-64.1024-40.1408-101.7856-36.0448-24.9856 2.6624-48.5376-5.7344-66.3552-23.552l-42.3936-42.3936-68.8128 68.608zM311.7056 431.9232l-28.4672-28.0576c-9.216-9.216-20.48-12.4928-32.768-9.8304-49.3568 10.24-100.9664-0.4096-141.9264-29.2864-52.4288-36.6592-81.3056-96.4608-77.4144-160.3584v-0.4096c1.024-12.6976 8.192-23.3472 19.6608-28.8768 11.4688-5.5296 24.576-4.3008 35.0208 3.072l88.6784 62.0544 49.152-70.4512-88.8832-62.2592c-10.6496-7.5776-15.9744-19.6608-14.5408-32.1536 1.4336-12.4928 9.216-22.7328 20.48-27.8528 58.9824-25.3952 125.1328-18.6368 177.3568 18.0224 60.0064 41.984 89.088 115.3024 73.9328 186.7776-2.4576 12.4928 0.8192 23.7568 9.8304 32.768L430.08 313.344l-118.3744 118.5792zM258.2528 352.256c19.6608 0 38.5024 7.5776 53.0432 21.9136l60.8256-60.8256c-18.2272-18.6368-25.3952-43.2128-20.2752-69.2224 11.6736-55.5008-10.8544-112.4352-57.344-144.9984-37.2736-26.0096-83.7632-32.5632-126.5664-18.2272l84.1728 58.9824c15.7696 11.0592 19.456 32.768 8.3968 48.3328l-56.5248 80.6912c-5.3248 7.5776-13.312 12.6976-22.3232 14.336-9.216 1.6384-18.432-0.4096-26.0096-5.7344l-83.968-58.9824c0.8192 45.056 22.9376 86.4256 60.416 112.64 32.3584 22.7328 71.2704 30.72 109.9776 22.7328 5.3248-1.2288 10.8544-1.6384 16.1792-1.6384z m-78.848-108.3392z m-117.3504-32.1536z m165.0688-47.104z"
fill="#333333" p-id="9266"></path>
</svg>
</el-icon>
</template>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

Before

Width:  |  Height:  |  Size: 276 B

View File

@ -1,35 +0,0 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}

View File

@ -1,54 +0,0 @@
<script setup lang="ts">
import ChatRecprdsHeader from '@/components/chat/ChatRecprdsHeader.vue';
import ChatRecordsMain from '@/components/chat/ChatRecordsMain.vue';
import {ref, defineProps, nextTick, onMounted, watch} from 'vue';
import http from "@/utils/axios.js";
import ChatExportMain from "@/components/chatBackup/ChatExportMain.vue";
import {apiMsgCount} from "@/api/chat";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
//
const is_export = ref(false);
const onExport = (exporting: boolean) => {
is_export.value = exporting;
}
// end
// start wxid
const init = () => {
is_export.value = false;
}
watch(() => props.wxid, async (newVal, oldVal) => {
if (newVal !== oldVal) {
init();
}
});
onMounted(() => {
init();
});
// end wxid
</script>
<template>
<el-container>
<el-header style="height: 40px; max-height: 40px; width: 100%;background-color: #d2d2fa;padding-top: 5px;">
<ChatRecprdsHeader :wxid="wxid" @exporting="onExport"/>
</el-header>
<el-main style="height: calc(100vh - 40px);padding: 0;margin: 0;background-color: #f5f5f5;">
<ChatExportMain v-if="is_export" :wxid="wxid"/>
<ChatRecordsMain v-else :wxid="wxid"/>
</el-main>
</el-container>
</template>
<style scoped>
</style>

View File

@ -1,346 +0,0 @@
<script setup lang="ts">
import {defineProps, ref, onMounted, watch, nextTick, defineExpose} from "vue";
import http from '@/utils/axios.js';
import MessageText from '@/components/chat/message/MessageText.vue';
import MessageImg from '@/components/chat/message/MessageImg.vue';
import MessageVideo from '@/components/chat/message/MessageVideo.vue';
import MessageAudio from '@/components/chat/message/MessageAudio.vue';
import MessageFile from '@/components/chat/message/MessageFile.vue';
import MessageEmoji from '@/components/chat/message/MessageEmoji.vue'
import MessageOther from "@/components/chat/message/MessageOther.vue";
import {apiMsgCountSolo, apiMsgs, apiMyWxid} from "@/api/chat";
import type {msg, User, UserList} from "@/utils/common_utils";
import {api_audio, api_img, api_video} from "@/api/base";
// v3 https://vue3-infinite-loading.netlify.app/api/props.html#distance
import InfiniteLoading from "v3-infinite-loading";
import "v3-infinite-loading/lib/style.css";
// props
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
//
const messages = ref<msg[]>([]);
const userlist = ref<UserList>({});
const msg_loading = ref(false);
const my_wxid = ref('');
const start = ref(0);
const limit = ref(100);
const min_id = ref(0);
const max_id = ref(0);
const msg_count = ref(0);
//
const _direction = (message: any) => {
if (message.talker == '我') {
message.talker = my_wxid.value;
}
const sendname = (message: msg) => {
const user = userlist.value[message.talker];
return user?.remark || user?.nickname || user?.account || message.talker;
}
return `${sendname(message)} [${message.type_name}] ${message.CreateTime}`;
}
const get_head_url = (message: any) => {
if (message.talker == '我') {
message.talker = my_wxid.value;
}
if (!userlist.value.hasOwnProperty(message.talker)) {
return '';
}
return api_img(userlist.value[message.talker].headImgUrl);
}
// END
//
const fetchData = async (scroll: String = '') => {
if (msg_loading.value) {
console.log("正在获取消息,请稍后再试!")
return;
}
if (props.wxid == '') {
console.log("wxid 为空, 请检查!")
return;
}
try {
msg_loading.value = true;
if (start.value < 0) {
start.value = 0;
}
console.log('fetchData', props.wxid, start.value, limit.value)
const body_data = await apiMsgs(props.wxid, start.value, limit.value);
// messages.value = [];
// messages.value = body_data.msg_list.concat(messages.value);
messages.value = body_data.msg_list
if (messages.value.length == 0) {
msg_loading.value = false;
return body_data;
}
userlist.value = Object.assign(userlist.value, body_data.user_list);
//
messages.value = messages.value.filter((item, index, array) => {
return index === 0 || item.id !== array[index - 1].id;
});
//
messages.value.sort((a, b) => {
return a.id - b.id
});
min_id.value = messages.value[0].id;
max_id.value = messages.value[messages.value.length - 1].id;
if (scroll == "top") {
scrollToId(messages.value[0].id);
} else if (scroll == "bottom") {
scrollToId(messages.value[messages.value.length - 1].id);
}
msg_loading.value = false;
return body_data;
} catch (error) {
msg_loading.value = false;
console.error('Error fetching data:', error);
return [];
}
};
//
//
// END
// userData username
const init = async () => {
try {
messages.value = [];
userlist.value = {};
msg_loading.value = false;
limit.value = limit.value || 100;
min_id.value = 0;
max_id.value = 0;
my_wxid.value = await apiMyWxid();
msg_count.value = await apiMsgCountSolo(props.wxid);
if (msg_count.value <= 0) {
return;
}
//
console.log('msg_count.value', msg_count.value, limit.value)
start.value = Math.floor((msg_count.value - 1) / limit.value) * limit.value || 0
await fetchData("bottom");
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
};
watch(() => props.wxid, (newUsername, oldUsername) => {
console.log('username changed ', oldUsername, newUsername);
init();
});
watch(() => start.value, (newVal, oldVal) => {
console.log('msg start changed ', oldVal, newVal);
if (newVal > oldVal) {
//
fetchData("top");
} else {
fetchData("bottom");
}
});
onMounted(() => {
init();
});
//
const loadMoreTop = async ($state: any) => {
try {
if (start.value == 0) {
$state.complete();
return;
}
start.value = start.value - limit.value;
if (start.value < 0) {
start.value = 0;
}
// await fetchData("bottom");
$state.loaded();
} catch (error) {
console.log("Error fetching Top data:", error)
$state.error();
}
};
const loadMoreBottom = async ($state: any) => {
try {
if (start.value + limit.value > msg_count.value) {
$state.complete();
return;
}
start.value = start.value + limit.value;
// await fetchData("top");
$state.loaded();
} catch (error) {
console.log("Error fetching data:", error)
$state.error();
}
};
// END
//
const handleLimitChange = (val: number) => {
limit.value = val;
fetchData();
};
const handleCurrentChange = (val: number) => {
start.value = (val - 1) * limit.value;
// fetchData();
};
// END
// id
const scrollToId = (id: number) => {
nextTick(() => {
const element = document.getElementById(`message-${id}`);
console.log(`scrollToId message-${id}`)
if (element) {
element.scrollIntoView({
behavior: 'instant',
block: 'center'
});
}
})
}
// END id
</script>
<template>
<div id="chat" v-if="messages.length>0 && msg_count>0">
<el-container class="chat-records-main-container">
<el-main class="chat-records-main-main">
<!-- <div class="infinite-container">-->
<!-- <InfiniteLoading @infinite="loadMoreTop" :top="true" :firstload="false">-->
<!-- <template #spinner>-->
<!-- <span class="spinner-text">加载中...</span>-->
<!-- </template>-->
<!-- <template #complete>-->
<!-- <span class="complete-text">没有更多啦</span>-->
<!-- </template>-->
<!-- <template #error="{ retry }">-->
<!-- <button @click="retry" class="retry-button">错误</button>-->
<!-- </template>-->
<!-- </InfiniteLoading>-->
<!-- </div>-->
<div class="message" v-for="(message,index) in messages" :key="index" :id="`message-${message.id}`">
<!-- 文字消息 -->
<MessageText v-if="message.type_name == '文本'" :is_sender="message.is_sender"
:direction="_direction(message)" :headUrl="get_head_url(message)"
:content="message.msg"></MessageText>
<!-- 图片消息 -->
<MessageImg v-else-if="message.type_name == '图片'" :is_sender="message.is_sender"
:direction="_direction(message)" :headUrl="get_head_url(message)"
:src="api_img(message.src)"></MessageImg>
<!-- 表情消息 -->
<MessageEmoji v-else-if="message.type_name == '动画表情'" :is_sender="message.is_sender"
:direction="_direction(message)" :headUrl="get_head_url(message)"
:src="api_img(message.src)"></MessageEmoji>
<!-- 视频消息 -->
<MessageVideo v-else-if="message.type_name == '视频'" :is_sender="message.is_sender"
:direction="_direction(message)" :headUrl="get_head_url(message)"
:src="api_video(message.src)"></MessageVideo>
<!-- 文件消息 -->
<MessageFile v-else-if="message.type_name == '文件'" :is_sender="message.is_sender"
:direction="_direction(message)" :headUrl="get_head_url(message)"
:src="message.src"></MessageFile>
<!-- 语音消息 -->
<MessageAudio v-else-if="message.type_name == '语音'" :is_sender="message.is_sender"
:direction="_direction(message)" :headUrl="get_head_url(message)"
:src="api_audio(message.src)"
:msg="message.msg"></MessageAudio>
<!-- 其他消息 -->
<MessageOther v-else :is_sender="message.is_sender" :direction="_direction(message)"
:headUrl="get_head_url(message)" :content="message.msg"></MessageOther>
</div>
<!-- <div class="infinite-container">-->
<!-- <InfiniteLoading @infinite="loadMoreBottom" :top="false" :firstload="false">-->
<!-- <template #spinner>-->
<!-- <span class="spinner-text">加载中...</span>-->
<!-- </template>-->
<!-- <template #complete>-->
<!-- <span class="complete-text">没有更多啦</span>-->
<!-- </template>-->
<!-- <template #error="{ retry }">-->
<!-- <button @click="retry" class="retry-button">错误</button>-->
<!-- </template>-->
<!-- </InfiniteLoading>-->
<!-- </div>-->
</el-main>
<el-footer height="20px" class="chat-records-main-footer">
<el-pagination background small layout="sizes, prev, pager, next, jumper" :total="msg_count"
:page-size="limit" :page-sizes="[50,100, 200, 300, 500]" @size-change="handleLimitChange"
:current-page="Math.floor(start / limit + 1)" @current-change="handleCurrentChange"
/>
</el-footer>
</el-container>
</div>
<el-skeleton v-else-if="messages.length<=0 && msg_count>0" :rows="30" animated/>
<el-empty description="无记录" v-else/>
</template>
<style scoped>
#chat {
position: relative;
width: 100%;
height: calc(100% - 15px);
display: flex;
flex-direction: column;
.chat-records-main-container {
height: calc(100% - 15px);
width: 100%;
display: flex;
flex-direction: column;
.chat-records-main-main {
padding: 0;
margin: 0;
.message:last-of-type {
margin-bottom: 0px;
}
}
.chat-records-main-footer {
display: grid;
place-items: center; /* 居中对齐 */
padding: 0;
margin: 0;
}
}
}
.infinite-container {
display: grid;
place-items: center; /* 居中对齐 */
.spinner-text,
.complete-text,
.retry-button {
font-size: 16px;
color: #9d09f3;
}
}
</style>

View File

@ -1,201 +0,0 @@
<script setup lang="ts">
import {defineEmits, defineProps, nextTick, onMounted, ref, watch} from 'vue';
import http from "@/utils/axios.js";
import {ElTable, ElNotification, ElMessage, ElMessageBox} from "element-plus";
import {apiMsgCount, apiMsgCountSolo, apiRealTime, apiUserList} from "@/api/chat";
import {gen_show_name, type User} from "@/utils/common_utils";
import UserInfoShow from "@/components/chat/components/UserInfoShow.vue";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
const msg_count = ref<number>(0);
const userinfo = ref<User>({
wxid: '',
nOrder: 0,
nUnReadCount: 0,
strNickName: '',
nStatus: 0,
nIsSend: 0,
strContent: '',
nMsgLocalID: 0,
nMsgStatus: 0,
nTime: '',
nMsgType: 0,
nMsgSubType: 0,
nickname: '',
remark: '',
account: '',
describe: '',
headImgUrl: '',
ExtraBuf: {
"个性签名": "",
"企微属性": "",
"公司名称": "",
"国": "",
"备注图片": "",
"备注图片2": "",
"市": "",
"性别[1男2女]": 0,
"手机号": "",
"朋友圈背景": "",
"省": ""
},
LabelIDList: [],
extra: null
});
// START
const req_user_info = async () => {
//
try {
const body_data = await apiUserList("", [props.wxid]);
userinfo.value.wxid = props.wxid;
userinfo.value.remark = body_data[props.wxid]?.remark;
userinfo.value.account = body_data[props.wxid]?.account;
userinfo.value.describe = body_data[props.wxid]?.describe;
userinfo.value.headImgUrl = body_data[props.wxid]?.headImgUrl;
userinfo.value.nickname = body_data[props.wxid]?.nickname;
userinfo.value.LabelIDList = body_data[props.wxid]?.LabelIDList;
userinfo.value.ExtraBuf = body_data[props.wxid]?.ExtraBuf;
userinfo.value.extra = body_data[props.wxid]?.extra;
return body_data;
} catch (error) {
console.error('Error fetching data wxid2user :', error);
return [];
}
}
const req_msg_count = async () => {
try {
msg_count.value = 0;
const body_data = await apiMsgCountSolo(props.wxid);
msg_count.value = body_data || 0;
return body_data;
} catch (error) {
console.error('Error fetching data msg_count:', error);
return [];
}
}
// END
// START
const init = () => {
is_export.value = false;
req_user_info();
req_msg_count();
}
onMounted(() => {
console.log('ChatRecprdsHeader onMounted', props.wxid)
init();
});
watch(() => props.wxid, async (newVal, oldVal) => {
if (newVal !== oldVal) {
init();
}
});
// END
// START
const is_show_more = ref(false);
// START
const is_getting_real_time_msg = ref(false);
const get_real_time_msg = async () => {
if (is_getting_real_time_msg.value) {
console.log("正在获取实时消息,请稍后再试!")
return;
}
is_getting_real_time_msg.value = true;
try {
const body_data = await apiRealTime();
is_getting_real_time_msg.value = false;
return body_data;
} catch (error) {
is_getting_real_time_msg.value = false;
return [];
}
}
// END
// START
const is_export = ref(false);
const emits = defineEmits(['exporting']);
const export_button = (val: boolean) => {
// is_export
emits('exporting', val);
is_export.value = val;
}
// END
</script>
<template>
<el-row :gutter="5" style="width: 100%;">
<el-col :span="6" style="white-space: nowrap;">
<el-text class="label_color mx-1" truncated>wxid:</el-text>&ensp;
<el-text class="data_color mx-1" truncated :title="userinfo?.wxid">{{ userinfo?.wxid }}</el-text>
</el-col>
<el-col :span="6" style="white-space: nowrap;">
<el-text class="label_color mx-1" truncated>名称:</el-text>&ensp;
<el-text class="data_color mx-1" truncated title="show_name">{{ gen_show_name(userinfo) }}</el-text>
</el-col>
<el-col :span="5" style="white-space: nowrap;">
<el-text class="label_color mx-1" truncated>数量:</el-text>&ensp;
<el-text class="data_color mx-1" truncated :title="msg_count">{{ msg_count }}</el-text>
</el-col>
<el-col :span="2" style="white-space: nowrap;">
<el-text class="button_color mx-1 underline" truncated @click="is_show_more=!is_show_more"> 详细信息</el-text>
</el-col>
<el-col :span="2" style="white-space: nowrap;">
<el-text v-if="!is_export" class="button_color mx-1 underline" truncated @click="export_button(true);">导出备份
</el-text>
<el-text v-if="is_export" class="button_color mx-1 underline" truncated @click="export_button(false);">聊天查看
</el-text>
</el-col>
<el-col :span="3" style="white-space: nowrap;">
<el-text class="button_color mx-1 underline" truncated @click="get_real_time_msg();">实时消息
<template v-if="is_getting_real_time_msg" style="color: #00bd7e">...</template>
</el-text>
</el-col>
</el-row>
<el-dialog v-model="is_show_more" title="详细信息" width="600" center>
<user-info-show :userinfo="userinfo" :show_all="true"></user-info-show>
</el-dialog>
</template>
<style scoped>
.label_color {
color: #333; /* 调整字体颜色 */
font-size: 15px;
padding-left: 15px;
padding-right: 0;
}
.data_color {
color: #08488c;
background-color: #f4f4f4; /* 调整背景颜色 */
font-size: 15px;
padding-left: 6px;
padding-right: 6px;
font-weight: bold; /* 使用 bold 表示加粗 */
white-space: nowrap;
max-width: 80%;
}
.button_color {
color: #0048ff; /* 调整字体颜色 */
font-size: 15px;
padding-left: 15px;
padding-right: 0;
text-decoration: underline;
}
</style>

View File

@ -1,122 +0,0 @@
<script setup lang="ts">
import {defineEmits, onMounted, ref} from 'vue';
import http from '@/utils/axios.js';
import {apiUserList, apiUserSessionList} from "@/api/chat";
import UserInfoShow from "@/components/chat/components/UserInfoShow.vue";
import {gen_show_name, type User} from "@/utils/common_utils";
import {api_img} from "@/api/base";
// "wxid": strUsrName, "nOrder": nOrder, "nUnReadCount": nUnReadCount, "strNickName": strNickName,
// "nStatus": nStatus, "nIsSend": nIsSend, "strContent": strContent, "nMsgLocalID": nMsgLocalID,
// "nMsgStatus": nMsgStatus, "nTime": nTime, "nMsgType": nMsgType, "nMsgSubType": nMsgSubType,
// "nickname": NickName, "remark": Remark, "account": Alias,
// "describe": describe, "headImgUrl": bigHeadImgUrl if bigHeadImgUrl else "",
// "ExtraBuf": ExtraBuf, "LabelIDList": tuple(LabelIDList)
const tableData = ref([]);
// session START
const fetchData = async () => {
try {
tableData.value = await apiUserSessionList();
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
};
onMounted(fetchData);
// END session END
// START
const search_word = ref('');
const search = async () => {
try {
// console.log(body_data);
if (search_word.value === '') {
tableData.value = await apiUserSessionList();
return;
}
console.log(search_word.value);
tableData.value = []
const ret = await apiUserList(search_word.value);
if (ret !== null && typeof ret === 'object') {
Object.entries(ret).forEach(([key, value]) => {
tableData.value.push(value);
});
}
// for (const key in ret) {
// if (ret.hasOwnProperty(key)) {
// tableData.value.push(ret[key]);
// }
// }
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
// END END
// user START
const emits = defineEmits(['wxid']);
const handleCurrentChange = (val: User | undefined) => {
//
if (val !== undefined) {
// user
// valwxid
if (val.wxid !== undefined) {
console.log('wxid:', val.wxid);
emits('wxid', val.wxid);
}
}
}
// END user END
// name
</script>
<template>
<div>
<!-- 搜索框以及按钮 -->
<div style="padding: 10px 10px;">
<el-input placeholder="请输入关键字" v-model="search_word" @keyup.enter="search"
style="width: 170px;margin-left: 15px;"></el-input>
<el-button type="primary" @click="search" style="width: 50px;">搜索</el-button>
</div>
<!-- 这是联系人的list -->
<el-table :data="tableData" stripe style="width: 100%" max-height="100%" height="100%" highlight-current-row loading="lazy"
@current-change="handleCurrentChange">
<el-table-column width="57">
<template v-slot="{ row }">
<el-avatar :size="33" :src="api_img(row.headImgUrl)" v-if="row.headImgUrl!==''"></el-avatar>
<el-avatar :size="33" v-else></el-avatar>
</template>
</el-table-column>
<el-table-column width="190">
<template v-slot="{ row }">
<el-tooltip class="item" effect="light" placement="right">
<div slot="content" class="tips">
<span>{{ gen_show_name(row) }}</span> <br>
<span v-if="row.nTime" style="color: #909399; font-size: 12px;">{{ row.nTime }}</span>
</div>
<template #content>
<user-info-show :userinfo="row" :show_all="false" style="max-width: 600px"></user-info-show>
</template>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
</template>
<style scoped>
/* 允许提示内容换行 */
.el-tooltip__popper .popper__content {
white-space: pre-line; /* 允许换行 */
}
</style>

View File

@ -1,111 +0,0 @@
<script setup lang="ts">
import {api_img} from "@/api/base";
const props = defineProps({
userinfo: {
type: Object,
default: () => {
return {}
}
},
show_all: {
type: Boolean,
default: true,
required: false
}
})
const tableRowClassName = ({
row,
rowIndex,
}: {
row: any
rowIndex: number
}) => {
console.log(row.wxid, props.userinfo.extra.owner.wxid, row.wxid == props.userinfo.extra.owner.wxid)
if (row.wxid == props.userinfo.extra.owner.wxid) {
console.log("table-success-row")
return 'table-success-row'
}
return ''
}
</script>
<template>
<div v-if="show_all" style="max-width: 560px">
<!-- 用于详细信息里面的更多信息显示 -->
<div>
<el-divider content-position="center">基本信息</el-divider>
<span>wxid{{ userinfo.wxid }}<br></span>
<span>账号{{ userinfo.account }}<br></span>
<span>昵称{{ userinfo.nickname }}<br></span>
<span>备注{{ userinfo.remark }}<br></span>
</div>
<div>
<el-divider content-position="center">账号信息</el-divider>
<span>性别{{
userinfo.ExtraBuf['性别[1男2女]'] == 1 ? '男' : userinfo.ExtraBuf['性别[1男2女]'] == 2 ? '女' : ''
}}<br></span>
<span>手机{{ userinfo.ExtraBuf['手机号'] }}<br></span>
<span>标签{{ userinfo.LabelIDList.join('/') }}<br></span>
<span>描述{{ userinfo.describe }}<br></span>
<span>个签{{ userinfo.ExtraBuf['个性签名'] }}<br></span>
<span>国家{{ userinfo.ExtraBuf['国'] }}<br></span>
<span>省份{{ userinfo.ExtraBuf['省'] }}<br></span>
<span>市名{{ userinfo.ExtraBuf['市'] }}<br></span>
</div>
<div>
<el-divider content-position="center">其他信息</el-divider>
<span>公司{{ userinfo.ExtraBuf['公司名称'] }}<br></span>
<span>企微{{ userinfo.ExtraBuf['企微属性'] }}<br></span>
<span>朋友圈背景<br></span>
<el-image v-if="userinfo.ExtraBuf['朋友圈背景']" :src="api_img(userinfo.ExtraBuf['朋友圈背景'])" alt="朋友圈背景"
style="max-width: 200px;max-height: 200px"
:preview-src-list="[api_img(userinfo.ExtraBuf['朋友圈背景'])]" :hide-on-click-modal="true"/>
</div>
<div v-if="userinfo.extra">
<el-divider content-position="center">群聊信息</el-divider>
<span>群主: {{ userinfo.extra.owner.wxid }}<br></span>
<span>群成员<br></span>
<el-table :data="Object.values(userinfo.extra.wxid2userinfo)" style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column prop="wxid" label="wxid"/>
<el-table-column prop="account" label="账号"/>
<el-table-column prop="nickname" label="昵称"/>
<el-table-column prop="remark" label="备注"/>
<el-table-column prop="roomNickname" label="群昵称"/>
</el-table>
</div>
</div>
<div v-else style="max-width: 560px">
<span>wxid{{ userinfo.wxid }}<br></span>
<span>账号{{ userinfo.account }}<br></span>
<span>昵称{{ userinfo.nickname }}<br></span>
<span v-if="userinfo.remark">备注{{ userinfo.remark }}<br></span>
<span v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['性别[1男2女]']">性别{{
userinfo.ExtraBuf['性别[1男2女]'] == 1 ? '男' : userinfo.ExtraBuf['性别[1男2女]'] == 2 ? '女' : ''
}}<br></span>
<span v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['手机号']">手机{{ userinfo.ExtraBuf['手机号'] }}<br></span>
<span v-if="userinfo.LabelIDList&&userinfo.LabelIDList.length>0">标签{{
userinfo.LabelIDList.join('/')
}}<br></span>
<span v-if="userinfo.describe">描述{{ userinfo.describe }}<br></span>
<span v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['个性签名']">个签{{ userinfo.ExtraBuf['个性签名'] }}<br></span>
<span v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['国']">国家{{ userinfo.ExtraBuf[''] }}<br></span>
<span v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['省']">省份{{ userinfo.ExtraBuf[''] }}<br></span>
<span v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['市']">市名{{ userinfo.ExtraBuf[''] }}<br></span>
<span v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['公司名称']">公司{{ userinfo.ExtraBuf['公司名称'] }}<br></span>
<span v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['企微属性']">企微{{ userinfo.ExtraBuf['企微属性'] }}<br></span>
<span v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['朋友圈背景']">朋友圈背景<br></span>
<el-image v-if="userinfo.ExtraBuf && userinfo.ExtraBuf['朋友圈背景']"
:src="api_img(userinfo.ExtraBuf['朋友圈背景'])"
style="max-width: 200px;max-height: 200px" alt="朋友圈背景"/>
</div>
</template>
<style scoped>
.table-success-row {
--el-table-tr-bg-color: var(--el-color-success-light-9);
}
</style>

View File

@ -1,216 +0,0 @@
<script setup lang="ts">
import {defineProps, onMounted, ref} from "vue";
// import http from '@/router/axios.js';
import ElTextarea from 'element-plus';
const props = defineProps({
is_sender: {
type: Number,
default: 0
},
headUrl: {
type: String,
default: ''
},
direction: {
type: String,
default: ''
},
src: {
type: String,
default: ''
},
msg: {
type: String,
default: ''
},
})
// const audioSrc = ref("");
// onMounted(async () => {
// // audioSrc.value = await read_audio_base64(props.src);
// });
//
// const read_audio_base64 = async (src: string) => {
//
// // try {
// // const body_data = await http.post('/rs_api/audio', {
// // 'savePath': src,
// // });
// // return body_data;
// // } catch (error) {
// // console.error('Error fetching data:', error);
// // return "";
// // }
// return src;
// }
</script>
<template>
<div class="chat-content">
<!-- recordContent 聊天记录数组-->
<!-- 对方 -->
<div class="word" v-if="!is_sender">
<img :src="headUrl">
<div class="info">
<p class="time">{{ direction }}</p>
<audio controls style="background-color:#fff ">
<source :src="src" type="audio/wav">
</audio>
<el-textarea
:rows="1"
:readonly="true"
:value="msg"
style="width: 100%;"/>
</div>
</div>
<!-- 我的 -->
<div class="word-my" v-else>
<div class="info">
<p class="time">{{ direction }}</p>
<audio controls style="background-color:#95EC69;">
<source :src="src" type="audio/wav">
</audio>
<el-textarea
:rows="1"
:readonly="true"
:value="msg"
style="width: 100%;"/>
</div>
<img :src="headUrl">
</div>
</div>
</template>
<style scoped lang="scss">
.chat-content {
width: 100%;
padding: 20px;
.word {
display: flex;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
margin-left: 10px;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
background: #fff;
position: relative;
margin-top: 8px;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::before {
position: absolute;
left: -8px;
top: 8px;
content: '';
border-right: 10px solid #FFF;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
.word-my {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
width: 90%;
margin-left: 10px;
text-align: right;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
margin-right: 10px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
float: right;
margin-right: 10px;
position: relative;
margin-top: 8px;
background: #95EC69;
text-align: left;
display: inline-block;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::after {
position: absolute;
right: -8px;
top: 8px;
content: '';
border-left: 10px solid #95EC69;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
}
.demo-image__error .image-slot {
font-size: 30px;
}
.demo-image__error .image-slot .el-icon {
font-size: 30px;
}
.demo-image__error .el-image {
width: 100%;
height: 200px;
}
</style>

View File

@ -1,206 +0,0 @@
<template>
<div class="chat-content">
<!-- recordContent 聊天记录数组-->
<!-- 对方 -->
<div class="word" v-if="!is_sender">
<img :src="headUrl">
<div class="info">
<p class="time">{{ direction }}</p>
<div class="demo-image__preview">
<el-image
style="max-width: 150px; max-height: 150px"
:src=src
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="[src]"
:hide-on-click-modal="true"
:initial-index="4"
fit="cover"
/>
</div>
</div>
</div>
<!-- 我的 -->
<div class="word-my" v-else>
<div class="info">
<p class="time">{{ direction }}</p>
<div class="demo-image__preview">
<!-- <image :src="src"></image> -->
<el-image
style="max-width: 150px; max-height: 150px"
:src=src
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:preview-src-list="[src]"
:hide-on-click-modal="true"
:initial-index="4"
fit="cover"
/>
</div>
<!-- <img class="chat_img" :src="src" alt="">-->
</div>
<img :src="headUrl">
</div>
</div>
</template>
<script setup lang="ts">
import {defineProps, onMounted, ref} from "vue";
const props = defineProps({
is_sender: {
type: Number,
default: 0
},
content: {
type: String,
default: ''
},
headUrl: {
type: String,
default: ''
},
direction: {
type: String,
default: ''
},
src: {
type: String,
default: ''
}
})
</script>
<style scoped lang="scss">
.chat-content {
width: 100%;
padding: 20px;
.word {
display: flex;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
margin-left: 10px;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
background: #fff;
position: relative;
margin-top: 8px;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::before {
position: absolute;
left: -8px;
top: 8px;
content: '';
border-right: 10px solid #FFF;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
.word-my {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
width: 90%;
margin-left: 10px;
text-align: right;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
margin-right: 10px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
float: right;
margin-right: 10px;
position: relative;
margin-top: 8px;
background: #95EC69;
text-align: left;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::after {
position: absolute;
right: -8px;
top: 8px;
content: '';
border-left: 10px solid #95EC69;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
}
.demo-image__error .image-slot {
font-size: 30px;
}
.demo-image__error .image-slot .el-icon {
font-size: 30px;
}
.demo-image__error .el-image {
width: 100%;
height: 200px;
}
</style>

View File

@ -1,223 +0,0 @@
<template>
<div class="chat-content">
<!-- recordContent 聊天记录数组-->
<!-- 对方 -->
<div class="word" v-if="!is_sender">
<img :src="headUrl">
<div class="info">
<p class="time">{{ direction }}</p>
<div style="float: left">
<el-card shadow="hover" style="width:fit-content;">文件 <a :href="videoSrc" download>{{
file_info.file_name }}</a>
<div>
文件大小<span>{{ file_info.file_size }}{{ file_info.file_size_unit }}</span>
</div>
</el-card>
</div>
</div>
</div>
<!-- 我的 -->
<div class="word-my" v-else>
<div class="info">
<p class="time">{{ direction }}</p>
<div style="float: right">
<el-card shadow="hover" style="width:fit-content;">文件 <a :href="videoSrc" download>{{
file_info.file_name }}</a>
<div>
文件大小<span>{{ file_info.file_size }}{{ file_info.file_size_unit }}</span>
</div>
</el-card>
</div>
</div>
<img :src="headUrl">
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps, onMounted, reactive, ref } from "vue";
import http from '@/utils/axios.js';
import {api_file, api_file_info} from "@/api/base";
const props = defineProps({
is_sender: {
type: Number,
default: 0
},
content: {
type: String,
default: ''
},
headUrl: {
type: String,
default: ''
},
direction: {
type: String,
default: ''
},
src: {
type: String,
default: ''
}
})
const videoSrc = ref("");
const file_info = reactive({
file_name: String,
file_size: Number,
file_size_unit: String,
});
onMounted(async () => {
console.log('文件加载中')
const file_info_resp = await api_file_info(props.src);
// console.log(file_info_resp)
file_info.file_name = file_info_resp.file_name;
file_info.file_size = Number(file_info_resp.file_size) / 1024;
if (file_info.file_size < 1024) {
file_info.file_size_unit = "kb";
} else {
file_info.file_size = file_info.file_size / 1024;
file_info.file_size_unit = "mb";
}
file_info.file_size = Number(file_info_resp.file_size) / 1024;
// float
// var file_size = file_info_resp.file_size/1024;
videoSrc.value = api_file(props.src);
});
</script>
<style scoped lang="scss">
.chat-content {
width: 100%;
padding: 20px;
.word {
display: flex;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
margin-left: 10px;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
background: #fff;
position: relative;
margin-top: 8px;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::before {
position: absolute;
left: -8px;
top: 8px;
content: '';
border-right: 10px solid #FFF;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
.word-my {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
width: 90%;
margin-left: 10px;
text-align: right;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
margin-right: 10px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
float: right;
margin-right: 10px;
position: relative;
margin-top: 8px;
background: #95EC69;
text-align: left;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::after {
position: absolute;
right: -8px;
top: 8px;
content: '';
border-left: 10px solid #95EC69;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
}
.demo-image__error .image-slot {
font-size: 30px;
}
.demo-image__error .image-slot .el-icon {
font-size: 30px;
}
.demo-image__error .el-image {
width: 100%;
height: 200px;
}
.demo-video__preview {
padding-left: 5px;
padding-right: 5px;
}
</style>

View File

@ -1,207 +0,0 @@
<template>
<div class="chat-content">
<!-- recordContent 聊天记录数组-->
<!-- 对方 -->
<div class="word" v-if="!is_sender">
<img :src="headUrl">
<div class="info">
<p class="time">{{ direction }}</p>
<div class="demo-image__preview">
<el-image
style="max-width: 150px; max-height: 150px"
:src="src"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:initial-index="4"
:preview-src-list="[src]"
:hide-on-click-modal="true"
fit="cover"
/>
</div>
</div>
</div>
<!-- 我的 -->
<div class="word-my" v-else>
<div class="info">
<p class="time">{{ direction }}</p>
<div class="demo-image__preview">
<el-image
style="max-width: 150px; max-height: 150px"
:src="src"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
:initial-index="4"
:preview-src-list="[src]"
:hide-on-click-modal="true"
fit="cover"
/>
</div>
<!-- <img class="chat_img" :src="imgSrc" alt="">-->
</div>
<img :src="headUrl">
</div>
</div>
</template>
<script setup lang="ts">
import {defineProps, onMounted, ref} from "vue";
import http from '@/utils/axios.js';
const props = defineProps({
is_sender: {
type: Number,
default: 0
},
content: {
type: String,
default: ''
},
headUrl: {
type: String,
default: ''
},
direction: {
type: String,
default: ''
},
src: {
type: String,
default: ''
}
})
</script>
<style scoped lang="scss">
.chat-content {
width: 100%;
padding: 20px;
.word {
display: flex;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
margin-left: 10px;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
background: #fff;
position: relative;
margin-top: 8px;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::before {
position: absolute;
left: -8px;
top: 8px;
content: '';
border-right: 10px solid #FFF;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
.word-my {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
width: 90%;
margin-left: 10px;
text-align: right;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
margin-right: 10px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
float: right;
margin-right: 10px;
position: relative;
margin-top: 8px;
background: #95EC69;
text-align: left;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::after {
position: absolute;
right: -8px;
top: 8px;
content: '';
border-left: 10px solid #95EC69;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
}
.demo-image__error .image-slot {
font-size: 30px;
}
.demo-image__error .image-slot .el-icon {
font-size: 30px;
}
.demo-image__error .el-image {
width: 100%;
height: 200px;
}
</style>

View File

@ -1,166 +0,0 @@
<template>
<div class="chat-content">
<!-- recordContent 聊天记录数组-->
<!-- 对方 -->
<div class="word" v-if="!is_sender">
<img :src="headUrl">
<div class="info">
<p class="time">{{ direction }}</p>
<div class="info-content" v-html="sanitizeHTML(content)"></div>
</div>
</div>
<!-- 我的 -->
<div class="word-my" v-else>
<div class="info">
<p class="time">{{ direction }}</p>
<div class="info-content" v-html="sanitizeHTML(content)"></div>
</div>
<img :src="headUrl">
</div>
</div>
</template>
<script setup lang="ts">
import {defineProps} from "vue";
const props = defineProps({
is_sender: {
type: Number,
default: 0
},
content: {
type: String,
default: ''
},
headUrl: {
type: String,
default: ''
},
direction: {
type: String,
default: ''
}
})
const sanitizeHTML = (html: any) => {
// Use DOMParser to parse the HTML and then serialize it to a trusted HTML string
html = html.replace(/\n/g, '<br>');
const doc = new DOMParser().parseFromString(html, 'text/html');
return doc.body.innerHTML;
};
</script>
<style scoped lang="scss">
.chat-content {
width: 100%;
max-width: 100%;
padding: 20px;
.word {
display: flex;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
margin-left: 10px;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
background: #fff;
position: relative;
margin-top: 8px;
display: inline-block;
word-break: break-word;
white-space: pre-wrap;
overflow: hidden;
}
//
.info-content::before {
position: absolute;
left: -8px;
top: 8px;
content: '';
border-right: 10px solid #FFF;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
.word-my {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
width: 90%;
margin-left: 10px;
text-align: right;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
margin-right: 10px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
float: right;
margin-right: 10px;
position: relative;
margin-top: 8px;
background: #95EC69;
text-align: left;
display: inline-block;
word-break: break-word;
white-space: pre-wrap;
overflow: hidden;
}
//
.info-content::after {
position: absolute;
right: -8px;
top: 8px;
content: '';
border-left: 10px solid #95EC69;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
}
</style>

View File

@ -1,164 +0,0 @@
<template>
<div class="chat-content">
<!-- recordContent 聊天记录数组-->
<!-- 对方 -->
<div class="word" v-if="!is_sender">
<img :src="headUrl">
<div class="info">
<p class="time">{{ direction }}</p>
<div class="info-content" v-html="sanitizeHTML(content)"></div>
</div>
</div>
<!-- 我的 -->
<div class="word-my" v-else>
<div class="info">
<p class="time">{{ direction }}</p>
<div class="info-content" v-html="sanitizeHTML(content)"></div>
</div>
<img :src="headUrl">
</div>
</div>
</template>
<script setup lang="ts">
import {defineProps} from "vue";
const props = defineProps({
is_sender: {
type: Number,
default: 0
},
content: {
type: String,
default: ''
},
headUrl: {
type: String,
default: ''
},
direction: {
type: String,
default: ''
}
})
const sanitizeHTML = (html) => {
// Use DOMParser to parse the HTML and then serialize it to a trusted HTML string
html = html.replace(/\n/g, '<br>');
const doc = new DOMParser().parseFromString(html, 'text/html');
return doc.body.innerHTML;
};
</script>
<style scoped lang="scss">
.chat-content {
width: 100%;
padding: 20px;
.word {
display: flex;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
margin-left: 10px;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
background: #fff;
position: relative;
margin-top: 8px;
display: inline-block;
word-break: break-word;
white-space: pre-wrap;
overflow: hidden;
}
//
.info-content::before {
position: absolute;
left: -8px;
top: 8px;
content: '';
border-right: 10px solid #FFF;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
.word-my {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
width: 90%;
margin-left: 10px;
text-align: right;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
margin-right: 10px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
float: right;
margin-right: 10px;
position: relative;
margin-top: 8px;
background: #95EC69;
text-align: left;
display: inline-block;
word-break: break-word;
white-space: pre-wrap;
overflow: hidden;
}
//
.info-content::after {
position: absolute;
right: -8px;
top: 8px;
content: '';
border-left: 10px solid #95EC69;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
}
</style>

View File

@ -1,193 +0,0 @@
<template>
<div class="chat-content">
<!-- recordContent 聊天记录数组-->
<!-- 对方 -->
<div class="word" v-if="!is_sender">
<img :src="headUrl">
<div class="info">
<p class="time">{{ direction }}</p>
<div class="demo-video__preview">
<video controls width="30%">
<source :src=src type="video/mp4" />
</video>
</div>
</div>
</div>
<!-- 我的 -->
<div class="word-my" v-else>
<div class="info">
<p class="time">{{ direction }}</p>
<div class="demo-video__preview">
<video controls width="50%">
<source :src=src type="video/mp4" />
</video>
</div>
</div>
<img :src="headUrl">
</div>
</div>
</template>
<script setup lang="ts">
import {defineProps, onMounted, ref} from "vue";
import http from '@/utils/axios.js';
const props = defineProps({
is_sender: {
type: Number,
default: 0
},
content: {
type: String,
default: ''
},
headUrl: {
type: String,
default: ''
},
direction: {
type: String,
default: ''
},
src: {
type: String,
default: ''
}
})
</script>
<style scoped lang="scss">
.chat-content {
width: 100%;
padding: 20px;
.word {
display: flex;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
margin-left: 10px;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
background: #fff;
position: relative;
margin-top: 8px;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::before {
position: absolute;
left: -8px;
top: 8px;
content: '';
border-right: 10px solid #FFF;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
.word-my {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.info {
width: 90%;
margin-left: 10px;
text-align: right;
.time {
font-size: 12px;
color: rgba(51, 51, 51, 0.8);
margin: 0;
height: 20px;
line-height: 20px;
margin-top: -5px;
margin-right: 10px;
}
.info-content {
max-width: 80%;
padding: 10px;
font-size: 14px;
float: right;
margin-right: 10px;
position: relative;
margin-top: 8px;
background: #95EC69;
text-align: left;
}
.chat_img {
width: 200px;
height: 200px;
border-radius: 5px;
}
//
.info-content::after {
position: absolute;
right: -8px;
top: 8px;
content: '';
border-left: 10px solid #95EC69;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
}
}
}
.demo-image__error .image-slot {
font-size: 30px;
}
.demo-image__error .image-slot .el-icon {
font-size: 30px;
}
.demo-image__error .el-image {
width: 100%;
height: 200px;
}
.demo-video__preview {
padding-left: 5px;
padding-right: 5px;
}
</style>

View File

@ -1,111 +0,0 @@
<script setup lang="ts">
import {ref, defineProps, nextTick, watch, type Ref} from 'vue';
import ChatRecprdsHeader from "@/components/chat/ChatRecprdsHeader.vue";
import DateTimeSelect from "@/components/utils/DateTimeSelect.vue";
import http from '@/utils/axios.js';
import {type Action, ElMessage, ElMessageBox} from "element-plus";
import ExportENDB from "@/components/chatBackup/ExportENDB.vue";
import ExportDEDB from "@/components/chatBackup/ExportDEDB.vue";
import ExportCSV from "@/components/chatBackup/ExportCSV.vue";
import ExportJSON from "@/components/chatBackup/ExportJSON.vue";
import ExportHTML from "@/components/chatBackup/ExportHTML.vue";
import ExportPDF from "@/components/chatBackup/ExportPDF.vue";
import ExportDOCX from "@/components/chatBackup/ExportDOCX.vue";
import ExportJSONMini from './ExportJSONMini.vue';
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
watch(() => props.wxid, (newVal: string, oldVal: String) => {
console.log(newVal);
});
const exportType: Ref<string> = ref(''); //
const result = ref(''); //
const setting = {
'endb': {
brief: '加密文件',
detail: "导出的内容为微信加密数据库。可还原回微信,但会覆盖微信后续消息。(全程不解密,所以数据安全)",
},
'dedb': {
brief: '解密文件',
detail: "导出的文件为解密后的sqlite数据库并且会自动合并msg和media数据库为同一个但是无法还原回微信。",
},
'csv': {
brief: 'csv',
detail: "只包含文本但是可以用excel软件wpsoffice打开。",
},
'json': {
brief: 'json',
detail: "只包含文本,可用于数据分析,情感分析等方面。",
},
'json-mini': {
brief: 'json-mini',
detail: "只包含文本只有最小化的json格式。支持选择时间注意不要选择太多时间会导致导出数据过大影响AI分析。",
},
'html': {
brief: 'html-测试中',
detail: "主要用于浏览器可视化查看。",
},
'pdf': {
brief: 'pdf-开发中',
detail: "pdf版本。",
},
'docx': {
brief: 'docx-开发中',
detail: "docx版本。",
},
};
</script>
<template>
<div id="chat_export" style="background-color: #d2d2fa;padding:0;">
<!-- 分割线 -->
<el-main style="overflow-y: auto; height: calc(100vh - 65px);padding: 0">
<div style="background-color: #d2d2fa;height: calc(100vh - 65px); display: grid; place-items: center; ">
<div
style="background-color: #fff; width: 70%; height: 70%; border-radius: 10px; padding: 20px; overflow: auto;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 20px; font-weight: bold;">导出与备份(未完待续...</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<!-- <el-button style="margin-right: 10px;" @click="exportData">导出</el-button>-->
</div>
</div>
<div style="margin-top: 20px;">
导出类型:
<el-select placeholder="请选择导出类型" style="width: 50%;" v-model="exportType">
<el-option :label="value.brief" :value="index" v-for="(value,index) in setting" :key="index">
{{ value.brief }}
</el-option>
</el-select>
<br/><br/>
<span v-if="exportType">
{{ setting[exportType].detail }}
</span>
</div>
<el-divider/>
<ExportENDB v-if="exportType=='endb'" :wxid="props.wxid"/>
<ExportDEDB v-if="exportType=='dedb'" :wxid="props.wxid"/>
<ExportCSV v-if="exportType=='csv'" :wxid="props.wxid"/>
<ExportJSON v-if="exportType=='json'" :wxid="props.wxid"/>
<ExportJSONMini v-if="exportType=='json-mini'" :wxid="props.wxid"/>
<ExportHTML v-if="exportType=='html'" :wxid="props.wxid"/>
<ExportPDF v-if="exportType=='pdf'" :wxid="props.wxid"/>
<ExportDOCX v-if="exportType=='docx'" :wxid="props.wxid"/>
</div>
</div>
</el-main>
</div>
</template>
<style scoped>
</style>

View File

@ -1,61 +0,0 @@
<script setup lang="ts">
import {defineProps, ref, watch} from "vue";
import http from "@/utils/axios.js";
import DateTimeSelect from "@/components/utils/DateTimeSelect.vue";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
watch(() => props.wxid, (newVal: string, oldVal: String) => {
console.log(newVal);
});
// props.wxidprops.wxid
// const datetime = ref([]);
const Result = ref("");
const requestExport = async () => {
Result.value = "正在处理中...";
try {
Result.value = await http.post('/api/rs/export_csv', {
'wxid': props.wxid,
// 'datetime': datetime.value,
});
} catch (error) {
console.error('Error fetching data msg_count:', error);
Result.value = "请求失败\n" + error;
return [];
}
}
//
// const handDatetimeChildData = (val: any) => {
// datetime.value = val;
// }
</script>
<template>
<div>
<!-- <div>-->
<!-- <strong>时间(默认全部)</strong>-->
<!-- <DateTimeSelect @datetime="handDatetimeChildData"/>-->
<!-- </div>-->
<div style="position: relative;">
<el-button type="primary" @click="requestExport()">导出</el-button>
</div>
<el-divider/>
<!-- 结果显示 -->
<el-input type="textarea" :rows="6" readonly placeholder="" v-model="Result"
style="width: 100%;"></el-input>
</div>
</template>
<style scoped>
</style>

View File

@ -1,63 +0,0 @@
<script setup lang="ts">
import {defineProps, ref, watch} from "vue";
import http from "@/utils/axios.js";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
watch(() => props.wxid, (newVal: string, oldVal: String) => {
console.log(newVal);
});
// props.wxidprops.wxid
const wx_path = ref("");
const key = ref("");
const Result = ref("");
const requestExport = async () => {
Result.value = "正在处理中...";
try {
Result.value = await http.post('/api/rs/export_dedb', {
'key': key.value,
'wx_path': wx_path.value,
});
} catch (error) {
console.error('Error fetching data msg_count:', error);
Result.value = "请求失败\n" + error;
return [];
}
}
</script>
<template>
<div>
密钥(可选)
<el-input placeholder="密钥[可为空,空表示使用默认的,无默认会报错]"
v-model="key"
style="width: 75%;"></el-input>
<br><br>
微信文件夹路径(可选)
<el-input placeholder="微信文件夹路径[可为空,空表示使用默认的,无默认会报错](eg: C:\****\WeChat Files\wxid_**** )"
v-model="wx_path"
style="width: 70%;"></el-input>
<br><br>
<div style="position: relative;">
<el-button type="primary" @click="requestExport()">导出</el-button>
</div>
<el-divider/>
<!-- 结果显示 -->
<el-input type="textarea" :rows="6" readonly placeholder="" v-model="Result"
style="width: 100%;"></el-input>
</div>
</template>
<style scoped>
</style>

View File

@ -1,31 +0,0 @@
<script setup lang="ts">
import {defineProps, watch} from "vue";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
watch(() => props.wxid, (newVal: string, oldVal: String) => {
console.log(newVal);
});
const requestExport = () => {
console.log('requestExport');
}
</script>
<template>
<div>
<span>{{props.wxid}}</span><br>
<el-button type="primary" @click="requestExport()">导出</el-button>
</div>
</template>
<style scoped>
</style>

View File

@ -1,54 +0,0 @@
<script setup lang="ts">
import {defineProps, ref, watch} from "vue";
import http from "@/utils/axios.js";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
watch(() => props.wxid, (newVal: string, oldVal: String) => {
console.log(newVal);
});
// props.wxidprops.wxid
const wx_path = ref("");
const Result = ref("");
const requestExport = async () => {
Result.value = "请求中...";
try {
Result.value = await http.post('/api/rs/export_endb', {
'wx_path': wx_path.value,
});
} catch (error) {
console.error('Error fetching data msg_count:', error);
Result.value = "请求失败\n"+error;
return [];
}
}
</script>
<template>
<div>
微信文件夹路径(可选)
<el-input placeholder="微信文件夹路径[可为空,空表示使用默认的,无默认会报错](eg: C:\****\WeChat Files\wxid_**** )"
v-model="wx_path"
style="width: 70%;"></el-input>
<br><br>
<div style="position: relative;">
<el-button type="primary" @click="requestExport()">导出</el-button>
</div>
<el-divider/>
<!-- 结果显示 -->
<el-input type="textarea" :rows="6" readonly placeholder="" v-model="Result"
style="width: 100%;"></el-input>
</div>
</template>
<style scoped>
</style>

View File

@ -1,62 +0,0 @@
<script setup lang="ts">
import {defineProps, ref, watch} from "vue";
import http from "@/utils/axios.js";
import DateTimeSelect from "@/components/utils/DateTimeSelect.vue";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
watch(() => props.wxid, (newVal: string, oldVal: String) => {
console.log(newVal);
});
// props.wxidprops.wxid
const datetime = ref([]);
const Result = ref("");
const requestExport = async () => {
Result.value = "正在处理中...";
try {
Result.value = await http.post('/api/rs/export_html', {
'wxid': props.wxid,
// 'datetime': datetime.value,
});
} catch (error) {
console.error('Error fetching data msg_count:', error);
Result.value = "请求失败\n" + error;
return [];
}
}
//
const handDatetimeChildData = (val: any) => {
datetime.value = val;
}
</script>
<template>
<div>
<!-- <div>-->
<!-- <strong>时间(默认全部)</strong>-->
<!-- <DateTimeSelect @datetime="handDatetimeChildData"/>-->
<!-- </div>-->
<span>使用说明1根据 https://blog.csdn.net/meser88/article/details/130229417 </span><br/>
<span>2打开导出的文件夹位置使用1设置的浏览器打开 index.html 文件</span>
<div style="position: relative;">
<el-button type="primary" @click="requestExport()">导出</el-button>
</div>
<el-divider/>
<!-- 结果显示 -->
<el-input type="textarea" :rows="6" readonly placeholder="" v-model="Result"
style="width: 100%;"></el-input>
</div>
</template>
<style scoped>
</style>

View File

@ -1,61 +0,0 @@
<script setup lang="ts">
import {defineProps, ref, watch} from "vue";
import http from "@/utils/axios.js";
import DateTimeSelect from "@/components/utils/DateTimeSelect.vue";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
watch(() => props.wxid, (newVal: string, oldVal: String) => {
console.log(newVal);
});
// props.wxidprops.wxid
const datetime = ref([]);
const Result = ref("");
const requestExport = async () => {
Result.value = "正在处理中...";
try {
Result.value = await http.post('/api/rs/export_json', {
'wxid': props.wxid,
// 'datetime': datetime.value,
});
} catch (error) {
console.error('Error fetching data msg_count:', error);
Result.value = "请求失败\n" + error;
return [];
}
}
//
const handDatetimeChildData = (val: any) => {
datetime.value = val;
}
</script>
<template>
<div>
<!-- <div>-->
<!-- <strong>时间(默认全部)</strong>-->
<!-- <DateTimeSelect @datetime="handDatetimeChildData"/>-->
<!-- </div>-->
<div style="position: relative;">
<el-button type="primary" @click="requestExport()">导出</el-button>
</div>
<el-divider/>
<!-- 结果显示 -->
<el-input type="textarea" :rows="6" readonly placeholder="" v-model="Result"
style="width: 100%;"></el-input>
</div>
</template>
<style scoped>
</style>

View File

@ -1,68 +0,0 @@
<script setup lang="ts">
import {defineProps, ref, watch} from "vue";
import http from "@/utils/axios.js";
import DateTimeSelect from "@/components/utils/DateTimeSelect.vue";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
watch(() => props.wxid, (newVal: string, oldVal: String) => {
console.log(newVal);
});
// props.wxidprops.wxid
const datetime = ref([]);
const Result = ref("");
const requestExport = async () => {
Result.value = "正在处理中...";
try {
console.log(datetime.value); // datetime.value
Result.value = await http.post('/api/rs/export_json_mini_select_time', {
'wxid': props.wxid,
// 'datetime': datetime.value,
"time":{
"start_createtime":datetime.value[0],
"end_createtime":datetime.value[1]
}
});
} catch (error) {
console.error('Error fetching data msg_count:', error);
Result.value = "请求失败\n" + error;
return [];
}
}
//
const handDatetimeChildData = (val: any) => {
// timer Date any
datetime.value = val;
}
</script>
<template>
<div>
<div>
<strong>时间(默认全部)</strong>
<DateTimeSelect @datetime="handDatetimeChildData"/>
</div>
<div style="position: relative;">
<el-button type="primary" @click="requestExport()">导出</el-button>
</div>
<el-divider/>
<!-- 结果显示 -->
<el-input type="textarea" :rows="6" readonly placeholder="" v-model="Result"
style="width: 100%;"></el-input>
</div>
</template>
<style scoped>
</style>

View File

@ -1,31 +0,0 @@
<script setup lang="ts">
import {defineProps, watch} from "vue";
const props = defineProps({
wxid: {
type: String,
required: true,
}
});
watch(() => props.wxid, (newVal: string, oldVal: String) => {
console.log(newVal);
});
const requestExport = () => {
console.log('requestExport');
}
</script>
<template>
<div>
<span>{{props.wxid}}</span><br>
<el-button type="primary" @click="requestExport()">导出</el-button>
</div>
</template>
<style scoped>
</style>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

View File

@ -1,7 +0,0 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

View File

@ -1,19 +0,0 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

View File

@ -1,173 +0,0 @@
<script setup lang="ts">
import * as echarts from "echarts";
import {onMounted, ref, shallowRef} from "vue";
import {apiDateCount, apiTalkerCount, apiWordcloud} from "@/api/stat";
import {apiUserList} from "@/api/chat";
import {gen_show_name, type User} from "@/utils/common_utils";
import DateTimeSelect from "@/components/utils/DateTimeSelect.vue";
import ColorSelect from "@/components/utils/ColorSelect.vue";
import ChartInit from "@/components/stats/components/ChartInit.vue";
// https://echarts.apache.org/examples/en/editor.html
interface gender_face {
: number
: number
未知: number
}
const user = ref<{ [key: string]: User }>({});
const gender_data = ref<gender_face>({});
const signature_count_dict = ref<{ [key: string]: number }>({});
const is_update = ref(false);
const chart_option = ref({
backgroundColor: "",
tooltip: {
trigger: 'item'
},
title: {
left: 'center',
text: '联系人画像'
},
toolbox: {
feature: {
saveAsImage: {}
}
},
series: [
{
name: '性别',
type: 'pie',
radius: ["10%", "20%"],
center: ['50%', '200px'],
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
data: <any>[]
}, {
name: '个性签名词云',
type: 'wordCloud',
sizeRange: [15, 80],
rotationRange: [0, 0],
rotationStep: 45,
gridSize: 8,
shape: 'cardioid',
keepAspect: false,
width: '100%',
height: '100%',
drawOutOfBound: false,
textStyle: {
normal: {
color: function () {
return 'rgb(' + [
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
Math.round(Math.random() * 160)
].join(',') + ')';
},
fontFamily: 'sans-serif',
fontWeight: 'normal'
},
emphasis: {
shadowBlur: 10,
shadowColor: '#333'
}
},
data: <any>[]
}
]
});
const get_data = async () => {
user.value = await apiUserList();
signature_count_dict.value = await apiWordcloud("signature");
let gender_data1 = {'男': 0, '女': 0, '未知': 0};
let province_data: { [key: string]: number } = {};
let city_data: { [key: string]: number } = {};
let signature_data: { [key: string]: number } = {};
for (let key in user.value) {
let u = user.value[key];
let ExtraBuf = u.ExtraBuf;
if (ExtraBuf) {
if (ExtraBuf["性别[1男2女]"] == 1) {
gender_data1['男'] += 1;
} else if (ExtraBuf["性别[1男2女]"] == 2) {
gender_data1['女'] += 1
} else {
gender_data1['未知'] += 1
}
} else {
gender_data1['未知'] += 1
}
}
gender_data.value = gender_data1;
}
// START
const refreshChart = async (is_get_data: boolean = true) => {
if (is_get_data) {
await get_data();
}
//
chart_option.value.series[0].data = [
{'value': gender_data.value["男"], 'name': '男', itemStyle: {color: '#4F6FE8'}},
{'value': gender_data.value["女"], 'name': '女', itemStyle: {color: '#FF6347'}}
]
chart_option.value.series[1].data = Object.keys(signature_count_dict.value).map((key) => {
return {name: key, value: signature_count_dict.value[key]}
});
is_update.value = !is_update.value;
}
// END
onMounted(() => {
refreshChart();
});
// END
</script>
<template>
<div class="common-layout" style="background-color: #d2d2fa;height: 100%;width: 100%;">
<el-container style="height: 100%;width: 100%;">
<el-header :height="'80px'" style="width: 100%;">
<strong>颜色设置</strong>
bg:
<color-select
@updateColors="(val:any)=>{val?chart_option.backgroundColor=val:'';refreshChart(false)}"></color-select>
</el-header>
<el-main style="height: calc(100% - 100px);width: 100%;">
<chart-init :option="chart_option" :update="is_update" class="charts_main"/>
</el-main>
</el-container>
</div>
</template>
<style scoped>
.charts_main {
width: 100%;
height: 100%;
}
</style>

View File

@ -1,227 +0,0 @@
<script setup lang="ts">
import * as echarts from "echarts";
import {onMounted, ref, shallowRef} from "vue";
import {apiDateCount, apiTalkerCount} from "@/api/stat";
import {apiUserList} from "@/api/chat";
import {gen_show_name, type User} from "@/utils/common_utils";
import DateTimeSelect from "@/components/utils/DateTimeSelect.vue";
import ColorSelect from "@/components/utils/ColorSelect.vue";
import NumberInput from "@/components/utils/NumberInput.vue";
import ChartInit from "@/components/stats/components/ChartInit.vue";
// https://echarts.apache.org/examples/en/editor.html
interface CountData {
sender_count: number
receiver_count: number
total_count: number
}
interface calendar_face {
top: number
left: number
orient: string
range: string
dayLabel: any
monthLabel: any
yearLabel: any
}
interface series_face {
type: string
coordinateSystem: string
calendarIndex: number
data: any[]
}
const datetime = ref([0, 0]);
const word = ref("");
const loading = ref(false);
const user_options = ref<User[]>([]);
const date_count_data = ref<any>({});
const top_user = ref<{ [key: string]: User }>({});
const top_user_count = ref<{ [key: string]: CountData }>({});
const is_update = ref(false);
const chart_option = ref({
backgroundColor: "#ffffff",
title: {
left: 'center',
text: '聊天记录(不包括群聊)'
},
toolbox: {
feature: {saveAsImage: {}}
},
tooltip: {
position: 'top',
formatter: function (p: any) {
return p.data[0] + '<br>聊天数量:' + p.data[1];
}
},
visualMap: {
min: 0,
max: 500,
calculable: true,
orient: 'vertical',
right: '0',
top: 'center'
},
calendar: <calendar_face[]>[],
series: <series_face[]>[],
});
const get_date_count_data = async () => {
// {"2024-12-20":{ "sender_count": sender_count, "receiver_count": receiver_count, "total_count": total_count },....}
date_count_data.value = await apiDateCount(word.value, datetime.value[0] / 1000, datetime.value[1] / 1000);
// key
date_count_data.value = Object.fromEntries(Object.entries(date_count_data.value).sort());
}
const get_top_user_count = async () => {
// {"wxid":{ "sender_count": sender_count, "receiver_count": receiver_count, "total_count": total_count },....}
const body_data = await apiTalkerCount();
top_user.value = await apiUserList("", Object.keys(body_data));
top_user_count.value = body_data;
// total_count
top_user_count.value = Object.fromEntries(Object.entries(top_user_count.value).sort((a, b) => b[1].total_count - a[1].total_count));
}
// START
const refreshChart = async (is_get_data: boolean = true) => {
if (is_get_data) {
await get_date_count_data();
}
//
let min_date = Object.keys(date_count_data.value)[0];
let max_date = Object.keys(date_count_data.value)[Object.keys(date_count_data.value).length - 1];
let min_year = parseInt(min_date.split("-")[0]);
let max_year = parseInt(max_date.split("-")[0]);
chart_option.value.calendar = [];
chart_option.value.series = [];
for (let i = min_year; i < max_year + 1; i++) {
chart_option.value.calendar.push({
top: 100,
left: 50 + 200 * (i - min_year),
orient: 'vertical',
range: i.toString(),
dayLabel: {
margin: 5, firstDay: 1, nameMap: ['日', '一', '二', '三', '四', '五', '六'],
},
monthLabel: {
margin: 5,
nameMap: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
},
yearLabel: {"color": "#000"}
});
chart_option.value.series.push({
type: 'heatmap', coordinateSystem: 'calendar', calendarIndex: i - min_year, data: []
});
}
// refreshData();
Object.keys(date_count_data.value).map(date => {
let year = parseInt(date.split("-")[0]);
let index = year - min_year;
chart_option.value.series[index].data.push([date, date_count_data.value[date].total_count]);
});
is_update.value = !is_update.value;
}
// END
onMounted(() => {
get_top_user_count();
refreshChart();
});
// START
const search_user = async (query: string) => {
try {
loading.value = true;
if (query === '') {
user_options.value = [];
return;
}
const body_data = await apiUserList(query);
loading.value = false;
user_options.value = Object.values(body_data);
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
const set_top_user = async (wxid: string) => {
try {
word.value = wxid;
await refreshChart();
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
// END
</script>
<template>
<div class="common-layout" style="background-color: #d2d2fa;height: 100%;width: 100%;">
<el-container style="height: 100%;width: 100%;">
<el-header :height="'80px'" style="width: 100%;">
<strong>时间(默认全部)</strong>
<DateTimeSelect @datetime="(val: any) => {datetime = val;}"/> &nbsp;
<el-select
v-model="word"
filterable
remote
reserve-keyword
placeholder="输入想查看的联系人"
remote-show-suffix
clearable
:remote-method="search_user"
:loading="loading"
style="width: 240px"
>
<el-option v-for="item in user_options" :key="item.wxid" :label="gen_show_name(item)" :value="item.wxid"/>
</el-select>&nbsp;
<el-button type="primary" @click="refreshChart">查看</el-button>
&nbsp
<strong>颜色设置</strong>
bg:
<color-select
@updateColors="(val:any)=>{val?chart_option.backgroundColor=val:'';refreshChart(false)}"></color-select>
min:
<number-input :n="chart_option.visualMap.min" :step="100"
@updateNumber="(val:any)=>{val?chart_option.visualMap.min=val:'';refreshChart(false)}"></number-input>
max:
<number-input :n="chart_option.visualMap.max" :step="100"
@updateNumber="(val:any)=>{val?chart_option.visualMap.max=val:'';refreshChart(false)}"></number-input>
<br>
<strong>top10[:(/)]</strong>
<template v-for="wxid in Object.keys(top_user_count)" :key="wxid">
<el-button type="primary" plain @click="set_top_user(wxid)" size="small">
{{ gen_show_name(top_user[wxid]) }} [{{ top_user_count[wxid]?.total_count }}({{
top_user_count[wxid]?.receiver_count
}}/{{ top_user_count[wxid]?.sender_count }})]
</el-button>
</template>
</el-header>
<el-main style="height: calc(100% - 100px);width: 100%;">
<chart-init :option="chart_option" :update="is_update"/>
</el-main>
</el-container>
</div>
</template>
<style scoped>
</style>

View File

@ -1,326 +0,0 @@
<script setup lang="ts">
import * as echarts from "echarts";
import {onMounted, ref, shallowRef} from "vue";
import {apiDateCount, apiTalkerCount} from "@/api/stat";
import {apiUserList} from "@/api/chat";
import {gen_show_name, type User} from "@/utils/common_utils";
import DateTimeSelect from "@/components/utils/DateTimeSelect.vue";
import ColorSelect from "@/components/utils/ColorSelect.vue";
import ChartInit from "@/components/stats/components/ChartInit.vue";
// https://echarts.apache.org/examples/en/editor.html
interface CountData {
sender_count: number
receiver_count: number
total_count: number
}
const date_count_data = ref<{ [key: string]: CountData }>({});
const datetime = ref([0, 0]);
const word = ref("");
const loading = ref(false);
const user_options = ref<User[]>([]);
const top_user = ref<{ [key: string]: User }>({});
const top_user_count = ref<{ [key: string]: CountData }>({});
const is_update = ref(false);
const colors = [
{
"color": '#ffeab6',
"areaStyle": new echarts.graphic.LinearGradient(0, 0, 0, 1,
[{offset: 0, color: "rgba(255,234,182,0)"},
{offset: 1, color: "rgba(255,234,182,0)"}])
}, {
"color": '#c0ffc2',
"areaStyle": new echarts.graphic.LinearGradient(0, 0, 0, 1,
[{offset: 0, color: "rgba(192,255,194,0)"},
{offset: 1, color: "rgba(192,255,194,0)"}])
}, {
"color": '#a1d9ff',
"areaStyle": new echarts.graphic.LinearGradient(0, 0, 0, 1,
[{offset: 0, color: "rgba(161,217,255,0)"},
{offset: 1, color: "rgba(161,217,255,0)"}])
}, {
"color": '#D37373',
"areaStyle": new echarts.graphic.LinearGradient(0, 0, 0, 1,
[{offset: 0, color: "rgba(192,255,194,0)"},
{offset: 1, color: "rgba(192,255,194,0)"}])
}, {
"color": '#e4ecf6',
"areaStyle": new echarts.graphic.LinearGradient(0, 0, 0, 1,
[{offset: 0, color: "rgba(161,217,255,0)"},
{offset: 1, color: "rgba(161,217,255,0)"}])
}, {
"color": 'rgba(185,4,245,0.44)',
"areaStyle": new echarts.graphic.LinearGradient(0, 0, 0, 1,
[{offset: 0, color: "rgba(161,217,255,0)"},
{offset: 1, color: "rgba(161,217,255,0)"}])
}
];
const bg_color = ref("");
const chart_option = ref({
backgroundColor: bg_color.value,
tooltip: {
trigger: 'axis',
position: function (pt: any) {
return [pt[0], '90%'];
},
formatter: function (params: any) {
let date = params[0].name;
// let total_count = params[0].value;
// let sender_count = params[1].value;
// let receiver_count = params[2].value;
let total_count = date_count_data.value[date].total_count;
let sender_count = date_count_data.value[date].sender_count;
let receiver_count = date_count_data.value[date].receiver_count;
return `${date}<br>
聊天记录数量${total_count}<br>
发送数量${sender_count}(${(sender_count / total_count * 100).toFixed(2)}%)<br>
接收数量${receiver_count}(${(receiver_count / total_count * 100).toFixed(2)}%) `
}
},
title: {
left: 'center',
text: '日聊天记录(不包括群聊)'
},
toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none'
},
saveAsImage: {}
}
},
dataZoom: [
{type: 'inside', start: 0, end: 100},
{start: 0, end: 100}
],
legend: {
right: '1%', // 5%
top: '5%', //
orient: 'vertical' //
},
xAxis: {
type: 'category', // x
boundaryGap: false, // x
data: <any>[], // x 使 TypeScript
},
yAxis: [
{type: 'value', boundaryGap: ['100%', '10%'], name: '数量', axisLabel: {formatter: '{value}'}},
{
type: 'value', boundaryGap: [0, '100%'], name: '百分比', position: 'right', max: 200,
axisLabel: {formatter: '{value} %'}
}
],
series: [
{
name: '聊天记录数量',
type: 'line',
symbol: 'none',
sampling: 'lttb',
yAxisIndex: 0,
itemStyle: {color: colors[0].color},
areaStyle: {color: colors[0].areaStyle},
data: <any>[]
}, {
name: '发送数量',
type: 'line',
showSymbol: false,
show: false,
symbol: 'none',
sampling: 'lttb',
yAxisIndex: 0,
itemStyle: {color: colors[1].color},
areaStyle: {color: colors[1].areaStyle},
data: <any>[]
}, {
name: '接收数量',
type: 'line',
symbol: 'none',
show: false,
showSymbol: false,
sampling: 'lttb',
yAxisIndex: 0,
itemStyle: {color: colors[2].color},
areaStyle: {color: colors[2].areaStyle},
data: <any>[]
}, {
name: '发送数量bar',
type: 'bar',
stack: 'total',
barWidth: '50%',
yAxisIndex: 1,
itemStyle: {color: colors[3].color},
areaStyle: {color: colors[3].areaStyle},
data: <any>[]
}, {
name: '接收数量bar',
type: 'bar',
stack: 'total',
barWidth: '50%',
yAxisIndex: 1,
itemStyle: {color: colors[4].color},
areaStyle: {color: colors[4].areaStyle},
data: <any>[]
}, {
name: '分界线',
type: 'line',
symbol: 'none',
sampling: 'lttb',
yAxisIndex: 1,
itemStyle: {color: colors[5].color},
areaStyle: {color: colors[5].areaStyle},
data: <any>[]
}
]
});
const update_chart_option = () => {
for (let i = 0; i < chart_option.value.series.length; i++) {
chart_option.value.series[i].itemStyle.color = colors[i].color;
chart_option.value.series[i].areaStyle.color = colors[i].areaStyle;
}
chart_option.value.backgroundColor = bg_color.value;
}
const get_date_count_data = async () => {
// {"2024-12-20":{ "sender_count": sender_count, "receiver_count": receiver_count, "total_count": total_count },....}
date_count_data.value = await apiDateCount(word.value, datetime.value[0] / 1000, datetime.value[1] / 1000);
// key
date_count_data.value = Object.fromEntries(Object.entries(date_count_data.value).sort());
}
const get_top_user_count = async () => {
// {"wxid":{ "sender_count": sender_count, "receiver_count": receiver_count, "total_count": total_count },....}
const body_data = await apiTalkerCount();
top_user.value = await apiUserList("", Object.keys(body_data));
top_user_count.value = body_data;
// total_count
top_user_count.value = Object.fromEntries(Object.entries(top_user_count.value).sort((a, b) => b[1].total_count - a[1].total_count));
}
// START
const refreshChart = async (is_get_data: boolean = true) => {
if (is_get_data) {
await get_date_count_data();
}
// refreshData();
chart_option.value.xAxis.data = Object.keys(date_count_data.value);
chart_option.value.series[0].data = Object.values(date_count_data.value).map((item: any) => item.total_count);
// chart_option.value.series[1].data = Object.values(date_count_data.value).map((item: any) => item.sender_count);
// chart_option.value.series[2].data = Object.values(date_count_data.value).map((item: any) => item.receiver_count);
chart_option.value.series[3].data = Object.values(date_count_data.value).map((item: any) => item.sender_count / item.total_count * 100);
chart_option.value.series[4].data = Object.values(date_count_data.value).map((item: any) => item.receiver_count / item.total_count * 100);
chart_option.value.series[5].data = Object.values(date_count_data.value).map((item: any) => 50);
//
is_update.value = !is_update.value;
}
// END
onMounted(() => {
get_top_user_count();
refreshChart();
});
// START
const search_user = async (query: string) => {
try {
loading.value = true;
if (query === '') {
user_options.value = [];
return;
}
const body_data = await apiUserList(query);
loading.value = false;
user_options.value = Object.values(body_data);
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
const search_change = async () => {
try {
console.log('search_change:', word.value);
// await get_data();
await refreshChart();
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
const set_top_user = async (wxid: string) => {
try {
word.value = wxid;
await search_change();
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
// END
</script>
<template>
<div class="common-layout" style="background-color: #d2d2fa;height: 100%;width: 100%;">
<el-container style="height: 100%;width: 100%;">
<el-header :height="'80px'" style="width: 100%;">
<strong>时间(默认全部)</strong>
<DateTimeSelect @datetime="(val: any) => {datetime = val;}"/> &nbsp;
<el-select
v-model="word"
filterable
remote
reserve-keyword
placeholder="输入想查看的联系人"
remote-show-suffix
clearable
:remote-method="search_user"
:loading="loading"
style="width: 240px"
>
<el-option v-for="item in user_options" :key="item.wxid" :label="gen_show_name(item)" :value="item.wxid"/>
</el-select>&nbsp;
<el-button type="primary" @click="search_change">查看</el-button>
&nbsp;
<strong>颜色设置</strong>
bg:
<color-select
@updateColors="(val:any)=>{val?chart_option.backgroundColor=val:'';refreshChart(false)}"></color-select>
<template v-for="(color, index) in chart_option.series" :key="index">
c{{ index + 1 }}
<color-select
@updateColors="(val:any)=>{val?chart_option.series[index].itemStyle.color=val:'';refreshChart(false)}"
></color-select>
</template>
<el-button @click="update_chart_option();refreshChart(false);" size="small">重置</el-button>
<br>
<strong>top10[:(/)]</strong>
<template v-for="wxid in Object.keys(top_user_count)" :key="wxid">
<el-button type="primary" plain @click="set_top_user(wxid)" size="small">
{{ gen_show_name(top_user[wxid]) }} [{{ top_user_count[wxid]?.total_count }}({{
top_user_count[wxid]?.receiver_count
}}/{{ top_user_count[wxid]?.sender_count }})]
</el-button>
</template>
</el-header>
<el-main style="height: calc(100% - 100px);width: 100%;">
<chart-init :option="chart_option" :update="is_update" id="charts_main"/>
</el-main>
</el-container>
</div>
</template>
<style scoped>
</style>

View File

@ -1,46 +0,0 @@
<script setup lang="ts">
import * as echarts from "echarts";
import 'echarts-wordcloud';
import {getCurrentInstance, onMounted, ref, shallowRef, watch,} from "vue";
const props = defineProps<{
option: any,
update: boolean
}>();
const chart_options = ref<any>(props.option);
const Chart = shallowRef<any>(null)
const init = () => {
let t = getCurrentInstance()?.proxy?.$refs.chart_main;
if (t instanceof HTMLElement) {
chart_options.value = props.option;
Chart.value = echarts.init(t);
Chart.value.clear();
Chart.value.setOption(chart_options.value, true);
} else {
console.error('chart_main is not HTMLElement');
}
}
onMounted(() => {
init();
})
watch(() => props.update, async (newVal, oldVal) => {
chart_options.value = props.option;
Chart.value.clear();
Chart.value.setOption(chart_options.value, true);
});
</script>
<template>
<div class="chart-div" ref="chart_main"></div>
</template>
<style scoped>
.chart-div {
width: 100%;
height: 100%;
}
</style>

View File

@ -1,19 +0,0 @@
<script setup lang="ts">
import {ref} from "vue";
const color = ref('');
const updateColors = (val: string) => {
emit('updateColors', val);
}
const emit = defineEmits(['updateColors']);
</script>
<template>
<el-color-picker v-model="color" @change="updateColors" size="small"></el-color-picker>
</template>
<style scoped>
</style>

View File

@ -1,135 +0,0 @@
<script setup lang="ts">
import {defineEmits, ref, watch} from 'vue';
const defaultTime: [Date, Date] = [
new Date(2000, 1, 1, 0, 0, 0),
new Date(2000, 2, 1, 23, 59, 59),
] // '12:00:00', '08:00:00'
const shortcuts = [
{
text: '全部',
value: () => {
const end = new Date();
const start = new Date(2010, 0, 1, 0, 0, 0);
return [start, end]
},
},
{
text: '最近一周',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
return [start, end]
},
},
{
text: '最近一个月',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
return [start, end]
},
},
{
text: '最近三个月',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
return [start, end]
},
},
{
text: '最近半年',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 180)
return [start, end]
},
},
{
text: '最近一年',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
return [start, end]
},
},
{
text: '去年一整年',
value: () => {
const start = new Date()
start.setFullYear(start.getFullYear() - 1)
start.setMonth(0)
start.setDate(1)
start.setHours(0, 0, 0)
const end = new Date()
end.setFullYear(end.getFullYear() - 1)
end.setMonth(11)
end.setDate(31)
end.setHours(23, 59, 59)
return [start, end]
},
},
]
const datetime = ref([Date, Date])
const emits = defineEmits(['datetime']);
//
// datetime
watch(() => datetime.value, (newVal: any, oldVal: any) => {
let start;
let end;
if (newVal) {
start = newVal[0].getTime();
end = newVal[1].getTime();
} else {
start = 0;
end = 0;
}
//
emits('datetime', [start, end]);
})
</script>
<template>
<!-- <div class="block">-->
<el-date-picker
v-model="datetime"
type="datetimerange"
:shortcuts="shortcuts"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="defaultTime"
format="YYYY-MM-DD HH:mm:ss"
/>
<!-- </div>-->
</template>
<style scoped>
.block {
padding: 30px 0;
text-align: center;
border-right: solid 1px var(--el-border-color);
flex: 1;
}
.block:last-child {
border-right: none;
}
.block .demonstration {
display: block;
color: var(--el-text-color-secondary);
font-size: 14px;
margin-bottom: 20px;
}
</style>

View File

@ -1,383 +0,0 @@
<script setup lang="ts">
import http from "@/utils/axios.js";
import ProgressBar from "@/components/utils/ProgressBar.vue";
import {defineEmits, onMounted, ref, watch} from "vue";
import {ElTable, ElTableColumn, ElMessage, ElMessageBox} from "element-plus";
import type {Action} from 'element-plus'
import router from "@/router";
interface wxinfo {
pid: string;
version: string;
account: string;
mobile: string;
nickname: string;
mail: string;
wxid: string;
wx_dir: string;
key: string;
}
const percentage = ref(0);
const startORstop = ref(-1); // 00% 1100%
const init_type = ref("");
const is_init = ref(false);
const wxinfoData = ref<wxinfo[]>([]);
const oneWx = ref("");
const decryping = ref(false);
const isErrorShow = ref(false);
const isUseKey = ref("false");
const merge_path = ref("");
const wx_path = ref("");
const key = ref("");
const my_wxid = ref("");
const local_wxids = ref([]);
const db_init = (init: boolean) => {
if (init) {
localStorage.setItem('isDbInit', "t");
router.push('/');
ElMessage({
type: 'success',
message: '初始化成功!',
})
}
}
// ** 使key** START
const init_key = async () => {
if (decryping.value) {
console.log("正在解密中,请稍后再试!")
return;
}
decryping.value = true;
try {
decryping.value = true;
startORstop.value = 0; //
let reqdata = {
"wx_path": wx_path.value,
"key": key.value,
"my_wxid": my_wxid.value
}
const body_data = await http.post('/api/ls/init_key', reqdata);
is_init.value = body_data.is_init;
if (body_data.is_init) {
percentage.value = 100; // 100%
}
decryping.value = false;
db_init(body_data.is_init);
} catch (error) {
percentage.value = 0; // 0%
isErrorShow.value = true;
decryping.value = false;
ElMessageBox.alert(error, 'error', {
confirmButtonText: '确认',
callback: (action: Action) => {
ElMessage({
type: 'error',
message: `action: ${action}`,
})
},
})
return [];
}
decryping.value = false;
}
const init_nokey = async () => {
try {
let reqdata = {
"wx_path": wx_path.value,
"merge_path": merge_path.value,
"my_wxid": my_wxid.value
}
const body_data = await http.post('/api/ls/init_nokey', reqdata);
is_init.value = body_data.is_init;
if (body_data.is_init) {
percentage.value = 100; // 100%
}
decryping.value = false;
db_init(body_data.is_init);
// emits('isAutoShow', body_data.is_init);
} catch (error) {
percentage.value = 0; // 0%
isErrorShow.value = true;
decryping.value = false;
ElMessageBox.alert(error, 'error', {
confirmButtonText: '确认',
callback: (action: Action) => {
init_type.value = "";//
},
})
// console.error('Error fetching data:', error);
return [];
}
decryping.value = false;
}
// ** 使key** END
// ** 使** START
const selectLastWx = async (row: wxinfo) => {
// console.log(row)
my_wxid.value = row.wxid;
}
const get_init_last_local_wxid = async () => {
try {
const body_data = await http.post('/api/ls/init_last_local_wxid'); //[ 'wx1234567890', 'wx0987654321' ]
local_wxids.value = body_data.local_wxids.map((item: string) => {
return {wxid: item}
});
if (local_wxids.value.length === 1) {
my_wxid.value = local_wxids.value[0].wxid;
// console.log("init_last")
await init_last();
}
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
const init_last = async () => {
try {
let reqdata = {
"wx_path": wx_path.value,
"merge_path": merge_path.value,
"my_wxid": my_wxid.value
}
const body_data = await http.post('/api/ls/init_last', reqdata);
is_init.value = body_data.is_init;
if (body_data.is_init) {
percentage.value = 100; // 100%
decryping.value = false;
db_init(body_data.is_init);
// emits('isAutoShow', body_data.is_init);
} else {
isErrorShow.value = true;
decryping.value = false;
ElMessageBox.alert("未发现上次的设置数据!", 'error', {
confirmButtonText: '确认',
callback: (action: Action) => {
init_type.value = "";//
},
})
}
decryping.value = false;
} catch (error) {
// percentage.value = 0; // 0%
isErrorShow.value = true;
decryping.value = false;
ElMessageBox.alert(error, 'error', {
confirmButtonText: '确认',
callback: (action: Action) => {
init_type.value = "";
},
})
// console.error('Error fetching data:', error);
return [];
}
decryping.value = false;
}
// ** 使** END
// **** START init_key
const get_wxinfo = async () => {
try {
wxinfoData.value = await http.post('/api/ls/wxinfo');
if (wxinfoData.value.length === 1) {
selectWx(wxinfoData.value[0]);
oneWx.value = " (检测到只有一个微信将在5秒后自动选择) ";
setTimeout(okWx, 5000);
}
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
const selectWx = async (row: wxinfo) => {
merge_path.value = "";
wx_path.value = row.wx_dir;
key.value = row.key;
my_wxid.value = row.wxid;
}
const okWx = () => {
if (wx_path.value === '' && key.value === '' && my_wxid.value === '') {
console.log("请填写完整信息! ")
return;
}
if (decryping.value) {
console.log("正在解密...,请稍后再试!")
return;
}
init_key();
}
// **** END init_key
// isAutoShowaotoget_wxinfo
watch(init_type, (val) => {
if (val === 'auto') {
get_wxinfo();
} else if (val === 'custom') {
// init();
} else if (val === 'last') {
get_init_last_local_wxid();
// init_last();
}
})
</script>
<template>
<div style="background-color: #d2d2fa; height: 100%; display: flex; justify-content: center; align-items: center;">
<!-- 上次数据 -->
<div v-if="init_type==='last'">
<div
style="background-color: #fff; width: 90%;min-width: 800px; height: 80%; border-radius: 10px; padding: 20px; overflow: auto;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 20px; font-weight: bold;">选择要查看的微信</div>
</div>
<div style="margin-top: 20px;">
<el-table :data="local_wxids" @current-change="selectLastWx" highlight-current-row style="width: 100%">
<el-table-column :min-width="50" prop="wxid" label="微信原始id"></el-table-column>
</el-table>
</div>
<div style="margin-top: 20px;">
<el-button style="margin-right: 10px;margin-top: 10px;width: 100%;" type="success" @click="init_last">确定
</el-button>
</div>
</div>
</div>
<!-- END -->
<!-- 自动解密和显示 -->
<div v-else-if="init_type==='auto'">
<!-- <el-progress v-if="decryping && !isErrorShow" type="dashboard" :percentage="percentage" :color="colors"/>-->
<div v-if="decryping">
<ProgressBar v-if="decryping" :startORstop="startORstop"/>
</div>
<div v-else
style="background-color: #fff; width: 90%;min-width: 800px; height: 80%; border-radius: 10px; padding: 20px; overflow: auto;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 20px; font-weight: bold;">选择要查看的微信(会清空work下对应wxid数据)</div>
</div>
<div style="margin-top: 20px;">
<el-table :data="wxinfoData" @current-change="selectWx" highlight-current-row style="width: 100%">
<el-table-column :min-width="30" prop="pid" label="进程id"></el-table-column>
<el-table-column :min-width="40" prop="version" label="微信版本"></el-table-column>
<el-table-column :min-width="40" prop="account" label="账号"></el-table-column>
<el-table-column :min-width="40" prop="nickname" label="昵称"></el-table-column>
<el-table-column :min-width="50" prop="wxid" label="微信原始id"></el-table-column>
</el-table>
</div>
<div style="margin-top: 20px;">
<el-button style="margin-right: 10px;margin-top: 10px;width: 100%;" type="success" @click="okWx">确定{{
oneWx
}}
</el-button>
</div>
</div>
</div>
<!-- END -->
<!-- 用于自定义参数 -->
<div v-else-if="init_type==='custom'">
<div v-if="decryping">
<ProgressBar v-if="decryping" :startORstop="startORstop"/>
</div>
<div v-else
style="background-color: #fff; width: 80%;min-width: 800px; height: 70%; border-radius: 10px; padding: 20px; overflow: auto;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 20px; font-weight: bold;">自定义-文件位置</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<!-- <el-button style="margin-right: 10px;" @click="exportData">导出</el-button>-->
</div>
</div>
<div style="margin-top: 20px;">
<!-- 单选按钮 -->
<input type="radio" v-model="isUseKey" value="true"/> 使用 KEY &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<input type="radio" v-model="isUseKey" value="false"/> 不使用 KEY
<div v-if="isUseKey=='false'">
说明1表示数据库已解密并合并<br>2合并后的数据库需要包含(MediaMSG,MSG,MicroMsg,OpenIMMsg)这些数据库合并的内容<br>
</div>
<div v-if="isUseKey=='true'">
说明1自动根据key解密微信文件夹下的数据库<br>2必须保证key正确否则解密失败<br>
</div>
<el-divider></el-divider> <!-- 线-->
<div v-if="isUseKey=='true'">
<label>密钥key(必填): </label>
<el-input placeholder="密钥key (64位)" v-model="key" style="width: 80%;"></el-input>
<br>
</div>
<div v-if="isUseKey=='false'">
<label>merge_all.db 文件路径(必填,非文件夹): </label>
<el-input placeholder="(MediaMSG.db,MSG.db,MicroMsg.db,OpenIMMsg.db)合并后的数据库" v-model="merge_path"
style="width: 80%;"></el-input>
<br>
</div>
<label>微信文件夹路径(必填): </label>
<el-input placeholder="C:\***\WeChat Files\wxid_*******" v-model="wx_path" style="width: 80%;"></el-input>
<br>
<label>微信原始id(必填): </label>
<el-input placeholder="wxid_*******" v-model="my_wxid" style="width: 80%;"></el-input>
<br>
<el-button v-if="isUseKey=='true'" style="margin-top: 10px;width: 100%;" type="success" @click="init_key">
确定
</el-button>
<el-button v-if="isUseKey=='false'" style="margin-top: 10px;width: 100%;" type="success" @click="init_nokey">
确定
</el-button>
</div>
</div>
</div>
<!-- END -->
<!-- 初始选择界面 -->
<div v-else-if="init_type === ''" style="display: flex; justify-content: space-between;">
<label
style="width: 200px; height: 150px; background-color: #fff; display: flex; flex-direction: column; align-items: center; border-radius: 10px; margin-right: 20px;">
<input type="radio" v-model="init_type" value="last"/>
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%;">
<div>使用历史数据</div>
</div>
</label>
<label
style="width: 200px; height: 150px; background-color: #fff; display: flex; flex-direction: column; align-items: center; border-radius: 10px; margin-right: 20px;">
<input type="radio" v-model="init_type" value="auto"/>
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%;">
<div>自动解密已登录微信</div>
</div>
</label>
<label
style="width: 200px; height: 150px; background-color: #fff; display: flex; flex-direction: column; align-items: center; border-radius: 10px;">
<input type="radio" v-model="init_type" value="custom"/>
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%;">
<div>自定义文件位置</div>
</div>
</label>
</div>
<!-- END -->
</div>
</template>
<style scoped>
</style>

View File

@ -1,72 +0,0 @@
<template>
<div class="deepseek-set">
<el-form :model="form" label-width="120px">
<el-form-item label="DeepSeek API Key">
<el-input
v-model="form.apiKey"
placeholder="请输入DeepSeek API Key"
show-password
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="handleSubmit"
:loading="submitting"
>
提交
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { apiDeepSeekSet, apiDeepSeekGet } from '@/api/base'
const form = ref({
apiKey: ''
})
const submitting = ref(false)
const fetchSettings = async () => {
try {
const res = await apiDeepSeekGet()
if (res?.API_KEY) {
form.value.apiKey = res.API_KEY
}
} catch (error) {
ElMessage.error('获取设置失败')
}
}
onMounted(() => {
fetchSettings()
})
const handleSubmit = async () => {
if (!form.value.apiKey) {
ElMessage.warning('请输入API Key')
return
}
submitting.value = true
try {
await apiDeepSeekSet(form.value.apiKey)
ElMessage.success('设置成功')
} catch (error) {
ElMessage.error('设置失败')
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.deepseek-set {
padding: 20px;
}
</style>

View File

@ -1,25 +0,0 @@
<script setup lang="ts">
import {ref} from "vue";
const props = defineProps<{
n: Number,
step: Number
}>();
const value = ref(props.n);
const updateNumber = (val: bigint) => {
emit('updateNumber', val);
}
const emit = defineEmits(['updateNumber']);
</script>
<template>
<el-input-number v-model="value" @change="updateNumber" size="small" :step="step"
style="width: 100px;"></el-input-number>
</template>
<style scoped>
</style>

View File

@ -1,56 +0,0 @@
<script setup lang="ts">
import {defineEmits, defineProps, ref} from 'vue';
//
const percentage = ref(0);
const timeout = ref(500);
const colors = [
{color: '#f56c6c', percentage: 20},
{color: '#e6a23c', percentage: 40},
{color: '#5cb87a', percentage: 60},
{color: '#1989fa', percentage: 80},
{color: '#6f7ad3', percentage: 100},
]
const last_time = ref(new Date().getTime());
// END
const props = defineProps({
startORstop: {
type: Number,
required: true,
}
});
const updateProgress = () => {
if (props.startORstop === 1) {
percentage.value = 100;
return;
}
last_time.value = new Date().getTime();
if (percentage.value >= 99) {
return;
}
if (percentage.value >= 60) {
timeout.value = timeout.value + 50;
}
percentage.value = percentage.value + 1;
//
setTimeout(updateProgress, timeout.value);
};
//
updateProgress();
</script>
<template>
<div class="progress-bar">
<el-progress type="dashboard" :percentage="percentage" :color="colors"/>
</div>
</template>
<style scoped>
</style>

View File

@ -1,28 +0,0 @@
import './assets/main.css'
import {createApp} from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import Markdown from 'vue3-markdown-it';
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
// import Appexport from "@/Appexport.vue";
// const app = createApp(Appexport)
app.use(ElementPlus)
app.use(Markdown)
app.provide('msg_path', '');
app.provide('micro_path', '');
app.provide('media_path', '');
app.provide('filestorage_path', '');
app.provide('user_list', []);
app.mount('#app')

View File

@ -1,108 +0,0 @@
import {createRouter, createWebHashHistory} from 'vue-router'
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'index',
component: () => import((`@/views/IndexView.vue`))
},
{
path: '/db_init',
name: 'db_init',
component: () => import((`@/views/DbInitView.vue`))
},
{
path: '/home',
name: 'home',
component: () => import((`@/views/HomeView.vue`))
},
{
path: '/chat',
name: 'chat',
component: () => import((`@/views/ChatView.vue`))
},
{
path: '/chat2ui_select',
name: 'chat2ui_select',
component: () => import((`@/views/Chat2UiSelectVue.vue`))
},
{
path: '/chat2ui',
name: 'chat2ui',
component: () => import((`@/views/Chat2UiView.vue`))
},
{
path: '/contacts',
name: 'contacts',
component: () => import((`@/views/ContactsView.vue`))
},
{
path: '/moments',
name: 'moments',
component: () => import((`@/views/MomentsView.vue`))
},
{
path: '/favorite',
name: 'favorite',
component: () => import((`@/views/FavoriteView.vue`))
},
{
path: '/cleanup',
name: 'cleanup',
component: () => import((`@/views/CleanupView.vue`))
},
{
path: '/statistics',
name: 'statistics',
component: () => import((`@/views/StatisticsView.vue`))
},
// 专业工具
{
path: '/wxinfo',
name: 'wxinfo',
component: () => import((`@/views/tools/WxinfoView.vue`))
},
{
path: '/bias',
name: 'bias',
component: () => import((`@/views/tools/BiasView.vue`))
},
{
path: '/merge',
name: 'merge',
component: () => import((`@/views/tools/MergeView.vue`))
},
{
path: '/decrypt',
name: 'decrypt',
component: () => import((`@/views/tools/DecryptView.vue`))
},
// 其他 关于、帮助、设置
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import((`@/views/other/AboutView.vue`))
},
{
path: '/help',
name: 'help',
component: () => import((`@/views/other/HelpView.vue`))
},
{
path: '/setting',
name: 'setting',
component: () => import((`@/views/other/SettingView.vue`))
},
]
})
export default router

View File

@ -1,76 +0,0 @@
// 创建一个 axios 实例
import axios from 'axios'
import { to_initview } from '@/utils/common_utils'
// import {inject, onMounted} from 'vue';
const params = process.env.NODE_ENV === 'development' ? {
baseURL: 'http://127.0.0.1:5000', // 根据你的实际情况设置基础URL
withCredentials: true, // 表示跨域请求时是否需要使用凭证,开启后,后端服务器要设置允许开启
} : {
withCredentials: true, // 表示跨域请求时是否需要使用凭证,开启后,后端服务器要设置允许开启
}
const http = axios.create(params)
// 请求拦截器
http.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么,比如添加请求头
config.headers['Content-Type'] = 'application/json' // 根据您的需求设置其他请求头
// config.headers['msg_path'] = inject("msg_path");
// config.headers['micro_path'] = inject("micro_path");
// config.headers['media_path'] = inject("media_path");
// config.headers['filestorage_path'] = inject("filestorage_path");
// 补全路径
// console.log('config.url', config.url);
return config
},
(error) => {
// 对请求错误做些什么
console.log('Error Message:', error.message)
return Promise.reject(error)
}
)
// 响应拦截器
http.interceptors.response.use(
(response) => {
// 对响应数据做点什么
if (response.data.code === 0) {
// 如果后端返回的状态码是0 ,说明接口请求成功
// 这里直接返回后端返回的数据
return response.data.body
} else if (response.data.code === 1001 && 'my_wxid is required' in response.data.body) {
// 如果后端返回的状态码是1001说明用户未登录
// 这里直接返回后端返回的数据
// 跳转到登录页面
to_initview();
return Promise.reject(response.data)
} else {
// 如果不是 200说明接口请求失败弹出后端给的错误提示
console.error('Error Message:', response.data)
return Promise.reject(response.data)
}
},
(error) => {
// 对响应错误做点什么
if (error.response) {
// 请求已发出,但服务器响应的状态码不在 2xx 范围内
console.error('HTTP Error Response:', error.response.status)
} else if (error.request) {
// 请求已发出,但没有收到响应
console.error('No response received:', error.request)
} else {
// 发送请求时发生了一些事情,触发了错误
console.error('Error sending request:', error.message)
}
// 把url+参数+错误传递给调用者
return Promise.reject({
message: error.message,
url: error.config.url,
params: error.config.params
})
}
)
export default http

View File

@ -1,82 +0,0 @@
import {ElMessage} from "element-plus";
import router from "@/router";
import http from '@/utils/axios.js';
import {api_db_init} from "@/api/base";
export interface ExtraBuf {
"个性签名": string
"企微属性": string
"公司名称": string
"国": string
"备注图片": string
"备注图片2": string
"市": string
"性别[1男2女]": number
"手机号": string
"朋友圈背景": string
"省": string
}
export interface User {
wxid: string
nOrder: number
nUnReadCount: number
strNickName: string
nStatus: number
nIsSend: number
strContent: string
nMsgLocalID: number
nMsgStatus: number
nTime: string
nMsgType: number
nMsgSubType: number
nickname: string
remark: string
account: string
describe: string
headImgUrl: string
ExtraBuf: ExtraBuf
LabelIDList: string[],
extra: object | null
}
export interface UserList {
[key: string]: User
}
export interface msg {
id: number
MsgSvrID: string
type_name: string
is_sender: number
talker: string
room_name: string
msg: string
src: string
CreateTime: string
extra: {}
}
// {"id": _id, "MsgSvrID": str(MsgSvrID), "type_name": type_name, "is_sender": IsSender,
// "talker": talker, "room_name": StrTalker, "msg": msg, "src": src, "extra": {},
// "CreateTime": CreateTime, }
export const to_initview = () => {
router.push({name: 'db_init'});
ElMessage.error('请先初始化数据');
}
export const is_db_init = async () => {
const t = await api_db_init();
localStorage.setItem('isDbInit', t ? 't' : 'f');
!t ? to_initview() : null;
return t;
}
export const is_use_local_data = () => {
return localStorage.getItem('isUseLocalData') === 't';
}
export const gen_show_name = (userinfo: User) => {
return userinfo?.remark || userinfo?.nickname || userinfo?.strNickName || userinfo?.account || userinfo?.wxid || '未知';
}

View File

@ -1,194 +0,0 @@
<template>
<div
style="
background-color: #d2d2fa;
height: 100vh;
display: grid;
place-items: center;
"
>
<div
style="
background-color: #fff;
width: 90%;
height: 80%;
border-radius: 10px;
padding: 20px;
overflow: auto;
"
>
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
"
>
<div style="font-size: 20px; font-weight: bold">微信数据可视化</div>
</div>
<div style="margin-top: 20px">
<el-table
:data="tableData"
style="width: 100%"
:default-sort="{ prop: 'startTime', order: 'descending' }"
>
<el-table-column
prop="wxid"
label="微信ID"
sortable
:filter-method="filterWxid"
></el-table-column>
<el-table-column
prop="start_time"
label="开始时间"
sortable
:filter-method="filterStartTime"
></el-table-column>
<el-table-column
prop="end_time"
label="结束时间"
sortable
:filter-method="filterEndTime"
></el-table-column>
<el-table-column
prop="flag"
label="状态"
:filters="[
{ text: '已生成', value: true },
{ text: '未生成', value: false },
]"
:filter-method="filterFlag"
>
<template #default="scope">
<el-tag :type="scope.row.flag ? 'success' : 'warning'">
{{ scope.row.flag ? "已生成" : "未生成" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button
v-if="scope.row.flag"
type="primary"
@click="handleJump(scope.row)"
>跳转</el-button
>
<el-button
v-else
type="success"
@click="handleGenerate(scope.row)"
>生成</el-button
>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script setup tang="ts">
import { onMounted, ref } from "vue";
import { apiAiList } from "@/api/chat";
import { useRoute, useRouter } from "vue-router";
import {ElLoading,ElMessage} from "element-plus";
import { apiAiUiCreateJson } from "@/api/chat";
const route = useRoute();
const router = useRouter();
const tableData = ref([
]);
/**
* 获取数据
*/
const getTableData = async () => {
try {
const res = await apiAiList();
// console.log("" + res.items[0]);
tableData.value = res.items;
} catch (error) {
console.error("获取数据失败:", error);
}
};
const filterWxid = (value, row) => {
return row.wxid === value;
};
const filterStartTime = (value, row) => {
return row.startTime.includes(value);
};
const filterEndTime = (value, row) => {
return row.endTime.includes(value);
};
const filterFlag = (value, row) => {
return row.flag === value;
};
const handleJump = (row) => {
//
/***
* 构建查询参数
* 格式
*/
console.log("跳转到可视化页面:", row);
router.push({
path: "/chat2ui",
query: {
wxid: row.wxid,
start_time: row.start_time,
end_time: row.end_time,
},
});
};
const handleGenerate = async (row) => {
const loading = ElLoading.service({
lock: true,
text: '正在生成可视化数据可能需要3分钟左右...',
background: 'rgba(0, 0, 0, 0.7)'
});
try {
// 3
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), 180000)
);
const response = await Promise.race([
apiAiUiCreateJson({
wxid: row.wxid,
start_time: row.start_time,
end_time: row.end_time
}),
timeoutPromise
]);
if (response) {
ElMessage.success('可视化数据生成成功');
await getTableData(); //
}
} catch (error) {
console.error('生成失败:', error);
ElMessage.error(error.message || '生成可视化数据失败');
} finally {
loading.close();
}
};
onMounted(async () => {
console.log("页面加载完成");
await getTableData();
});
</script>
<style scoped></style>

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +0,0 @@
<script setup lang="ts">
import ContactsList from '@/components/chat/ContactsList.vue';
import ChatRecords from '@/components/chat/ChatRecords.vue';
import {onMounted, ref} from "vue";
import IndexView from "@/views/IndexView.vue";
import {apiVersion} from "@/api/base";
import {is_db_init} from "@/utils/common_utils";
const wxid = ref('');
onMounted(() => {
apiVersion().then((data: string) => {
console.log("API version: " + data);
}).catch((error: string) => {
console.error('Error fetching API version:', error);
});
is_db_init();
})
</script>
<template>
<div id="chat_view" class="common-layout">
<div>
<el-container>
<!-- 这是左边的list -->
<el-aside width="auto" style="overflow-y: auto; height: calc(100vh);">
<ContactsList @wxid="(val: any) => { wxid = val;}"/>
</el-aside>
<!-- END 这是左边的list -->
<!--这是右边的具体聊天记录-->
<div v-if="wxid != ''" style="height: calc(100vh);width: 100%;">
<ChatRecords :wxid="wxid"/>
</div>
<div v-else style="width: 100%;height: 100%">
<IndexView/>
</div>
<!-- END 这是右边的具体聊天记录 -->
</el-container>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,27 +0,0 @@
<script setup lang="ts">
import {ref} from "vue";
import HomeView from "@/views/HomeView.vue";
import ContactsList from "@/components/chat/ContactsList.vue";
import ChatExportMain from "@/components/chatBackup/ChatExportMain.vue";
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<h2 style="text-align: center">欢迎使用<a href="https://github.com/xaoyaoo/PyWxDump.git">PyWxDump</a>聊天记录查看工具!
</h2>
<h3 style="text-align: center">
微信存储空间清理减少微信占用空间<br>通过选择某个人或群把这群里的聊天记录中涉及的图片视频文件语音等的媒体文件找出来<br>
以群对话为单位有选择性的比如时间段或按群会话批量从电脑的缓存中清除
</h3>
<h3 style="text-align: center">
打开电脑微信点击左下角的菜单选择设置->通用设置->存储空间管理->清理空间即可查看微信占用的空间点击清理即可清理微信占用的空间<br>
如果这些自带功能无法满足需要请提交issue我会增加点新的功能
</h3>
<p>如需提前体验更多功能请多多支持多多鼓励</p>
</div>
</template>
<style scoped>
</style>

View File

@ -1,29 +0,0 @@
<script setup lang="ts">
import http from '@/utils/axios.js';
import {onMounted, ref} from "vue";
import {apiVersion} from "@/api/base";
const version = ref(''); //
const getVersion = () => {
apiVersion().then((data: string) => {
version.value = data;
}).catch((error: string) => {
console.error('Error fetching API version:', error);
});
}
onMounted(getVersion);
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<h2 style="text-align: center">欢迎使用<a href="https://github.com/xaoyaoo/PyWxDump.git">PyWxDump</a>聊天记录查看工具!
<p style="text-align: center">当前版本{{ version }}</p>
</h2>
</div>
</template>
<style scoped>
</style>

View File

@ -1,11 +0,0 @@
<script setup lang="ts">
import DbInitComponent from "@/components/utils/DbInitComponent.vue";
</script>
<template>
<db-init-component></db-init-component>
</template>
<style scoped>
</style>

View File

@ -1,30 +0,0 @@
<script setup lang="ts">
import http from '@/utils/axios.js';
import {onMounted, ref} from "vue";
import {apiVersion} from "@/api/base";
const version = ref(''); //
const getVersion = () => {
apiVersion().then((data: string) => {
version.value = data;
}).catch((error: string) => {
console.error('Error fetching API version:', error);
});
}
onMounted(getVersion);
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<h2 style="text-align: center">欢迎使用<a href="https://github.com/xaoyaoo/PyWxDump.git">PyWxDump</a>聊天记录查看工具!
<p style="text-align: center">当前版本{{ version }}</p>
</h2>
</div>
</template>
<style scoped>
</style>

View File

@ -1,30 +0,0 @@
<script setup lang="ts">
import http from '@/utils/axios.js';
import {onMounted, ref} from "vue";
import {apiVersion} from "@/api/base";
const version = ref(''); //
const getVersion = () => {
apiVersion().then((data: string) => {
version.value = data;
}).catch((error: string) => {
console.error('Error fetching API version:', error);
});
}
onMounted(getVersion);
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<h2 style="text-align: center">欢迎使用<a href="https://github.com/xaoyaoo/PyWxDump.git">PyWxDump</a>聊天记录查看工具!
<p style="text-align: center">当前版本{{ version }}</p>
</h2>
</div>
</template>
<style scoped>
</style>

View File

@ -1,30 +0,0 @@
<script setup lang="ts">
import http from '@/utils/axios.js';
import {onMounted, ref} from "vue";
import {apiVersion} from "@/api/base";
const version = ref(''); //
const getVersion = () => {
apiVersion().then((data: string) => {
version.value = data;
}).catch((error: string) => {
console.error('Error fetching API version:', error);
});
}
onMounted(getVersion);
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<h2 style="text-align: center">欢迎使用<a href="https://github.com/xaoyaoo/PyWxDump.git">PyWxDump</a>聊天记录查看工具!
<p style="text-align: center">当前版本{{ version }}</p>
</h2>
</div>
</template>
<style scoped>
</style>

View File

@ -1,30 +0,0 @@
<script setup lang="ts">
import http from '@/utils/axios.js';
import {onMounted, ref} from "vue";
import {apiVersion} from "@/api/base";
const version = ref(''); //
const getVersion = () => {
apiVersion().then((data: string) => {
version.value = data;
}).catch((error: string) => {
console.error('Error fetching API version:', error);
});
}
onMounted(getVersion);
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<h2 style="text-align: center">欢迎使用<a href="https://github.com/xaoyaoo/PyWxDump.git">PyWxDump</a>聊天记录查看工具!
<p style="text-align: center">当前版本{{ version }}</p>
</h2>
</div>
</template>
<style scoped>
</style>

View File

@ -1,48 +0,0 @@
<script setup lang="ts">
import DateChatStats from "@/components/stats/DateChatStats.vue";
import {ref} from "vue";
import ContactStats from "@/components/stats/ContactStats.vue";
import DateChatHeatmapStats from "@/components/stats/DateChatHeatmapStats.vue";
const mene_selected = ref("date_chat_heatmap");
</script>
<template>
<!-- <h2 style="text-align: center">欢迎使用<a href="https://github.com/xaoyaoo/PyWxDump.git">PyWxDump</a>聊天记录查看工具!-->
<!-- </h2>-->
<!-- <h3 style="text-align: center">-->
<!-- 用于统计微信聊天记录包括聊天记录数量聊天记录总字数聊天记录总图片数量<br>聊天记录总视频数量聊天记录总文件数量聊天记录总语音数量等等-->
<!-- 行成统计报表方便用户查看自己的聊天记录情况-->
<!-- </h3>-->
<div class="common-layout" style="height: 100vh;width: 100%;background-color: #d2d2fa;">
<el-container style="height: calc(100vh);width: 100%;">
<el-aside width="120px" style="height: 100%;">
<el-menu style="height: 100%;background-color: #F7F7F7;color:#262626;"
:default-active="mene_selected"
class="el-menu-vertical-demo"
@select="(val: string)=>{mene_selected = val}"
>
<el-menu-item index="date_chat_heatmap">
<span>聊天热力图</span>
</el-menu-item>
<el-menu-item index="date_chat_count">
<span>日聊天数据</span>
</el-menu-item>
<el-menu-item index="contact_stats">
<span>联系人画像</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main style="height: 100%;width: 100%;margin: 0;padding: 0;">
<date-chat-stats v-if="mene_selected=='date_chat_count'"/>
<date-chat-heatmap-stats v-if="mene_selected=='date_chat_heatmap'"/>
<contact-stats v-if="mene_selected=='contact_stats'"/>
</el-main>
</el-container>
</div>
</template>
<style scoped>
</style>

View File

@ -1,67 +0,0 @@
<template>
<div class="about">
<h1 id="-center-pywxdump-center-" style="text-align: center">
PyWxDump<a @click="check_update" target="_blank" style="float: right; margin-right: 30px;">检查更新</a>
</h1>
<!-- 在右上角添加按钮 检查更新 -->
<Markdown :source="source" style="background-color: #d2d2fa;"/>
</div>
</template>
<script setup lang="ts">
import Markdown from 'vue3-markdown-it';
import http from '@/utils/axios.js';
import {type Action, ElMessage, ElMessageBox} from "element-plus";
import {onMounted, ref} from "vue";
const check_update = async () => {
try {
const body_data = await http.post('/api/rs/check_update');
const latest_version = body_data.latest_version;
const msg = body_data.msg;
const url = body_data.latest_url;
const showtext = `${msg}${latest_version} \n ${url || ''}`;
ElMessageBox.alert(showtext, 'info', {
confirmButtonText: '确认',
callback: (action: Action) => {
ElMessage({
type: 'info',
message: `action: ${action}`,
})
},
})
} catch (error) {
// console.error('Error fetching data:', error);
return [];
}
}
const source = ref("# 加载中")
const get_readme_md = async () => {
try {
const body_data = await http.post('/api/rs/get_readme');
source.value = body_data;
} catch (error) {
// console.error('Error fetching data:', error);
return [];
}
}
onMounted(() => {
get_readme_md()
})
</script>
<style>
.about {
background-color: #d2d2fa;
height: 100%;
}
</style>

View File

@ -1,33 +0,0 @@
<template>
<div class="about">
<h1 id="-center-pywxdump-center-" style="text-align: center">
PyWxDump UserGuide & FAQ
</h1>
<br><br><br><br><br><br>
<!-- <Markdown :source="source" />-->
<h2 style="text-align: center">
<a href="https://github.com/xaoyaoo/PyWxDump/blob/master/doc/UserGuide.md" target="_blank">使用教程</a>
<br>
<a href="https://github.com/xaoyaoo/PyWxDump/blob/master/doc/FAQ.md" target="_blank">常见问题</a>
<br>
<a href="https://github.com/xaoyaoo/PyWxDump/blob/master/doc/CE%E8%8E%B7%E5%8F%96%E5%9F%BA%E5%9D%80.md"
target="_blank">CE获取基址</a>
<br>
<a href="https://github.com/xaoyaoo/PyWxDump/blob/master/doc/wx%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%80%E8%BF%B0.md"
target="_blank">wx数据库简述</a>
<br>
<a href="https://github.com/xaoyaoo/PyWxDump/blob/master/doc/MAC%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A7%A3%E5%AF%86.md"
target="_blank">MAC数据库解密</a>
</h2>
</div>
</template>
<script setup lang="ts">
import Markdown from 'vue3-markdown-it';
const source = "";
</script>
<style>
</style>

View File

@ -1,54 +0,0 @@
<script setup lang="ts">
import DbInitView from "@/views/DbInitView.vue";
import {ref} from "vue";
import DbInitComponent from "@/components/utils/DbInitComponent.vue";
import deepseekSet from "@/components/utils/DeepSeekSet.vue";
const setting_selected = ref("")
const MeneSelect = (val: any) => {
setting_selected.value = val
}
</script>
<template>
<div class="common-layout" style="height: 100vh;width: 100%;background-color: #F7F7F7;">
<el-container>
<el-header style="height: 35px; max-height: 35px; width: 100%;">
<h2 style="text-align: center">欢迎使用<a
href="https://github.com/xaoyaoo/PyWxDump.git">PyWxDump</a>聊天记录查看工具!
<span style="font-size: 14px">(如需提前体验更多功能请开通超级vip)</span>
</h2>
</el-header>
<el-container style="height: calc(100vh - 35px);width: 100%;">
<el-aside width="200px" style="height: 100%;">
<el-menu style="height: 100%;background-color: #F7F7F7;color:#262626;"
default-active="2"
class="el-menu-vertical-demo"
@select="MeneSelect"
>
<el-menu-item index="-1" disabled>
<span style="color: #043bea">设置</span>
</el-menu-item>
<el-menu-item index="db_init">
<span>初始化设置</span>
</el-menu-item>
<el-menu-item index="deepseek_set">
<span>DeepSeek设置</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main style="height: 100%;max-height: 100%;width: 100%;margin: 0;padding: 0;">
<db-init-component v-if="setting_selected=='db_init'"></db-init-component>
<deepseek-set v-if="setting_selected=='deepseek_set'"></deepseek-set>
</el-main>
</el-container>
</el-container>
</div>
</template>
<style scoped>
</style>

View File

@ -1,75 +0,0 @@
<script setup lang="ts">
import {ref} from "vue";
import http from '@/utils/axios.js';
const mobile = ref<string>('');
const name = ref<string>('');
const account = ref<string>('');
const key = ref<string>('');
const wxdbPath = ref<string>('');
const result = ref<string>(''); //
const decrypt = async () => {
try {
// keywxdbPath
if (key.value === '' && wxdbPath.value === '') {
result.value = 'key与wxdbPath必须填写一个';
return;
}
result.value = await http.post('/api/ls/biasaddr', {
mobile: mobile.value,
name: name.value,
account: account.value,
key: key.value,
wxdbPath: wxdbPath.value
});
result.value = "{版本号:昵称,账号,手机号,邮箱,KEY}\n"+result.value;
} catch (error) {
result.value = 'Error fetching data: \n' + error;
console.error('Error fetching data:', error);
return [];
}
};
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<div style="background-color: #fff; width: 70%; height: 70%; border-radius: 10px; padding: 20px; overflow: auto;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 20px; font-weight: bold;">基址偏移</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<!-- <el-button style="margin-right: 10px;" @click="exportData">导出</el-button>-->
</div>
</div>
<div style="margin-top: 20px;">
<label>手机号: </label>
<el-input placeholder="请输入手机号" v-model="mobile" style="width: 80%;"></el-input>
<br>
<label>昵称: </label>
<el-input placeholder="请输入昵称" v-model="name" style="width: 80%;"></el-input>
<br>
<label>微信账号: </label>
<el-input placeholder="请输入微信号" v-model="account" style="width: 80%;"></el-input>
<br>
<label>密钥key: </label>
<el-input placeholder="请输入密钥key可选" v-model="key" style="width: 80%;"></el-input>
<br>
<label>微信数据库路径: </label>
<el-input placeholder="请输入微信数据库路径(可选)" v-model="wxdbPath" style="width: 75%;"></el-input>
<br>
<el-button style="margin-top: 10px;width: 50%;" type="success" @click="decrypt">偏移</el-button>
<!-- 分割线 -->
<el-divider></el-divider>
<!-- 分割线 -->
<el-input type="textarea" :rows="10" readonly placeholder="输出结果" v-model="result"
style="width: 100%;color: #00bd7e;"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,60 +0,0 @@
<script setup lang="ts">
import {ref} from "vue";
import http from '@/utils/axios.js';
const wxdbPath = ref<string>('');
const key = ref<string>('');
const outPath = ref<string>('');
const decryptResult = ref<string>('');
const decrypt = async () => {
try {
decryptResult.value = await http.post('/api/ls/decrypt', {
wxdbPath: wxdbPath.value,
key: key.value,
outPath: outPath.value
});
} catch (error) {
decryptResult.value = 'Error fetching data: \n' + error;
console.error('Error fetching data:', error);
return [];
}
};
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<div style="background-color: #fff; width: 70%; height: 70%; border-radius: 10px; padding: 20px; overflow: auto;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 20px; font-weight: bold;">解密-微信数据库</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<!-- <el-button style="margin-right: 10px;" @click="exportData">导出</el-button>-->
</div>
</div>
<div style="margin-top: 20px;">
<label>密钥key: </label>
<el-input placeholder="请输入密钥key" v-model="key" style="width: 82%;"></el-input>
<br>
<label>微信数据库路径: </label>
<el-input placeholder="请输入微信数据库路径" v-model="wxdbPath" style="width: 80%;"></el-input>
<br>
<label>解密后输出文件夹路径: </label>
<el-input placeholder="请输入解密后输出文件夹路径" v-model="outPath" style="width: 75%;"></el-input>
<br>
<el-button style="margin-top: 10px;width: 50%;" type="success" @click="decrypt">解密</el-button>
<!-- 分割线 -->
<el-divider></el-divider>
<!-- 分割线 -->
<el-input type="textarea" :rows="10" readonly placeholder="解密后数据库路径" v-model="decryptResult"
style="width: 100%;"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,53 +0,0 @@
<script setup lang="ts">
import {ref} from "vue";
import http from '@/utils/axios.js';
const dbPath = ref<string>('');
const outPath = ref<string>('');
const Result = ref<string>('');
const decrypt = async () => {
try {
Result.value = await http.post('/api/ls/merge', {
dbPath: dbPath.value,
outPath: outPath.value
});
} catch (error) {
Result.value = 'Error fetching data: \n' + error;
console.error('Error fetching data:', error);
return [];
}
};
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<div style="background-color: #fff; width: 70%; height: 70%; border-radius: 10px; padding: 20px; overflow: auto;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 20px; font-weight: bold;">合并-微信数据库</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<!-- <el-button style="margin-right: 10px;" @click="exportData">导出</el-button>-->
</div>
</div>
<div style="margin-top: 20px;">
<label>数据库路径: </label>
<el-input placeholder="数据库路径文件夹并且确保文件夹下的db文件已经解密" v-model="dbPath" style="width: 80%;"></el-input>
<br>
<label>微信数据库路径: </label>
<el-input placeholder="输出合并后的数据库路径" v-model="outPath" style="width: 80%;"></el-input>
<br>
<el-button style="margin-top: 10px;width: 50%;" type="success" @click="decrypt">合并</el-button>
<!-- 分割线 -->
<el-divider></el-divider>
<!-- 分割线 -->
<el-input type="textarea" :rows="10" readonly placeholder="合并后数据库路径" v-model="Result"
style="width: 100%;"></el-input>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,95 +0,0 @@
<script setup lang="ts">
import {defineComponent, reactive, ref, onMounted, toRefs} from 'vue'
import {ElNotification, ElTable, ElTableColumn} from 'element-plus'
import http from '@/utils/axios.js';
interface wxinfo {
pid: string;
version: string;
account: string;
mobile: string;
name: string;
mail: string;
wxid: string;
filePath: string;
key: string;
}
const wxinfoData = ref<wxinfo[]>([]);
const get_wxinfo = async () => {
try {
wxinfoData.value = await http.post('/api/ls/wxinfo');
} catch (error) {
console.error('Error fetching data:', error);
return [];
}
}
onMounted(get_wxinfo); //
const refreshData = async () => {
await get_wxinfo();
};
const exportData = () => {
const csvContent = convertToCSV(wxinfoData.value);
downloadCSV(csvContent, 'wxinfo_data.csv');
};
const convertToCSV = (data: wxinfo[]) => {
const header = Object.keys(data[0]).join(',');
const rows = data.map((item) => Object.values(item).join(','));
return `${header}\n${rows.join('\n')}`;
};
const downloadCSV = (csvContent: string, fileName: string) => {
const blob = new Blob([new Uint8Array([0xEF, 0xBB, 0xBF]), csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, fileName);
} else {
link.href = URL.createObjectURL(blob);
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
ElNotification.success({
title: 'Success',
message: 'CSV file exported successfully!',
});
};
</script>
<template>
<div style="background-color: #d2d2fa; height: 100vh; display: grid; place-items: center; ">
<div style="background-color: #fff; width: 90%; height: 80%; border-radius: 10px; padding: 20px; overflow: auto;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 20px; font-weight: bold;">微信信息已经登录</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<el-button style="margin-right: 10px;" @click="refreshData">刷新</el-button>
<el-button style="margin-right: 10px;" @click="exportData">导出</el-button>
</div>
</div>
<div style="margin-top: 20px;">
<el-table :data="wxinfoData" style="width: 100%">
<el-table-column :min-width="30" prop="pid" label="进程id"></el-table-column>
<el-table-column :min-width="40" prop="version" label="微信版本"></el-table-column>
<el-table-column :min-width="40" prop="account" label="账号"></el-table-column>
<el-table-column :min-width="45" prop="mobile" label="手机号"></el-table-column>
<el-table-column :min-width="40" prop="nickname" label="昵称"></el-table-column>
<el-table-column :min-width="30" prop="mail" label="邮箱"></el-table-column>
<el-table-column :min-width="50" prop="wxid" label="微信原始id"></el-table-column>
<el-table-column prop="wx_dir" label="微信文件夹路径"></el-table-column>
<el-table-column prop="key" label="密钥(key)"></el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -1,13 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@ -1,18 +0,0 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
],
"compilerOptions": {
"types": [
"node",
"axios"
],
"allowJs": true
}
}

Some files were not shown because too many files have changed in this diff Show More