1
This commit is contained in:
465
.idea/workspace.xml
generated
465
.idea/workspace.xml
generated
@@ -1,465 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="71d19dd6-7472-4ebf-b309-b7afee3f99de" name="更改" comment="1">
|
||||
<change afterPath="$PROJECT_DIR$/electron-vue-template/public/erp_client_sb-2.4.7.jar" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.claude/settings.local.json" beforeDir="false" afterPath="$PROJECT_DIR$/.claude/settings.local.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/data/device.id" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/data/erp-cache.db" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/data/erp-cache.db-shm" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/data/erp-cache.db-wal" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/data/jwt_rsa_private.pem" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/data/jwt_rsa_public.pem" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/electron-builder.json" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/electron-builder.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/main/main.ts" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/main/main.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/main/preload.ts" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/main/preload.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/App.vue" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/App.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/api/http.ts" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/api/http.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/api/zebra.ts" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/api/zebra.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/amazon/AmazonDashboard.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/auth/RegisterDialog.vue" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/auth/RegisterDialog.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/common/AccountManager.vue" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/common/AccountManager.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/common/UpdateDialog.vue" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/common/UpdateDialog.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/layout/NavigationBar.vue" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/layout/NavigationBar.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/rakuten/RakutenDashboard.vue" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/rakuten/RakutenDashboard.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/zebra/ZebraDashboard.vue" beforeDir="false" afterPath="$PROJECT_DIR$/electron-vue-template/src/renderer/components/zebra/ZebraDashboard.vue" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/erp_client_sb/data/device.id" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/erp_client_sb/data/erp-cache.db" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/erp_client_sb/src/main/java/com/tashow/erp/service/impl/AmazonScrapingServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/erp_client_sb/src/main/java/com/tashow/erp/service/impl/AmazonScrapingServiceImpl.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/BanmaOrderController.java" beforeDir="false" afterPath="$PROJECT_DIR$/ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/BanmaOrderController.java" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="MavenImportPreferences">
|
||||
<option name="generalSettings">
|
||||
<MavenGeneralSettings>
|
||||
<option name="userSettingsFile" value="C:\Program Files\apache-maven-3.9.4\conf\settings.xml" />
|
||||
</MavenGeneralSettings>
|
||||
</option>
|
||||
</component>
|
||||
<component name="MavenRunner">
|
||||
<option name="skipTests" value="true" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"customColor": "",
|
||||
"associatedIndex": 0
|
||||
}</component>
|
||||
<component name="ProjectId" id="332JslhtSnNRRZRMrLiHaPZ3q2S" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="autoscrollFromSource" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"Maven.erp_client_sb [clean].executor": "Run",
|
||||
"Maven.erp_client_sb [install].executor": "Run",
|
||||
"Maven.erp_client_sb [verify].executor": "Run",
|
||||
"Maven.ruoyi [clean].executor": "Run",
|
||||
"Maven.ruoyi [install].executor": "Run",
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RequestMappingsPanelOrder0": "0",
|
||||
"RequestMappingsPanelOrder1": "1",
|
||||
"RequestMappingsPanelWidth0": "75",
|
||||
"RequestMappingsPanelWidth1": "75",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"Spring Boot.ErpClientSbApplication.executor": "Debug",
|
||||
"Spring Boot.RuoYiApplication.executor": "Debug",
|
||||
"Spring Boot.未命名.executor": "Debug",
|
||||
"git-widget-placeholder": "master",
|
||||
"last_opened_file_path": "C:/Users/ZiJIe/Desktop/wox/RuoYi-Vue/electron-vue-template/public",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"npm.build.executor": "Run",
|
||||
"npm.build:win.executor": "Run",
|
||||
"npm.run.executor": "Run",
|
||||
"settings.editor.selected.configurable": "com.intellij.platform.ide.impl.presentationAssistant.PresentationAssistantConfigurable",
|
||||
"ts.external.directory.path": "C:\\Users\\ZiJIe\\Desktop\\wox\\RuoYi-Vue\\electron-vue-template\\node_modules\\typescript\\lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}</component>
|
||||
<component name="ReactorSettings">
|
||||
<option name="notificationShown" value="true" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\ZiJIe\Desktop\wox\RuoYi-Vue\electron-vue-template\public" />
|
||||
<recent name="C:\Users\ZiJIe\Desktop\wox\RuoYi-Vue\electron-vue-template\dist\win-unpacked" />
|
||||
<recent name="C:\Users\ZiJIe\Desktop\wox\RuoYi-Vue" />
|
||||
</key>
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\ZiJIe\Desktop\wox\RuoYi-Vue\electron-vue-template\dist\win-unpacked" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunDashboard">
|
||||
<option name="configurationTypes">
|
||||
<set>
|
||||
<option value="SpringBootApplicationConfigurationType" />
|
||||
<option value="js.build_tools.npm" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
<component name="RunManager" selected="npm.build:win">
|
||||
<configuration default="true" type="JetRunConfigurationType">
|
||||
<module name="RuoYi-Vue" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
|
||||
<module name="RuoYi-Vue" />
|
||||
<option name="filePath" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="ErpClientSbApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
|
||||
<option name="ALTERNATIVE_JRE_PATH" value="17" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||
<module name="erp_client_sb" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.tashow.erp.ErpClientSbApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="RuoYiApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
|
||||
<option name="ALTERNATIVE_JRE_PATH" value="17" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||
<module name="ruoyi-admin" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.ruoyi.RuoYiApplication" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="build" type="js.build_tools.npm" temporary="true" nameIsGenerated="true">
|
||||
<package-json value="$PROJECT_DIR$/electron-vue-template/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="build" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="build:win" type="js.build_tools.npm" temporary="true" nameIsGenerated="true">
|
||||
<package-json value="$PROJECT_DIR$/electron-vue-template/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="build:win" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="run" type="js.build_tools.npm" nameIsGenerated="true">
|
||||
<package-json value="$PROJECT_DIR$/electron-vue-template/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="dev" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<list>
|
||||
<item itemvalue="npm.run" />
|
||||
<item itemvalue="npm.build" />
|
||||
<item itemvalue="npm.build:win" />
|
||||
<item itemvalue="Spring Boot.RuoYiApplication" />
|
||||
<item itemvalue="Spring Boot.ErpClientSbApplication" />
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="npm.build:win" />
|
||||
<item itemvalue="npm.build" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-jdk-9823dce3aa75-fbdcb00ec9e3-intellij.indexing.shared.core-IU-251.23774.435" />
|
||||
<option value="bundled-js-predefined-d6986cc7102b-f27c65a3e318-JavaScript-IU-251.23774.435" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="默认任务">
|
||||
<changelist id="71d19dd6-7472-4ebf-b309-b7afee3f99de" name="更改" comment="" />
|
||||
<created>1758509443816</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1758509443816</updated>
|
||||
<workItem from="1758509446082" duration="2966000" />
|
||||
<workItem from="1758512571699" duration="311000" />
|
||||
<workItem from="1758694285287" duration="16954000" />
|
||||
<workItem from="1758765346934" duration="13417000" />
|
||||
<workItem from="1758780949720" duration="2793000" />
|
||||
<workItem from="1758783772919" duration="3262000" />
|
||||
<workItem from="1758787264618" duration="6950000" />
|
||||
<workItem from="1758794319987" duration="13284000" />
|
||||
<workItem from="1758860156056" duration="54529000" />
|
||||
<workItem from="1759112919536" duration="1025000" />
|
||||
<workItem from="1759114000963" duration="12514000" />
|
||||
<workItem from="1759130476790" duration="1021000" />
|
||||
<workItem from="1759131666876" duration="1987000" />
|
||||
<workItem from="1759133675854" duration="9993000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758511833782</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758511833782</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758512348322</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758512348322</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758521046022</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758521046022</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758522202417</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758522202417</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758522758523</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758522758523</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758523822682</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758523822682</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00007" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758524938236</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758524938236</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00008" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758525299299</created>
|
||||
<option name="number" value="00008" />
|
||||
<option name="presentableId" value="LOCAL-00008" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758525299299</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00009" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758525500986</created>
|
||||
<option name="number" value="00009" />
|
||||
<option name="presentableId" value="LOCAL-00009" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758525500986</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00010" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758526085800</created>
|
||||
<option name="number" value="00010" />
|
||||
<option name="presentableId" value="LOCAL-00010" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758526085800</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00011" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758528696003</created>
|
||||
<option name="number" value="00011" />
|
||||
<option name="presentableId" value="LOCAL-00011" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758528696003</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00012" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758529627894</created>
|
||||
<option name="number" value="00012" />
|
||||
<option name="presentableId" value="LOCAL-00012" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758529627894</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00013" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758619260259</created>
|
||||
<option name="number" value="00013" />
|
||||
<option name="presentableId" value="LOCAL-00013" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758619260259</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00014" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758619552979</created>
|
||||
<option name="number" value="00014" />
|
||||
<option name="presentableId" value="LOCAL-00014" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758619552979</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00015" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758683139068</created>
|
||||
<option name="number" value="00015" />
|
||||
<option name="presentableId" value="LOCAL-00015" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758683139068</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00016" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758683285305</created>
|
||||
<option name="number" value="00016" />
|
||||
<option name="presentableId" value="LOCAL-00016" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758683285305</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00017" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758683484212</created>
|
||||
<option name="number" value="00017" />
|
||||
<option name="presentableId" value="LOCAL-00017" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758683484212</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00018" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758787441900</created>
|
||||
<option name="number" value="00018" />
|
||||
<option name="presentableId" value="LOCAL-00018" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758787441900</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00019" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758787531138</created>
|
||||
<option name="number" value="00019" />
|
||||
<option name="presentableId" value="LOCAL-00019" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758787531138</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00020" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758787581342</created>
|
||||
<option name="number" value="00020" />
|
||||
<option name="presentableId" value="LOCAL-00020" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758787581342</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00021" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758787954763</created>
|
||||
<option name="number" value="00021" />
|
||||
<option name="presentableId" value="LOCAL-00021" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758787954763</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00022" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758788061529</created>
|
||||
<option name="number" value="00022" />
|
||||
<option name="presentableId" value="LOCAL-00022" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758788061529</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00023" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758795230077</created>
|
||||
<option name="number" value="00023" />
|
||||
<option name="presentableId" value="LOCAL-00023" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758795230077</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00024" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758875248722</created>
|
||||
<option name="number" value="00024" />
|
||||
<option name="presentableId" value="LOCAL-00024" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758875248722</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00025" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758877545022</created>
|
||||
<option name="number" value="00025" />
|
||||
<option name="presentableId" value="LOCAL-00025" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758877545022</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00026" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758953146637</created>
|
||||
<option name="number" value="00026" />
|
||||
<option name="presentableId" value="LOCAL-00026" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758953146637</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00027" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1758966134103</created>
|
||||
<option name="number" value="00027" />
|
||||
<option name="presentableId" value="LOCAL-00027" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1758966134103</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00028" summary="1">
|
||||
<option name="closed" value="true" />
|
||||
<created>1759129266815</created>
|
||||
<option name="number" value="00028" />
|
||||
<option name="presentableId" value="LOCAL-00028" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1759129266815</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="29" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="1" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="1" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -57,26 +57,43 @@ function getJavaExecutablePath(): string {
|
||||
return 'java';
|
||||
}
|
||||
|
||||
function findJarFile(directory: string): string {
|
||||
if (!existsSync(directory)) return '';
|
||||
|
||||
const files = require('fs').readdirSync(directory);
|
||||
const jarFile = files.find((f: string) => f.startsWith('erp_client_sb-') && f.endsWith('.jar'));
|
||||
|
||||
return jarFile ? join(directory, jarFile) : '';
|
||||
}
|
||||
|
||||
function extractVersionFromJar(jarPath: string): string {
|
||||
if (!jarPath) return '';
|
||||
const match = require('path').basename(jarPath).match(/erp_client_sb-(\d+\.\d+\.\d+)\.jar/);
|
||||
return match?.[1] || '';
|
||||
}
|
||||
|
||||
function getJarFilePath(): string {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return join(__dirname, '../../public/erp_client_sb-2.4.7.jar');
|
||||
return findJarFile(join(__dirname, '../../public'));
|
||||
}
|
||||
|
||||
// 生产环境:需要将JAR包从asar提取到临时位置
|
||||
const tempDir = join(app.getPath('temp'), 'erp-client');
|
||||
const tempJarPath = join(tempDir, 'erp_client_sb-2.4.7.jar');
|
||||
if (!existsSync(tempDir)) mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
// 确保临时目录存在
|
||||
if (!existsSync(tempDir)) {
|
||||
mkdirSync(tempDir, { recursive: true });
|
||||
const asarJarPath = findJarFile(join(__dirname, '../assets'));
|
||||
if (!asarJarPath) return '';
|
||||
|
||||
const asarFileName = require('path').basename(asarJarPath);
|
||||
const tempJarPath = join(tempDir, asarFileName);
|
||||
|
||||
// 如果临时目录版本不同,删除旧版本并复制新版本
|
||||
const existingJar = findJarFile(tempDir);
|
||||
if (existingJar && require('path').basename(existingJar) !== asarFileName) {
|
||||
require('fs').unlinkSync(existingJar);
|
||||
}
|
||||
|
||||
// 如果临时JAR不存在,从asar中复制
|
||||
if (!existsSync(tempJarPath)) {
|
||||
const asarJarPath = join(__dirname, '../assets/erp_client_sb-2.4.7.jar');
|
||||
if (existsSync(asarJarPath)) {
|
||||
copyFileSync(asarJarPath, tempJarPath);
|
||||
}
|
||||
copyFileSync(asarJarPath, tempJarPath);
|
||||
}
|
||||
|
||||
return tempJarPath;
|
||||
@@ -213,7 +230,7 @@ function startSpringBoot() {
|
||||
}
|
||||
}
|
||||
|
||||
//startSpringBoot();
|
||||
startSpringBoot();
|
||||
|
||||
function stopSpringBoot() {
|
||||
if (!springProcess) return;
|
||||
@@ -299,9 +316,9 @@ app.whenReady().then(() => {
|
||||
}
|
||||
|
||||
//11111
|
||||
setTimeout(() => {
|
||||
openAppIfNotOpened();
|
||||
}, 2000);
|
||||
// setTimeout(() => {
|
||||
// openAppIfNotOpened();
|
||||
// }, 2000);
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
@@ -323,6 +340,8 @@ ipcMain.on('message', (event, message) => {
|
||||
console.log(message);
|
||||
});
|
||||
|
||||
ipcMain.handle('get-jar-version', () => extractVersionFromJar(getJarFilePath()));
|
||||
|
||||
function checkPendingUpdate() {
|
||||
try {
|
||||
const updateFilePath = join(process.resourcesPath, 'app.asar.update');
|
||||
|
||||
@@ -3,6 +3,8 @@ import { contextBridge, ipcRenderer } from 'electron'
|
||||
const electronAPI = {
|
||||
sendMessage: (message: string) => ipcRenderer.send('message', message),
|
||||
|
||||
getJarVersion: () => ipcRenderer.invoke('get-jar-version'),
|
||||
|
||||
downloadUpdate: (downloadUrl: string) => ipcRenderer.invoke('download-update', downloadUrl),
|
||||
getDownloadProgress: () => ipcRenderer.invoke('get-download-progress'),
|
||||
installUpdate: () => ipcRenderer.invoke('install-update'),
|
||||
|
||||
@@ -134,56 +134,43 @@ function handleMenuSelect(key: string) {
|
||||
async function handleLoginSuccess(data: { token: string; permissions?: string }) {
|
||||
isAuthenticated.value = true
|
||||
showAuthDialog.value = false
|
||||
showRegDialog.value = false // 确保注册对话框也关闭
|
||||
showRegDialog.value = false
|
||||
|
||||
try {
|
||||
// 保存token到本地数据库
|
||||
await authApi.saveToken(data.token)
|
||||
|
||||
const username = getUsernameFromToken(data.token)
|
||||
currentUsername.value = username
|
||||
userPermissions.value = data?.permissions || ''
|
||||
await deviceApi.register({username})
|
||||
|
||||
// 建立SSE连接
|
||||
SSEManager.connect()
|
||||
} catch (e: any) {
|
||||
// 设备注册失败时回滚登录状态
|
||||
isAuthenticated.value = false
|
||||
showAuthDialog.value = true
|
||||
await authApi.deleteTokenCache()
|
||||
ElMessage({
|
||||
message: e?.message || '设备注册失败,请重试',
|
||||
type: 'error'
|
||||
})
|
||||
ElMessage.error(e?.message || '设备注册失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
// 主动设置设备离线
|
||||
try {
|
||||
const deviceId = await getClientIdFromToken()
|
||||
if (deviceId) {
|
||||
await deviceApi.offline({ deviceId })
|
||||
}
|
||||
if (deviceId) await deviceApi.offline({ deviceId })
|
||||
} catch (error) {
|
||||
console.warn('离线通知失败:', error)
|
||||
}
|
||||
|
||||
const token = await authApi.getToken()
|
||||
if (token) {
|
||||
await authApi.logout(token)
|
||||
}
|
||||
try {
|
||||
const tokenRes: any = await authApi.getToken()
|
||||
const token = typeof tokenRes === 'string' ? tokenRes : tokenRes?.data
|
||||
if (token) await authApi.logout(token)
|
||||
} catch {}
|
||||
|
||||
await authApi.deleteTokenCache()
|
||||
// 清理前端状态
|
||||
isAuthenticated.value = false
|
||||
currentUsername.value = ''
|
||||
userPermissions.value = ''
|
||||
showAuthDialog.value = true
|
||||
showDeviceDialog.value = false
|
||||
|
||||
// 关闭SSE连接
|
||||
SSEManager.disconnect()
|
||||
}
|
||||
|
||||
@@ -199,12 +186,8 @@ async function handleUserClick() {
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
await logout()
|
||||
ElMessage({
|
||||
message: '已退出登录',
|
||||
type: 'success'
|
||||
})
|
||||
} catch {
|
||||
}
|
||||
ElMessage.success('已退出登录')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function showRegisterDialog() {
|
||||
@@ -218,26 +201,23 @@ function backToLogin() {
|
||||
}
|
||||
|
||||
async function checkAuth() {
|
||||
const authRequiredMenus = ['rakuten', 'amazon', 'zebra', 'shopee']
|
||||
|
||||
try {
|
||||
await authApi.sessionBootstrap().catch(() => undefined)
|
||||
const token = await authApi.getToken()
|
||||
const tokenRes: any = await authApi.getToken()
|
||||
const token = typeof tokenRes === 'string' ? tokenRes : tokenRes?.data
|
||||
|
||||
if (token) {
|
||||
const response = await authApi.verifyToken(token)
|
||||
if (response?.success) {
|
||||
isAuthenticated.value = true
|
||||
currentUsername.value = getUsernameFromToken(token) || ''
|
||||
SSEManager.connect()
|
||||
return
|
||||
}
|
||||
await authApi.deleteTokenCache()
|
||||
await authApi.verifyToken(token)
|
||||
isAuthenticated.value = true
|
||||
currentUsername.value = getUsernameFromToken(token) || ''
|
||||
SSEManager.connect()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// 忽略
|
||||
await authApi.deleteTokenCache()
|
||||
}
|
||||
|
||||
if (authRequiredMenus.includes(activeMenu.value)) {
|
||||
if (['rakuten', 'amazon', 'zebra', 'shopee'].includes(activeMenu.value)) {
|
||||
showAuthDialog.value = true
|
||||
}
|
||||
}
|
||||
@@ -246,11 +226,11 @@ async function getClientIdFromToken(token?: string) {
|
||||
try {
|
||||
let t = token
|
||||
if (!t) {
|
||||
t = await authApi.getToken()
|
||||
const tokenRes: any = await authApi.getToken()
|
||||
t = typeof tokenRes === 'string' ? tokenRes : tokenRes?.data
|
||||
}
|
||||
if (!t) return ''
|
||||
|
||||
const payload = JSON.parse(atob(t.split('.')[1] || ''))
|
||||
const payload = JSON.parse(atob(t.split('.')[1]))
|
||||
return payload.clientId || ''
|
||||
} catch {
|
||||
return ''
|
||||
@@ -259,7 +239,7 @@ async function getClientIdFromToken(token?: string) {
|
||||
|
||||
function getUsernameFromToken(token: string) {
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1] || ''))
|
||||
const payload = JSON.parse(atob(token.split('.')[1]))
|
||||
return payload.username || ''
|
||||
} catch {
|
||||
return ''
|
||||
@@ -273,21 +253,24 @@ const SSEManager = {
|
||||
if (this.connection) return
|
||||
|
||||
try {
|
||||
const token = await authApi.getToken()
|
||||
if (!token) return
|
||||
const tokenRes: any = await authApi.getToken()
|
||||
const token = typeof tokenRes === 'string' ? tokenRes : tokenRes?.data
|
||||
if (!token) {
|
||||
console.warn('SSE连接失败: 没有有效的 token')
|
||||
return
|
||||
}
|
||||
|
||||
const clientId = await getClientIdFromToken(token)
|
||||
if (!clientId) return
|
||||
if (!clientId) {
|
||||
console.warn('SSE连接失败: 无法从 token 获取 clientId')
|
||||
return
|
||||
}
|
||||
|
||||
let sseUrl = 'http://192.168.1.89:8080/monitor/account/events'
|
||||
try {
|
||||
const resp = await fetch('/api/config/server')
|
||||
if (resp.ok) {
|
||||
const config = await resp.json()
|
||||
sseUrl = config.sseUrl || sseUrl
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
if (resp.ok) sseUrl = (await resp.json()).sseUrl || sseUrl
|
||||
} catch {}
|
||||
|
||||
const src = new EventSource(`${sseUrl}?clientId=${clientId}&token=${token}`)
|
||||
this.connection = src
|
||||
@@ -296,15 +279,13 @@ const SSEManager = {
|
||||
src.onerror = () => this.handleError()
|
||||
} catch (e: any) {
|
||||
console.warn('SSE连接失败:', e?.message || e)
|
||||
this.disconnect()
|
||||
}
|
||||
},
|
||||
|
||||
handleMessage(e: MessageEvent) {
|
||||
try {
|
||||
// 处理ping心跳
|
||||
if (e.type === 'ping') {
|
||||
return // ping消息自动保持连接,无需处理
|
||||
}
|
||||
if (e.type === 'ping') return
|
||||
|
||||
console.log('SSE消息:', e.data)
|
||||
const payload = JSON.parse(e.data)
|
||||
@@ -314,17 +295,11 @@ const SSEManager = {
|
||||
break
|
||||
case 'DEVICE_REMOVED':
|
||||
logout()
|
||||
ElMessage({
|
||||
message: '您的设备已被移除,请重新登录',
|
||||
type: 'warning'
|
||||
})
|
||||
ElMessage.warning('您的设备已被移除,请重新登录')
|
||||
break
|
||||
case 'FORCE_LOGOUT':
|
||||
logout()
|
||||
ElMessage({
|
||||
message: '会话已失效,请重新登录',
|
||||
type: 'warning'
|
||||
})
|
||||
ElMessage.warning('会话已失效,请重新登录')
|
||||
break
|
||||
case 'PERMISSIONS_UPDATED':
|
||||
checkAuth()
|
||||
@@ -336,18 +311,16 @@ const SSEManager = {
|
||||
},
|
||||
|
||||
handleError() {
|
||||
this.disconnect()
|
||||
setTimeout(() => this.connect(), 3000)
|
||||
if (!this.connection) return
|
||||
try { this.connection.close() } catch {}
|
||||
this.connection = null
|
||||
console.warn('SSE连接失败,已断开')
|
||||
},
|
||||
|
||||
disconnect() {
|
||||
if (this.connection) {
|
||||
try {
|
||||
this.connection.close()
|
||||
} catch {
|
||||
}
|
||||
this.connection = null
|
||||
}
|
||||
if (!this.connection) return
|
||||
try { this.connection.close() } catch {}
|
||||
this.connection = null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -366,26 +339,22 @@ function openSettings() {
|
||||
|
||||
async function fetchDeviceData() {
|
||||
if (!currentUsername.value) {
|
||||
ElMessage({
|
||||
message: '未获取到用户名,请重新登录',
|
||||
type: 'warning'
|
||||
})
|
||||
ElMessage.warning('未获取到用户名,请重新登录')
|
||||
return
|
||||
}
|
||||
try {
|
||||
deviceLoading.value = true
|
||||
const [quota, list] = await Promise.all([
|
||||
const [quotaRes, listRes] = await Promise.all([
|
||||
deviceApi.getQuota(currentUsername.value),
|
||||
deviceApi.list(currentUsername.value),
|
||||
])
|
||||
deviceQuota.value = quota || {limit: 0, used: 0}
|
||||
]) as any[]
|
||||
|
||||
deviceQuota.value = quotaRes?.data || quotaRes || {limit: 0, used: 0}
|
||||
const clientId = await getClientIdFromToken()
|
||||
devices.value = (list || []).map(d => ({...d, isCurrent: d.deviceId === clientId})) as any
|
||||
const list = listRes?.data || listRes || []
|
||||
devices.value = list.map(d => ({...d, isCurrent: d.deviceId === clientId}))
|
||||
} catch (e: any) {
|
||||
ElMessage({
|
||||
message: e?.message || '获取设备列表失败',
|
||||
type: 'error'
|
||||
})
|
||||
ElMessage.error(e?.message || '获取设备列表失败')
|
||||
} finally {
|
||||
deviceLoading.value = false
|
||||
}
|
||||
@@ -403,21 +372,12 @@ async function confirmRemoveDevice(row: DeviceItem & { isCurrent?: boolean }) {
|
||||
devices.value = devices.value.filter(d => d.deviceId !== row.deviceId)
|
||||
deviceQuota.value.used = Math.max(0, (deviceQuota.value.used || 0) - 1)
|
||||
|
||||
// 如果是本机设备被移除,执行logout
|
||||
const clientId = await getClientIdFromToken()
|
||||
if (row.deviceId === clientId) {
|
||||
await logout()
|
||||
}
|
||||
if (row.deviceId === clientId) await logout()
|
||||
|
||||
ElMessage({
|
||||
message: '已移除设备',
|
||||
type: 'success'
|
||||
})
|
||||
ElMessage.success('已移除设备')
|
||||
} catch (e: any) {
|
||||
ElMessage({
|
||||
message: '移除设备失败: ' + ((e as any)?.message || '未知错误'),
|
||||
type: 'error'
|
||||
})
|
||||
if (e !== 'cancel') ElMessage.error('移除设备失败: ' + (e?.message || '未知错误'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,113 +1,39 @@
|
||||
import { http } from './http';
|
||||
|
||||
// 统一响应处理函数 - 适配ERP客户端格式
|
||||
function unwrap<T>(res: any): T {
|
||||
if (res && typeof res.success === 'boolean') {
|
||||
if (!res.success) {
|
||||
const message: string = res.message || res.msg || '请求失败';
|
||||
throw new Error(message);
|
||||
}
|
||||
return res as T;
|
||||
}
|
||||
// 兼容标准格式
|
||||
if (res && typeof res.code === 'number') {
|
||||
if (res.code !== 0) {
|
||||
const message: string = res.msg || '请求失败';
|
||||
throw new Error(message);
|
||||
}
|
||||
return (res.data as T) ?? ({} as T);
|
||||
}
|
||||
return res as T;
|
||||
}
|
||||
|
||||
// 认证相关类型定义
|
||||
interface LoginRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface RegisterRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface LoginResponse {
|
||||
success: boolean;
|
||||
token: string;
|
||||
permissions: string[];
|
||||
username: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
interface RegisterResponse {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
import { http } from './http'
|
||||
|
||||
export const authApi = {
|
||||
// 用户登录
|
||||
login(params: LoginRequest) {
|
||||
return http
|
||||
.post('/api/login', params)
|
||||
.then(res => unwrap<LoginResponse>(res));
|
||||
login(params: { username: string; password: string }) {
|
||||
return http.post('/api/login', params)
|
||||
},
|
||||
|
||||
// 用户注册
|
||||
register(params: RegisterRequest) {
|
||||
return http
|
||||
.post('/api/register', params)
|
||||
.then(res => unwrap<RegisterResponse>(res));
|
||||
register(params: { username: string; password: string }) {
|
||||
return http.post('/api/register', params)
|
||||
},
|
||||
|
||||
// 检查用户名可用性
|
||||
checkUsername(username: string) {
|
||||
return http
|
||||
.get('/api/check-username', { username })
|
||||
.then(res => {
|
||||
if (res && res.code === 200) {
|
||||
return { available: res.data };
|
||||
}
|
||||
throw new Error(res?.msg || '检查用户名失败');
|
||||
});
|
||||
return http.get('/api/check-username', { username })
|
||||
},
|
||||
|
||||
// 验证token有效性
|
||||
verifyToken(token: string) {
|
||||
return http
|
||||
.post('/api/verify', { token })
|
||||
.then(res => unwrap<{ success: boolean }>(res));
|
||||
return http.post('/api/verify', { token })
|
||||
},
|
||||
|
||||
// 用户登出
|
||||
logout(token: string) {
|
||||
return http.postVoid('/api/logout', { token });
|
||||
return http.postVoid('/api/logout', { token })
|
||||
},
|
||||
|
||||
// 删除token缓存
|
||||
deleteTokenCache() {
|
||||
return http.postVoid('/api/cache/delete?key=token');
|
||||
return http.postVoid('/api/cache/delete?key=token')
|
||||
},
|
||||
// 保存token到本地数据库
|
||||
|
||||
saveToken(token: string) {
|
||||
return http.postVoid('/api/cache/save', { key: 'token', value: token });
|
||||
return http.postVoid('/api/cache/save', { key: 'token', value: token })
|
||||
},
|
||||
|
||||
// 从本地数据库获取token
|
||||
getToken(): Promise<string | undefined> {
|
||||
return http.get<any>('/api/cache/get?key=token').then((res: any) => {
|
||||
if (typeof res === 'string') return res;
|
||||
if (res && typeof res === 'object') {
|
||||
if (typeof res.code === 'number') {
|
||||
return res.code === 0 ? (res.data as string | undefined) : undefined;
|
||||
}
|
||||
if (typeof (res as any).data === 'string') return (res as any).data as string;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
getToken() {
|
||||
return http.get('/api/cache/get?key=token')
|
||||
},
|
||||
|
||||
// 会话引导:检查并恢复会话(返回体各异,这里保持 any)
|
||||
sessionBootstrap() {
|
||||
return http.get<any>('/api/session/bootstrap');
|
||||
},
|
||||
};
|
||||
return http.get('/api/session/bootstrap')
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,27 @@
|
||||
import { http } from './http'
|
||||
|
||||
// 与老版保持相同的接口路径与参数
|
||||
const base = '/api/device'
|
||||
|
||||
export interface DeviceQuota {
|
||||
limit: number
|
||||
used: number
|
||||
}
|
||||
|
||||
export interface DeviceItem {
|
||||
deviceId: string
|
||||
name?: string
|
||||
status?: 'online' | 'offline'
|
||||
lastActiveAt?: string
|
||||
}
|
||||
|
||||
// 统一处理AjaxResult格式
|
||||
function handleAjaxResult(res: any) {
|
||||
if (res?.code !== 200) {
|
||||
throw new Error(res?.msg || '操作失败')
|
||||
}
|
||||
return res.data
|
||||
}
|
||||
|
||||
export const deviceApi = {
|
||||
getQuota(username: string): Promise<DeviceQuota> {
|
||||
return http.get(`${base}/quota`, { username }).then(handleAjaxResult)
|
||||
getQuota(username: string) {
|
||||
return http.get('/api/device/quota', { username })
|
||||
},
|
||||
|
||||
list(username: string): Promise<DeviceItem[]> {
|
||||
return http.get(`${base}/list`, { username }).then(handleAjaxResult)
|
||||
list(username: string) {
|
||||
return http.get('/api/device/list', { username })
|
||||
},
|
||||
|
||||
register(payload: { username: string }) {
|
||||
return http.post(`${base}/register`, payload).then(handleAjaxResult)
|
||||
return http.post('/api/device/register', payload)
|
||||
},
|
||||
|
||||
remove(payload: { deviceId: string }) {
|
||||
return http.post(`${base}/remove`, payload).then(handleAjaxResult)
|
||||
return http.post('/api/device/remove', payload)
|
||||
},
|
||||
|
||||
heartbeat(payload: { username: string; deviceId: string; version?: string }) {
|
||||
return http.post(`${base}/heartbeat`, payload).then(handleAjaxResult)
|
||||
return http.post('/api/device/heartbeat', payload)
|
||||
},
|
||||
|
||||
offline(payload: { deviceId: string }) {
|
||||
return http.post(`${base}/offline`, payload).then(handleAjaxResult)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
return http.post('/api/device/offline', payload)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,21 @@
|
||||
import { http } from './http';
|
||||
|
||||
function unwrap<T>(res: any): T {
|
||||
if (res && typeof res.code === 'number') {
|
||||
if (res.code !== 0) {
|
||||
const message: string = res.msg || '请求失败';
|
||||
throw new Error(message);
|
||||
}
|
||||
return (res.data as T) ?? ({} as T);
|
||||
}
|
||||
return res as T;
|
||||
}
|
||||
import { http } from './http'
|
||||
|
||||
export const rakutenApi = {
|
||||
// 上传 Excel 或按店铺名查询
|
||||
getProducts(params: { file?: File; shopName?: string; batchId?: string }) {
|
||||
const formData = new FormData();
|
||||
if (params.file) formData.append('file', params.file);
|
||||
if (params.batchId) formData.append('batchId', params.batchId);
|
||||
if (params.shopName) formData.append('shopName', params.shopName);
|
||||
return http
|
||||
.upload('/api/rakuten/products', formData)
|
||||
.then(res => unwrap<{ products: any[]; total?: number; sessionId?: string }>(res));
|
||||
const formData = new FormData()
|
||||
if (params.file) formData.append('file', params.file)
|
||||
if (params.batchId) formData.append('batchId', params.batchId)
|
||||
if (params.shopName) formData.append('shopName', params.shopName)
|
||||
return http.upload('/api/rakuten/products', formData)
|
||||
},
|
||||
|
||||
search1688(imageUrl: string, sessionId?: string) {
|
||||
const payload: Record<string, unknown> = { imageUrl };
|
||||
if (sessionId) payload.sessionId = sessionId;
|
||||
return http.post('/api/rakuten/search1688', payload).then(res => unwrap<any>(res));
|
||||
const payload: Record<string, unknown> = { imageUrl }
|
||||
if (sessionId) payload.sessionId = sessionId
|
||||
return http.post('/api/rakuten/search1688', payload)
|
||||
},
|
||||
|
||||
getLatestProducts() {
|
||||
return http.get('/api/rakuten/products/latest').then(res => unwrap<{ products: any[] }>(res));
|
||||
},
|
||||
};
|
||||
return http.get('/api/rakuten/products/latest')
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { http } from './http';
|
||||
|
||||
export const shopeeApi = {
|
||||
getAdHosting(params: Record<string, unknown> = {}) {
|
||||
return http.get('/api/shopee/ad-hosting', params);
|
||||
},
|
||||
getReviews(params: Record<string, unknown> = {}) {
|
||||
return http.get('/api/shopee/reviews', params);
|
||||
},
|
||||
exportData(exportParams: Record<string, unknown> = {}) {
|
||||
return http.post('/api/shopee/export', exportParams);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
11
electron-vue-template/src/renderer/api/update.ts
Normal file
11
electron-vue-template/src/renderer/api/update.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { http } from './http'
|
||||
|
||||
export const updateApi = {
|
||||
getVersion() {
|
||||
return http.get('/api/update/version')
|
||||
},
|
||||
|
||||
checkUpdate(currentVersion: string) {
|
||||
return http.get(`/system/version/check?currentVersion=${currentVersion}`)
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,39 @@
|
||||
// 斑马订单模型(根据页面所需字段精简定义)
|
||||
export interface ZebraOrder {
|
||||
orderedAt?: string;
|
||||
productImage?: string;
|
||||
productTitle?: string;
|
||||
shopOrderNumber?: string;
|
||||
timeSinceOrder?: string;
|
||||
priceJpy?: number;
|
||||
productQuantity?: number;
|
||||
shippingFeeJpy?: number;
|
||||
serviceFee?: string;
|
||||
productNumber?: string;
|
||||
poNumber?: string;
|
||||
shippingFeeCny?: number;
|
||||
internationalShippingFee?: number;
|
||||
poLogisticsCompany?: string;
|
||||
poTrackingNumber?: string;
|
||||
internationalTrackingNumber?: string;
|
||||
trackInfo?: string;
|
||||
}
|
||||
import { http } from './http'
|
||||
|
||||
export interface ZebraOrdersResp {
|
||||
orders: ZebraOrder[];
|
||||
total?: number;
|
||||
totalPages?: number;
|
||||
}
|
||||
|
||||
import { http } from './http';
|
||||
|
||||
export interface BanmaAccount {
|
||||
id?: number;
|
||||
name?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
token?: string;
|
||||
tokenExpireAt?: string | number;
|
||||
isDefault?: number;
|
||||
status?: number;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
// 斑马 API:与原 zebra-api.js 对齐的接口封装
|
||||
export const zebraApi = {
|
||||
// 账号管理(ruoyi-admin)
|
||||
getAccounts() {
|
||||
return http.get<{ code?: number; msg?: string; data: BanmaAccount[] }>('/tool/banma/accounts');
|
||||
return http.get('/tool/banma/accounts')
|
||||
},
|
||||
saveAccount(body: BanmaAccount) {
|
||||
return http.post<{ id: number }>('/tool/banma/accounts', body);
|
||||
|
||||
saveAccount(body: any) {
|
||||
return http.post('/tool/banma/accounts', body)
|
||||
},
|
||||
|
||||
removeAccount(id: number) {
|
||||
// 用 postVoid 也可,但这里前端未用到,保留以备将来
|
||||
return http.delete<void>(`/tool/banma/accounts/${id}`);
|
||||
return http.delete(`/tool/banma/accounts/${id}`)
|
||||
},
|
||||
|
||||
// 业务采集
|
||||
getShops(params?: { accountId?: number }) {
|
||||
return http.get<{ data?: { list?: Array<{ id: string; shopName: string }> } }>(
|
||||
'/api/banma/shops', params as unknown as Record<string, unknown>
|
||||
);
|
||||
},
|
||||
getOrders(params: { accountId?: number; startDate?: string; endDate?: string; page?: number; pageSize?: number; shopIds?: string; batchId: string }) {
|
||||
return http.get<ZebraOrdersResp>('/api/banma/orders', params as unknown as Record<string, unknown>);
|
||||
return http.get('/api/banma/shops', params as Record<string, unknown>)
|
||||
},
|
||||
|
||||
getOrders(params: any) {
|
||||
return http.get('/api/banma/orders', params as Record<string, unknown>)
|
||||
},
|
||||
|
||||
// 其他功能(客户端微服务)
|
||||
getOrdersByBatch(batchId: string) {
|
||||
return http.get<ZebraOrdersResp>(`/api/banma/orders/batch/${batchId}`);
|
||||
return http.get(`/api/banma/orders/batch/${batchId}`)
|
||||
},
|
||||
|
||||
getLatestOrders() {
|
||||
return http.get<ZebraOrdersResp>('/api/banma/orders/latest');
|
||||
return http.get('/api/banma/orders/latest')
|
||||
},
|
||||
|
||||
getOrderStats() {
|
||||
return http.get('/api/banma/orders/stats');
|
||||
return http.get('/api/banma/orders/stats')
|
||||
},
|
||||
|
||||
searchOrders(searchParams: Record<string, unknown>) {
|
||||
return http.get('/api/banma/orders/search', searchParams);
|
||||
},
|
||||
};
|
||||
return http.get('/api/banma/orders/search', searchParams)
|
||||
}
|
||||
}
|
||||
@@ -295,6 +295,17 @@ onMounted(async () => {
|
||||
<div class="body-layout">
|
||||
<!-- 左侧步骤栏 -->
|
||||
<aside class="steps-sidebar">
|
||||
<!-- 顶部标签栏 -->
|
||||
<div class="top-tabs">
|
||||
<div class="tab-item active">
|
||||
<span class="tab-icon">📦</span>
|
||||
<span class="tab-text">ASIN查询</span>
|
||||
</div>
|
||||
<div class="tab-item" @click="openGenmaiSpirit">
|
||||
<span class="tab-icon">🔍</span>
|
||||
<span class="tab-text">跟卖精灵</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="steps-title">操作流程:</div>
|
||||
<div class="steps-flow">
|
||||
<!-- 1 -->
|
||||
@@ -356,7 +367,6 @@ onMounted(async () => {
|
||||
</div>
|
||||
<div class="export-progress-text">{{ Math.round(exportProgress) }}%</div>
|
||||
</div>
|
||||
<el-button size="small" class="w100 btn-blue" :loading="genmaiLoading" @click="openGenmaiSpirit">{{ genmaiLoading ? '启动中...' : '跟卖精灵' }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -453,14 +463,25 @@ onMounted(async () => {
|
||||
<style scoped>
|
||||
.amazon-root { position: absolute; inset: 0; background: #f5f5f5; padding: 12px; box-sizing: border-box; }
|
||||
.main-container { background: #fff; border-radius: 4px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); height: 100%; display: flex; flex-direction: column; }
|
||||
.body-layout { display: flex; gap: 12px; height: 100%; }
|
||||
|
||||
/* 顶部标签栏 */
|
||||
.top-tabs { display: flex; margin-bottom: 8px; }
|
||||
.tab-item { flex: 1; display: flex; align-items: center; justify-content: center; gap: 3px; padding: 4px 6px; cursor: pointer; transition: all 0.2s ease; background: #f5f7fa; color: #606266; font-size: 11px; font-weight: 500; border: 1px solid #ebeef5; }
|
||||
.tab-item:first-child { border-radius: 3px 0 0 3px; }
|
||||
.tab-item:last-child { border-radius: 0 3px 3px 0; border-left: none; }
|
||||
.tab-item:hover { background: #e8f4ff; color: #409EFF; }
|
||||
.tab-item.active { background: #1677FF; color: #fff; border-color: #1677FF; cursor: default; }
|
||||
.tab-icon { font-size: 12px; }
|
||||
.tab-text { line-height: 1; }
|
||||
|
||||
.body-layout { display: flex; gap: 12px; flex: 1; overflow: hidden; }
|
||||
.steps-sidebar { width: 220px; background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 10px; height: 100%; flex-shrink: 0; }
|
||||
.steps-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 8px; text-align: left; }
|
||||
.steps-title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
|
||||
.steps-flow { position: relative; }
|
||||
.steps-flow:before { content: ''; position: absolute; left: 12px; top: 0; bottom: 0; width: 2px; background: #e5e7eb; }
|
||||
.flow-item { position: relative; display: grid; grid-template-columns: 24px 1fr; gap: 10px; padding: 8px 0; }
|
||||
.steps-flow:before { content: ''; position: absolute; left: 11px; top: 20px; bottom: 0; width: 1px; background: rgba(229, 231, 235, 0.6); }
|
||||
.flow-item { position: relative; display: grid; grid-template-columns: 22px 1fr; gap: 10px; padding: 8px 0; }
|
||||
.flow-item + .flow-item { border-top: 1px dashed #ebeef5; }
|
||||
.flow-item .step-index { position: static; width: 24px; height: 24px; line-height: 24px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 12px; font-weight: 600; margin-top: 2px; }
|
||||
.flow-item .step-index { position: static; width: 22px; height: 22px; line-height: 22px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 12px; font-weight: 600; margin-top: 2px; }
|
||||
.flow-item:after { display: none; }
|
||||
.step-card { border: none; border-radius: 0; padding: 0; background: transparent; }
|
||||
.step-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
||||
@@ -521,14 +542,14 @@ onMounted(async () => {
|
||||
.table-wrapper::-webkit-scrollbar-thumb { background: #c0c4cc; border-radius: 3px; }
|
||||
.table-wrapper:hover::-webkit-scrollbar-thumb { background: #a8abb2; }
|
||||
.table { width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed; }
|
||||
.table th { background: #f5f7fa; color: #909399; font-weight: 600; padding: 12px 8px; border-bottom: 2px solid #ebeef5; text-align: left; }
|
||||
.table td { padding: 10px 8px; border-bottom: 1px solid #f0f0f0; vertical-align: middle; }
|
||||
.table th { background: #f5f7fa; color: #909399; font-weight: 600; padding: 12px 8px; border-bottom: 2px solid #ebeef5; text-align: center; }
|
||||
.table td { padding: 10px 8px; border-bottom: 1px solid #f0f0f0; vertical-align: middle; text-align: center; }
|
||||
.table tbody tr:hover { background: #f9f9f9; }
|
||||
.table th:nth-child(1), .table td:nth-child(1) { width: 33.33%; }
|
||||
.table th:nth-child(2), .table td:nth-child(2) { width: 33.33%; }
|
||||
.table th:nth-child(3), .table td:nth-child(3) { width: 33.33%; }
|
||||
.asin-out { color: #f56c6c; font-weight: 600; }
|
||||
.seller-info { display: flex; align-items: center; gap: 4px; }
|
||||
.seller-info { display: flex; align-items: center; gap: 4px; justify-content: center; }
|
||||
.seller { color: #303133; font-weight: 500; }
|
||||
.shipper { color: #909399; font-size: 12px; }
|
||||
.price { color: #e6a23c; font-weight: 600; }
|
||||
|
||||
@@ -31,11 +31,10 @@ async function handleAuth() {
|
||||
|
||||
authLoading.value = true
|
||||
try {
|
||||
// 1. 先检查设备限制
|
||||
await deviceApi.register({ username: authForm.value.username })
|
||||
|
||||
// 2. 设备检查通过,进行登录
|
||||
const data = await authApi.login(authForm.value)
|
||||
const loginRes: any = await authApi.login(authForm.value)
|
||||
const data = loginRes?.data || loginRes
|
||||
|
||||
emit('loginSuccess', {
|
||||
token: data.token,
|
||||
user: {
|
||||
|
||||
@@ -41,8 +41,9 @@ async function checkUsernameAvailability() {
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await authApi.checkUsername(registerForm.value.username)
|
||||
usernameCheckResult.value = data.available
|
||||
const res: any = await authApi.checkUsername(registerForm.value.username)
|
||||
const data = res?.data || res
|
||||
usernameCheckResult.value = data?.available || false
|
||||
} catch {
|
||||
usernameCheckResult.value = null
|
||||
}
|
||||
@@ -53,18 +54,17 @@ async function handleRegister() {
|
||||
|
||||
registerLoading.value = true
|
||||
try {
|
||||
// 1. 注册
|
||||
await authApi.register({
|
||||
username: registerForm.value.username,
|
||||
password: registerForm.value.password
|
||||
})
|
||||
|
||||
// 2. 注册成功后直接登录
|
||||
const loginData = await authApi.login({
|
||||
|
||||
const loginRes: any = await authApi.login({
|
||||
username: registerForm.value.username,
|
||||
password: registerForm.value.password
|
||||
})
|
||||
|
||||
const loginData = loginRes?.data || loginRes
|
||||
|
||||
emit('loginSuccess', {
|
||||
token: loginData.token,
|
||||
user: {
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
getSettings,
|
||||
saveSettings,
|
||||
savePlatformSettings,
|
||||
type Platform,
|
||||
type PlatformExportSettings
|
||||
} from '../../utils/settings'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// 平台配置
|
||||
const platforms = [
|
||||
{ key: 'amazon' as Platform, name: 'Amazon', icon: '🛒', color: '#FF9900' },
|
||||
{ key: 'rakuten' as Platform, name: 'Rakuten', icon: '🛍️', color: '#BF0000' },
|
||||
{ key: 'zebra' as Platform, name: 'Zebra', icon: '🦓', color: '#34495E' }
|
||||
]
|
||||
|
||||
// 设置项
|
||||
const platformSettings = ref<Record<Platform, PlatformExportSettings>>({
|
||||
amazon: { exportPath: '' },
|
||||
rakuten: { exportPath: '' },
|
||||
zebra: { exportPath: '' }
|
||||
})
|
||||
|
||||
const activeTab = ref<Platform>('amazon')
|
||||
|
||||
const show = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
// 选择导出路径
|
||||
async function selectExportPath(platform: Platform) {
|
||||
const result = await (window as any).electronAPI.showOpenDialog({
|
||||
title: `选择${platforms.find(p => p.key === platform)?.name}默认导出路径`,
|
||||
properties: ['openDirectory'],
|
||||
defaultPath: platformSettings.value[platform].exportPath
|
||||
})
|
||||
|
||||
if (!result.canceled && result.filePaths?.length > 0) {
|
||||
platformSettings.value[platform].exportPath = result.filePaths[0]
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
function saveAllSettings() {
|
||||
Object.keys(platformSettings.value).forEach(platformKey => {
|
||||
const platform = platformKey as Platform
|
||||
const platformConfig = platformSettings.value[platform]
|
||||
savePlatformSettings(platform, platformConfig)
|
||||
})
|
||||
|
||||
ElMessage({ message: '设置已保存', type: 'success' })
|
||||
show.value = false
|
||||
}
|
||||
|
||||
// 加载设置
|
||||
function loadAllSettings() {
|
||||
const settings = getSettings()
|
||||
platformSettings.value = {
|
||||
amazon: { ...settings.platforms.amazon },
|
||||
rakuten: { ...settings.platforms.rakuten },
|
||||
zebra: { ...settings.platforms.zebra }
|
||||
}
|
||||
}
|
||||
|
||||
// 重置单个平台设置
|
||||
function resetPlatformSettings(platform: Platform) {
|
||||
platformSettings.value[platform] = {
|
||||
exportPath: ''
|
||||
}
|
||||
}
|
||||
|
||||
// 重置所有设置
|
||||
function resetAllSettings() {
|
||||
platforms.forEach(platform => {
|
||||
resetPlatformSettings(platform.key)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadAllSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="show"
|
||||
title="应用设置"
|
||||
width="480px"
|
||||
:close-on-click-modal="false"
|
||||
class="settings-dialog">
|
||||
|
||||
<div class="settings-content">
|
||||
<!-- 平台选择标签 -->
|
||||
<div class="platform-tabs">
|
||||
<div
|
||||
v-for="platform in platforms"
|
||||
:key="platform.key"
|
||||
:class="['platform-tab', { active: activeTab === platform.key }]"
|
||||
@click="activeTab = platform.key"
|
||||
:style="{ '--platform-color': platform.color }">
|
||||
<span class="platform-icon">{{ platform.icon }}</span>
|
||||
<span class="platform-name">{{ platform.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前平台设置 -->
|
||||
<div class="setting-section">
|
||||
<div class="section-title">
|
||||
<span class="title-icon">📁</span>
|
||||
<span>{{ platforms.find(p => p.key === activeTab)?.name }} 导出设置</span>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-label">默认导出路径</div>
|
||||
<div class="setting-desc">设置 {{ platforms.find(p => p.key === activeTab)?.name }} Excel文件的默认保存位置</div>
|
||||
<div class="path-input-group">
|
||||
<el-input
|
||||
v-model="platformSettings[activeTab].exportPath"
|
||||
placeholder="留空时自动弹出保存对话框"
|
||||
readonly
|
||||
class="path-input">
|
||||
<template #suffix>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="selectExportPath(activeTab)"
|
||||
class="select-btn">
|
||||
浏览
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-actions">
|
||||
<el-button
|
||||
size="small"
|
||||
@click="resetPlatformSettings(activeTab)">
|
||||
重置此平台
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="resetAllSettings">重置全部</el-button>
|
||||
<el-button @click="show = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveAllSettings">保存设置</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.settings-dialog :deep(.el-dialog__body) {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.platform-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
padding: 4px;
|
||||
background: #F8F9FA;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.platform-tab {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: transparent;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.platform-tab:hover {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
color: var(--platform-color);
|
||||
}
|
||||
|
||||
.platform-tab.active {
|
||||
background: #fff;
|
||||
color: var(--platform-color);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.platform-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.platform-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.setting-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.setting-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.setting-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.path-input-group {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.path-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.path-input :deep(.el-input__wrapper) {
|
||||
padding-right: 80px;
|
||||
}
|
||||
|
||||
.select-btn {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
height: 24px;
|
||||
padding: 0 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
background: #F8F9FA;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.info-content p {
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
|
||||
.info-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.setting-actions {
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.settings-dialog :deep(.el-dialog__header) {
|
||||
text-align: center;
|
||||
padding-right: 40px; /* 为右侧关闭按钮留出空间 */
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SettingsDialog'
|
||||
}
|
||||
</script>
|
||||
@@ -2,15 +2,18 @@
|
||||
<div>
|
||||
<div class="version-info" @click="autoCheck">v{{ version || '-' }}</div>
|
||||
|
||||
<el-dialog v-model="show" width="522px" :close-on-click-modal="false" align-center class="update-dialog" :title="stage === 'downloading' ? `正在更新 ${appName}` : '软件更新'">
|
||||
<el-dialog v-model="show" width="522px" :close-on-click-modal="false" align-center class="update-dialog"
|
||||
:title="stage === 'downloading' ? `正在更新 ${appName}` : '软件更新'">
|
||||
<div v-if="stage === 'check'" class="update-content">
|
||||
<div class="update-layout">
|
||||
<div class="left-pane">
|
||||
<img src="/icon/icon.png" class="app-icon app-icon-large" alt="App Icon" />
|
||||
<img src="/icon/icon.png" class="app-icon app-icon-large" alt="App Icon"/>
|
||||
</div>
|
||||
<div class="right-pane">
|
||||
<p class="announce">新版本的"{{ appName }}"已经发布</p>
|
||||
<p class="desc">{{ appName }} {{ info.latestVersion }} 可供安装,您现在的版本是 {{ version }},要现在安装吗?</p>
|
||||
<p class="desc">{{ appName }} {{ info.latestVersion }} 可供安装,您现在的版本是 {{
|
||||
version
|
||||
}},要现在安装吗?</p>
|
||||
|
||||
<div class="update-details form">
|
||||
<h4>更新信息</h4>
|
||||
@@ -20,7 +23,7 @@
|
||||
class="notes-box"
|
||||
:rows="6"
|
||||
readonly
|
||||
resize="none" />
|
||||
resize="none"/>
|
||||
</div>
|
||||
|
||||
<div class="update-actions row">
|
||||
@@ -41,7 +44,7 @@
|
||||
<div v-else-if="stage === 'downloading'" class="update-content">
|
||||
<div class="download-main">
|
||||
<div class="download-icon">
|
||||
<img src="/icon/icon.png" class="app-icon" alt="App Icon" />
|
||||
<img src="/icon/icon.png" class="app-icon" alt="App Icon"/>
|
||||
</div>
|
||||
<div class="download-content">
|
||||
<div class="download-info">
|
||||
@@ -52,7 +55,7 @@
|
||||
:percentage="prog.percentage"
|
||||
:show-text="false"
|
||||
:stroke-width="6"
|
||||
color="#409EFF" />
|
||||
color="#409EFF"/>
|
||||
<div class="progress-details">
|
||||
<span style="font-weight: 500">{{ prog.current }} / {{ prog.total }}</span>
|
||||
<el-button size="small" @click="cancelDownload">取消</el-button>
|
||||
@@ -64,7 +67,7 @@
|
||||
|
||||
<div v-else-if="stage === 'completed'" class="update-content">
|
||||
<div class="update-header text-center">
|
||||
<img src="/icon/icon.png" class="app-icon" alt="App Icon" />
|
||||
<img src="/icon/icon.png" class="app-icon" alt="App Icon"/>
|
||||
<h3>更新完成</h3>
|
||||
<p>更新文件已下载,将在重启后自动应用</p>
|
||||
</div>
|
||||
@@ -77,7 +80,7 @@
|
||||
:percentage="100"
|
||||
:show-text="false"
|
||||
:stroke-width="6"
|
||||
color="#67C23A" />
|
||||
color="#67C23A"/>
|
||||
</div>
|
||||
|
||||
<div class="update-buttons">
|
||||
@@ -90,9 +93,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { updateApi } from '../../api/update'
|
||||
import {ref, computed, onMounted, onUnmounted} from 'vue'
|
||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||
import {updateApi} from '../../api/update'
|
||||
|
||||
const props = defineProps<{ modelValue: boolean }>()
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
||||
@@ -105,8 +108,8 @@ const show = computed({
|
||||
type Stage = 'check' | 'downloading' | 'completed'
|
||||
const stage = ref<Stage>('check')
|
||||
const appName = ref('我了个电商')
|
||||
const version = ref('2.0.0')
|
||||
const prog = ref({ percentage: 0, current: '0 MB', total: '0 MB', speed: '' })
|
||||
const version = ref('')
|
||||
const prog = ref({percentage: 0, current: '0 MB', total: '0 MB', speed: ''})
|
||||
const info = ref({
|
||||
latestVersion: '2.4.8',
|
||||
downloadUrl: '',
|
||||
@@ -117,49 +120,48 @@ const info = ref({
|
||||
|
||||
async function autoCheck() {
|
||||
try {
|
||||
ElMessage({ message: '正在检查更新...', type: 'info' })
|
||||
version.value = await (window as any).electronAPI.getJarVersion()
|
||||
const checkRes: any = await updateApi.checkUpdate(version.value)
|
||||
const result = checkRes?.data || checkRes
|
||||
|
||||
try {
|
||||
version.value = await updateApi.getVersion()
|
||||
} catch (error) {
|
||||
console.error('获取版本失败:', error)
|
||||
version.value = '2.0.0'
|
||||
if (!result.needUpdate) {
|
||||
ElMessage.info('当前已是最新版本')
|
||||
return
|
||||
}
|
||||
|
||||
info.value = {
|
||||
currentVersion: version.value,
|
||||
latestVersion: '2.4.9',
|
||||
downloadUrl: 'https://qiniu.pxdj.tashowz.com/2025/09/becac13811214c909d11162d2ff2c863.asar',
|
||||
currentVersion: result.currentVersion,
|
||||
latestVersion: result.latestVersion,
|
||||
downloadUrl: result.downloadUrl || '',
|
||||
updateNotes: '• 优化了用户界面体验\n• 修复了已知问题\n• 提升了系统稳定性\n• 轻量级更新(仅替换app.asar)',
|
||||
hasUpdate: true
|
||||
}
|
||||
|
||||
show.value = true
|
||||
stage.value = 'check'
|
||||
ElMessage({ message: '发现新版本', type: 'success' })
|
||||
ElMessage.success('发现新版本')
|
||||
} catch (error) {
|
||||
console.error('检查更新失败:', error)
|
||||
ElMessage({ message: '检查更新失败', type: 'error' })
|
||||
ElMessage.error('检查更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function start() {
|
||||
if (!info.value.downloadUrl) {
|
||||
ElMessage({ message: '下载链接不可用', type: 'error' });
|
||||
return;
|
||||
ElMessage.error('下载链接不可用')
|
||||
return
|
||||
}
|
||||
|
||||
stage.value = 'downloading';
|
||||
prog.value = { percentage: 0, current: '0 MB', total: '0 MB', speed: '' };
|
||||
stage.value = 'downloading'
|
||||
prog.value = {percentage: 0, current: '0 MB', total: '0 MB', speed: ''}
|
||||
|
||||
(window as any).electronAPI.onDownloadProgress((progress: any) => {
|
||||
;(window as any).electronAPI.onDownloadProgress((progress: any) => {
|
||||
prog.value = {
|
||||
percentage: progress.percentage || 0,
|
||||
current: progress.current || '0 MB',
|
||||
total: progress.total || '0 MB',
|
||||
speed: progress.speed || ''
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await (window as any).electronAPI.downloadUpdate(info.value.downloadUrl)
|
||||
@@ -167,14 +169,14 @@ async function start() {
|
||||
if (response.success) {
|
||||
stage.value = 'completed'
|
||||
prog.value.percentage = 100
|
||||
ElMessage({ message: '下载完成', type: 'success' })
|
||||
ElMessage.success('下载完成')
|
||||
} else {
|
||||
ElMessage({ message: '下载失败: ' + (response.error || '未知错误'), type: 'error' })
|
||||
ElMessage.error('下载失败: ' + (response.error || '未知错误'))
|
||||
stage.value = 'check'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error)
|
||||
ElMessage({ message: '下载失败', type: 'error' })
|
||||
ElMessage.error('下载失败')
|
||||
stage.value = 'check'
|
||||
}
|
||||
}
|
||||
@@ -195,37 +197,26 @@ async function cancelDownload() {
|
||||
async function installUpdate() {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'安装过程中程序将自动重启,请确保已保存所有工作。确定要立即安装更新吗?',
|
||||
'确认安装',
|
||||
{
|
||||
confirmButtonText: '立即安装',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
'安装过程中程序将自动重启,请确保已保存所有工作。确定要立即安装更新吗?',
|
||||
'确认安装',
|
||||
{
|
||||
confirmButtonText: '立即安装',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
const response = await (window as any).electronAPI.installUpdate()
|
||||
|
||||
if (response.success) {
|
||||
ElMessage({ message: '应用即将重启', type: 'success' })
|
||||
ElMessage.success('应用即将重启')
|
||||
setTimeout(() => show.value = false, 1000)
|
||||
} else {
|
||||
ElMessage({ message: '重启失败: ' + (response.error || '未知错误'), type: 'error' })
|
||||
stage.value = 'check'
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('安装失败:', error)
|
||||
ElMessage({ message: '安装失败', type: 'error' })
|
||||
}
|
||||
if (error !== 'cancel') ElMessage.error('安装失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
version.value = await updateApi.getVersion()
|
||||
} catch (error) {
|
||||
console.error('获取版本失败:', error)
|
||||
}
|
||||
version.value = await (window as any).electronAPI.getJarVersion()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -238,7 +229,7 @@ onUnmounted(() => {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
background: rgba(255,255,255,0.9);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
@@ -273,11 +264,38 @@ onUnmounted(() => {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.left-pane { display: flex; flex-direction: column; align-items: flex-start; }
|
||||
.app-icon-large { width: 70px; height: 70px; border-radius: 12px; margin: 4px 0 0 0; }
|
||||
.right-pane { min-width: 0; }
|
||||
.right-pane .announce { font-size: 16px; font-weight: 600; color: #1f2937; margin: 4px 0 6px; word-break: break-word; }
|
||||
.right-pane .desc { font-size: 13px; color: #6b7280; line-height: 1.6; margin: 0; word-break: break-word; }
|
||||
.left-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.app-icon-large {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
border-radius: 12px;
|
||||
margin: 4px 0 0 0;
|
||||
}
|
||||
|
||||
.right-pane {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.right-pane .announce {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 4px 0 6px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.right-pane .desc {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.update-header {
|
||||
display: flex;
|
||||
@@ -324,8 +342,13 @@ onUnmounted(() => {
|
||||
margin: 12px 0 8px 0;
|
||||
}
|
||||
|
||||
.update-details.form { max-height: none; }
|
||||
.notes-box :deep(textarea.el-textarea__inner) { white-space: pre-wrap; }
|
||||
.update-details.form {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.notes-box :deep(textarea.el-textarea__inner) {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.update-details h4 {
|
||||
font-size: 14px;
|
||||
@@ -347,10 +370,24 @@ onUnmounted(() => {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.update-actions.row .update-buttons { justify-content: space-between; }
|
||||
:deep(.update-actions.row .update-buttons .el-button) { flex: none; min-width: 100px; }
|
||||
.left-actions { display: flex; gap: 12px; }
|
||||
.right-actions { display: flex; gap: 8px; }
|
||||
.update-actions.row .update-buttons {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
:deep(.update-actions.row .update-buttons .el-button) {
|
||||
flex: none;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.left-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.update-buttons .el-button) {
|
||||
flex: 1;
|
||||
|
||||
@@ -49,9 +49,7 @@ const selectedFileName = ref('')
|
||||
const pendingFile = ref<File | null>(null)
|
||||
const region = ref('JP')
|
||||
const regionOptions = [
|
||||
{ label: '日本 (Japan)', value: 'JP', flag: '🇯🇵' },
|
||||
{ label: '美国 (USA)', value: 'US', flag: '🇺🇸' },
|
||||
{ label: '中国 (China)', value: 'CN', flag: '🇨🇳' },
|
||||
{ label: '日本 (Japan)', value: 'JP', flag: '🇯🇵' }
|
||||
]
|
||||
// 获取数据筛选:查询日期
|
||||
const dateRange = ref<string[] | null>(null)
|
||||
@@ -432,7 +430,7 @@ onMounted(loadLatest)
|
||||
<div class="title">网站地区</div>
|
||||
</div>
|
||||
<div class="desc">请选择目标网站地区,如:日本区。</div>
|
||||
<el-select v-model="region" placeholder="选择地区" size="small" style="width: 100%">
|
||||
<el-select v-model="region" placeholder="选择地区" size="small" style="width: 100%" disabled>
|
||||
<el-option v-for="opt in regionOptions" :key="opt.value" :label="opt.label" :value="opt.value">
|
||||
<span style="margin-right:6px">{{ opt.flag }}</span>{{ opt.label }}
|
||||
</el-option>
|
||||
@@ -602,14 +600,14 @@ onMounted(loadLatest)
|
||||
|
||||
.body-layout { display: flex; gap: 12px; height: 100%; }
|
||||
.steps-sidebar { width: 220px; background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 10px; height: 100%; flex-shrink: 0; }
|
||||
.steps-title { font-size: 14px; font-weight: 600; color: #303133; margin-bottom: 8px; text-align: left; }
|
||||
.steps-title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
|
||||
|
||||
/* 卡片式步骤,与示例一致 */
|
||||
.steps-flow { position: relative; }
|
||||
.steps-flow:before { content: ''; position: absolute; left: 12px; top: 0; bottom: 0; width: 2px; background: #e5e7eb; }
|
||||
.flow-item { position: relative; display: grid; grid-template-columns: 24px 1fr; gap: 10px; padding: 8px 0; }
|
||||
.steps-flow:before { content: ''; position: absolute; left: 11px; top: 20px; bottom: 0; width: 1px; background: rgba(229, 231, 235, 0.6); }
|
||||
.flow-item { position: relative; display: grid; grid-template-columns: 22px 1fr; gap: 10px; padding: 8px 0; }
|
||||
.flow-item + .flow-item { border-top: 1px dashed #ebeef5; }
|
||||
.flow-item .step-index { position: static; width: 24px; height: 24px; line-height: 24px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 12px; font-weight: 600; margin-top: 2px; }
|
||||
.flow-item .step-index { position: static; width: 22px; height: 22px; line-height: 22px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 12px; font-weight: 600; margin-top: 2px; }
|
||||
.flow-item:after { display: none; }
|
||||
.step-card { border: none; border-radius: 0; padding: 0; background: transparent; }
|
||||
.step-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
||||
|
||||
@@ -538,12 +538,12 @@ export default {
|
||||
.aside.collapsed { width: 56px; overflow: hidden; }
|
||||
.aside-header { display: flex; justify-content: flex-start; align-items: center; font-weight: 600; color: #606266; margin-bottom: 8px; }
|
||||
.aside-steps { position: relative; }
|
||||
.step { display: grid; grid-template-columns: 24px 1fr; gap: 10px; position: relative; padding: 8px 0; }
|
||||
.step { display: grid; grid-template-columns: 22px 1fr; gap: 10px; position: relative; padding: 8px 0; }
|
||||
.step + .step { border-top: 1px dashed #ebeef5; }
|
||||
.step-index { width: 24px; height: 24px; background: #1677FF; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; margin-top: 2px; }
|
||||
.step-index { width: 22px; height: 22px; background: #1677FF; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; margin-top: 2px; }
|
||||
.step-body { min-width: 0; text-align: left; }
|
||||
.step-title { font-size: 13px; color: #606266; margin-bottom: 6px; font-weight: 600; text-align: left; }
|
||||
.aside-steps:before { content: ''; position: absolute; left: 12px; top: 0; bottom: 0; width: 2px; background: #e5e7eb; }
|
||||
.aside-steps:before { content: ''; position: absolute; left: 11px; top: 20px; bottom: 0; width: 1px; background: rgba(229, 231, 235, 0.6); }
|
||||
.account-list {height: auto; }
|
||||
.step-actions { margin-top: 8px; display: flex; gap: 8px; }
|
||||
.step-accounts { position: relative; }
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
<groupId>com.tashow.erp</groupId>
|
||||
<artifactId>erp_client_sb</artifactId>
|
||||
<version>2.4.7</version>
|
||||
<version>2.4.8</version>
|
||||
<name>erp_client_sb</name>
|
||||
<description>erp客户端</description>
|
||||
<properties>
|
||||
|
||||
@@ -13,23 +13,15 @@ public class ErpClientSbApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ConfigurableApplicationContext applicationContext = SpringApplication.run(ErpClientSbApplication.class, args);
|
||||
try {
|
||||
ErrorReporter errorReporter = applicationContext.getBean(ErrorReporter.class);
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> {
|
||||
log.error("捕获到未处理异常: " + ex.getMessage(), ex);
|
||||
errorReporter.reportSystemError("未捕获异常: " + thread.getName(), (Exception) ex);
|
||||
});
|
||||
log.info("Started Success");
|
||||
} catch (Exception e) {
|
||||
log.warn("未设置 ErrorReporter,继续启动: {}", e.getMessage());
|
||||
}
|
||||
ErrorReporter errorReporter = applicationContext.getBean(ErrorReporter.class);
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> {
|
||||
log.error("捕获到未处理异常: " + ex.getMessage(), ex);
|
||||
errorReporter.reportSystemError("未捕获异常: " + thread.getName(), (Exception) ex);
|
||||
});
|
||||
log.info("Started Success");
|
||||
ResourcePreloader.init();
|
||||
ResourcePreloader.preloadErpDashboard();
|
||||
ResourcePreloader.executePreloading();
|
||||
|
||||
try {
|
||||
ResourcePreloader.init();
|
||||
ResourcePreloader.preloadErpDashboard();
|
||||
ResourcePreloader.executePreloading();
|
||||
} catch (Throwable t) {
|
||||
log.warn("资源预加载失败: {}", t.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ public class AmazonProductEntity {
|
||||
@Column
|
||||
private String asin;
|
||||
|
||||
@Column(name = "region", length = 10)
|
||||
private String region; // 地区代码(JP/US)
|
||||
|
||||
@Column(name = "price")
|
||||
private String price;
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ import java.util.Optional;
|
||||
public interface AmazonProductRepository extends JpaRepository<AmazonProductEntity, Long> {
|
||||
|
||||
/**
|
||||
* 根据ASIN查找产品(取最新的一条)
|
||||
* 根据ASIN和地区查找产品(取最新的一条)
|
||||
*/
|
||||
@Query(value = "SELECT * FROM amazon_products WHERE asin = :asin ORDER BY created_at DESC LIMIT 1", nativeQuery = true)
|
||||
Optional<AmazonProductEntity> findByAsin(@Param("asin") String asin);
|
||||
@Query(value = "SELECT * FROM amazon_products WHERE asin = :asin AND region = :region ORDER BY created_at DESC LIMIT 1", nativeQuery = true)
|
||||
Optional<AmazonProductEntity> findByAsinAndRegion(@Param("asin") String asin, @Param("region") String region);
|
||||
|
||||
/**
|
||||
* 根据会话ID查找产品(分页)
|
||||
|
||||
@@ -113,7 +113,7 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
|
||||
if (asin == null || asin.trim().isEmpty()) continue;
|
||||
String cleanAsin = asin.replaceAll("[^a-zA-Z0-9]", "");
|
||||
|
||||
Optional<AmazonProductEntity> cached = amazonProductRepository.findByAsin(cleanAsin);
|
||||
Optional<AmazonProductEntity> cached = amazonProductRepository.findByAsinAndRegion(cleanAsin, region);
|
||||
if (cached.isPresent() && !isEmpty(cached.get().getPrice()) && !isEmpty(cached.get().getSeller())) {
|
||||
AmazonProductEntity entity = cached.get();
|
||||
entity.setSessionId(sessionId);
|
||||
@@ -131,6 +131,7 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
|
||||
}
|
||||
AmazonProductEntity entity = resultCache.getOrDefault(cleanAsin, new AmazonProductEntity());
|
||||
entity.setAsin(cleanAsin);
|
||||
entity.setRegion(region);
|
||||
entity.setSessionId(sessionId);
|
||||
entity.setUpdatedAt(LocalDateTime.now());
|
||||
amazonProductRepository.save(entity);
|
||||
@@ -160,56 +161,27 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
|
||||
* 根据地区配置Site
|
||||
*/
|
||||
private Site configureSiteForRegion(String region) {
|
||||
Site baseSite = Site.me()
|
||||
boolean isUS = "US".equals(region);
|
||||
return Site.me()
|
||||
.setRetryTimes(3)
|
||||
.setSleepTime(3000 + random.nextInt(3000))
|
||||
.setTimeOut(20000)
|
||||
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0")
|
||||
.addHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
||||
.addHeader("accept-encoding", "gzip, deflate, br, zstd")
|
||||
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
|
||||
.addHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8")
|
||||
.addHeader("accept-encoding", "gzip, deflate, br")
|
||||
.addHeader("accept-language", isUS ? "zh-CN,zh;q=0.9,en;q=0.8" : "ja,en;q=0.9,zh-CN;q=0.8")
|
||||
.addHeader("cache-control", "max-age=0")
|
||||
.addHeader("upgrade-insecure-requests", "1")
|
||||
.addHeader("sec-ch-ua", "\"Not)A;Brand\";v=\"99\", \"Microsoft Edge\";v=\"127\", \"Chromium\";v=\"127\"")
|
||||
.addHeader("sec-ch-ua-mobile", "?0")
|
||||
.addHeader("sec-ch-ua-platform", "\"Windows\"")
|
||||
.addHeader("sec-ch-ua-platform-version", "\"10.0.0\"")
|
||||
.addHeader("sec-ch-device-memory", "8")
|
||||
.addHeader("sec-ch-viewport-width", "1400")
|
||||
.addHeader("device-memory", "8")
|
||||
.addHeader("viewport-width", "1400")
|
||||
.addHeader("dpr", "0.9")
|
||||
.addHeader("downlink", "10")
|
||||
.addHeader("ect", "4g")
|
||||
.addHeader("rtt", "150")
|
||||
.addHeader("referer", isUS ? "https://www.amazon.com/" : "https://www.amazon.co.jp/")
|
||||
.addHeader("sec-fetch-site", "none")
|
||||
.addHeader("sec-fetch-mode", "navigate")
|
||||
.addHeader("sec-fetch-user", "?1")
|
||||
.addHeader("sec-fetch-dest", "document");
|
||||
|
||||
if ("US".equals(region)) {
|
||||
// 美区配置
|
||||
baseSite.addHeader("accept-language", "zh-CN,zh;q=0.9,en;q=0.8")
|
||||
.addHeader("referer", "https://www.amazon.com/")
|
||||
.addCookie("i18n-prefs", "USD")
|
||||
.addCookie("lc-main", "en_US")
|
||||
.addCookie("session-id", "134-6097934-2082600")
|
||||
.addCookie("session-id-time", "2082787201l")
|
||||
.addCookie("ubid-main", "132-7547587-3056927")
|
||||
.addCookie("skin", "noskin")
|
||||
.addCookie("csm-hit", "tb:s-6ZB8JV440R1VZ54PSE5W|1759200029304&t:1759200030204&adb:adblk_no");
|
||||
} else {
|
||||
// 日区配置
|
||||
baseSite.addHeader("accept-language", "ja,en;q=0.9,zh-CN;q=0.8,zh;q=0.7")
|
||||
.addHeader("referer", "https://www.amazon.co.jp/")
|
||||
.addCookie("i18n-prefs", "JPY")
|
||||
.addCookie("lc-acbjp", "zh_CN")
|
||||
.addCookie("session-id", "358-1261309-0483141")
|
||||
.addCookie("session-id-time", "2082787201l")
|
||||
.addCookie("ubid-acbjp", "357-8224002-9668932")
|
||||
.addCookie("skin", "noskin");
|
||||
}
|
||||
|
||||
return baseSite;
|
||||
.addHeader("sec-fetch-dest", "document")
|
||||
.addCookie("i18n-prefs", isUS ? "USD" : "JPY")
|
||||
.addCookie(isUS ? "lc-main" : "lc-acbjp", isUS ? "en_US" : "zh_CN")
|
||||
.addCookie("session-id", isUS ? "134-6097934-2082600" : "358-1261309-0483141")
|
||||
.addCookie("session-id-time", "2082787201l")
|
||||
.addCookie(isUS ? "ubid-main" : "ubid-acbjp", isUS ? "132-7547587-3056927" : "357-8224002-9668932")
|
||||
.addCookie("skin", "noskin");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class VersionController extends BaseController {
|
||||
@Autowired
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
private static final String VERSION_REDIS_KEY = "erp:client:version";
|
||||
private static final String DOWNLOAD_URL_REDIS_KEY = "erp:client:download:url";
|
||||
private static final String DOWNLOAD_URL_REDIS_KEY = "erp:client:url";
|
||||
|
||||
/**
|
||||
* 检查版本更新
|
||||
@@ -85,23 +85,12 @@ public class VersionController extends BaseController {
|
||||
public AjaxResult updateVersionInfo(@RequestParam("version") String version,
|
||||
@RequestParam("downloadUrl") String downloadUrl) {
|
||||
try {
|
||||
if (StringUtils.isEmpty(version)) {
|
||||
return AjaxResult.error("版本号不能为空");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(downloadUrl)) {
|
||||
return AjaxResult.error("下载链接不能为空");
|
||||
}
|
||||
|
||||
// 更新Redis中的版本信息和下载链接
|
||||
redisTemplate.opsForValue().set(VERSION_REDIS_KEY, version);
|
||||
redisTemplate.opsForValue().set(DOWNLOAD_URL_REDIS_KEY, downloadUrl);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("version", version);
|
||||
result.put("downloadUrl", downloadUrl);
|
||||
result.put("updateTime", System.currentTimeMillis());
|
||||
|
||||
return AjaxResult.success("版本信息更新成功", result);
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("版本信息更新失败: " + e.getMessage());
|
||||
|
||||
@@ -42,9 +42,6 @@ public class FileController {
|
||||
|
||||
@PostMapping("/uploads")
|
||||
public AjaxResult uploadFiles(@RequestParam("files") List<MultipartFile> files) {
|
||||
if (files == null || files.isEmpty()) {
|
||||
return AjaxResult.error("没有选择文件");
|
||||
}
|
||||
|
||||
List<FileDto> fileDtoS = new ArrayList<>();
|
||||
for (MultipartFile file : files) {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<el-button type="success" icon="el-icon-upload" size="mini" @click="handleUpload" v-hasPermi="['system:version:upload']">上传新版本</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 版本信息卡片 -->
|
||||
<el-row class="mb8">
|
||||
<el-col s>
|
||||
@@ -44,14 +43,14 @@
|
||||
ref="upload"
|
||||
action="#"
|
||||
:limit="1"
|
||||
accept=".exe"
|
||||
accept=".asar"
|
||||
:on-exceed="handleExceed"
|
||||
:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:on-remove="handleFileRemove">
|
||||
<el-button slot="trigger" size="small" type="primary">选择文件</el-button>
|
||||
<div slot="tip" class="el-upload__tip">只能上传exe文件,且不超过800MB</div>
|
||||
<div slot="tip" class="el-upload__tip">只能上传asar文件,且不超过800MB</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -169,8 +168,8 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.uploadForm.file.name.endsWith('.exe')) {
|
||||
this.$modal.msgError("只支持上传.exe文件");
|
||||
if (!this.uploadForm.file.name.endsWith('.asar')) {
|
||||
this.$modal.msgError("只支持上传.asar文件");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user