Browse Source

first commit

master
wxc 8 months ago
commit
571716d92e
  1. 26
      .editorconfig
  2. 23
      .gitignore
  3. 8
      .idea/.gitignore
  4. 8
      .idea/modules.xml
  5. 6
      .idea/vcs.xml
  6. 26
      .npmignore
  7. 6
      .prettierignore
  8. 10
      .prettierrc
  9. 3
      .vscode/extensions.json
  10. 15
      CHANGELOG.md
  11. 21
      LICENSE
  12. 44
      README.md
  13. BIN
      build.crx
  14. 28
      build.pem
  15. 14
      devtools.html
  16. 232
      download.html
  17. 46
      globalConfig.ts
  18. 9
      my-crx.iml
  19. 14
      newtab.html
  20. 13
      options.html
  21. 6864
      package-lock.json
  22. 40
      package.json
  23. 13
      popup.html
  24. BIN
      public/icons/logo.ico
  25. 9
      public/icons/logo.svg
  26. BIN
      public/img/logo-128.png
  27. BIN
      public/img/logo-16.png
  28. BIN
      public/img/logo-34.png
  29. BIN
      public/img/logo-48.png
  30. 13
      sidepanel.html
  31. BIN
      src/assets/logo.png
  32. 316
      src/background/index.ts
  33. 113
      src/contentScript/ajhc/getInfo.ts
  34. 1
      src/contentScript/index.ts
  35. 207
      src/contentScript/netComplaint/getInfo.ts
  36. 177
      src/contentScript/submit/getInfo.ts
  37. 69
      src/devtools/DevTools.vue
  38. 8
      src/devtools/index.ts
  39. 8
      src/global.d.ts
  40. 79
      src/manifest.ts
  41. 80
      src/options/Options.vue
  42. 5
      src/options/index.ts
  43. 563
      src/popup/Popup.vue
  44. 5
      src/popup/index.ts
  45. 66
      src/sidepanel/SidePanel.vue
  46. 5
      src/sidepanel/index.ts
  47. 20
      src/utils/ajax.ts
  48. 10
      src/zip.js
  49. 18
      tsconfig.json
  50. 8
      tsconfig.node.json
  51. 4
      version.json
  52. 23
      vite.config.ts

26
.editorconfig

@ -0,0 +1,26 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,jsx,ts,tsx,md}]
charset = utf-8
indent_style = space
indent_size = 2
tab_width = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

23
.gitignore vendored

@ -0,0 +1,23 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
/package
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.history
*.log
# secrets
secrets.*.js

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/my-crx.iml" filepath="$PROJECT_DIR$/my-crx.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

26
.npmignore

@ -0,0 +1,26 @@
# OS
.DS_Store
# ignore node dependency directories & lock
node_modules
yarn.lock
pnpm-lock.yaml
package-lock.json
# ignore log files and local
*.log
*.local
.env.local
.env.development.local
.env.test.local
.env.production.local
.history
# ignore compiled files
build
types
coverage
# ignore ide settings
.idea
.vscode

6
.prettierignore

@ -0,0 +1,6 @@
# Ignore artifacts:
build
coverage
node_modules
pnpm-lock.yaml
pnpm-workspace.yaml

10
.prettierrc

@ -0,0 +1,10 @@
{
"jsxSingleQuote": false,
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "lf",
"printWidth": 100,
"semi": false,
"tabWidth": 2,
"useTabs": false
}

3
.vscode/extensions.json vendored

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

15
CHANGELOG.md

@ -0,0 +1,15 @@
# CHANGELOG
```txt
Summary
1. document grouping follow 'SemVer2.0' protocol
2. use 'PATCH' as a minimum granularity
3. use concise descriptions
4. type: feat \ fix \ update \ perf \ remove \ docs \ chore
5. version timestamp follow the yyyy.MM.dd format
```
## 0.0.0 [2024.12.19]
- feat: initial
- feat: generator by ![create-chrome-ext](https://github.com/guocaoyi/create-chrome-ext)

21
LICENSE

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2024-present, no one
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

44
README.md

@ -0,0 +1,44 @@
# my-crx
> a chrome extension tools built with Vite + Vue, and Manifest v3
## Installing
1. Check if your `Node.js` version is >= **14**.
2. Change or configurate the name of your extension on `src/manifest`.
3. Run `npm install` to install the dependencies.
## Developing
run the command
```shell
$ cd my-crx
$ npm run dev
```
### Chrome Extension Developer Mode
1. set your Chrome browser 'Developer mode' up
2. click 'Load unpacked', and select `my-crx/build` folder
### Nomal FrontEnd Developer Mode
1. access `http://0.0.0.0:3000/`
2. when debugging popup page, open `http://0.0.0.0:3000//popup.html`
3. when debugging options page, open `http://0.0.0.0:3000//options.html`
## Packing
After the development of your extension run the command
```shell
$ npm run build
```
Now, the content of `build` folder will be the extension ready to be submitted to the Chrome Web Store. Just take a look at the [official guide](https://developer.chrome.com/webstore/publish) to more infos about publishing.
---
Generated by [create-chrome-ext](https://github.com/guocaoyi/create-chrome-ext)

BIN
build.crx

Binary file not shown.

28
build.pem

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDj8i8RxTfm5x/H
nWGgwQLuN2WOFLL4qseldExeSmSIwPIdCDf0s50u74GMp7UhCfTAIRNFVj26FHts
Re6lpu8d/lgmUVg+x5xqZ0nsix64JQZkvCUnh0B3ljgM0ohKq4t/h1gTRdARWdIx
SES9oyKv9FwmSOZirjlZGhDYA90rYJJaRKrsAkLL/BOi1QBfOecGi1KKtPfZgxZm
g0EiY4o2dKl7uD5nAg8T8HBqkNqAOXcDvuI4Xw4sLTXSVBFqDr7FY/eDwSlBDS91
v5xaMte8In4tEW1cvqIwiaEgcB3+rsPWHm/xcE902MtfCGZckAdVk/uJi4NYI2HB
nFYZ+4l7AgMBAAECggEARr8xfbX0yj0cv3MozGBrXO0jn2vsOkKjEUadtzwmpM0h
uAIHf/LJS7p5PmME1BrHdGQvm3sPUHQv9GRmE9LSb0A0MrxXg7Cnatzg9EIfiC6x
o81DSNfxGcytW8lyFJ7Wfn/OxbiKwf/bPYF9GFCMIQlaGmXQ33rMgMuWw7y7Rc0u
0OZ1RI1IiZqrcgDDjOTos5Jrvutl31A18j0hhlwpEQ/t6k1r7zXCBp1JulKMNG8s
to8cvYIUAF4xAvP0FyF/xOhZ6zSplS4tAYbRfR4TdA2bO67IRDONtU4KKc3vNXm6
FPEnvu3AxRcv5Z1yh3BQ6gd912wweSyzZgPyB9SRFQKBgQD3b9N1jZVoUB45Tv9A
/KwQzqyVy5T2ZGKMTiygVeNjJzYUH0vQejtuVf7DRDK2NSQDIJEswkVbNqFM2lB4
6DO7lvJl2VLW2WA4J0UOdzzElzmj8lGSpacJNMn07ARCr6xsa2ohjMU6FRIs9eih
mHDzXJ5jFJ90dc12AnEU9isdnQKBgQDr1a25jn7psFcXLWOjb7aDPaObBNKufWBB
+SMsofb86db4wpvwAmOze9VkVXDxyxUJFpmIhcDGRC+e0xAS7XTBMHfqFggXN15N
ippbsgvFjTG1J5MrW8QftyQLVGTlh4UWbAd1MAwhKaHxsv3fma2QGedYBNVN8Lmn
0z0mmXOj9wKBgQDE8llZw+wcL0kfTGWXkIZUimyMfpJ+28Ak9URICpUHIvrEYXSy
59fOJty1B99lcD7Nzmo/OhKCW6VfypUq54fl1Gvz7Vsmb7dKetdYgEf6InRarlGH
bBrcFQx6yjFJ8xJG6eh789Q4OvVQnBsiJHgi/KGFM7EHvYxgVek0SgxfcQKBgQDi
bosaeiKIpXM5Ia9lIMGQSQJouLzAJEjjjx2ioO/P7YUl21R1oRWxItjEOPT0cxSD
YVuQpTtuIdHHIMdyJOPRCYRZEUY7ZEH7GXUTTWPYDbUIRjMbkkRAX0sQRbuKVhAG
czrF3ZgHzz1aH415vsfSb8ybyigzaUoYRM2V3ggWCQKBgA1LvK/iRNC1VYFWBjNG
8kYD6SsyLUffrvZzRdTPx21nCuTJGnVLi2mUEM+YKJEHGLUvNmqFRdNwlOPAslmG
4VZFMStllrQb5aPMxzkYW8y2DOZx7OfDCpCvxrK7O/zdpoZCwzFbsNTRm1iY4JKy
vDtr/sYmttfmrjEBL7LG8+VG
-----END PRIVATE KEY-----

14
devtools.html

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/icons/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chrome Extension + Vue + TS + Vite</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/devtools/index.ts"></script>
</body>
</html>

232
download.html

@ -0,0 +1,232 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数字督查插件下载</title>
<style>
body {
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background-color: #f9f9f9;
color: #000;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
}
.container {
background: #fff;
padding: 3rem;
border-radius: 18px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
max-width: 500px;
width: 100%;
}
h1 {
font-size: 2.5rem;
margin-bottom: 2rem;
color: #007aff;
font-weight: 600;
}
@keyframes smooth-gradient {
0% {
background-position: 0% 50%;
}
100% {
background-position: 100% 50%;
}
}
#downloadBtn {
background-color: #007aff;
color: white;
border: none;
padding: 1.2rem 2.5rem;
font-size: 1.1rem;
border-radius: 24px;
cursor: pointer;
transition: background-color 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 122, 255, 0.2);
display: inline-flex;
align-items: center;
}
#downloadBtn:hover {
background: linear-gradient(270deg, #fe0000, #ff5d00, #ffff00, #fe0000);
background-size: 400% 400%;
animation: smooth-gradient 3s ease infinite;
}
#downloadBtn:active, #downloadBtn:focus {
animation: none;
background-color: #007aff;
}
.info {
margin-top: 2rem;
font-size: 0.9rem;
color: #888;
}
.info p {
margin: 0.7rem 0;
}
.instructions {
margin-top: 3rem;
font-size: 0.9rem;
color: #555;
text-align: left;
line-height: 1.8;
}
.instructions h2 {
font-size: 1.3rem;
color: #007aff;
margin-bottom: 1rem;
font-weight: 600;
}
.instructions p {
position: relative;
padding-left: 2rem;
}
.instructions p::before {
content: counter(step-counter);
position: absolute;
left: 0;
top: 0;
color: white;
background-color: #007aff;
width: 20px;
height: 20px;
border-radius: 50%;
text-align: center;
line-height: 20px;
font-size: 0.9rem;
}
.instructions {
counter-reset: step-counter;
}
.instructions p {
counter-increment: step-counter;
}
/* 新增样式 */
.browser-nav {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 1rem;
}
.browser-btn {
padding: 0.5rem 1.5rem;
border: 1px solid #ddd;
border-radius: 8px;
cursor: pointer;
font-size: 0.9rem;
background-color: #fff;
transition: background-color 0.3s ease-out, color 0.3s ease-out;
}
.browser-btn.active {
background-color: #007aff;
color: white;
border-color: #007aff;
}
.install-content {
display: none;
}
.install-content.active {
display: block;
}
</style>
</head>
<body>
<div class="container">
<h1>欢迎使用数字督查插件</h1>
<button id="downloadBtn">
<span class="download-logo"></span> 点此下载插件
</button>
<div class="info">
<p>版本号 : 1.0.1</p>
<p>更新时间 : 2025年2月20日</p>
<p>兼容内核 : Chromium 88及以上</p>
<p>©Copr. 长沙创客</p>
</div>
<div class="instructions">
<h2>插件安装方法</h2>
<div class="browser-nav">
<button class="browser-btn active" data-browser="native">国产浏览器</button>
<button class="browser-btn" data-browser="chrome">Chrome浏览器</button>
<button class="browser-btn" data-browser="edge">Edge浏览器</button>
</div>
<!-- 国产浏览器安装方法 -->
<div class="install-content active" id="native">
<p>下载插件(crx 文件)后,找到下载的文件。</p>
<p>启动浏览器,将下载好的 crx 文件拖动至浏览器页面。</p>
<p>松开鼠标,3秒内系统将弹出“添加数字督察”提示框。</p>
<p>点击“添加”按钮,即可完成插件安装(或升级)操作。</p>
</div>
<!-- Chrome浏览器安装方法 -->
<div class="install-content" id="chrome">
<p>在浏览器地址栏输入 <b>chrome://extensions/</b> 并按下回车键,打开扩展程序页面。</p>
<p>打开右上方的 “开发者模式” 开关。</p>
<p>将下载的 crx 插件后缀名改为 <b>.zip</b>,并解压成文件夹。</p>
<p>点击浏览器左上方 “加载已解压的扩展程序” 按钮,选择刚才的文件夹。</p>
</div>
<!-- Edge浏览器安装方法 -->
<div class="install-content" id="edge">
<p>在浏览器地址栏输入 <b>edge://extensions/</b> 并按下回车键,打开扩展页面。</p>
<p>打开左侧的 “开发人员模式” 开关。</p>
<p>将下载的 crx 插件拖动到浏览器框内。</p>
<p>在弹出框里点击 “添加扩展” 即可完成插件安装(或升级)操作。</p>
</div>
</div>
</div>
<script>
document.getElementById('downloadBtn').addEventListener('click', () => {
window.location.href = 'http://65.47.6.108/extension/shuziducha.crx';
});
// 浏览器按钮点击事件
document.querySelectorAll('.browser-btn').forEach(button => {
button.addEventListener('click', () => {
// 移除所有按钮的active类
document.querySelectorAll('.browser-btn').forEach(btn => {
btn.classList.remove('active');
});
// 移除所有安装内容的active类
document.querySelectorAll('.install-content').forEach(content => {
content.classList.remove('active');
});
// 添加当前按钮的active类
button.classList.add('active');
// 显示对应的内容
const browserType = button.dataset.browser;
document.getElementById(browserType).classList.add('active');
});
});
</script>
</body>
</html>

46
globalConfig.ts

@ -0,0 +1,46 @@
// 后端接口
// http://localhost:5172/api/v2
// http://192.168.31.146:5172/api/v2
// http://65.47.6.108/api/v2
export const supervisionUrl = 'http://65.47.6.108/api/v2';
// 检测更新接口
// http://localhost:8080/version.json
// http://65.47.6.108/extension/version.json
export const checkUpdateUrl = 'http://65.47.6.108/extension/version.json';
// 后端案件核查信息上传 POST
export const supervisionAjhcInfoUrl = 'http://65.47.6.108/api/v2/crx/ajhc';
// 后端案件核查ID列表上传 POST
export const supervisionAjhcIdsUrl = 'http://65.47.6.108/api/v2/crx/ajhc/outerIds';
// 后端案件核查附件上传 POST
export const supervisionAjhcFileUploadUrl = 'http://65.47.6.108/api/v2/crx/ajhc/file';
// 后端案件核查处置意见上传 POST
export const supervisionAjhcDisposalOpinionUrl = 'http://65.47.6.108/api/v2/crx/ajhc/disposalOpinion';
// 后端案件核查处置明细上传 POST
export const supervisionAjhcDisposalDetailsUrl = 'http://65.47.6.108/api/v2/crx/ajhc/disposalDetails';
// 后端案件核查检查是否提交过办结信息 POST(已弃用)
export const supervisionAjhcCheckCompletionInformationUrl = 'http://65.47.6.108/api/v2/crx/ajhcCheckCompletionInformation';
// 后端案件核查检查是否需要填表 GET
export const supervisionAjhcCheckTrueSituationUrl = 'http://65.47.6.108/api/v2/crx/ajhc/trueSituation?outerId=';
// 案件核查,本地测试用
export const ajhcSysUrl = 'http://localhost:5172';
export const ajhcInfoUrl = '/#/sensitivePerception/modelClue';
// 插件界面更多按钮
// 'http://localhost:5172/#/data/ajhc'
// 'http://65.47.6.108/v2/#/data/Ajhc'
export const morePageUrl = 'http://65.47.6.108/v2/#/data/Ajhc';
// 案件核查子系统
export const ajhcOutSysUrl = 'http://11.33.3.4';
// 案件核查子系统-投诉受理
export const ajhcOutInfoUrl = 'http://11.33.3.4/complaintacceptance';
// 案件核查子系统-在办案件
export const ajhcInProgressUrl = 'http://11.33.3.4/workcenterlist/doingcases';
// 案件核查投诉受理(已弃用)
export const netComplaintUrl = 'http://11.33.3.4/complaintacceptance/netcomplaint';
export const phoneComplaintUrl = 'http://11.33.3.4/complaintacceptance/phonecomplaint';
export const letterComplaintUrl = 'http://11.33.3.4/complaintacceptance/lettercomplaint';
export const otherComplaintUrl = 'http://11.33.3.4/complaintacceptance/othercomplaint';

9
my-crx.iml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

14
newtab.html

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/icons/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chrome Extension + Vue + TS + Vite</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/newtab/index.ts"></script>
</body>
</html>

13
options.html

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/icons/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chrome Extension + Vue + TS + Vite</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/options/index.ts"></script>
</body>
</html>

6864
package-lock.json generated

File diff suppressed because it is too large Load Diff

40
package.json

@ -0,0 +1,40 @@
{
"name": "my-crx",
"displayName": "数字督察",
"version": "1.0.1",
"author": "I",
"description": "数字督察插件",
"type": "module",
"license": "MIT",
"keywords": [
"chrome-extension",
"vue",
"vite",
"create-chrome-ext"
],
"engines": {
"node": ">=14.18.0"
},
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"fmt": "prettier --write '**/*.{vue,ts,json,css,scss,md}'",
"zip": "npm run build && node src/zip.js"
},
"dependencies": {
"vue": "^3.3.4"
},
"devDependencies": {
"@crxjs/vite-plugin": "^2.0.0-beta.19",
"@types/chrome": "^0.0.246",
"@vitejs/plugin-vue": "^4.4.0",
"element-plus": "^2.8.8",
"gulp": "^4.0.2",
"gulp-zip": "^6.0.0",
"prettier": "^3.0.3",
"typescript": "5.6.2",
"vite": "^4.4.11",
"vue-tsc": "2.0.29"
}
}

13
popup.html

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/icon/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chrome Extension + Vue + TS + Vite</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/popup/index.ts"></script>
</body>
</html>

BIN
public/icons/logo.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

9
public/icons/logo.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 750 KiB

BIN
public/img/logo-128.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
public/img/logo-16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

BIN
public/img/logo-34.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
public/img/logo-48.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

13
sidepanel.html

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/icons/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chrome Extension + Vue + TS + Vite</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/sidepanel/index.ts"></script>
</body>
</html>

BIN
src/assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

316
src/background/index.ts

@ -0,0 +1,316 @@
import {
checkUpdateUrl,
supervisionAjhcCheckTrueSituationUrl,
supervisionAjhcDisposalDetailsUrl,
supervisionAjhcDisposalOpinionUrl,
supervisionAjhcFileUploadUrl,
supervisionAjhcIdsUrl,
supervisionAjhcInfoUrl
} from "../../globalConfig";
import { ajax } from "../utils/ajax";
console.log('后台开始运行');
// 当前申请案件办结的outerId
let curCompletionOuterId: string | null = null;
chrome.webRequest.onBeforeRequest.addListener(
/*
--
*/
(details) => {
// 检查是否是目标接口
if (details.url === "http://11.33.3.4/api/v5/gzzx/ajhc/hcbg") {
// 获取请求体
const requestBody = details.requestBody;
if (requestBody) {
// 检查请求体是否为JSON格式
if (requestBody.raw && requestBody.raw.length > 0) {
const rawBytes = requestBody.raw[0].bytes;
// 检查 rawBytes 是否为 undefined
if (rawBytes) {
const decoder = new TextDecoder('utf-8');
const requestBodyString = decoder.decode(rawBytes);
try {
const requestBodyJson = JSON.parse(requestBodyString);
const fileUrl = requestBodyJson.wjUrl;
const fileName = requestBodyJson.wjMc;
const outerId = requestBodyJson.ajId;
const fileType = "核查报告";
// 发送wjUrl和ajId到指定接口
ajax(supervisionAjhcFileUploadUrl, {
method: "POST",
body: { fileUrl, fileName, outerId, fileType }
})
.then((data: any) => console.log("Success:", data))
.catch((error: any) => console.error("Error:", error));
} catch (error) {
console.error("上传核查报告Error parsing JSON:", error);
}
} else {
console.error("上传核查报告requestBody.raw[0].bytes is undefined");
}
}
}
} else if (details.url === "http://11.33.3.4/api/v5/gzzx/ajhc/zjxx") {
/*
--
*/
// 获取请求体
const requestBody = details.requestBody;
if (requestBody) {
if (requestBody.raw && requestBody.raw.length > 0) {
const rawBytes = requestBody.raw[0].bytes;
if (rawBytes) {
const decoder = new TextDecoder('utf-8');
const requestBodyString = decoder.decode(rawBytes);
try {
const requestBodyJson = JSON.parse(requestBodyString);
if (requestBodyJson.fj && requestBodyJson.fj.length > 0) {
const fileUrl = requestBodyJson.fj[0].wjUrl;
const fileName = requestBodyJson.fj[0].wjMc;
const outerId = requestBodyJson.ajId;
const fileType = "核查证据";
// 发送wjUrl和ajId到指定接口
ajax(supervisionAjhcFileUploadUrl, {
method: "POST",
body: { fileUrl, fileName, outerId, fileType }
})
.then((data: any) => console.log("Success:", data))
.catch((error: any) => console.error("Error:", error));
}
} catch (error) {
console.error("上传核查证据Error parsing JSON:", error);
}
} else {
console.error("上传核查证据requestBody.raw[0].bytes is undefined");
}
}
}
} else if (details.url === "http://11.33.3.4/api/v5/gzzx/ajcz") {
/*
-
*/
const requestBody = details.requestBody;
if (requestBody) {
if (requestBody.raw && requestBody.raw.length > 0) {
const rawBytes = requestBody.raw[0].bytes;
if (rawBytes) {
const decoder = new TextDecoder('utf-8');
const requestBodyString = decoder.decode(rawBytes);
try {
const requestBodyJson = JSON.parse(requestBodyString);
const handlingSuggestions = requestBodyJson.cljg;
const outerId = requestBodyJson.ajId;
const trueSituation = requestBodyJson.ssqkMc;
const complaintNature = requestBodyJson.tsWtxz;
ajax(supervisionAjhcDisposalOpinionUrl, {
method: "POST",
body: { handlingSuggestions, outerId, trueSituation, complaintNature }
})
.then((data: any) => console.log("Success:", data))
.catch((error: any) => console.error("Error:", error));
} catch (error) {
console.error("上传处置意见Error parsing JSON:", error);
}
} else {
console.error("上传处置意见requestBody.raw[0].bytes is undefined");
}
}
}
} else if (details.url === "http://11.33.3.4/api/v5/gzzx/ajcz/czmx") {
/*
-
*/
const requestBody = details.requestBody;
if (requestBody) {
if (requestBody.raw && requestBody.raw.length > 0) {
const rawBytes = requestBody.raw[0].bytes;
if (rawBytes) {
const decoder = new TextDecoder('utf-8');
const requestBodyString = decoder.decode(rawBytes);
try {
const requestBodyJson = JSON.parse(requestBodyString);
const outerId = requestBodyJson.ajId;
const disposalType = requestBodyJson.czlx;
const disposalDepart = requestBodyJson.czDw;
const disposalAlarm = requestBodyJson.czJh;
const disposalName = requestBodyJson.czMc;
const disposalIdNo = requestBodyJson.czSfz;
const disposalSituation = requestBodyJson.czqk;
const disposalFileUrl = requestBodyJson.flwsUrl;
const disposalFileName = requestBodyJson.flwsMc;
const disposalSituationDescription = requestBodyJson.qksm;
ajax(supervisionAjhcDisposalDetailsUrl, {
method: "POST",
body: { outerId, disposalType, disposalDepart, disposalAlarm, disposalName, disposalIdNo, disposalSituation, disposalFileUrl, disposalFileName, disposalSituationDescription }
})
.then((data: any) => console.log("Success:", data))
.catch((error: any) => console.error("Error:", error));
} catch (error) {
console.error("上传处置明细Error parsing JSON:", error);
}
} else {
console.error("上传处置明细requestBody.raw[0].bytes is undefined");
}
}
}
} else if (details.url.startsWith("http://11.33.3.4/api/v5/gzzx/bj/anqx/")) {
/*
-id
*/
const url = new URL(details.url);
const pathSegments = url.pathname.split('/');
curCompletionOuterId = pathSegments[pathSegments.length - 1];
}
},
{ urls: ["<all_urls>"] },
["requestBody"] // 显式请求 requestBody
);
/*
-outerId
*/
// 监听来自 contentScript 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'getCurCompletionOuterId') {
console.log('发送getCurCompletionOuterId:' + curCompletionOuterId);
sendResponse(curCompletionOuterId);
}
});
/*
-ID列表上传
*/
// 监听来自 contentScript 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'fetchDataRowKeys') {
const dataRowKeyMaps = request.data;
// 使用 ajax 方法发送请求
ajax(supervisionAjhcIdsUrl, {
method: 'POST',
body: dataRowKeyMaps
})
.then((data: any) => {
console.log('ID列表上传Success:', data);
sendResponse({ status: 'success', data });
})
.catch((error: any) => {
console.error('ID列表上传Error:', error);
sendResponse({ status: 'error', error });
});
return true;
}
});
/*
-
*/
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'sendAjhcData') {
const { caseNumber, caseSource, complaintTime, reporterName, reporterContact, verifiedObjectUnit, verifiedObjectUnitCode, verifiedObjectName, verifiedObjectPosition, briefCase, organization, complaintIssueNature, acceptanceLevel } = request.data;
// 使用 ajax 方法发送请求
ajax(supervisionAjhcInfoUrl, {
method: 'POST',
body: { caseNumber, caseSource, complaintTime, reporterName, reporterContact, verifiedObjectUnit, verifiedObjectUnitCode, verifiedObjectName, verifiedObjectPosition, briefCase, organization, complaintIssueNature, acceptanceLevel }
})
.then(response => response.json())
.then((data: any) => {
console.log('投诉受理数据上传Success:', data);
sendResponse({ status: 'success', data });
})
.catch((error: any) => {
console.error('投诉受理数据上传Error:', error);
sendResponse({ status: 'error', error });
});
return true;
}
});
/*
-
*/
// 监听来自 contentScript 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'checkTrueSituation') {
// 检查 curCompletionOuterId 是否为 null
if (curCompletionOuterId !== null) {
// 使用 ajax 方法发送请求
ajax(`${supervisionAjhcCheckTrueSituationUrl}${encodeURIComponent(curCompletionOuterId)}`, {
method: 'GET'
})
.then((data: any) => {
console.log('检查是否需要填表Success:', data);
sendResponse({ status: 'success', data });
})
.catch((error: any) => {
console.error('检查是否需要填表Error:', error);
sendResponse({ status: 'error', error });
});
} else {
console.error('curCompletionOuterId is null, cannot send request');
sendResponse({ status: 'error', error: 'curCompletionOuterId is null' });
}
return true;
}
});
/*
*/
checkUpdate(); // 插件刚打开时立即运行一次检查更新方法
setInterval(checkUpdate, 600000); // 每10分钟检查一次
// 版本检查
async function checkUpdate() {
try {
const response = await fetch(checkUpdateUrl);
const data = await response.json();
const currentVersion = chrome.runtime.getManifest().version;
if (compareVersions(data.version, currentVersion) > 0) {
showUpdateNotification(data.downloadUrl);
}
} catch (error) {
console.error('检测更新失败:', error);
}
}
// 版本号比较
function compareVersions(a: string, b: string): number {
const aParts = a.split('.').map(Number);
const bParts = b.split('.').map(Number);
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
const aVal = aParts[i] || 0;
const bVal = bParts[i] || 0;
if (aVal !== bVal) return aVal - bVal;
}
return 0;
}
// 显示更新通知
function showUpdateNotification(downloadUrl: string) {
console.log('发现新版本,显示更新通知');
chrome.notifications.create({
type: 'basic',
iconUrl: 'img/logo-128.png',
title: '发现新版本,为避免出错,请立即更新!',
message: '点击此处下载数字督查最新版本插件',
priority: 2
});
// 保存下载URL
chrome.storage.local.set({ updateUrl: downloadUrl });
}
// 处理通知点击
chrome.notifications.onClicked.addListener(() => {
chrome.storage.local.get('updateUrl', (result) => {
if (result.updateUrl) {
chrome.tabs.create({ url: result.updateUrl });
}
});
});

113
src/contentScript/ajhc/getInfo.ts

@ -0,0 +1,113 @@
import {ajhcInfoUrl, ajhcSysUrl} from "../../../globalConfig";
// 获取样本源头编号和问题发现时间
function getSampleData(): { sampleSourceNumber: string | null, issueDiscoveryTime: string | null } {
const elements = Array.from(document.querySelectorAll('div.col.col-6'));
let sampleSourceNumber: string | null = null;
let issueDiscoveryTime: string | null = null;
for (const element of elements) {
const labelElement = element.querySelector('label');
const spanElement = element.querySelector('span');
if (labelElement && spanElement) {
if (labelElement.textContent === '样本源头编号') {
sampleSourceNumber = spanElement.textContent;
} else if (labelElement.textContent === '问题发现时间') {
issueDiscoveryTime = spanElement.textContent;
}
}
}
return { sampleSourceNumber, issueDiscoveryTime };
}
// 发送样本源头编号和问题发现时间到指定接口
function sendData(sampleSourceNumber: string, issueDiscoveryTime: string) {
fetch(ajhcSysUrl + '/data/caseVerif', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ sampleSourceNumber, issueDiscoveryTime }),
})
.then(response => response.json())
.then(data => console.log('Success:', data))
.catch((error) => console.error('Error:', error));
}
// 持续监控页面
function monitorPage() {
let lastSampleSourceNumber: string | null = null;
let lastIssueDiscoveryTime: string | null = null;
setInterval(() => {
const { sampleSourceNumber, issueDiscoveryTime } = getSampleData();
if (sampleSourceNumber && issueDiscoveryTime &&
(sampleSourceNumber !== lastSampleSourceNumber || issueDiscoveryTime !== lastIssueDiscoveryTime)) {
console.log('已找到样本源头编号和问题发现时间:', sampleSourceNumber, issueDiscoveryTime);
sendData(sampleSourceNumber, issueDiscoveryTime);
lastSampleSourceNumber = sampleSourceNumber; // 更新上次检测到的编号
lastIssueDiscoveryTime = issueDiscoveryTime; // 更新上次检测到的时间
}
}, 1000); // 每秒检查一次
}
let isMonitoring = false; // 添加标志位
// 使用 MutationObserver 监控 URL 变化
function observeUrlChange() {
const observer = new MutationObserver(() => {
if (!isMonitoring && window.location.href.includes(ajhcInfoUrl)) {
console.log('URL 包含指定路径,开始监控页面');
monitorPage();
isMonitoring = true; // 设置标志位为 true
observer.disconnect(); // 断开观察
}
checkAndShowWarning();
});
observer.observe(document.body, { childList: true, subtree: true });
}
// 执行 URL 监控函数
observeUrlChange();
// 创建警告消息的 div 元素
function createWarningDiv(): HTMLElement {
const warningDiv = document.createElement('div');
warningDiv.style.position = 'fixed';
warningDiv.style.top = '10px';
warningDiv.style.right = '100px';
warningDiv.style.color = 'red';
warningDiv.style.fontWeight = 'bold';
warningDiv.textContent = '请登录插件,否则数据无法同步至一体化平台!';
warningDiv.classList.add('crx-login-warning');
warningDiv.style.display = 'none'; // 默认隐藏
document.body.appendChild(warningDiv);
return warningDiv;
}
// 显示警告消息的函数
function showWarningMessage(warningDiv: HTMLElement, showMessage: boolean) {
warningDiv.style.display = showMessage ? 'block' : 'none';
}
// 检查并显示警告的函数
function checkAndShowWarning() {
const warningDiv = createWarningDiv(); // 创建警告 div
setInterval(() => {
console.log('检查并显示警告');
if (window.location.href.includes(ajhcSysUrl)) {
chrome.storage.local.get(['srcAuthToken'], (result) => {
const token = result.srcAuthToken;
if (!token) {
console.log('未找到 srcAuthToken');
showWarningMessage(warningDiv, true); // 显示警告
} else {
showWarningMessage(warningDiv, false); // 隐藏警告
}
});
}
}, 5000); // 每5秒检查一次
}

1
src/contentScript/index.ts

@ -0,0 +1 @@
console.info('contentScript is running')

207
src/contentScript/netComplaint/getInfo.ts

@ -0,0 +1,207 @@
import {ajhcOutInfoUrl} from "../../../globalConfig";
/*
*/
// 获取样本源头编号和问题发现时间
function getSampleData(): { caseNumber: string | null, caseSource: string | null, complaintTime: string | null, reporterName: string | null, reporterContact: string | null, verifiedObjectUnit: string | null, verifiedObjectUnitCode: string | null, verifiedObjectName: string | null, verifiedObjectPosition: string | null, briefCase: string | null, organization: string | null, complaintIssueNature: string | null, acceptanceLevel: string | null } {
console.log('开始获取元素');
// 根据文本内容查找元素
function findElementByText(selector: string, text: string): HTMLElement | null {
const elements = document.querySelectorAll(selector);
for (const element of Array.from(elements)) {
if (element.textContent && element.textContent.trim().includes(text)) {
return element as HTMLElement;
}
}
return null;
}
// 获取基本信息栏中的数据
const basicInfoTitle = findElementByText('div.ant-card-head-title', '基本信息');
console.log('获取到basicInfoTitle' + basicInfoTitle);
let caseNumber = null;
let caseSource = null;
let complaintTime = null;
if (basicInfoTitle) {
const basicInfoSection = basicInfoTitle.closest('.ant-card')?.querySelector('.ant-card-body');
console.log('获取到basicInfoSection' + basicInfoSection);
if (basicInfoSection) {
const caseNumberLabel = basicInfoSection.querySelector('div.ant-col.ant-form-item-label:has(label[title="案件编号"])');
caseNumber = caseNumberLabel?.nextElementSibling?.querySelector('span.ant-form-item-children span')?.textContent || null;
console.log('获取到caseNumber' + caseNumber);
const caseSourceLabel = basicInfoSection.querySelector('div.ant-col.ant-form-item-label:has(label[title="案件来源"])');
caseSource = caseSourceLabel?.nextElementSibling?.querySelector('span.ant-form-item-children')?.textContent || null;
console.log('获取到caseSource' + caseSource);
const complaintTimeLabel = basicInfoSection.querySelector('div.ant-col.ant-form-item-label:has(label[title="投诉时间"])');
complaintTime = complaintTimeLabel?.nextElementSibling?.querySelector('span.ant-form-item-children')?.textContent || null;
console.log('获取到complaintTime' + complaintTime);
} else {
console.log('basicInfoSection没有找到');
}
} else {
console.log('基本信息没有找到');
}
// 获取举报人信息栏中的数据
const reporterInfoTitle = findElementByText('div.ant-card-head-title', '举报人信息');
let reporterName = null;
let reporterContact = null;
if (reporterInfoTitle) {
const reporterInfoSection = reporterInfoTitle.closest('.ant-card')?.querySelector('.ant-card-body');
console.log('获取到reporterInfoSection' + reporterInfoSection);
if (reporterInfoSection) {
const reporterNameLabel = reporterInfoSection.querySelector('div.ant-col.ant-form-item-label:has(label[title="举报人姓名"])');
reporterName = reporterNameLabel?.nextElementSibling?.querySelector('span.ant-form-item-children')?.textContent || null;
console.log('获取到reporterName' + reporterName);
const reporterContactLabel = reporterInfoSection.querySelector('div.ant-col.ant-form-item-label:has(label[title="联系方式"])');
reporterContact = reporterContactLabel?.nextElementSibling?.querySelector('span.ant-form-item-children')?.textContent || null;
console.log('获取到reporterContact' + reporterContact);
} else {
console.log('reporterInfoSection没有找到');
}
} else {
console.log('举报人信息没有找到');
}
// 获取被核查对象信息栏中的数据
const verifiedObjectTitle = findElementByText('div.ant-card-head-title', '被核查对象信息');
let verifiedObjectUnit = null;
let verifiedObjectUnitCode = null;
let verifiedObjectName = null;
let verifiedObjectPosition = null;
if (verifiedObjectTitle) {
const verifiedObjectSection = verifiedObjectTitle.closest('.ant-card')?.querySelector('.ant-card-body');
console.log('获取到verifiedObjectSection' + verifiedObjectSection);
if (verifiedObjectSection) {
const verifiedObjectUnitLabel = verifiedObjectSection.querySelector('div.ant-col.ant-form-item-label:has(label[title="单位"])');
verifiedObjectUnit = verifiedObjectUnitLabel?.nextElementSibling?.querySelector('span.ant-form-item-children')?.textContent || null;
console.log('获取到verifiedObjectUnit' + verifiedObjectUnit);
const verifiedObjectUnitCodeString = verifiedObjectUnitLabel?.nextElementSibling?.querySelector('span.ant-form-item-children a')?.getAttribute('href') || null;
if (verifiedObjectUnitCodeString !== null) {
const verifiedObjectUnitCodeMatch = verifiedObjectUnitCodeString.match(/bjbdw_(\d+)/);
if (verifiedObjectUnitCodeMatch && verifiedObjectUnitCodeMatch[1]) {
verifiedObjectUnitCode = verifiedObjectUnitCodeMatch[1];
}
}
console.log('获取到verifiedObjectUnitCode' + verifiedObjectUnitCode);
const verifiedObjectNameLabel = verifiedObjectSection.querySelector('div.ant-col.ant-form-item-label:has(label[title="姓名"])');
verifiedObjectName = verifiedObjectNameLabel?.nextElementSibling?.querySelector('span.ant-form-item-children')?.textContent || null;
console.log('获取到verifiedObjectName' + verifiedObjectName);
const verifiedObjectPositionLabel = verifiedObjectSection.querySelector('div.ant-col.ant-form-item-label:has(label[title="职务"])');
verifiedObjectPosition = verifiedObjectPositionLabel?.nextElementSibling?.querySelector('span.ant-form-item-children')?.textContent || null;
console.log('获取到verifiedObjectPosition' + verifiedObjectPosition);
} else {
console.log('verifiedObjectSection没有找到');
}
} else {
console.log('被核查对象信息没有找到');
}
// 获取投诉案情栏中的数据
const complaintCaseTitle = findElementByText('div.ant-card-head-title', '投诉案情');
let organization = null;
let briefCase = null;
if (complaintCaseTitle) {
const complaintCaseSection = complaintCaseTitle.closest('.ant-card')?.querySelector('.ant-card-body');
console.log('获取到complaintCaseSection' + complaintCaseSection);
if (complaintCaseSection) {
const organizationLabel = complaintCaseSection.querySelector('div.ant-col.ant-col-7.org-label');
if (organizationLabel && organizationLabel.textContent && organizationLabel.textContent.trim() === "组织机构:") {
organization = organizationLabel.nextElementSibling?.textContent || null;
console.log('获取到organization: ' + organization);
} else {
console.log('未找到"组织机构"标签');
}
const briefCaseLabel = complaintCaseSection.querySelector('div.ant-col.ant-col-5 span');
if (briefCaseLabel && briefCaseLabel.textContent && briefCaseLabel.textContent.trim() === "简要案情:") {
briefCase = briefCaseLabel?.parentElement?.nextElementSibling?.textContent || null;
console.log('获取到briefCase:', briefCase);
} else {
console.log('未找到"简要案情"标签');
}
} else {
console.log('complaintCaseSection没有找到');
}
} else {
console.log('投诉案情信息没有找到');
}
// 获取问题类型栏中的数据
const questionTypeTitle = findElementByText('div.ant-card-head-title', '问题类型');
let complaintIssueNature = null;
if (questionTypeTitle) {
const questionTypeSection = questionTypeTitle.closest('.ant-card')?.querySelector('.ant-card-body');
console.log('获取到questionTypeSection' + questionTypeSection);
if (questionTypeSection) {
const complaintIssueNatureLabel = questionTypeSection.querySelector('div.ant-col.ant-form-item-label:has(label[title="投诉问题性质"])');
complaintIssueNature = complaintIssueNatureLabel?.nextElementSibling?.querySelector('ul.ant-select-selection__rendered')?.textContent || null;
console.log('获取到complaintIssueNature' + complaintIssueNature);
} else {
console.log('questionTypeSection没有找到');
}
} else {
console.log('投诉案情信息没有找到');
}
// 获取受理意见栏中的数据
const acceptanceOpinionTitle = findElementByText('div.ant-card-head-title', '受理意见');
let acceptanceLevel = null;
if (acceptanceOpinionTitle) {
const acceptanceOpinionSection = acceptanceOpinionTitle.closest('.ant-card')?.querySelector('.ant-card-body');
console.log('获取到acceptanceOpinionSection' + acceptanceOpinionSection);
if (acceptanceOpinionSection) {
const acceptanceLevelLabel = acceptanceOpinionSection.querySelector('div.ant-col.ant-form-item-label:has(label.ant-form-item-required[title="拟办方式"])');
acceptanceLevel = acceptanceLevelLabel?.nextElementSibling?.querySelector('label.ant-radio-wrapper.ant-radio-wrapper-checked')?.textContent || "本级办理";
console.log('获取到acceptanceLevel' + acceptanceLevel);
} else {
console.log('acceptanceOpinionSection没有找到');
}
} else {
console.log('受理意见没有找到');
}
return { caseNumber, caseSource, complaintTime, reporterName, reporterContact, verifiedObjectUnit, verifiedObjectUnitCode, verifiedObjectName, verifiedObjectPosition, briefCase, organization, complaintIssueNature, acceptanceLevel };
}
/*
*/
// 发送样本源头编号和问题发现时间到指定接口
function sendData(caseNumber: string | null, caseSource: string | null, complaintTime: string | null, reporterName: string | null, reporterContact: string | null, verifiedObjectUnit: string | null, verifiedObjectUnitCode: string | null, verifiedObjectName: string | null, verifiedObjectPosition: string | null, briefCase: string | null, organization: string | null, complaintIssueNature: string | null, acceptanceLevel: string | null) {
// 发送消息到 background 脚本
chrome.runtime.sendMessage({ action: 'sendAjhcData', data: { caseNumber, caseSource, complaintTime, reporterName, reporterContact, verifiedObjectUnit, verifiedObjectUnitCode, verifiedObjectName, verifiedObjectPosition, briefCase, organization, complaintIssueNature, acceptanceLevel } }, response => {
console.log('获取投诉受理数据结果:', response);
});
}
// 定期检查确认按钮并执行代码
function checkButtonAndExecute() {
let confirmButtonClickListener: () => void;
setInterval(() => {
const confirmButton = Array.from(document.querySelectorAll('button.ant-btn.ant-btn-primary'))
.find(button => button.querySelector('span')?.textContent === '确 认');
if (confirmButton && window.location.href.includes(ajhcOutInfoUrl)) {
console.log('确认按钮已找到');
// 移除旧的事件监听器
if (confirmButtonClickListener) {
confirmButton.removeEventListener('click', confirmButtonClickListener);
}
// 定义新的事件监听器并添加
confirmButtonClickListener = () => {
const { caseNumber, caseSource, complaintTime, reporterName, reporterContact, verifiedObjectUnit, verifiedObjectUnitCode, verifiedObjectName, verifiedObjectPosition, briefCase, organization, complaintIssueNature, acceptanceLevel } = getSampleData();
console.log('已找到案件信息:', caseNumber, caseSource, complaintTime, reporterName, reporterContact, verifiedObjectUnit, verifiedObjectUnitCode, verifiedObjectName, verifiedObjectPosition, briefCase, organization, complaintIssueNature, acceptanceLevel);
sendData(caseNumber, caseSource, complaintTime, reporterName, reporterContact, verifiedObjectUnit, verifiedObjectUnitCode, verifiedObjectName, verifiedObjectPosition, briefCase, organization, complaintIssueNature, acceptanceLevel);
};
confirmButton.addEventListener('click', confirmButtonClickListener);
}
}, 1000); // 每1秒检查一次
}
// 执行定期检查函数
checkButtonAndExecute();

177
src/contentScript/submit/getInfo.ts

@ -0,0 +1,177 @@
import {
ajhcInProgressUrl,
} from "../../../globalConfig";
/*
*/
// 根据文本内容查找元素
function findElementByText(selector: string, text: string): HTMLElement | null {
const elements = document.querySelectorAll(selector);
for (const element of Array.from(elements)) {
if (element.textContent && element.textContent.trim().includes(text)) {
return element as HTMLElement;
}
}
return null;
}
/*
*/
// 创建警告消息的 div 元素
function createWarningDiv(): HTMLElement {
const warningDiv = document.createElement('div');
warningDiv.style.position = 'fixed';
warningDiv.style.top = '10%';
warningDiv.style.left = '50%';
warningDiv.style.transform = 'translate(-50%, -50%)';
warningDiv.style.fontSize = '30px';
warningDiv.style.color = 'red';
warningDiv.style.fontWeight = 'bold';
warningDiv.textContent = '请登录数字督察插件,否则数据无法同步!';
warningDiv.classList.add('crx-login-warning');
warningDiv.style.display = 'none'; // 默认隐藏
document.body.appendChild(warningDiv);
return warningDiv;
}
// 显示警告消息的函数
function showWarningMessage(warningDiv: HTMLElement, showMessage: boolean) {
warningDiv.style.display = showMessage ? 'block' : 'none';
}
// 检查并显示警告的函数
function checkAndShowWarning() {
const warningDiv = createWarningDiv(); // 创建警告 div
setInterval(() => {
chrome.storage.local.get(['srcAuthToken'], (result) => {
const token = result.srcAuthToken;
if (!token) {
console.log('插件未登录');
showWarningMessage(warningDiv, true); // 显示警告
} else {
showWarningMessage(warningDiv, false); // 隐藏警告
}
});
}, 5000); // 每5秒检查一次
}
checkAndShowWarning(); // 在脚本加载时立即调用 checkAndShowWarning
/*
ID列表上传
*/
let windowOn: boolean = false;
function checkAndCallUpdateDataRowKeys() {
if (window.location.href.includes(ajhcInProgressUrl)) {
const currentResult = document.querySelector('div.antd-pro-pages-workcenter-ajhc-ajhc-hcbg-index-hc');
updateDataRowKeys();
if (currentResult !== null && !windowOn) {
}
windowOn = currentResult !== null;
}
}
setInterval(checkAndCallUpdateDataRowKeys, 1000);
const sentData = new Set<string>(); // 用于存储已经发送过的 caseNumber
function updateDataRowKeys() {
const scroll = document.querySelector('div.ant-table-scroll');
if (!scroll) {
return;
}
const dataRowKeyMaps = Array.from(scroll.querySelectorAll('tr.ant-table-row'))
.map(tr => {
const outerId = tr.getAttribute('data-row-key');
const caseNumber = tr.querySelector('.ant-table-fixed-columns-in-body.ant-table-row-cell-break-word a')?.textContent;
return { caseNumber, outerId };
})
.filter(({ caseNumber }) => caseNumber !== null && caseNumber !== undefined && !sentData.has(caseNumber)); // 过滤掉已经发送过的 caseNumber
// 检查 dataRowKeyMaps 是否为空
if (dataRowKeyMaps.length > 0) {
chrome.runtime.sendMessage({ action: 'fetchDataRowKeys', data: dataRowKeyMaps }, response => {
console.log('上传ID列表结果:', response);
if (response.status === "success") {
dataRowKeyMaps.forEach(({ caseNumber }) => {
if (caseNumber !== null && caseNumber !== undefined) {
sentData.add(caseNumber); // 更新已发送数据的存储
}
});
}
});
}
}
/*
*/
function createCompletionInformationWarningDiv(): HTMLElement {
const completionInformationWarningDiv = document.createElement('div');
completionInformationWarningDiv.style.position = 'fixed';
completionInformationWarningDiv.style.top = '0%';
completionInformationWarningDiv.style.right = '0%';
completionInformationWarningDiv.style.fontSize = '60px';
completionInformationWarningDiv.style.color = 'rgb(101 161 255)';
completionInformationWarningDiv.style.fontWeight = 'bold';
completionInformationWarningDiv.textContent = '请点击右上方的数字督察插件按钮填写补充信息';
completionInformationWarningDiv.classList.add('crx-completion-information-warning');
completionInformationWarningDiv.style.display = 'none'; // 默认隐藏
completionInformationWarningDiv.style.zIndex = '1000'; // 设置z-index确保在最上层
// 创建 img 元素并设置属性
const logoImg = document.createElement('img');
logoImg.src = chrome.runtime.getURL('img/logo-128.png');
logoImg.style.width = '150px';
logoImg.style.height = '150px';
// 将 img 元素添加到 completionInformationWarningDiv 中
completionInformationWarningDiv.appendChild(logoImg);
document.body.appendChild(completionInformationWarningDiv);
return completionInformationWarningDiv;
}
function showCompletionInformationWarningMessage(completionInformationWarningDiv: HTMLElement, showMessage: boolean) {
completionInformationWarningDiv.style.display = showMessage ? 'block' : 'none';
}
// 定期检查案件办结的确定按钮并执行代码
function checkButtonAndExecute() {
const completionInformationWarningDiv = createCompletionInformationWarningDiv();
setInterval(() => {
const ajbj = findElementByText('div.ant-tabs-tab-active.ant-tabs-tab', '案件办结');
const confirmButton = Array.from(document.querySelectorAll('button.ant-btn.ant-btn-primary'))
.find(button => button.querySelector('span')?.textContent === '确 定') as HTMLButtonElement;
const currentState = Boolean(ajbj && confirmButton && window.location.href.includes(ajhcInProgressUrl));
if (currentState) {
console.log('找到按钮:', confirmButton);
chrome.runtime.sendMessage({action: 'checkTrueSituation'}, response => {
if (response.data.data !== true) {
confirmButton.disabled = true;
showCompletionInformationWarningMessage(completionInformationWarningDiv, true);
} else {
confirmButton.disabled = false;
showCompletionInformationWarningMessage(completionInformationWarningDiv, false);
}
});
} else {
showCompletionInformationWarningMessage(completionInformationWarningDiv, false);
confirmButton.disabled = false;
}
// 发送消息到 popup
chrome.runtime.sendMessage({ action: 'showUpdateWriteOn', value: currentState });
}, 2000); // 每2秒检查一次
}
// 执行定期检查函数
checkButtonAndExecute();

69
src/devtools/DevTools.vue

@ -0,0 +1,69 @@
<script setup lang="ts">
import { ref } from 'vue'
const link = ref('https://github.com/guocaoyi/create-chrome-ext')
</script>
<template>
<main>
<h3>DevTools Page</h3>
<a :href="link" target="_blank"> generated by create-chrome-ext </a>
</main>
</template>
<style>
:root {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
color-scheme: light dark;
background-color: #242424;
}
@media (prefers-color-scheme: light) {
:root {
background-color: #fafafa;
}
a:hover {
color: #42b983;
}
}
body {
min-width: 20rem;
}
main {
text-align: center;
padding: 1em;
margin: 0 auto;
}
h3 {
color: #42b983;
text-transform: uppercase;
font-size: 1.5rem;
font-weight: 200;
line-height: 1.2rem;
margin: 2rem auto;
}
a {
font-size: 0.5rem;
margin: 0.5rem;
color: #cccccc;
text-decoration: none;
}
</style>

8
src/devtools/index.ts

@ -0,0 +1,8 @@
import { createApp } from 'vue'
import App from './DevTools.vue'
chrome.devtools.panels.create('VueCrx', '', '../../devtools.html', function () {
console.log('devtools panel create')
})
createApp(App).mount('#app')

8
src/global.d.ts vendored

@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}

79
src/manifest.ts

@ -0,0 +1,79 @@
import { defineManifest } from '@crxjs/vite-plugin'
import packageData from '../package.json'
import {
ajhcSysUrl,
ajhcOutSysUrl
} from "../globalConfig";
//@ts-ignore
const isDev = process.env.NODE_ENV == 'development'
export default defineManifest({
name: `${packageData.displayName || packageData.name}${isDev ? ` Dev` : ''}`,
description: packageData.description,
version: packageData.version,
manifest_version: 3,
icons: {
16: 'img/logo-16.png',
32: 'img/logo-34.png',
48: 'img/logo-48.png',
128: 'img/logo-128.png',
},
action: {
default_popup: 'popup.html',
default_icon: 'img/logo-48.png',
},
options_page: 'options.html',
devtools_page: 'devtools.html',
background: {
service_worker: 'src/background/index.ts',
type: 'module',
},
content_scripts: [
{
matches: ['http://*/*', 'https://*/*'],
js: ['src/contentScript/index.ts'],
},
{
matches: [`${ajhcSysUrl}` + '/*'],
js: ['src/contentScript/ajhc/getInfo.ts'],
},
{
matches: [`${ajhcOutSysUrl}` + '/*'],
js: ['src/contentScript/netComplaint/getInfo.ts'],
},
{
matches: [`${ajhcOutSysUrl}` + '/*'],
js: ['src/contentScript/submit/getInfo.ts'],
},
],
side_panel: {
default_path: 'sidepanel.html',
},
web_accessible_resources: [
{
resources: [
'img/logo-16.png',
'img/logo-34.png',
'img/logo-48.png',
'img/logo-128.png',
'public/img/logo-128.png'
],
matches: ['<all_urls>'],
},
],
permissions: [
'alarms',
'sidePanel',
'storage',
'webRequest',
'webRequestBlocking',
'activeTab',
'notifications',
'scripting',
'<all_urls>',
],
host_permissions: [
'<all_urls>'
]
})

80
src/options/Options.vue

@ -0,0 +1,80 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const countSync = ref(0)
const link = ref('https://github.com/guocaoyi/create-chrome-ext')
onMounted(() => {
chrome.storage.sync.get(['count'], (result) => {
countSync.value = result.count ?? 0
})
chrome.runtime.onMessage.addListener((request) => {
if (request.type === 'COUNT') {
countSync.value = request?.count ?? 0
}
})
})
</script>
<template>
<main>
<h3>数字督查</h3>
</main>
</template>
<style>
:root {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
color-scheme: light dark;
background-color: #242424;
}
@media (prefers-color-scheme: light) {
:root {
background-color: #fafafa;
}
a:hover {
color: #42b983;
}
}
body {
min-width: 20rem;
}
main {
text-align: center;
padding: 1em;
margin: 0 auto;
}
h3 {
color: #42b983;
text-transform: uppercase;
font-size: 1.5rem;
font-weight: 200;
line-height: 1.2rem;
margin: 2rem auto;
}
a {
font-size: 0.5rem;
margin: 0.5rem;
color: #cccccc;
text-decoration: none;
}
</style>

5
src/options/index.ts

@ -0,0 +1,5 @@
import { createApp } from 'vue'
import App from './Options.vue'
createApp(App).mount('#app')

563
src/popup/Popup.vue

@ -0,0 +1,563 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ElTable, ElTableColumn, ElButton } from 'element-plus'
import { supervisionUrl, morePageUrl } from "../../globalConfig";
/*
定义-用户管理
*/
// ref
const username = ref('')
const password = ref('')
//
const isLoggedIn = ref(false)
/*
定义-表格管理
*/
// ref
interface RecordType {
originId: string;
crxState: string;
responderName: string;
sourceInvolveDepartName: string;
createTime: string;
}
const formRecords = ref<RecordType[]>([]);
//
const formLoading = ref(true);
/*
定义-通用
*/
// Options
type Options = {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE',
headers?: Record<string, string>,
body?: any,
};
// writeOn
const writeOn = ref(false);
/*
方法-通用
*/
//
const ajax = async (url: string, options: Options = {}) => {
let token: string | null;
const result = await chrome.storage.local.get(['srcAuthToken']);
token = result.srcAuthToken;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options.headers,
'Authorization': `${token}`, // 使 Authorization token
};
return fetch(`${supervisionUrl}${url}`, {
method: options.method || 'GET',
body: options.body ? JSON.stringify(options.body) : undefined,
headers,
credentials: 'include', // Cookie
}).then(response => {
if (!response.ok) {
throw new Error('请求失败');
}
return response.json();
});
};
// :
const getStatusClass = (crxState: string): string => {
switch (crxState) {
case '1':
return 'status-synced';
case '2':
return 'status-pending';
case '3':
return 'status-completed';
default:
return 'status-unknown';
}
};
/*
方法-用户管理
*/
//
const handleLogin = async () => {
try {
const response = await ajax('/login', {
method: 'POST',
body: {
account: username.value,
password: password.value,
},
});
if (response.code === 200) {
console.log('登录成功');
const token = response.data.token;
await chrome.storage.local.set({ srcAuthToken: token });
document.cookie = `token=${token}; path=/;`;
isLoggedIn.value = true; //
await fetchRecords();
} else {
throw new Error('登录失败:', response.message);
}
} catch (error) {
console.error('登录错误:', error);
alert('登录失败,请检查用户名和密码');
}
};
// 退
const handleLogout = async () => {
try {
await ajax('/logout', {
method: 'POST',
})
await chrome.storage.local.remove('srcAuthToken')
//
formRecords.value = []
isLoggedIn.value = false; //
} catch (error) {
console.error('退出登录错误:', error)
await chrome.storage.local.remove('srcAuthToken')
//
formRecords.value = []
isLoggedIn.value = false; //
}
}
/*
方法-表格管理
*/
//
const fetchRecords = async () => {
try {
const response = await ajax('/crx/ajhc');
if (response.code === 200) {
// : response.data
formRecords.value = response.data || [];
console.log('获取记录成功:', formRecords.value)
} else {
console.error('获取记录失败:', response.message);
}
} catch (error) {
console.error('获取记录错误:', error);
formRecords.value = [];
} finally {
formLoading.value = false;
}
};
/*
vue生命周期
*/
onMounted(async () => {
const result = await chrome.storage.local.get(['srcAuthToken']);
const token = result.srcAuthToken;
if (token) {
isLoggedIn.value = true; //
fetchRecords();
}
});
/*
显示补充内容页面
*/
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'showUpdateWriteOn') {
if (request.value) {
handleWrite();
} else {
handleBack();
}
}
});
// tokeniframe URL
const handleWrite = async () => {
try {
const result = await chrome.storage.local.get(['srcAuthToken']);
const token = result.srcAuthToken;
if (token) {
chrome.runtime.sendMessage({ action: 'getCurCompletionOuterId' }, (curCompletionOuterId) => {
if (curCompletionOuterId) {
const iframeUrl = `http://65.47.6.108/plugin/#/work/verifySubmit?outerId=${curCompletionOuterId}&token=${token}`;
writeOn.value = true;
iframeSrc.value = iframeUrl;
} else {
alert('获取 curCompletionOuterId 失败');
}
});
} else {
alert('未登录,请先登录');
}
} catch (error) {
console.error('获取token失败:', error);
alert('获取token失败,请稍后再试');
}
};
// refiframesrc
const iframeSrc = ref('');
//
const handleBack = () => {
writeOn.value = false;
};
/*
网页跳转
*/
const openMorePage = () => {
window.open(`${morePageUrl}`, '_blank');
}
//
const defaultPopupStyle = {
width: '350px',
height: '400px',
};
const largePopupStyle = {
width: '800px',
height: '800px',
};
</script>
<template>
<div class="records-header">长沙公安数字督察一体化平台</div>
<main>
<!-- 登录界面 -->
<h3 v-if="!isLoggedIn">登录</h3>
<div v-if="!isLoggedIn" class="login-form">
<input type="text" v-model="username" placeholder="用户名" />
<input type="password" v-model="password" placeholder="密码" />
<button class="loginButton" @click="handleLogin">登录</button>
</div>
<!-- 表格界面 -->
<div v-else-if="!writeOn" class="records-page">
<el-button class="logout-button" @click="handleLogout">退出登录</el-button>
<!-- 填写按钮 -->
<!-- <el-button class="write-button" @click="handleWrite">填写</el-button>-->
<div v-if="!formLoading && formRecords.length > 0" class="records-list">
<div v-for="(record, index) in formRecords" :key="record.originId" class="record-item">
<div class="record-row">
<!-- 修改: 使用 getStatusClass 方法动态设置类名 -->
<span :class="getStatusClass(record.crxState)">{{ record.crxState === '1' ? '已同步' : record.crxState === '2' ? '待完善' : record.crxState === '3' ? '已办结' : '未知状态' }}</span>
<span class="origin-id">信件编号 {{ record.originId }}</span>
</div>
<div class="record-row2">
<span class="responder-name">{{ record.responderName }}</span>
<span class="source-involve-depart-name">{{ record.sourceInvolveDepartName }}</span>
</div>
<div class="record-row-time">
<span class="create-time" style="margin-left: auto;">{{ record.createTime }}</span>
</div>
<div v-if="index < formRecords.length - 1" class="separator"></div>
</div>
</div>
<div v-else-if="formLoading" class="loading-text">加载中···</div>
<div v-else class="no-data-text">无数据</div>
<!-- 查看更多按钮 -->
<el-button class="more-button" @click="openMorePage">查看更多记录</el-button>
</div>
<!-- 填写界面 -->
<div v-else class="write-page" :style="writeOn ? largePopupStyle : defaultPopupStyle">
<!-- <el-button class="back-button" @click="handleBack">返回</el-button>-->
<iframe :src="iframeSrc" style="width: 100%; height: 800px;"></iframe>
</div>
</main>
</template>
<style>
:root {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
color-scheme: light dark;
background-color: #242424;
}
@media (prefers-color-scheme: light) {
:root {
background-color: #fafafa;
}
a:hover {
color: #42b983;
}
}
body {
min-width: 300px;
color-scheme: light dark;
margin: 0;
}
main {
text-align: center;
margin: 0 auto;
width: 100%;
}
h3 {
color: #19257D;
text-transform: uppercase;
font-size: 1.5rem;
font-weight: 200;
line-height: 1rem;
}
a {
font-size: 0.5rem;
margin: 0.5rem;
color: #cccccc;
text-decoration: none;
}
/*
样式-登录
*/
.login-form {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 1rem;
}
.login-form input {
margin: 0.5rem 0;
padding: 0.5rem;
font-size: 1rem;
width: 80%;
}
.login-form button {
font-size: 1rem;
padding: 0.5rem 1rem;
border: 1px solid #0914b1;
border-radius: 0.25rem;
background-color: #0914b1;
color: white;
cursor: pointer;
outline: none;
}
/*
样式-表格
*/
.records-page {
display: flex;
flex-direction: column;
align-items: center;
width: 350px;
height: 400px;
position: relative;
}
./**/logout-button {
align-self: flex-end;
margin: 0.5rem;
font-size: 13px;
padding: 0.2rem 0.6rem;
border: 1px solid white;
border-radius: 0.25rem;
background-color: white;
color: #0914b1;
cursor: pointer;
outline: none;
}
.loginButton {
margin: 0.5rem auto 1rem auto;
}
.records-header {
font-size: 1.3rem;
background-color: #0914b1;
color: white;
width: 100%;
height: 50px;
text-align: center; /* 文字水平居中 */
display: flex; /* 使用 Flexbox 布局 */
align-items: center; /* 垂直居中文本 */
justify-content: center; /* 水平居中文本,可选,因为已经使用了 text-align: center */
}
.status-synced {
color: white;
background-color: #19257D;
border-radius: 3px;
float: left;
padding: 3px 10px;
font-size: 12px;
}
.status-pending {
color: white;
background-color: #F04E00;
border-radius: 3px;
float: left;
padding: 3px 10px;
font-size: 12px;
}
.status-completed {
color: white;
background-color: #228B22;
border-radius: 3px;
float: left;
padding: 3px 10px;
font-size: 12px;
}
.status-unknown {
color: white;
background-color: #808080;
border-radius: 3px;
float: left;
padding: 3px 10px;
font-size: 12px;
}
.separator{
padding: 2px 2px;
border-top: 1px solid #d3d3d3; /* 添加一根灰色横线 */
}
.origin-id{
margin: 0 10px;
}
.record-row{
display: flex;
justify-content: flex-start;
align-items: center;
font-size: 13px;
color: #666666
}
.record-row2{
display: flex;
font-size: 15px;
color: #333333;
margin: 5px 0;
}
.record-row-time{
display: flex;
justify-content: flex-end;
font-size: 12px;
color: #666666
}
.records-list {
max-height: 350px;
max-width: 400px;
overflow-x: auto;
overflow-y: auto;
width: 100%;
padding: 1rem;
box-sizing: border-box;
white-space: nowrap;
text-overflow: ellipsis; /* 当内容溢出时显示省略号 */
border: 1px solid #ebeef5;
border-radius: 4px;
margin-bottom: 35px;
}
/* 表头样式 */
.records-list th {
background-color: #f5f7fa;
color: #909399;
font-weight: bold;
padding: 12px;
text-align: left;
}
/* 单元格样式 */
.records-list td {
padding: 12px;
border-bottom: 1px solid #ebeef5;
}
/* 悬停效果 */
.records-list tr:hover {
background-color: #f5f7fa;
white-space: normal; /* 允许换行 */
overflow: visible; /* 显示溢出内容 */
text-overflow: clip; /* 去掉省略号 */
}
/* 奇偶行背景色 */
.records-list tr:nth-child(even) {
background-color: #f9fafc;
}
.more-button {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
margin-top: 0;
font-size: 1rem;
padding: 0.5rem 0;
border: 1px solid #ECEEFA;
border-radius: 0.25rem;
background-color: #ECEEFA;
color: #19257D;
cursor: pointer;
outline: none;
}
/*
样式-填写按钮
*/
.write-button {
align-self: flex-end;
margin: 1rem;
font-size: 1rem;
padding: 0.5rem 1rem;
border: 1px solid #0914b1;
border-radius: 0.25rem;
background-color: #0914b1;
color: white;
cursor: pointer;
outline: none;
}
/*
样式-返回按钮
*/
.back-button {
align-self: flex-end;
margin: 1rem;
font-size: 1rem;
padding: 0.5rem 1rem;
border: 1px solid #0914b1;
border-radius: 0.25rem;
background-color: #0914b1;
color: white;
cursor: pointer;
outline: none;
}
</style>

5
src/popup/index.ts

@ -0,0 +1,5 @@
import { createApp } from 'vue'
import Popup from './Popup.vue'
createApp(Popup).mount('#app')

66
src/sidepanel/SidePanel.vue

@ -0,0 +1,66 @@
<script setup lang="ts">
</script>
<template>
<main>
<h3>数字督查</h3>
</main>
</template>
<style>
:root {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
color-scheme: light dark;
background-color: #242424;
}
@media (prefers-color-scheme: light) {
:root {
background-color: #fafafa;
}
a:hover {
color: #42b983;
}
}
body {
min-width: 20rem;
}
main {
text-align: center;
padding: 1em;
margin: 0 auto;
}
h3 {
color: #42b983;
text-transform: uppercase;
font-size: 1.5rem;
font-weight: 200;
line-height: 1.2rem;
margin: 2rem auto;
}
a {
font-size: 0.5rem;
margin: 0.5rem;
color: #cccccc;
text-decoration: none;
}
</style>

5
src/sidepanel/index.ts

@ -0,0 +1,5 @@
import { createApp } from 'vue'
import SidePanel from './SidePanel.vue'
createApp(SidePanel).mount('#app')

20
src/utils/ajax.ts

@ -0,0 +1,20 @@
export function ajax(url: string, options: { method: string, body?: any }): Promise<any> {
let token: string | null;
return chrome.storage.local.get(['srcAuthToken']).then(result => {
token = result.srcAuthToken;
return fetch(url, {
method: options.method,
headers: {
'Content-Type': 'application/json',
'Authorization': `${token}`, // 使用 Authorization 头传递 token
},
body: JSON.stringify(options.body),
credentials: 'include', // 确保请求中包含 Cookie
}).then(response => {
if (!response.ok) {
throw new Error('请求失败');
}
return response.json();
});
});
}

10
src/zip.js

@ -0,0 +1,10 @@
import gulp from 'gulp'
import zip from 'gulp-zip'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const manifest = require('../build/manifest.json')
gulp
.src('build/**')
.pipe(zip(`${manifest.name.replaceAll(' ', '-')}-${manifest.version}.zip`))
.pipe(gulp.dest('package'))

18
tsconfig.json

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

8
tsconfig.node.json

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node"
},
"include": ["vite.config.ts"]
}

4
version.json

@ -0,0 +1,4 @@
{
"version": "1.0.1",
"downloadUrl": "http://65.47.6.108/extension/download.html"
}

23
vite.config.ts

@ -0,0 +1,23 @@
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import vue from '@vitejs/plugin-vue'
import manifest from './src/manifest'
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const production = mode === 'production'
return {
build: {
cssCodeSplit: true,
emptyOutDir: true,
outDir: 'build',
rollupOptions: {
output: {
chunkFileNames: 'assets/chunk-[hash].js',
},
},
},
plugins: [crx({ manifest }), vue()],
}
})
Loading…
Cancel
Save