Compare commits
25 Commits
mini-1.0.4
...
main
Author | SHA1 | Date | |
---|---|---|---|
be6919124d | |||
2d145793b4 | |||
d96fd3d536 | |||
0dc02ae454 | |||
83047d7877 | |||
85472f43e8 | |||
8ff95cdc07 | |||
c2a914d7f9 | |||
2038d76b1e | |||
0bcef8d497 | |||
9a0fe1715e | |||
482d91eba3 | |||
95a485cfc4 | |||
64b4fe31a3 | |||
65563f5b75 | |||
b68fde365f | |||
d3fb7827f1 | |||
9a8f3d050c | |||
2d82571303 | |||
2f469aec14 | |||
83f424b80f | |||
152ff7d8e5 | |||
f5bc5b9eef | |||
d4edba9212 | |||
68217b81dc |
60
20230425-epp.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 基于微服务的社区疫情防控系统
|
||||
|
||||
Epidemic prevention platform
|
||||
|
||||
> 项目首页:
|
||||
>
|
||||
> https://epp.only4.work/
|
||||
>
|
||||
> 代码仓库地址:
|
||||
>
|
||||
> https://git.only4.work/coder-xiaomo/epp
|
||||
|
||||
本项目采用 monorepo 单仓库模式进行维护,项目完整代码均在此仓库中。
|
||||
|
||||
|
||||
|
||||
## 管理员后台
|
||||
|
||||
> 管理员后台地址:
|
||||
>
|
||||
> https://epp.only4.work/manage/index.html
|
||||
|
||||
社区管理员 测试账号:admin<span style="user-select: none;"> </span>admin
|
||||
|
||||
系统管理员 测试账号:root<span style="user-select: none;"> </span>root
|
||||
|
||||
|
||||
|
||||
## 门禁端
|
||||
|
||||
>网页版地址:
|
||||
>
|
||||
>https://epp.only4.work/guard/index.html
|
||||
|
||||
可以直接用 **微信扫一扫功能** 或 **小程序端“扫门禁码”功能** 扫描门禁码,**扫描**及**确认进门**操作门禁端会弹出成功提示
|
||||
|
||||
\* 跨端桌面应用界面和功能均与网页版一致
|
||||
|
||||
|
||||
|
||||
## 小程序
|
||||
|
||||
> 小程序已审核上线,可以搜索 **devprogram** 或者扫描下方小程序码
|
||||
|
||||
\* 由于生活物资、进出码无法审核通过,所以**提审时隐藏了部分功能入口**。
|
||||
|
||||
如果登录之后**下方只有体温上报功能**,可以**点击右上角三个点→重新进入小程序**即可展示全部功能。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
-----
|
||||
|
||||
<div style="text-align: center;">
|
||||
张博凯
|
||||
</div>
|
252
JMeter测试-HTTP请求.jmx
Normal file
@@ -0,0 +1,252 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.4.3">
|
||||
<hashTree>
|
||||
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="测试计划" enabled="true">
|
||||
<stringProp name="TestPlan.comments"></stringProp>
|
||||
<boolProp name="TestPlan.functional_mode">false</boolProp>
|
||||
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
|
||||
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
|
||||
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="TestPlan.user_define_classpath"></stringProp>
|
||||
</TestPlan>
|
||||
<hashTree>
|
||||
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="线程组" enabled="true">
|
||||
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
|
||||
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="循环控制器" enabled="true">
|
||||
<boolProp name="LoopController.continue_forever">false</boolProp>
|
||||
<intProp name="LoopController.loops">-1</intProp>
|
||||
</elementProp>
|
||||
<stringProp name="ThreadGroup.num_threads">10</stringProp>
|
||||
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
|
||||
<boolProp name="ThreadGroup.scheduler">false</boolProp>
|
||||
<stringProp name="ThreadGroup.duration"></stringProp>
|
||||
<stringProp name="ThreadGroup.delay"></stringProp>
|
||||
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
|
||||
</ThreadGroup>
|
||||
<hashTree>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP请求" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain">epp.only4.work</stringProp>
|
||||
<stringProp name="HTTPSampler.port">80</stringProp>
|
||||
<stringProp name="HTTPSampler.protocol">https</stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">/user/manage/getUserList</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree>
|
||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="响应断言" enabled="true">
|
||||
<collectionProp name="Asserion.test_strings"/>
|
||||
<stringProp name="Assertion.custom_message"></stringProp>
|
||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
||||
<intProp name="Assertion.test_type">16</intProp>
|
||||
</ResponseAssertion>
|
||||
<hashTree/>
|
||||
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="察看结果树" enabled="true">
|
||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||
<objProp>
|
||||
<name>saveConfig</name>
|
||||
<value class="SampleSaveConfiguration">
|
||||
<time>true</time>
|
||||
<latency>true</latency>
|
||||
<timestamp>true</timestamp>
|
||||
<success>true</success>
|
||||
<label>true</label>
|
||||
<code>true</code>
|
||||
<message>true</message>
|
||||
<threadName>true</threadName>
|
||||
<dataType>true</dataType>
|
||||
<encoding>false</encoding>
|
||||
<assertions>true</assertions>
|
||||
<subresults>true</subresults>
|
||||
<responseData>false</responseData>
|
||||
<samplerData>false</samplerData>
|
||||
<xml>false</xml>
|
||||
<fieldNames>true</fieldNames>
|
||||
<responseHeaders>false</responseHeaders>
|
||||
<requestHeaders>false</requestHeaders>
|
||||
<responseDataOnError>false</responseDataOnError>
|
||||
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
|
||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
||||
<bytes>true</bytes>
|
||||
<sentBytes>true</sentBytes>
|
||||
<url>true</url>
|
||||
<threadCounts>true</threadCounts>
|
||||
<idleTime>true</idleTime>
|
||||
<connectTime>true</connectTime>
|
||||
</value>
|
||||
</objProp>
|
||||
<stringProp name="filename"></stringProp>
|
||||
</ResultCollector>
|
||||
<hashTree/>
|
||||
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="聚合报告" enabled="true">
|
||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||
<objProp>
|
||||
<name>saveConfig</name>
|
||||
<value class="SampleSaveConfiguration">
|
||||
<time>true</time>
|
||||
<latency>true</latency>
|
||||
<timestamp>true</timestamp>
|
||||
<success>true</success>
|
||||
<label>true</label>
|
||||
<code>true</code>
|
||||
<message>true</message>
|
||||
<threadName>true</threadName>
|
||||
<dataType>true</dataType>
|
||||
<encoding>false</encoding>
|
||||
<assertions>true</assertions>
|
||||
<subresults>true</subresults>
|
||||
<responseData>false</responseData>
|
||||
<samplerData>false</samplerData>
|
||||
<xml>false</xml>
|
||||
<fieldNames>true</fieldNames>
|
||||
<responseHeaders>false</responseHeaders>
|
||||
<requestHeaders>false</requestHeaders>
|
||||
<responseDataOnError>false</responseDataOnError>
|
||||
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
|
||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
||||
<bytes>true</bytes>
|
||||
<sentBytes>true</sentBytes>
|
||||
<url>true</url>
|
||||
<threadCounts>true</threadCounts>
|
||||
<idleTime>true</idleTime>
|
||||
<connectTime>true</connectTime>
|
||||
</value>
|
||||
</objProp>
|
||||
<stringProp name="filename"></stringProp>
|
||||
</ResultCollector>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="线程组" enabled="true">
|
||||
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
|
||||
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="循环控制器" enabled="true">
|
||||
<boolProp name="LoopController.continue_forever">false</boolProp>
|
||||
<intProp name="LoopController.loops">-1</intProp>
|
||||
</elementProp>
|
||||
<stringProp name="ThreadGroup.num_threads">800</stringProp>
|
||||
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
|
||||
<boolProp name="ThreadGroup.scheduler">false</boolProp>
|
||||
<stringProp name="ThreadGroup.duration"></stringProp>
|
||||
<stringProp name="ThreadGroup.delay"></stringProp>
|
||||
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
|
||||
</ThreadGroup>
|
||||
<hashTree>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP请求" enabled="true">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="HTTPSampler.domain">localhost</stringProp>
|
||||
<stringProp name="HTTPSampler.port">80</stringProp>
|
||||
<stringProp name="HTTPSampler.protocol">http</stringProp>
|
||||
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
|
||||
<stringProp name="HTTPSampler.path">/access/code/getCodeInfo?id=1</stringProp>
|
||||
<stringProp name="HTTPSampler.method">POST</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
|
||||
<boolProp name="HTTPSampler.DO_MULTIPART_POST">true</boolProp>
|
||||
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
|
||||
<stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout">300000</stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout"></stringProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree>
|
||||
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="响应断言" enabled="true">
|
||||
<collectionProp name="Asserion.test_strings"/>
|
||||
<stringProp name="Assertion.custom_message"></stringProp>
|
||||
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
|
||||
<boolProp name="Assertion.assume_success">false</boolProp>
|
||||
<intProp name="Assertion.test_type">16</intProp>
|
||||
</ResponseAssertion>
|
||||
<hashTree/>
|
||||
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="察看结果树" enabled="true">
|
||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||
<objProp>
|
||||
<name>saveConfig</name>
|
||||
<value class="SampleSaveConfiguration">
|
||||
<time>true</time>
|
||||
<latency>true</latency>
|
||||
<timestamp>true</timestamp>
|
||||
<success>true</success>
|
||||
<label>true</label>
|
||||
<code>true</code>
|
||||
<message>true</message>
|
||||
<threadName>true</threadName>
|
||||
<dataType>true</dataType>
|
||||
<encoding>false</encoding>
|
||||
<assertions>true</assertions>
|
||||
<subresults>true</subresults>
|
||||
<responseData>false</responseData>
|
||||
<samplerData>false</samplerData>
|
||||
<xml>false</xml>
|
||||
<fieldNames>true</fieldNames>
|
||||
<responseHeaders>false</responseHeaders>
|
||||
<requestHeaders>false</requestHeaders>
|
||||
<responseDataOnError>false</responseDataOnError>
|
||||
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
|
||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
||||
<bytes>true</bytes>
|
||||
<sentBytes>true</sentBytes>
|
||||
<url>true</url>
|
||||
<threadCounts>true</threadCounts>
|
||||
<idleTime>true</idleTime>
|
||||
<connectTime>true</connectTime>
|
||||
</value>
|
||||
</objProp>
|
||||
<stringProp name="filename"></stringProp>
|
||||
</ResultCollector>
|
||||
<hashTree/>
|
||||
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="聚合报告" enabled="true">
|
||||
<boolProp name="ResultCollector.error_logging">false</boolProp>
|
||||
<objProp>
|
||||
<name>saveConfig</name>
|
||||
<value class="SampleSaveConfiguration">
|
||||
<time>true</time>
|
||||
<latency>true</latency>
|
||||
<timestamp>true</timestamp>
|
||||
<success>true</success>
|
||||
<label>true</label>
|
||||
<code>true</code>
|
||||
<message>true</message>
|
||||
<threadName>true</threadName>
|
||||
<dataType>true</dataType>
|
||||
<encoding>false</encoding>
|
||||
<assertions>true</assertions>
|
||||
<subresults>true</subresults>
|
||||
<responseData>false</responseData>
|
||||
<samplerData>false</samplerData>
|
||||
<xml>false</xml>
|
||||
<fieldNames>true</fieldNames>
|
||||
<responseHeaders>false</responseHeaders>
|
||||
<requestHeaders>false</requestHeaders>
|
||||
<responseDataOnError>false</responseDataOnError>
|
||||
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
|
||||
<assertionsResultsToSave>0</assertionsResultsToSave>
|
||||
<bytes>true</bytes>
|
||||
<sentBytes>true</sentBytes>
|
||||
<url>true</url>
|
||||
<threadCounts>true</threadCounts>
|
||||
<idleTime>true</idleTime>
|
||||
<connectTime>true</connectTime>
|
||||
</value>
|
||||
</objProp>
|
||||
<stringProp name="filename"></stringProp>
|
||||
</ResultCollector>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
</jmeterTestPlan>
|
5
MoveBackendJar.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
move .\backend\microservice-gateway\target\microservice-gateway-0.0.1-SNAPSHOT.jar .\ignore
|
||||
move .\backend\microservice-provider-user-8001\target\microservice-provider-user-8001-0.0.1-SNAPSHOT.jar .\ignore
|
||||
move .\backend\microservice-provider-access-8002\target\microservice-provider-access-8002-0.0.1-SNAPSHOT.jar .\ignore
|
||||
move .\backend\microservice-provider-shop-8003\target\microservice-provider-shop-8003-0.0.1-SNAPSHOT.jar .\ignore
|
||||
pause
|
192
README.md
@@ -4,6 +4,8 @@
|
||||
|
||||
代码仓库:[GitHub](https://github.com/coder-xiaomo/epp) [Gitee](https://gitee.com/coder-xiaomo/epp) [自建](https://git.only4.work/coder-xiaomo/epp)
|
||||
|
||||
本项目采用 monorepo 单仓库模式进行维护,项目完整代码均在此仓库中。
|
||||
|
||||
|
||||
|
||||
## 简介
|
||||
@@ -122,6 +124,9 @@ git clone https://github.com/coder-xiaomo/epp
|
||||
# git clone https://git.only4.work/coder-xiaomo/epp
|
||||
# 进入项目根目录
|
||||
cd epp
|
||||
|
||||
# 切换到 production 分支
|
||||
git checkout production
|
||||
```
|
||||
|
||||
|
||||
@@ -212,9 +217,7 @@ cd ../
|
||||
# ############
|
||||
# 进入 frontend 目录
|
||||
cd frontend
|
||||
# 建议使用 cnpm 安装依赖,如果没有安装 cnpm,可以通过 npm i cnpm 进行安装
|
||||
cnpm install
|
||||
# npm install --legacy-peer-deps
|
||||
npm install
|
||||
# 回到项目根目录下
|
||||
cd ../
|
||||
```
|
||||
@@ -304,20 +307,42 @@ npm run package
|
||||
|
||||
|
||||
|
||||
#### 管理员前端项目打包并嵌入后端
|
||||
|
||||
```
|
||||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
然后将 `frontend/dist` 文件夹移动到 `backend/microservice-gateway/src/main/resources/static` 文件夹下,并修改文件夹名称 `dist` 为 `manage`
|
||||
|
||||
|
||||
|
||||
#### 后端配置
|
||||
|
||||
##### 配置业务域名
|
||||
|
||||
TODO
|
||||
|
||||
修改 `backend\microservice-provider-access-8002\src\main\resources\static\access\assets\js\websocket.js` 文件
|
||||
修改 `backend/microservice-provider-access-8002/src/main/resources/static/access/renderer.js` 文件
|
||||
|
||||
```
|
||||
window.wsUrl = 'ws://127.0.0.1:80/access/websocket/1';
|
||||
const baseUrl = "https://【⚠此处修改为你的业务域名】/" // 以 / 结尾,例如:https://epp.only4.work/
|
||||
|
||||
// 或者也可使用相对路径,例如:
|
||||
const baseUrl = "/"
|
||||
```
|
||||
|
||||
|
||||
|
||||
修改 `backend/microservice-provider-access-8002/src/main/resources/static/access/assets/js/websocket.js` 文件
|
||||
|
||||
```
|
||||
window.wsUrl = 'ws://【⚠此处修改为你的业务域名】/access/websocket/';
|
||||
```
|
||||
|
||||
> 注意,如果使用了 SSL 证书,那么 ws:// 要换成 wss://
|
||||
|
||||
|
||||
|
||||
##### 配置小程序 APPID 与 APPSECRET
|
||||
|
||||
修改 `backend/microservice-provider-access-8002/src/main/java/com/cxyxiaomo/epp/access/service/WeChatTokenServiceImpl.java` 文件
|
||||
@@ -330,9 +355,38 @@ window.wsUrl = 'ws://127.0.0.1:80/access/websocket/1';
|
||||
|
||||
|
||||
|
||||
##### 配置门禁端生成的小程序码环境
|
||||
|
||||
修改 `backend/microservice-provider-access-8002/src/main/resources/static/access/renderer.js` 文件
|
||||
|
||||
```
|
||||
const envVersion = "【⚠此处修改为当前小程序环境】" // 正式版为 "release",体验版为 "trial",开发版为 "develop"
|
||||
```
|
||||
|
||||
|
||||
|
||||
##### 配置 Gateway 限流策略(可选)
|
||||
|
||||
> 如果你不懂这是在做什么,请直接跳过这一步
|
||||
|
||||
修改 `backend/microservice-gateway/src/main/resources/application.yml` 文件中 `routes` 中各个微服务的 `filters` 例如:
|
||||
|
||||
```yml
|
||||
filters: # 路由过滤器,使用自定义的限流过滤器工厂
|
||||
- name: RateLimitByIp # 设置每秒允许5个请求,每次请求需要1个令牌
|
||||
args:
|
||||
rate: 5.0
|
||||
permits: 1
|
||||
```
|
||||
|
||||
|
||||
|
||||
##### 打 jar 包
|
||||
|
||||
IDEA 中右侧 Maven 双击 Lifestyle 的 package,打包完成后的 jar 包可在以下位置找到
|
||||
> 打包需要一起打包,每个项目单独打包可能会提示找不到 microservice-common 相关依赖
|
||||
|
||||
IDEA 中右侧 Maven 双击 Lifestyle 的 package(建议先 clean 一下,并点击选中 Maven 的跳过测试,再进行 package),打包完成后的 jar 包可在以下位置找到
|
||||
|
||||
- backend/microservice-gateway/target/microservice-gateway-0.0.1-SNAPSHOT.jar
|
||||
- backend/microservice-provider-user-8001/target/microservice-provider-user-8001-0.0.1-SNAPSHOT.jar
|
||||
- backend/microservice-provider-access-8002/target/microservice-provider-access-8002-0.0.1-SNAPSHOT.jar
|
||||
@@ -340,22 +394,70 @@ IDEA 中右侧 Maven 双击 Lifestyle 的 package,打包完成后的 jar 包
|
||||
|
||||
|
||||
|
||||
#### nginx 代理配置(可选)
|
||||
#### Nacos 配置
|
||||
|
||||
> nacos 后台地址:http://127.0.0.1:8488/nacos/index.html
|
||||
|
||||
1. 进入 ,修改 nacos 登录密码(可选)
|
||||
|
||||
> 左侧 **权限控制** → **用户列表** → **修改**
|
||||
|
||||
2. 修改 nacos 端口为 8488(与后端项目配置文件中保持一致)
|
||||
|
||||
> nacos/conf/application.properties 中的端口号 port 改为 8488
|
||||
|
||||
3. 创建两个命名空间(命名空间ID分别为 `develop` 和 `production`)
|
||||
|
||||
> 左侧 **命名空间** → **新建命名空间**
|
||||
|
||||
| 命名空间名称 | 命名空间ID | 描述 | 配置数 | 操作 |
|
||||
| :--------------- | :--------- | :--------- | :----- | :----------- |
|
||||
| public(保留空间) | | | 0 | 详情删除编辑 |
|
||||
| develop | develop | develop | 0 | 详情删除编辑 |
|
||||
| production | production | production | 0 | 详情删除编辑 |
|
||||
|
||||
##### 新版 Nacos 需要配置密钥
|
||||
|
||||
**若使用的 Nacos 版本小于或等于 Nacos 2.2.0,不需要进行如下配置。**
|
||||
|
||||
🌟若使用的 Nacos 版本大于或等于 Nacos 2.2.0.1,则需要配置自定义密钥:
|
||||
|
||||
```properties
|
||||
# 密钥值可以自己指定,此处的自定义密钥来自 nacos 官网
|
||||
nacos.core.auth.plugin.nacos.token.secret.key=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=
|
||||
```
|
||||
|
||||
参考:https://nacos.io/zh-cn/docs/v2/guide/user/auth.html
|
||||
|
||||
|
||||
|
||||
#### nginx 代理配置
|
||||
|
||||
##### 反向代理(可选)
|
||||
|
||||
配置文件在 `nginx-conf` 目录下(不能直接拿来用,需要根据自己的实际情况来改)
|
||||
|
||||
> 以下配置仅供参考
|
||||
|
||||
```conf
|
||||
server
|
||||
{
|
||||
server_name epp.only4.work; # ⚠ Api 业务域名
|
||||
server_name epp.only4.work; # ⚠ Api 业务域名
|
||||
listen 80;
|
||||
listen 443 ssl http2;
|
||||
|
||||
# 并发限制 限制当前站点最大并发数
|
||||
limit_conn perserver 50;
|
||||
# 单IP限制 限制单个IP访问最大并发数
|
||||
limit_conn perip 10;
|
||||
# 流量限制 限制每个请求的流量上限(单位:KB)
|
||||
limit_rate 8192k;
|
||||
|
||||
# 核心配置
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5203; # ⚠ Gateway 微服务项目本地运行的端口
|
||||
|
||||
# websocket
|
||||
# websocket # ⚠ 门禁端用户扫码后通过 websocket 推送到门禁端完成开门
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
@@ -367,7 +469,9 @@ server
|
||||
}
|
||||
```
|
||||
|
||||
##### SSL证书配置(可选)
|
||||
|
||||
如果域名配置为 https:// 则需要配置 SSL 证书。SSL证书可在 nginx 中进行配置,具体配置方法此处省略。
|
||||
|
||||
|
||||
|
||||
@@ -421,23 +525,64 @@ Nacos 后台地址为:http://localhost:8848/nacos/index.html,默认用户名
|
||||
|
||||
#### [部署&开发] 启动 MySQL
|
||||
|
||||
略
|
||||
```
|
||||
# Ubuntu 下
|
||||
sudo systemctl start mysql
|
||||
```
|
||||
|
||||
#### [部署&开发] 启动各个微服务
|
||||
|
||||
开发:直接在 IDEA 中启动即可
|
||||
开发环境:Windows下
|
||||
|
||||
部署:
|
||||
> 直接在 IDEA 中启动即可
|
||||
>
|
||||
> 若系统内存够用,可以适当调大内存参数,如:-Xmx1024M -Xms256M
|
||||
|
||||
```
|
||||
# 启动 UserProvider
|
||||
```bash
|
||||
# 启动 Gateway
|
||||
# ......
|
||||
java -jar -Xmx512M -Xms128M -Dspring.profiles.active=develop ./microservice-gateway-0.0.1-SNAPSHOT.jar
|
||||
|
||||
# 启动 UserProvider
|
||||
java -jar -Xmx512M -Xms128M -Dspring.profiles.active=develop ./microservice-provider-user-8001-0.0.1-SNAPSHOT.jar
|
||||
|
||||
# 启动 AccessProvider
|
||||
java -jar -Xmx512M -Xms128M -Dspring.profiles.active=develop ./microservice-provider-access-8002-0.0.1-SNAPSHOT.jar
|
||||
|
||||
# 启动 ShopProvider
|
||||
java -jar -Xmx512M -Xms128M -Dspring.profiles.active=develop ./microservice-provider-shop-8003-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
|
||||
|
||||
生产环境:Ubuntu下
|
||||
|
||||
> 假设生产环境已有如下配置:
|
||||
>
|
||||
> - JDK 中 java 可执行文件所在路径为:/www/wwwroot/env/jdk-11/bin/java
|
||||
> - jar 包所在路径为:/www/wwwroot/workspace/
|
||||
>
|
||||
> 若系统内存够用,可以适当调大内存参数,如:-Xmx1024M -Xms256M
|
||||
|
||||
```bash
|
||||
# 启动 Gateway
|
||||
/www/wwwroot/env/jdk-11/bin/java -jar -Xmx512M -Xms128M -Dspring.profiles.active=production -Dserver.port=8000 /www/wwwroot/workspace/microservice-gateway-0.0.1-SNAPSHOT.jar
|
||||
|
||||
# 启动 UserProvider
|
||||
/www/wwwroot/env/jdk-11/bin/java -jar -Xmx512M -Xms128M -Dspring.profiles.active=production /www/wwwroot/workspace/microservice-provider-user-8001-0.0.1-SNAPSHOT.jar
|
||||
|
||||
# 启动 AccessProvider
|
||||
/www/wwwroot/env/jdk-11/bin/java -jar -Xmx512M -Xms128M -Dspring.profiles.active=production /www/wwwroot/workspace/microservice-provider-access-8002-0.0.1-SNAPSHOT.jar
|
||||
|
||||
# 启动 ShopProvider
|
||||
/www/wwwroot/env/jdk-11/bin/java -jar -Xmx512M -Xms128M -Dspring.profiles.active=production /www/wwwroot/workspace/microservice-provider-shop-8003-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
#### [可选] 启动 nginx
|
||||
|
||||
略
|
||||
```bash
|
||||
# Ubuntu 下
|
||||
sudo systemctl start nginx
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -461,6 +606,17 @@ npm run serve
|
||||
|
||||
|
||||
|
||||
## 项目相关地址
|
||||
|
||||
### 门禁端网页
|
||||
|
||||
https://epp.only4.work/access/index.html
|
||||
|
||||
Nacos 注册中心
|
||||
|
||||
http://106.75.217.14:8488/nacos/index.html
|
||||
|
||||
|
||||
## 停止项目
|
||||
|
||||
这个应该不用多说吧
|
||||
|
164
TODOs.md
@@ -1,77 +1,139 @@
|
||||
还要做的部分
|
||||
|
||||
小程序提审要做的:
|
||||
|
||||
小程序扫门禁码之后门禁开门(扫码 websocket 推到门禁端),小程序端显示开门成功
|
||||
|
||||
项目部署到服务器
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
小程序端用户头像
|
||||
|
||||
大门 人员进出 后台管理(进出日志)
|
||||
|
||||
后台订单管理页
|
||||
|
||||
每个人创建账号的时候填充一些测试数据进去
|
||||
|
||||
门禁端左侧提示文字修改
|
||||
### 还要做的部分
|
||||
|
||||
后台管理两个端分开
|
||||
用户管理 社区管理员不能修改管理员账号
|
||||
后台管理分两个管理员身份
|
||||
- 不可以删除用户,只允许禁用用户
|
||||
|
||||
后台管理 按照id进行筛选
|
||||
社区管理员管理人员进出权限
|
||||
|
||||
& 演示时快速创建账号
|
||||
& 小程序提审(提审时隐藏小商店 调试按钮、上帝按钮)
|
||||
& 发给老班看一看
|
||||
发给老班看一看
|
||||
|
||||
数据库填充测试数据(以及创建一些测试账号)并备份,线上演示数据修改删除避免真删
|
||||
|
||||
项目中的TODO
|
||||
|
||||
|
||||
### 可能会被问到的问题
|
||||
|
||||
商品库存问题
|
||||
|
||||
|
||||
### 其他
|
||||
|
||||
小程序端用户头像
|
||||
|
||||
大门 人员进出 后台管理(进出日志)
|
||||
|
||||
每个人创建账号的时候填充一些测试数据进去
|
||||
|
||||
后台管理 按照id进行筛选
|
||||
|
||||
|
||||
|
||||
更多:
|
||||
|
||||
完成项目代码中的 TODO 部分
|
||||
|
||||
Java代码中小程序AppID、密钥处理,小程序代码中小程序AppID处理
|
||||
|
||||
|
||||
# VSCode 全局搜索排除
|
||||
|
||||
node_modules,.git,.idea,target,out,./postman-collection,@deprecated
|
||||
|
||||
|
||||
|
||||
# host 文件地址
|
||||
|
||||
C:\Windows\System32\drivers\etc
|
||||
|
||||
配置项
|
||||
|
||||
127.0.0.1 epp.only4.work
|
||||
|
||||
查看配置
|
||||
|
||||
ping epp.only4.work
|
||||
|
||||
|
||||
|
||||
# 后端项目启动命令
|
||||
|
||||
```bash
|
||||
; /www/wwwroot/env/jdk-11/bin/java -jar -Xmx1024M -Xms256M /www/wwwroot/workspace/microservice-gateway-0.0.1-SNAPSHOT.jar --server.port=7087
|
||||
/www/wwwroot/env/jdk-11/bin/java -jar -Xmx512M -Xms128M -Dspring.profiles.active=production -Dserver.port=8000 /www/wwwroot/workspace/microservice-gateway-0.0.1-SNAPSHOT.jar
|
||||
# 小程序提审期间
|
||||
# /www/wwwroot/env/jdk-11/bin/java -jar -Xmx512M -Xms128M -Dspring.profiles.active=production -Dserver.port=8000 -Depp.miniprogram.config=hide /www/wwwroot/workspace/microservice-gateway-0.0.1-SNAPSHOT.jar
|
||||
/www/wwwroot/env/jdk-11/bin/java -jar -Xmx512M -Xms128M -Dspring.profiles.active=production /www/wwwroot/workspace/microservice-provider-user-8001-0.0.1-SNAPSHOT.jar
|
||||
/www/wwwroot/env/jdk-11/bin/java -jar -Xmx512M -Xms128M -Dspring.profiles.active=production /www/wwwroot/workspace/microservice-provider-access-8002-0.0.1-SNAPSHOT.jar
|
||||
/www/wwwroot/env/jdk-11/bin/java -jar -Xmx512M -Xms128M -Dspring.profiles.active=production /www/wwwroot/workspace/microservice-provider-shop-8003-0.0.1-SNAPSHOT.jar
|
||||
|
||||
java -jar -Xmx512M -Xms128M -Dspring.profiles.active=develop ./microservice-gateway-0.0.1-SNAPSHOT.jar
|
||||
java -jar -Xmx512M -Xms128M -Dspring.profiles.active=develop ./microservice-provider-user-8001-0.0.1-SNAPSHOT.jar
|
||||
java -jar -Xmx512M -Xms128M -Dspring.profiles.active=develop ./microservice-provider-access-8002-0.0.1-SNAPSHOT.jar
|
||||
java -jar -Xmx512M -Xms128M -Dspring.profiles.active=develop ./microservice-provider-shop-8003-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
|
||||
# BT宝塔登录密钥
|
||||
|
||||
==================================================================
|
||||
外网面板地址: http://106.75.217.14:18734/107bb99c
|
||||
内网面板地址: http://10.23.189.141:18734/107bb99c
|
||||
username: qmud57rc
|
||||
password: 80347b8d
|
||||
If you cannot access the panel,
|
||||
release the following panel port [18734] in the security group
|
||||
若无法访问面板,请检查防火墙/安全组是否有放行面板[18734]端口
|
||||
==================================================================
|
||||
|
||||
|
||||
项目开发中遇到的问题
|
||||
小程序双端不一致的问题,参数转义,页面CSS样式是不完全一样的。
|
||||
|
||||
# IP 配置
|
||||
|
||||
develop
|
||||
|
||||
内网穿透:106.75.217.14
|
||||
|
||||
nacos:http://106.75.217.14:8488/nacos/index.html
|
||||
|
||||
数据库:本地
|
||||
|
||||
|
||||
|
||||
production
|
||||
|
||||
nacos:http://106.75.217.14:8488/nacos/index.html
|
||||
|
||||
数据库:服务器上
|
||||
|
||||
|
||||
|
||||
|
||||
# QCloud
|
||||
|
||||
主账号ID 100014397291
|
||||
|
||||
用户名 epp
|
||||
|
||||
登录密码 -
|
||||
|
||||
SecretId AKIDSkmeXTHsTqzwe8ZDiGcomW4OYXcZIerp
|
||||
|
||||
SecretKey 22sVt494mGZeV7sQkqwxnNjneHesqXxA
|
||||
|
||||
# 门禁端网页
|
||||
|
||||
https://epp.only4.work/access/index.html
|
||||
https://epp-prod.only4.work/access/index.html
|
||||
|
||||
|
||||
|
||||
# 启动远程 Nacos
|
||||
|
||||
> /www/wwwserv/epp/nacos/conf/application.properties port 改为 8488
|
||||
|
||||
```
|
||||
cd /www/wwwserv/epp/nacos/bin
|
||||
bash startup.sh -m standalone
|
||||
bash shutdown.sh
|
||||
```
|
||||
|
||||
http://106.75.217.14:8488/nacos/index.html
|
||||
nacos
|
||||
@@ -81,51 +143,41 @@ socan
|
||||
|
||||
# 启动本地 Nacos
|
||||
|
||||
```bash
|
||||
E:
|
||||
cd E:\nacos\bin
|
||||
startup.cmd -m standalone
|
||||
```
|
||||
|
||||
|
||||
|
||||
# 内网穿透
|
||||
|
||||
```bash
|
||||
cd E:\Project\毕业设计\epp\intranet-penetration\bin
|
||||
rathole.exe ../conf/client.toml
|
||||
```
|
||||
|
||||
|
||||
|
||||
# 微信小程序后台设置 小程序最低基础库 2.21.3
|
||||
|
||||
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/userProfile.html
|
||||
|
||||
|
||||
**项目亮点**:
|
||||
|
||||
优点:
|
||||
订单id使用 雪花id(可以分布式部署)
|
||||
|
||||
遇到的问题:
|
||||
|
||||
|
||||
**项目开发中遇到的问题**:
|
||||
|
||||
nacos CPU占满问题,解决方案:提issue,多次测试,找到问题(HTTPDebug),问题解决
|
||||
|
||||
iPhone部分版本不支持webp格式图片
|
||||
|
||||
小程序双端不一致的问题,参数转义,页面CSS样式是不完全一样的。
|
||||
|
||||
-----
|
||||
|
||||
Nacos CPU 跑满问题 我提的 GitHub issue
|
||||
https://github.com/alibaba/nacos/issues/10080
|
||||
|
||||
-----
|
||||
|
||||
Nacos 2.2.0.1 需要配置
|
||||
nacos.core.auth.plugin.nacos.token.secret.key=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=
|
||||
参考:
|
||||
https://nacos.io/zh-cn/docs/v2/guide/user/auth.html
|
||||
|
||||
-----
|
||||
|
||||
体温填报接口:
|
||||
今日是否填报过,上报体温,上报的体温的历史记录
|
||||
|
||||
更多:
|
||||
完成项目代码中的 TODO 部分
|
||||
身份码后端接口考虑与其他系统的集成逻辑
|
||||
|
||||
Java代码中小程序AppID、密钥处理,小程序代码中小程序AppID处理
|
||||
https://github.com/alibaba/nacos/issues/10080
|
19
backend/.idea/runConfigurations/Dev_Gateway__epp_miniprogram_config_hide_.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Dev-Gateway (epp.miniprogram.config=hide)" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
|
||||
<envs>
|
||||
<env name="--spring.profiles.active" value="develop" />
|
||||
</envs>
|
||||
<module name="microservice-gateway" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.cxyxiaomo.epp.gateway.Gateway" />
|
||||
<option name="VM_PARAMETERS" value="-Xms64m -Xmx256m -Depp.miniprogram.config=hide" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="com.cxyxiaomo.epp.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@@ -3,6 +3,7 @@ package com.cxyxiaomo.epp.PageTable.enums;
|
||||
public enum AddType {
|
||||
CAN_NOT_ADD("plainText"),
|
||||
INPUT("input"),
|
||||
INPUT_NUMBER("input-number"),
|
||||
TEXTAREA("textarea"),
|
||||
SELECT("select"),
|
||||
IMAGE("image");
|
||||
|
@@ -3,6 +3,7 @@ package com.cxyxiaomo.epp.PageTable.enums;
|
||||
public enum EditType {
|
||||
CAN_NOT_EDIT("plainText"),
|
||||
INPUT("input"),
|
||||
INPUT_NUMBER("input-number"),
|
||||
TEXTAREA("textarea"),
|
||||
SELECT("select"),
|
||||
IMAGE("image");
|
||||
|
@@ -43,11 +43,12 @@ public class FieldRuleBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FieldRuleBuilder number() {
|
||||
rules.put("type", "number");
|
||||
rules.put("message", fieldDisplayName + "必须为数字");
|
||||
return this;
|
||||
}
|
||||
// public FieldRuleBuilder number() {
|
||||
// // rules.put("type", "number"); // 字符串类型的数字匹配不上
|
||||
// rules.put("regexp", "^\\d+?$");
|
||||
// rules.put("message", fieldDisplayName + "必须为数字");
|
||||
// return this;
|
||||
// }
|
||||
|
||||
public FieldRuleBuilder min(Integer min) {
|
||||
rules.put("min", min);
|
||||
@@ -61,6 +62,7 @@ public class FieldRuleBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
// 字符串类型长度范围
|
||||
public FieldRuleBuilder minMax(Integer min, Integer max) {
|
||||
rules.put("min", min);
|
||||
rules.put("max", max);
|
||||
@@ -68,6 +70,28 @@ public class FieldRuleBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
// 数字类型数值范围
|
||||
// public FieldRuleBuilder range(Integer min, Integer max) {
|
||||
// // rules.put("min", min);
|
||||
// // rules.put("max", max);
|
||||
// rules.put("message", fieldDisplayName + "应大于 " + min + " 且小于 " + max);
|
||||
// return this;
|
||||
// }
|
||||
|
||||
// // 数字类型数字个数
|
||||
// public FieldRuleBuilder price(Integer minLen, Integer maxLen) {
|
||||
// rules.put("regexp", "^\\d{" + minLen + "," + maxLen + "}(\\.\\d{1,2})?$");
|
||||
// rules.put("message", fieldDisplayName + "应大于等于 " + Math.pow(10, minLen - 1) + " ,小于等于 " + (Math.pow(10, maxLen) - 1) + " 且小数位数不超过 2 位");
|
||||
// return this;
|
||||
// }
|
||||
|
||||
// public FieldRuleBuilder regexp(String regexp) {
|
||||
// // rules.put("type", "regexp");
|
||||
// rules.put("pattern", regexp);
|
||||
// rules.put("message", fieldDisplayName + "输入不符合要求,请检查");
|
||||
// return this;
|
||||
// }
|
||||
|
||||
public FieldRuleBuilder length(Integer len) {
|
||||
rules.put("min", len);
|
||||
rules.put("max", len);
|
||||
|
@@ -4,7 +4,7 @@ import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
public enum OrderStatus {
|
||||
PENDING("Pending", "等待确认"),
|
||||
PENDING("Pending", "等待支付"),
|
||||
PROCESSING("Processing", "已支付,等待发货中"),
|
||||
SHIPPED("Shipped", "已发货,等待确认收货"),
|
||||
DELIVERED("Delivered", "已送达"),
|
||||
|
@@ -4,10 +4,12 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true) // 链式写法
|
||||
public class Good {
|
||||
public class Good implements Serializable {
|
||||
Long id;
|
||||
String goodsName;
|
||||
Integer categoryId;
|
||||
|
@@ -4,10 +4,12 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true) // 链式写法
|
||||
public class GoodCategory {
|
||||
public class GoodCategory implements Serializable {
|
||||
Long id;
|
||||
String categoryName;
|
||||
Integer order;
|
||||
|
@@ -4,13 +4,14 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true) // 链式写法
|
||||
public class Order {
|
||||
public class Order implements Serializable {
|
||||
private Long id;
|
||||
private Integer userId;
|
||||
private LocalDateTime orderDate;
|
||||
|
@@ -4,10 +4,12 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true) // 链式写法
|
||||
public class OrderDetail {
|
||||
public class OrderDetail implements Serializable {
|
||||
private Long id;
|
||||
private Long orderId;
|
||||
private Long goodId;
|
||||
|
@@ -0,0 +1,18 @@
|
||||
package com.cxyxiaomo.epp.common.query;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Accessors(chain = true) // 链式写法
|
||||
// 微服务必须要实现Serializable
|
||||
public class OrderQuery implements Serializable {
|
||||
private Long id;
|
||||
private Integer userId;
|
||||
private String orderStatusCode;
|
||||
private String expressId;
|
||||
}
|
@@ -9,7 +9,6 @@ import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -22,10 +21,10 @@ import java.util.stream.Collectors;
|
||||
public class OrderVO implements Serializable {
|
||||
private String id;
|
||||
private Integer userId;
|
||||
private LocalDateTime orderDate;
|
||||
private String orderStatus;
|
||||
private String orderStatusCode;
|
||||
private String orderPrice;
|
||||
private String orderDate;
|
||||
private String payDate;
|
||||
private String cancelDate;
|
||||
private String shipDate;
|
||||
@@ -47,6 +46,9 @@ public class OrderVO implements Serializable {
|
||||
String price = order.getOrderPrice().setScale(2, RoundingMode.FLOOR).toPlainString();
|
||||
orderVO.setOrderPrice(price);
|
||||
|
||||
if (order.getOrderDate() != null) {
|
||||
orderVO.setOrderDate(order.getOrderDate().toString().replace("T", " "));
|
||||
}
|
||||
if (order.getPayDate() != null) {
|
||||
orderVO.setPayDate(order.getPayDate().toString().replace("T", " "));
|
||||
}
|
||||
|
@@ -0,0 +1,79 @@
|
||||
package com.cxyxiaomo.epp.gateway.Factory;
|
||||
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component
|
||||
// 一个自定义的限流过滤器工厂
|
||||
public class RateLimitByIpGatewayFilterFactory extends AbstractGatewayFilterFactory<RateLimitByIpGatewayFilterFactory.Config> {
|
||||
|
||||
// 用于存储IP地址和对应的计数器
|
||||
private static final Map<String, RateLimiter> RATE_LIMITER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public RateLimitByIpGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Config config) {
|
||||
return (exchange, chain) -> {
|
||||
// 获取请求的IP地址
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String ip = request.getRemoteAddress().getAddress().getHostAddress();
|
||||
|
||||
// 根据IP地址获取对应的限流器
|
||||
RateLimiter rateLimiter = RATE_LIMITER_CACHE.get(ip);
|
||||
if (rateLimiter == null) {
|
||||
// 如果不存在,则创建一个新的限流器,并放入缓存中
|
||||
rateLimiter = RateLimiter.create(config.getRate());
|
||||
RATE_LIMITER_CACHE.put(ip, rateLimiter);
|
||||
}
|
||||
|
||||
// 判断请求是否被限流
|
||||
if (rateLimiter.tryAcquire(config.getPermits())) {
|
||||
// 如果没有被限流,则放行
|
||||
return chain.filter(exchange);
|
||||
} else {
|
||||
System.out.println("限流!ip: " + ip);
|
||||
// 如果被限流,则返回429状态码(Too Many Requests)
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
|
||||
return response.setComplete();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 配置类,用于接收配置参数
|
||||
public static class Config {
|
||||
|
||||
// 每秒允许的请求数
|
||||
private double rate;
|
||||
|
||||
// 每次请求需要的令牌数
|
||||
private int permits;
|
||||
|
||||
public double getRate() {
|
||||
return rate;
|
||||
}
|
||||
|
||||
public void setRate(double rate) {
|
||||
this.rate = rate;
|
||||
}
|
||||
|
||||
public int getPermits() {
|
||||
return permits;
|
||||
}
|
||||
|
||||
public void setPermits(int permits) {
|
||||
this.permits = permits;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,10 +1,13 @@
|
||||
package com.cxyxiaomo.epp.gateway.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
public class Controller {
|
||||
@@ -14,17 +17,29 @@ public class Controller {
|
||||
return "[ERROR] 500 Internal Server Error";
|
||||
}
|
||||
|
||||
@Value("${epp.miniprogram.config}")
|
||||
private String miniprogramConfig;
|
||||
|
||||
/**
|
||||
* 为了通过微信的小程序审核所特别处理的
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping("/getConfig")
|
||||
public HashMap<String, Object> WxMiniProgramAuditSpecialHandle() {
|
||||
boolean showCode = false;
|
||||
boolean showShop = false;
|
||||
public HashMap<String, Object> WxMiniProgramAuditSpecialHandle(@RequestParam(value = "v", required = false) String version) {
|
||||
boolean showCode = true;
|
||||
boolean showShop = true;
|
||||
boolean showReport = true;
|
||||
|
||||
// 第一版提审,已通过,在线上 version == null
|
||||
if (Objects.equals(version, "2")) {
|
||||
// 第一版提审,按照配置文件中的配置来
|
||||
if (Objects.equals(miniprogramConfig, "hide")) {
|
||||
showCode = false;
|
||||
showShop = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 底部 tabbar
|
||||
LinkedList<String> tabbarItem = new LinkedList<>();
|
||||
tabbarItem.push("pages/index/index");
|
||||
|
@@ -40,20 +40,38 @@ spring:
|
||||
predicates:
|
||||
- Path=/user/**
|
||||
- Method=GET,POST
|
||||
filters: # 路由过滤器,使用自定义的限流过滤器工厂
|
||||
- name: RateLimitByIp # 设置每秒允许5个请求,每次请求需要1个令牌
|
||||
args:
|
||||
rate: 10.0
|
||||
permits: 1
|
||||
|
||||
- id: access
|
||||
uri: lb://microservice-provider-access
|
||||
predicates:
|
||||
- Path=/access/**
|
||||
- Method=GET,POST
|
||||
filters: # 路由过滤器,使用自定义的限流过滤器工厂
|
||||
- name: RateLimitByIp # 设置每秒允许5个请求,每次请求需要1个令牌
|
||||
args:
|
||||
rate: 10.0
|
||||
permits: 1
|
||||
|
||||
- id: access-websocket
|
||||
uri: lb:ws://microservice-provider-access
|
||||
predicates:
|
||||
- Path=/access/websocket/**
|
||||
|
||||
- id: shop
|
||||
uri: lb://microservice-provider-shop
|
||||
predicates:
|
||||
- Path=/shop/**
|
||||
- Method=GET,POST
|
||||
filters: # 路由过滤器,使用自定义的限流过滤器工厂
|
||||
- name: RateLimitByIp # 设置每秒允许5个请求,每次请求需要1个令牌
|
||||
args:
|
||||
rate: 10.0
|
||||
permits: 1
|
||||
|
||||
- id: test1
|
||||
uri: lb://microservice-provider-test
|
||||
@@ -79,3 +97,9 @@ spring:
|
||||
args:
|
||||
status: 302
|
||||
url: https://www.baidu.com/?wd=
|
||||
|
||||
epp:
|
||||
miniprogram:
|
||||
# 为了通过微信的小程序审核所特别处理的
|
||||
# show / hide
|
||||
config: show
|
||||
|
@@ -1 +1,3 @@
|
||||
编辑后右键 Compile And Reload File 修改即可生效,不用频繁重启项目
|
||||
|
||||
manage 文件夹下为 frontend 项目打包产物
|
After Width: | Height: | Size: 86 KiB |
@@ -1,6 +1,7 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html,
|
@@ -0,0 +1,20 @@
|
||||
.scan-result-container {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.scan-result-image {
|
||||
width: min(55vw, 55vh);
|
||||
transition: 0.3s;
|
||||
transform: scale(0.001);
|
||||
}
|
||||
|
||||
.scan-result-image-show {
|
||||
transform: scale(1);
|
||||
}
|
@@ -62,7 +62,7 @@
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 弹窗列表样式 */
|
||||
@@ -82,6 +82,7 @@
|
||||
box-sizing: border-box;
|
||||
place-items: center;
|
||||
background-color: #f6f6f6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gate-list-item > p {
|
@@ -0,0 +1,39 @@
|
||||
const URLScanSuccessImage = "./assets/svg/scan-success.svg"
|
||||
const URLEnterGateImage = "./assets/svg/enter-gate.svg"
|
||||
const DOMFullScreenMask = document.getElementById("full-screen-mask")
|
||||
const DOMScanResultContainer = document.getElementById("scan-result-container")
|
||||
const DOMQRCodeScanSuccessImage = document.getElementById("qrcode-scan-success")
|
||||
|
||||
var isShowing = false
|
||||
|
||||
function showResult(imageType) {
|
||||
console.log("showResult")
|
||||
if (isShowing) {
|
||||
// console.log("showResult() skipped coz showing.")
|
||||
// 等待 0.1s 重试
|
||||
setTimeout(() => showResult(imageType), 100)
|
||||
return
|
||||
}
|
||||
isShowing = true
|
||||
DOMQRCodeScanSuccessImage.src = imageType === "onscan" ? URLScanSuccessImage : URLEnterGateImage
|
||||
DOMFullScreenMask.classList.remove("hidden")
|
||||
DOMScanResultContainer.classList.remove("hidden")
|
||||
//
|
||||
setTimeout(() => {
|
||||
DOMQRCodeScanSuccessImage.classList.add("scan-result-image-show")
|
||||
}, 100)
|
||||
|
||||
setTimeout(function hideResult() {
|
||||
console.log("hideResult")
|
||||
DOMQRCodeScanSuccessImage.classList.remove("scan-result-image-show")
|
||||
setTimeout(() => {
|
||||
DOMQRCodeScanSuccessImage.src = ""
|
||||
DOMFullScreenMask.classList.add("hidden")
|
||||
DOMScanResultContainer.classList.add("hidden")
|
||||
isShowing = false
|
||||
}, 300)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
// setTimeout(showResult, 100)
|
||||
window.showResult = showResult
|
@@ -1,6 +1,12 @@
|
||||
window.wsUrl = 'wss://epp-prod.only4.work/access/websocket/1';
|
||||
// window.wsUrl = 'ws://127.0.0.1:80/access/websocket/1';
|
||||
// window.wsUrl = 'ws://127.0.0.1:8002/access/websocket/1';
|
||||
/**
|
||||
* FIXME 环境配置
|
||||
*
|
||||
* window.wsUrl
|
||||
* - 线上环境:'wss://epp.only4.work/access/websocket/'
|
||||
* - 开发环境:'ws://127.0.0.1:80/access/websocket/'
|
||||
* 'ws://127.0.0.1:8002/access/websocket/'
|
||||
*/
|
||||
window.wsUrl = 'ws://127.0.0.1:80/access/websocket/';
|
||||
|
||||
window.ws = null; // WebSocket 实例对象
|
||||
|
||||
@@ -10,9 +16,20 @@ window.ws = null; // WebSocket 实例对象
|
||||
return
|
||||
}
|
||||
|
||||
function getWsUrl() {
|
||||
function getUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
return `${window.wsUrl}${getUUID()}`
|
||||
}
|
||||
|
||||
function createConn() {
|
||||
// 创建webscoket 对象
|
||||
const ws = new WebSocket(window.wsUrl)
|
||||
const ws = new WebSocket(getWsUrl() /* window.wsUrl */)
|
||||
// 执行上面的语句之后,客户端就会与服务器进行连接
|
||||
|
||||
// readyState返回当前实例对象的当前状态
|
||||
@@ -34,9 +51,23 @@ window.ws = null; // WebSocket 实例对象
|
||||
ws.onmessage = ({data}) => {
|
||||
console.log('onmessage readyState', ws.readyState)
|
||||
// 注意此时的data是json格式的 需要转化下
|
||||
console.log('onmessage 有新消息啦=======>', JSON.parse(data))
|
||||
let result = JSON.parse(data)
|
||||
console.log('onmessage 有新消息啦=======>', result)
|
||||
// 实例对象的send方法给服务器发送消息
|
||||
ws.send('客户端发送的消息')
|
||||
|
||||
switch (result.action) {
|
||||
case 'onscan':
|
||||
case 'onopen':
|
||||
if (window.currentGate && result.gateId === window.currentGate.id) {
|
||||
console.log(result.action)
|
||||
window.showResult(result.action)
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log("switch=>default", result.action)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 实例对象的onclose属性,用于连接关闭后的回调 函数
|
||||
@@ -58,5 +89,6 @@ window.ws = null; // WebSocket 实例对象
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
window.ws = createConn()
|
||||
})()
|
Before Width: | Height: | Size: 886 B After Width: | Height: | Size: 886 B |
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<circle cx="50" cy="50" r="45" stroke="white" stroke-width="7" fill="white"/>
|
||||
|
||||
<!-- 外围圆圈 -->
|
||||
<circle cx="50" cy="50" r="45" stroke="#006600" stroke-width="8" fill="none">
|
||||
<animate id="circle" attributeName="stroke-dasharray" from="0,282.743" to="282.743,0" dur="1s" begin="0s" fill="freeze"/>
|
||||
<animateTransform attributeName="transform" type="rotate" from="-90 50 50" to="270 50 50" dur="1s" begin="0s" fill="freeze"/>
|
||||
</circle>
|
||||
|
||||
<!-- 对勾动画 -->
|
||||
<path d="M20 50 L40 70 L80 30" stroke="#006600" stroke-width="8" fill="none" visibility="hidden">
|
||||
<set attributeName="visibility" from="hidden" to="visible" begin="circle.end+0.1s"/>
|
||||
<!-- 对勾开始动画 -->
|
||||
<!-- <animate attributeName="stroke-dasharray" from="0,120" to="120,0" dur="1s" begin="circle.end+0.1s" repeatCount="indefinite"/> -->
|
||||
<animate id="check" attributeName="stroke-dasharray" from="0,120" to="120,0" dur="1s" begin="circle.end+0.1s" repeatCount="1"/>
|
||||
<!-- 对勾隐藏动画 -->
|
||||
<set attributeName="visibility" from="visible" to="hidden" begin="check.end+0.1s"/>
|
||||
</path>
|
||||
|
||||
<!-- 扫码成功 -->
|
||||
<!-- <text x="50" y="55" font-size="20" text-anchor="middle" fill="#006600" visibility="hidden">
|
||||
扫码成功
|
||||
<set attributeName="visibility" from="hidden" to="visible" begin="check.end+0.1s"/>
|
||||
</text> -->
|
||||
<text x="50" y="50" font-weight="bold" text-anchor="middle" fill="#006600" visibility="hidden">
|
||||
<tspan x="50" dx="3" dy="-3" font-size="26">大门</tspan>
|
||||
<tspan x="50" dy="23" font-size="20">已开启</tspan>
|
||||
<set attributeName="visibility" from="hidden" to="visible" begin="check.end+0.1s"/>
|
||||
</text>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<circle cx="50" cy="50" r="45" stroke="white" stroke-width="7" fill="white"/>
|
||||
|
||||
<!-- 外围圆圈 -->
|
||||
<circle cx="50" cy="50" r="45" stroke="#006600" stroke-width="8" fill="none">
|
||||
<animate id="circle" attributeName="stroke-dasharray" from="0,282.743" to="282.743,0" dur="1s" begin="0s" fill="freeze"/>
|
||||
<animateTransform attributeName="transform" type="rotate" from="-90 50 50" to="270 50 50" dur="1s" begin="0s" fill="freeze"/>
|
||||
</circle>
|
||||
|
||||
<!-- 对勾动画 -->
|
||||
<path d="M20 50 L40 70 L80 30" stroke="#006600" stroke-width="8" fill="none" visibility="hidden">
|
||||
<set attributeName="visibility" from="hidden" to="visible" begin="circle.end+0.1s"/>
|
||||
<!-- 对勾开始动画 -->
|
||||
<!-- <animate attributeName="stroke-dasharray" from="0,120" to="120,0" dur="1s" begin="circle.end+0.1s" repeatCount="indefinite"/> -->
|
||||
<animate id="check" attributeName="stroke-dasharray" from="0,120" to="120,0" dur="1s" begin="circle.end+0.1s" repeatCount="1"/>
|
||||
<!-- 对勾隐藏动画 -->
|
||||
<set attributeName="visibility" from="visible" to="hidden" begin="check.end+0.1s"/>
|
||||
</path>
|
||||
|
||||
<!-- 扫码成功 -->
|
||||
<!-- <text x="50" y="55" font-size="20" text-anchor="middle" fill="#006600" visibility="hidden">
|
||||
扫码成功
|
||||
<set attributeName="visibility" from="hidden" to="visible" begin="check.end+0.1s"/>
|
||||
</text> -->
|
||||
<text x="50" y="50" font-size="27" font-weight="bold" text-anchor="middle" fill="#006600" visibility="hidden">
|
||||
<tspan x="50" dx="4" dy="-5">扫码</tspan>
|
||||
<tspan x="50" dy="28">成功</tspan>
|
||||
<set attributeName="visibility" from="hidden" to="visible" begin="check.end+0.1s"/>
|
||||
</text>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -10,6 +10,7 @@
|
||||
<title>社区疫情防控系统 - 门禁端</title>
|
||||
<link rel="stylesheet" href="./assets/css/index.css"/>
|
||||
<link rel="stylesheet" href="./assets/css/setting-panel.css"/>
|
||||
<link rel="stylesheet" href="./assets/css/scan-result.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -23,6 +24,9 @@
|
||||
</div>
|
||||
<div class="left-container">
|
||||
<h1>进出社区请扫码</h1>
|
||||
<p>1. 打开 微信 > 扫一扫,扫描右侧小程序码</p>
|
||||
<p>2. 点击确认进入,门即开启</p>
|
||||
<!--<h1>进出社区请扫码</h1>
|
||||
<h3>社区居民</h3>
|
||||
<p>1. 打开 微信 > 扫一扫,扫描右侧小程序码</p>
|
||||
<p>2. 点击确认进入,门即开启</p>
|
||||
@@ -32,15 +36,16 @@
|
||||
<p>2. 填写进入申请表</p>
|
||||
|
||||
<h3>长期租客</h3>
|
||||
<p>1. 请联系管理员为你添加进出权限</p>
|
||||
<p>1. 请联系管理员为你添加进出权限</p>-->
|
||||
</div>
|
||||
<div class="right-container">
|
||||
<h1 id="no-qrcode">请选择大门</h1>
|
||||
<img id="qrcode" src="" style="display: none">
|
||||
<img id="qrcode" src="" style="display: none;"><br>
|
||||
<p id="refreshTimeCountDown"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置页面 -->
|
||||
<div id="full-screen-mask" class="full-screen-mask hidden"></div>
|
||||
<div id="setting-container" class="setting-container hidden">
|
||||
<div class="setting-panel">
|
||||
@@ -63,8 +68,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 扫码成功 -->
|
||||
<div id="scan-result-container" class="scan-result-container hidden">
|
||||
<img id="qrcode-scan-success" class="scan-result-image" src="./assets/svg/scan-success.svg">
|
||||
</div>
|
||||
|
||||
<script src="./assets/js/setting-panel.js" type="module"></script>
|
||||
<script src="./renderer.js" type="module"></script>
|
||||
<script src="assets/js/websocket-message-panel.js"></script>
|
||||
<script src="./assets/js/websocket.js"></script>
|
||||
</body>
|
||||
|
@@ -1,7 +1,18 @@
|
||||
/**
|
||||
* FIXME 环境配置
|
||||
*
|
||||
* baseUrl
|
||||
* - 线上环境:"https://epp.only4.work/"
|
||||
* - 开发环境:"/"
|
||||
* envVersion
|
||||
* - 线上环境:"release"
|
||||
* - 开发环境:"develop"
|
||||
*/
|
||||
// 定义常量
|
||||
const url = "https://epp-prod.only4.work/access/wechat/getUnlimitedQRCode"
|
||||
const baseUrl = "/"
|
||||
const url = baseUrl + "access/wechat/getUnlimitedQRCode"
|
||||
const page = "pages/index/index" // "pages/scan/entrance"
|
||||
const envVersion = "develop" // 正式版为 "release",体验版为 "trial",开发版为 "develop"
|
||||
const envVersion = "release" // 正式版为 "release",体验版为 "trial",开发版为 "develop"
|
||||
const autoColor = true
|
||||
const isHyaline = false
|
||||
const width = 500
|
||||
@@ -102,7 +113,7 @@ window.changePanelSelectGate = changePanelSelectGate
|
||||
|
||||
// 发送请求,获取大门列表
|
||||
async function getGateList() {
|
||||
const response = await fetch('https://epp-prod.only4.work/access/gate/guard-client/getGateList');
|
||||
const response = await fetch(baseUrl + 'access/gate/guard-client/getGateList');
|
||||
const data = await response.json();
|
||||
return data.data;
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>基于微服务的社区疫情防控系统</title>
|
||||
<style>
|
||||
body {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.grid-item ul {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div>
|
||||
<h1 style="text-align: center;">基于微服务的社区疫情防控系统</h1>
|
||||
<p style="text-align: center;">epp.only4.work</p>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr);">
|
||||
<div class="grid-item">
|
||||
<h3>
|
||||
门禁端 跨端桌面应用
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
方式1:访问
|
||||
<a target="_blank" href="./guard/index.html">https://epp.only4.work/guard/index.html</a>
|
||||
</li>
|
||||
<li>
|
||||
方式2:运行跨端桌面应用
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h3>
|
||||
居民端 小程序
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
方式1:扫描下方小程序码
|
||||
<p style="text-align: center">
|
||||
<img src="./assets/image/miniprogram.jpg" style="width: 180px;">
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
方式2:微信搜索小程序 <b>devprogram</b>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h3>
|
||||
社区管理员端 管理后台
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
访问
|
||||
<a target="_blank" href="./manage/index.html">https://epp.only4.work/manage/index.html</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h3>
|
||||
系统管理员端 管理后台
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
访问
|
||||
<a target="_blank" href="./manage/index.html">https://epp.only4.work/manage/index.html</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>开发</h2>
|
||||
<p>小程序后台:<a target="_blank">https://mp.weixin.qq.com/</a></p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
2
backend/microservice-gateway/src/main/resources/static/manage/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
@@ -0,0 +1 @@
|
||||
.error-page[data-v-d4771405]{display:flex;justify-content:center;align-items:center;flex-direction:column;width:100%;height:100%;background:#f3f3f3;box-sizing:border-box}.error-code[data-v-d4771405]{line-height:1;font-size:250px;font-weight:bolder;color:#f02d2d}.error-code span[data-v-d4771405]{color:#00a854}.error-desc[data-v-d4771405]{font-size:30px;color:#777}.error-handle[data-v-d4771405]{margin-top:30px;padding-bottom:200px}.error-btn[data-v-d4771405]{margin-left:100px}
|
@@ -0,0 +1 @@
|
||||
.el-card{--el-card-border-color:var(--el-border-color-light);--el-card-border-radius:4px;--el-card-padding:20px;--el-card-bg-color:var(--el-fill-color-blank)}.el-card{border-radius:var(--el-card-border-radius);border:1px solid var(--el-card-border-color);background-color:var(--el-card-bg-color);overflow:hidden;color:var(--el-text-color-primary);transition:var(--el-transition-duration)}.el-card.is-always-shadow{box-shadow:var(--el-box-shadow-light)}.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover{box-shadow:var(--el-box-shadow-light)}.el-card__header{padding:calc(var(--el-card-padding) - 2px) var(--el-card-padding);border-bottom:1px solid var(--el-card-border-color);box-sizing:border-box}.el-card__body{padding:var(--el-card-padding)}
|
@@ -0,0 +1 @@
|
||||
:root{--el-popup-modal-bg-color:var(--el-color-black);--el-popup-modal-opacity:.5}.v-modal-enter{-webkit-animation:v-modal-in var(--el-transition-duration-fast) ease;animation:v-modal-in var(--el-transition-duration-fast) ease}.v-modal-leave{-webkit-animation:v-modal-out var(--el-transition-duration-fast) ease forwards;animation:v-modal-out var(--el-transition-duration-fast) ease forwards}@-webkit-keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{to{opacity:0}}@keyframes v-modal-out{to{opacity:0}}.v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:var(--el-popup-modal-opacity);background:var(--el-popup-modal-bg-color)}.el-popup-parent--hidden{overflow:hidden}.el-dialog{--el-dialog-width:50%;--el-dialog-margin-top:15vh;--el-dialog-bg-color:var(--el-bg-color);--el-dialog-box-shadow:var(--el-box-shadow);--el-dialog-title-font-size:var(--el-font-size-large);--el-dialog-content-font-size:14px;--el-dialog-font-line-height:var(--el-font-line-height-primary);--el-dialog-padding-primary:20px;--el-dialog-border-radius:var(--el-border-radius-small);position:relative;margin:var(--el-dialog-margin-top,15vh) auto 50px;background:var(--el-dialog-bg-color);border-radius:var(--el-dialog-border-radius);box-shadow:var(--el-dialog-box-shadow);box-sizing:border-box;width:var(--el-dialog-width,50%)}.el-dialog:focus{outline:0!important}.el-dialog.is-align-center{margin:auto}.el-dialog.is-fullscreen{--el-dialog-width:100%;--el-dialog-margin-top:0;margin-bottom:0;height:100%;overflow:auto}.el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.el-dialog.is-draggable .el-dialog__header{cursor:move;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.el-dialog__header{padding:var(--el-dialog-padding-primary);padding-bottom:10px;margin-right:16px}.el-dialog__headerbtn{position:absolute;top:6px;right:0;padding:0;width:54px;height:54px;background:0 0;border:none;outline:0;cursor:pointer;font-size:var(--el-message-close-size,16px)}.el-dialog__headerbtn .el-dialog__close{color:var(--el-color-info);font-size:inherit}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:var(--el-color-primary)}.el-dialog__title{line-height:var(--el-dialog-font-line-height);font-size:var(--el-dialog-title-font-size);color:var(--el-text-color-primary)}.el-dialog__body{padding:calc(var(--el-dialog-padding-primary) + 10px) var(--el-dialog-padding-primary);color:var(--el-text-color-regular);font-size:var(--el-dialog-content-font-size)}.el-dialog__footer{padding:var(--el-dialog-padding-primary);padding-top:10px;text-align:right;box-sizing:border-box}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial;padding:25px calc(var(--el-dialog-padding-primary) + 5px) 30px}.el-dialog--center .el-dialog__footer{text-align:inherit}.el-overlay-dialog{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto}.dialog-fade-enter-active{-webkit-animation:modal-fade-in var(--el-transition-duration);animation:modal-fade-in var(--el-transition-duration)}.dialog-fade-enter-active .el-overlay-dialog{-webkit-animation:dialog-fade-in var(--el-transition-duration);animation:dialog-fade-in var(--el-transition-duration)}.dialog-fade-leave-active{-webkit-animation:modal-fade-out var(--el-transition-duration);animation:modal-fade-out var(--el-transition-duration)}.dialog-fade-leave-active .el-overlay-dialog{-webkit-animation:dialog-fade-out var(--el-transition-duration);animation:dialog-fade-out var(--el-transition-duration)}@-webkit-keyframes dialog-fade-in{0%{transform:translate3d(0,-20px,0);opacity:0}to{transform:translateZ(0);opacity:1}}@keyframes dialog-fade-in{0%{transform:translate3d(0,-20px,0);opacity:0}to{transform:translateZ(0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{transform:translateZ(0);opacity:1}to{transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{transform:translateZ(0);opacity:1}to{transform:translate3d(0,-20px,0);opacity:0}}@-webkit-keyframes modal-fade-in{0%{opacity:0}to{opacity:1}}@keyframes modal-fade-in{0%{opacity:0}to{opacity:1}}@-webkit-keyframes modal-fade-out{0%{opacity:1}to{opacity:0}}@keyframes modal-fade-out{0%{opacity:1}to{opacity:0}}.el-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:2000;height:100%;background-color:var(--el-overlay-color-lighter);overflow:auto}.el-overlay .el-overlay-root{height:0}
|
After Width: | Height: | Size: 6.0 KiB |
@@ -0,0 +1 @@
|
||||
.login-wrap[data-v-ae431c4c]{width:100%;height:100%}.login-container[data-v-ae431c4c]{width:100%;height:100%;display:grid;place-items:center}.ms-title[data-v-ae431c4c]{width:100%;padding:18px 24px;box-sizing:border-box;text-align:center;font-size:20px;color:#fff;border-bottom:1px solid #ddd}.ms-login[data-v-ae431c4c]{width:min(380px,95vw);padding:5px 10px;border-radius:5px;background:rgba(255,255,255,.3);overflow:hidden}.ms-content[data-v-ae431c4c]{padding:30px}.login-btn[data-v-ae431c4c]{text-align:center}.login-btn button[data-v-ae431c4c]{width:100%;height:36px;margin-bottom:10px}.company-info[data-v-ae431c4c]{color:#7589b6;text-align:center;position:absolute;left:0;right:0;bottom:10px;font-size:13px;letter-spacing:1px}
|
@@ -0,0 +1 @@
|
||||
.line-height{line-height:2.7em}.row-index{width:110px;display:inline-block}
|
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Cropper.js v1.5.13
|
||||
* https://fengyuanchen.github.io/cropperjs
|
||||
*
|
||||
* Copyright 2015-present Chen Fengyuan
|
||||
* Released under the MIT license
|
||||
*
|
||||
* Date: 2022-11-20T05:30:43.444Z
|
||||
*/.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-wrap-box,.cropper-canvas,.cropper-drag-box,.cropper-crop-box,.cropper-modal{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-wrap-box,.cropper-canvas{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:#3399ffbf;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:calc(100% / 3);left:0;top:calc(100% / 3);width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:calc(100% / 3);top:0;width:calc(100% / 3)}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:before,.cropper-center:after{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width: 768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width: 992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width: 1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}.info[data-v-e7288b09]{text-align:center;padding:35px 0}.info-image[data-v-e7288b09]{position:relative;margin:auto;width:100px;height:100px;background:#f8f8f8;border:1px solid #eee;border-radius:50px;overflow:hidden}.info-edit[data-v-e7288b09]{display:flex;justify-content:center;align-items:center;position:absolute;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,.5);opacity:0;transition:opacity .3s ease}.info-edit i[data-v-e7288b09]{color:#eee;font-size:25px}.info-image:hover .info-edit[data-v-e7288b09]{opacity:1}.info-name[data-v-e7288b09]{margin:15px 0 10px;font-size:24px;font-weight:500;color:#262626}.crop-demo-btn[data-v-e7288b09]{position:relative}.crop-input[data-v-e7288b09]{position:absolute;width:100px;height:40px;left:0;top:0;opacity:0;cursor:pointer}
|
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title></title>
|
||||
<link rel="stylesheet" href="https://at.alicdn.com/t/font_830376_qzecyukz0s.css">
|
||||
<script type="module" crossorigin src="./assets/index.f92c5f31.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.ca6f3a66.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
@@ -61,6 +61,13 @@ public class AccessLogController {
|
||||
return Res.error("参数错误");
|
||||
}
|
||||
|
||||
// 推送到门禁端
|
||||
JSONObject websocketData = new JSONObject();
|
||||
websocketData.put("gateId", gateId);
|
||||
websocketData.put("action", "onopen");
|
||||
String websocketJSON = websocketData.toString();
|
||||
WebSocketServer.sendAllMessage(websocketJSON);
|
||||
|
||||
AccessLog accessLog = new AccessLog();
|
||||
accessLog.setId(null);
|
||||
accessLog.setType(type);
|
||||
|
@@ -58,6 +58,14 @@ public class GateController {
|
||||
Long gateId = Long.valueOf(id);
|
||||
Gate gate = gateService.getGateById(gateId);
|
||||
GateVO gateVO = GateVO.convertFrom(gate);
|
||||
|
||||
// 推送到门禁端
|
||||
JSONObject websocketData = new JSONObject();
|
||||
websocketData.put("gateId", id);
|
||||
websocketData.put("action", "onscan");
|
||||
String websocketJSON = websocketData.toString();
|
||||
WebSocketServer.sendAllMessage(websocketJSON);
|
||||
|
||||
return Res.success(gateVO);
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.cxyxiaomo.epp.access.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.cxyxiaomo.epp.access.pojo.UnlimitedQRCodeParam;
|
||||
import com.cxyxiaomo.epp.access.service.WeChatTokenServiceImpl;
|
||||
import com.cxyxiaomo.epp.common.response.Res;
|
||||
@@ -47,7 +48,28 @@ public class WeChatTokenController {
|
||||
unlimitedQRCodeParam.setIsHyaline(isHyaline);
|
||||
okhttp3.ResponseBody responseBody = weChatTokenService.getUnlimitedQRCodeFromApi(accessToken, unlimitedQRCodeParam);
|
||||
|
||||
return responseBody.bytes();
|
||||
try {
|
||||
// {"errcode":40001,"errmsg":"invalid credential, access_token is invalid or not latest, could get access_token by getStableAccessToken, more details at https://mmbizurl.cn/s/JtxxFh33r rid: 6446bac4-4a6b0410-118a16e7"}
|
||||
if (Objects.requireNonNull(responseBody.contentType()).subtype().equals("json")) {
|
||||
// 返回了 JSON 说明失败了
|
||||
String jsonString = responseBody.string();
|
||||
JSONObject jsonObject = JSONObject.parseObject(jsonString);
|
||||
String errcode = jsonObject.getString("errcode");
|
||||
if (errcode.equals("40001")) {
|
||||
// 重新获取 Access Token
|
||||
accessToken = weChatTokenService.getAccessToken(true);
|
||||
responseBody = weChatTokenService.getUnlimitedQRCodeFromApi(accessToken, unlimitedQRCodeParam);
|
||||
} else if (errcode.equals("40013")) {
|
||||
System.out.println("40013 invalid appid 不合法的 AppID ,请开发者检查 AppID 的正确性,避免异常字符,注意大小写\n" +
|
||||
"docs: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// 返回了图片,直接返回
|
||||
return responseBody.bytes();
|
||||
} catch (NullPointerException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping(value = "/rpc/getOpenIdFromApi")
|
||||
|
@@ -18,7 +18,7 @@ public class WebSocketServer {
|
||||
private Session session;
|
||||
|
||||
// session集合,存放对应的session
|
||||
private static ConcurrentHashMap<Integer, Session> sessionPool = new ConcurrentHashMap<>();
|
||||
private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();
|
||||
|
||||
// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。
|
||||
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
|
||||
@@ -30,7 +30,7 @@ public class WebSocketServer {
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam(value = "userId") Integer userId) {
|
||||
public void onOpen(Session session, @PathParam(value = "userId") String userId) {
|
||||
log.info("WebSocket建立连接中,连接用户ID:{}", userId);
|
||||
try {
|
||||
Session historySession = sessionPool.get(userId);
|
||||
@@ -84,7 +84,7 @@ public class WebSocketServer {
|
||||
* @param userId 用户ID
|
||||
* @param message 发送的消息
|
||||
*/
|
||||
public static void sendMessageByUser(Integer userId, String message) {
|
||||
public static void sendMessageByUser(String userId, String message) {
|
||||
log.info("用户ID:" + userId + ",推送内容:" + message);
|
||||
Session session = sessionPool.get(userId);
|
||||
try {
|
||||
|
@@ -35,11 +35,17 @@ public class WeChatTokenServiceImpl implements WeChatTokenService {
|
||||
|
||||
@Override
|
||||
public String getAccessToken() {
|
||||
// 首先从数据库中查询是否存在 access_token
|
||||
// 如果存在且没有过期,那么就直接返回(距离失效时间小于 3 分钟就当作过期)
|
||||
Setting atSetting = accessDao.getValueByKey(SETTING_KEY);
|
||||
if (atSetting != null && LocalDateTime.now().plusMinutes(3L).compareTo(atSetting.getTime()) < 0) {
|
||||
return atSetting.getValue();
|
||||
return getAccessToken(false);
|
||||
}
|
||||
|
||||
public String getAccessToken(Boolean forceUpdate) {
|
||||
if (!forceUpdate) {
|
||||
// 首先从数据库中查询是否存在 access_token
|
||||
// 如果存在且没有过期,那么就直接返回(距离失效时间小于 3 分钟就当作过期)
|
||||
Setting atSetting = accessDao.getValueByKey(SETTING_KEY);
|
||||
if (atSetting != null && LocalDateTime.now().plusMinutes(3L).compareTo(atSetting.getTime()) < 0) {
|
||||
return atSetting.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
// 否则则去请求一个新的 access_token
|
||||
|
@@ -56,7 +56,7 @@ public class GoodController {
|
||||
searchText = null;
|
||||
}
|
||||
}
|
||||
List<GoodVO> list = goodService.list(cateId, searchText);
|
||||
List<GoodVO> list = goodService.listOnSale(cateId, searchText);
|
||||
return Res.success(list);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,8 @@ public class GoodController {
|
||||
|
||||
// 分类列表
|
||||
List<GoodCategory> cateList = goodService.getCateList();
|
||||
String cateListForMock = JSONArray.from(cateList.stream().map(i -> i.getId()).collect(Collectors.toList())).toString();
|
||||
String cateListForMock = JSONArray.from(cateList.stream()
|
||||
.map(i -> i.getId().toString()).collect(Collectors.toList())).toString();
|
||||
|
||||
// id列 字段名(区分大小写;以VO中的变量名为准)
|
||||
// 新增、修改弹窗时,使用该列作为主键列进行操作
|
||||
@@ -119,21 +120,30 @@ public class GoodController {
|
||||
null // "IMG 120x120,随机图片"
|
||||
)
|
||||
.add("goodsName", "goodsName", "商品名称", "",
|
||||
FieldType.TEXT, SearchType.INPUT, AddType.INPUT, EditType.CAN_NOT_EDIT,
|
||||
FieldType.TEXT, SearchType.INPUT, AddType.INPUT, EditType.INPUT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
"商品名称", FieldBuilder.EDIT_PLACEHOLDER_SAME_AS_ADD_PLACEHOLDER,
|
||||
FieldRuleListBuilder.create()
|
||||
.add(FieldRuleBuilder.create("商品名称").required())
|
||||
.add(FieldRuleBuilder.create("商品名称").minMax(6, 20)),
|
||||
.add(FieldRuleBuilder.create("商品名称").minMax(2, 20)),
|
||||
"DPD @cword(6, 20)"
|
||||
)
|
||||
.add("brief", "brief", "商品简介", "暂无简介",
|
||||
FieldType.LONG_TEXT, SearchType.INPUT, AddType.INPUT, EditType.INPUT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
"商品简介", FieldBuilder.EDIT_PLACEHOLDER_SAME_AS_ADD_PLACEHOLDER,
|
||||
FieldRuleListBuilder.create()
|
||||
.add(FieldRuleBuilder.create("商品简介").required())
|
||||
.add(FieldRuleBuilder.create("商品简介").minMax(2, 30)),
|
||||
"DPD @cword(15, 30)"
|
||||
)
|
||||
.add("categoryId", "categoryName", "所属分类", "",
|
||||
FieldType.TEXT, SearchType.SELECT, AddType.SELECT, EditType.SELECT,
|
||||
"商品分类",
|
||||
"商品分类", FieldBuilder.EDIT_PLACEHOLDER_SAME_AS_ADD_PLACEHOLDER,
|
||||
FieldRuleListBuilder.create()
|
||||
.add(FieldRuleBuilder.create("商品分类").required()),
|
||||
"DPD @pick(" + cateListForMock + "])"
|
||||
"DPD @pick(" + cateListForMock + ")"
|
||||
)
|
||||
.add("brand", "brand", "商品品牌", "",
|
||||
FieldType.TEXT, SearchType.INPUT, AddType.INPUT, EditType.INPUT,
|
||||
@@ -141,19 +151,10 @@ public class GoodController {
|
||||
"商品品牌", FieldBuilder.EDIT_PLACEHOLDER_SAME_AS_ADD_PLACEHOLDER,
|
||||
FieldRuleListBuilder.create()
|
||||
.add(FieldRuleBuilder.create("商品品牌").required())
|
||||
.add(FieldRuleBuilder.create("商品品牌").minMax(6, 20)),
|
||||
.add(FieldRuleBuilder.create("商品品牌").minMax(2, 20)),
|
||||
"DPD @cword(6, 15)"
|
||||
)
|
||||
.add("brief", "brief", "商品简介", "暂无简介",
|
||||
FieldType.HIDDEN, SearchType.INPUT, AddType.INPUT, EditType.INPUT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
"商品简介", FieldBuilder.EDIT_PLACEHOLDER_SAME_AS_ADD_PLACEHOLDER,
|
||||
FieldRuleListBuilder.create()
|
||||
.add(FieldRuleBuilder.create("商品简介").required())
|
||||
.add(FieldRuleBuilder.create("商品简介").minMax(6, 30)),
|
||||
"DPD @cword(15, 30)"
|
||||
)
|
||||
.add("isOnSale", "isOnSale", "状态", true,
|
||||
.add("isOnSale", "isOnSaleDisplay", "状态", true,
|
||||
FieldType.TEXT, SearchType.SELECT, AddType.SELECT, EditType.SELECT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
"状态", FieldBuilder.EDIT_PLACEHOLDER_SAME_AS_ADD_PLACEHOLDER,
|
||||
@@ -177,20 +178,24 @@ public class GoodController {
|
||||
"DPD @cword(1, 2)"
|
||||
)
|
||||
.add("counterPrice", "counterPrice", "专柜价格", "",
|
||||
FieldType.TEXT, SearchType.INPUT, AddType.INPUT, EditType.INPUT,
|
||||
FieldType.TEXT, SearchType.INPUT, AddType.INPUT_NUMBER, EditType.INPUT_NUMBER,
|
||||
"专柜价/原价/划线价",
|
||||
"专柜价格 / 原价 / 划线价", FieldBuilder.EDIT_PLACEHOLDER_SAME_AS_ADD_PLACEHOLDER,
|
||||
FieldRuleListBuilder.create()
|
||||
.add(FieldRuleBuilder.create("专柜价格").required()),
|
||||
"DTD /^\\d+?(\\.?\\d+?)?$/"
|
||||
.add(FieldRuleBuilder.create("专柜价格").required())
|
||||
// .add(FieldRuleBuilder.create("专柜价格").regexp("^(\\d+(\\.\\d{1,2})?)$"))
|
||||
,
|
||||
"DTD /^[1-9]\\d{0,4}(\\.?\\d{1,2})?$/"
|
||||
)
|
||||
.add("retailPrice", "retailPrice", "零售价格", "",
|
||||
FieldType.TEXT, SearchType.INPUT, AddType.INPUT, EditType.INPUT,
|
||||
FieldType.TEXT, SearchType.INPUT, AddType.INPUT_NUMBER, EditType.INPUT_NUMBER,
|
||||
"零售价/售价/未划线价",
|
||||
"零售价 / 售价 / 未划线价", FieldBuilder.EDIT_PLACEHOLDER_SAME_AS_ADD_PLACEHOLDER,
|
||||
FieldRuleListBuilder.create()
|
||||
.add(FieldRuleBuilder.create("零售价格").required()),
|
||||
"DTD /^\\d+?(\\.?\\d+?)?$/"
|
||||
.add(FieldRuleBuilder.create("零售价格").required())
|
||||
// .add(FieldRuleBuilder.create("零售价格").regexp("^(\\d+(\\.\\d{1,2})?)$"))
|
||||
,
|
||||
"DTD /^[1-9]\\d{0,2}(\\.?\\d{1,2})?$/"
|
||||
)
|
||||
.add("detail", "detail", "商品详细介绍", "暂无商品详细介绍",
|
||||
FieldType.LONG_TEXT, SearchType.INPUT, AddType.TEXTAREA, EditType.TEXTAREA,
|
||||
@@ -212,7 +217,7 @@ public class GoodController {
|
||||
// build
|
||||
JSONArray fieldMapper = FieldMapperBuilder.create()
|
||||
.add("categoryId", "categoryName", cateMap)
|
||||
.add("isOnSale", "isOnSale", stateMap)
|
||||
.add("isOnSale", "isOnSaleDisplay", stateMap)
|
||||
.build();
|
||||
|
||||
// 拼装返回结果
|
||||
@@ -237,6 +242,18 @@ public class GoodController {
|
||||
@ResponseBody
|
||||
public Res editGood(@ModelAttribute GoodVO goodVO) {
|
||||
Good good = GoodVO.convertTo(goodVO);
|
||||
Double retailPrice = good.getRetailPrice();
|
||||
if (Objects.nonNull(retailPrice)) {
|
||||
if (retailPrice < 0 || retailPrice > 99999999.99) {
|
||||
return Res.error("零售价格输入不符,须在 0 - 99999999.99 之间");
|
||||
}
|
||||
}
|
||||
Double counterPrice = good.getCounterPrice();
|
||||
if (Objects.nonNull(counterPrice)) {
|
||||
if (counterPrice < 0 || counterPrice > 99999999.99) {
|
||||
return Res.error("专柜价格输入不符,须在 0 - 99999999.99 之间");
|
||||
}
|
||||
}
|
||||
|
||||
// 先查询商品是否存在
|
||||
Good existGood = goodService.getGoodById(good.getId());
|
||||
|
@@ -2,18 +2,31 @@ package com.cxyxiaomo.epp.shop.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.cxyxiaomo.epp.PageTable.enums.AddType;
|
||||
import com.cxyxiaomo.epp.PageTable.enums.EditType;
|
||||
import com.cxyxiaomo.epp.PageTable.enums.FieldType;
|
||||
import com.cxyxiaomo.epp.PageTable.enums.SearchType;
|
||||
import com.cxyxiaomo.epp.PageTable.query.PageQuery;
|
||||
import com.cxyxiaomo.epp.PageTable.utils.FieldBuilder;
|
||||
import com.cxyxiaomo.epp.PageTable.utils.FieldMapperBuilder;
|
||||
import com.cxyxiaomo.epp.PageTable.utils.FieldRuleBuilder;
|
||||
import com.cxyxiaomo.epp.PageTable.utils.FieldRuleListBuilder;
|
||||
import com.cxyxiaomo.epp.common.enums.OrderStatus;
|
||||
import com.cxyxiaomo.epp.common.pojo.Order;
|
||||
import com.cxyxiaomo.epp.common.pojo.OrderDetail;
|
||||
import com.cxyxiaomo.epp.common.query.OrderQuery;
|
||||
import com.cxyxiaomo.epp.common.response.Res;
|
||||
import com.cxyxiaomo.epp.common.vo.GoodVO;
|
||||
import com.cxyxiaomo.epp.common.vo.OrderDetailVO;
|
||||
import com.cxyxiaomo.epp.common.vo.OrderVO;
|
||||
import com.cxyxiaomo.epp.shop.service.GoodService;
|
||||
import com.cxyxiaomo.epp.shop.service.OrderService;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -30,8 +43,8 @@ public class OrderController {
|
||||
/**
|
||||
* 小程序端创建订单
|
||||
*
|
||||
* @param userId 下单用户
|
||||
* @param orderList 下单商品 [ { goodId, count }, ... ]
|
||||
* @param params userId 下单用户
|
||||
* @param params orderList 下单商品 [ { goodId, count }, ... ]
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/miniprogram/createOrder")
|
||||
@@ -184,6 +197,12 @@ public class OrderController {
|
||||
return Res.success(success ? "支付成功" : "支付失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户取消订单
|
||||
*
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/miniprogram/cancelOrder")
|
||||
@ResponseBody
|
||||
public Res cancelOrder(@RequestBody JSONObject params) {
|
||||
@@ -224,6 +243,106 @@ public class OrderController {
|
||||
return Res.success(success ? "取消成功" : "取消失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单发货
|
||||
*
|
||||
* @param orderId
|
||||
* @param expressId
|
||||
* @param comment
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/manage/deliverOrder")
|
||||
@ResponseBody
|
||||
public Res deliverOrder(Long orderId, String expressId, String comment) {
|
||||
if (orderId == null || expressId == null || comment == null) {
|
||||
return Res.error("参数错误");
|
||||
}
|
||||
|
||||
if (expressId.equals("") && comment.equals("")) {
|
||||
return Res.error("运单号 和 发货备注 不可同时为空");
|
||||
}
|
||||
|
||||
// 查询订单详情
|
||||
Order order = orderService.getOrderById(orderId);
|
||||
if (order == null) {
|
||||
return Res.error("订单不存在");
|
||||
}
|
||||
|
||||
//判断是否可以发货
|
||||
OrderStatus orderStatus = OrderStatus.get(order.getOrderStatus());
|
||||
switch (orderStatus) {
|
||||
case PENDING:
|
||||
return Res.error("订单尚未支付,不可发货");
|
||||
case PROCESSING: // 订单未发货
|
||||
// 更新订单发货信息
|
||||
orderService.updateOrderShipInfo(orderId, expressId, comment);
|
||||
// 更新支付信息
|
||||
orderService.updateOrderStatus(orderId, OrderStatus.SHIPPED);
|
||||
return Res.success("发货成功");
|
||||
case SHIPPED: // 订单已发货,修改发货信息
|
||||
// 更新订单发货信息
|
||||
orderService.updateOrderShipInfo(orderId, expressId, comment);
|
||||
return Res.success("发货信息修改成功");
|
||||
case DELIVERED:
|
||||
return Res.error("订单已完成,无法再次发货");
|
||||
case CANCELLED:
|
||||
return Res.error("订单已取消,不可发货");
|
||||
default:
|
||||
return Res.error("当前订单状态不可发货");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员取消订单
|
||||
*
|
||||
* @param orderId
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/manage/cancelOrder")
|
||||
@ResponseBody
|
||||
public Res cancelOrderByManager(Long orderId) {
|
||||
if (orderId == null) {
|
||||
return Res.error("参数错误");
|
||||
}
|
||||
|
||||
// 查询订单详情
|
||||
Order order = orderService.getOrderById(orderId);
|
||||
if (order == null) {
|
||||
return Res.error("订单不存在");
|
||||
}
|
||||
|
||||
//判断是否可以取消
|
||||
OrderStatus orderStatus = OrderStatus.get(order.getOrderStatus());
|
||||
switch (orderStatus) {
|
||||
case PENDING:
|
||||
// 更新订单发货信息
|
||||
orderService.updateOrderShipInfo(orderId, "", "管理员取消了您的订单,如有疑问请联系管理员");
|
||||
// 更新支付信息
|
||||
orderService.updateOrderStatus(orderId, OrderStatus.CANCELLED);
|
||||
return Res.success("操作成功");
|
||||
case PROCESSING:
|
||||
// 更新订单发货信息
|
||||
orderService.updateOrderShipInfo(orderId, "", "管理员取消了您的订单,费用已退回至您的帐户,如有疑问请联系管理员");
|
||||
// 更新支付信息
|
||||
orderService.updateOrderStatus(orderId, OrderStatus.CANCELLED);
|
||||
return Res.success("操作成功");
|
||||
case SHIPPED:
|
||||
return Res.error("订单已发货,不可取消");
|
||||
case DELIVERED:
|
||||
return Res.error("订单已送达,不可取消");
|
||||
case CANCELLED:
|
||||
return Res.error("订单已取消");
|
||||
default:
|
||||
return Res.error("当前订单状态无法取消");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单确认收货
|
||||
*
|
||||
* @param params
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/miniprogram/confirmOrder")
|
||||
@ResponseBody
|
||||
public Res confirmOrder(@RequestBody JSONObject params) {
|
||||
@@ -261,4 +380,189 @@ public class OrderController {
|
||||
|
||||
return Res.success(success ? "确认收货成功" : "确认收货失败");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/manage/getOrderList")
|
||||
@ResponseBody
|
||||
public Res getOrderList(PageQuery pageQuery, OrderQuery orderQuery) {
|
||||
// 查询分页数据
|
||||
PageHelper.startPage(pageQuery.getPageIndex(), pageQuery.getPageSize());
|
||||
List<Order> orderList = orderService.getOrderList(orderQuery);
|
||||
PageInfo<Order> orderPageInfo = new PageInfo<>(orderList);
|
||||
List<Order> list = orderPageInfo.getList();
|
||||
List<OrderVO> voList = OrderVO.convertFrom(list);
|
||||
|
||||
// id列 字段名(区分大小写;以VO中的变量名为准)
|
||||
// 新增、修改弹窗时,使用该列作为主键列进行操作
|
||||
String idFieldName = "id";
|
||||
|
||||
// 当前管理页面
|
||||
String pageName = "订单管理";
|
||||
|
||||
// 指定前端表格显示列
|
||||
JSONArray columns = FieldBuilder.create()
|
||||
.add("userId", "userId", "用户ID", "",
|
||||
FieldType.TEXT, SearchType.INPUT, AddType.CAN_NOT_ADD, EditType.CAN_NOT_EDIT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, null, null, null
|
||||
)
|
||||
.add("orderStatusCode", "orderStatusName", "订单状态", true,
|
||||
FieldType.TEXT, SearchType.SELECT, AddType.CAN_NOT_ADD, EditType.SELECT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, "订单状态",
|
||||
FieldRuleListBuilder.create()
|
||||
.add(FieldRuleBuilder.create("订单状态").required()),
|
||||
null
|
||||
)
|
||||
.add("orderPrice", "orderPrice", "订单价格", "",
|
||||
FieldType.TEXT, SearchType.CAN_NOT_SEARCH, AddType.CAN_NOT_ADD, EditType.CAN_NOT_EDIT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, null, null, null
|
||||
)
|
||||
.add("orderDate", "orderDate", "下单时间", "",
|
||||
FieldType.TEXT, SearchType.CAN_NOT_SEARCH, AddType.CAN_NOT_ADD, EditType.CAN_NOT_EDIT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, null, null, null
|
||||
)
|
||||
.add("payDate", "payDate", "支付时间", "",
|
||||
FieldType.TEXT, SearchType.CAN_NOT_SEARCH, AddType.CAN_NOT_ADD, EditType.CAN_NOT_EDIT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, null, null, null
|
||||
)
|
||||
.add("cancelDate", "cancelDate", "取消时间", "",
|
||||
FieldType.TEXT, SearchType.CAN_NOT_SEARCH, AddType.CAN_NOT_ADD, EditType.CAN_NOT_EDIT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, null, null, null
|
||||
)
|
||||
.add("shipDate", "shipDate", "发货时间", "",
|
||||
FieldType.TEXT, SearchType.CAN_NOT_SEARCH, AddType.CAN_NOT_ADD, EditType.CAN_NOT_EDIT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, null, null, null
|
||||
)
|
||||
.add("deliverDate", "deliverDate", "送达时间", "",
|
||||
FieldType.TEXT, SearchType.CAN_NOT_SEARCH, AddType.CAN_NOT_ADD, EditType.CAN_NOT_EDIT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, null, null, null
|
||||
)
|
||||
.add("expressId", "expressId", "运单号", "",
|
||||
FieldType.TEXT, SearchType.INPUT, AddType.CAN_NOT_ADD, EditType.CAN_NOT_EDIT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, null, null, null
|
||||
)
|
||||
.add("comment", "comment", "发货备注", "",
|
||||
FieldType.LONG_TEXT, SearchType.CAN_NOT_SEARCH, AddType.CAN_NOT_ADD, EditType.CAN_NOT_EDIT,
|
||||
FieldBuilder.SEARCH_PLACEHOLDER_SAME_AS_FIELDNAME,
|
||||
null, null, null, null
|
||||
)
|
||||
.build();
|
||||
|
||||
// 指定需要翻译的字段
|
||||
OrderStatus[] orderStatusList = OrderStatus.values();
|
||||
HashMap<String, String> orderStatusMap = new HashMap<>(orderStatusList.length);
|
||||
for (OrderStatus orderStatus : orderStatusList) {
|
||||
orderStatusMap.put(orderStatus.getValue(), orderStatus.toString());
|
||||
}
|
||||
// build
|
||||
JSONArray fieldMapper = FieldMapperBuilder.create()
|
||||
.add("orderStatusCode", "orderStatusName", orderStatusMap)
|
||||
.build();
|
||||
|
||||
// 拼装返回结果
|
||||
JSONObject map = new JSONObject(6);
|
||||
map.put("total", orderPageInfo.getTotal());
|
||||
map.put("list", voList);
|
||||
map.put("columns", columns);
|
||||
map.put("fieldMapper", fieldMapper);
|
||||
map.put("idFieldName", idFieldName);
|
||||
map.put("pageName", pageName);
|
||||
|
||||
// 返回结果
|
||||
return Res.success(map);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 编辑订单
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// @PostMapping("/manage/editOrder")
|
||||
// @ResponseBody
|
||||
// public Res editOrder(@ModelAttribute OrderVO OrderVO) {
|
||||
// Order Order = OrderVO.convertTo(OrderVO);
|
||||
//
|
||||
// // 先查询订单是否存在
|
||||
// Order existOrder = orderService.getOrderById(Order.getId());
|
||||
//
|
||||
// if (Order.getId() == null || Order.getId() < 1) {
|
||||
// // 新增订单
|
||||
// if (existOrder != null) {
|
||||
// return Res.error("订单已存在,操作失败");
|
||||
// }
|
||||
//
|
||||
// if (Order.getOrdersName() == null || "".equals(Order.getOrdersName())) {
|
||||
// return Res.error("订单名称不能为空");
|
||||
// }
|
||||
// Order.setId(null);
|
||||
// orderService.addOrder(Order);
|
||||
// } else {
|
||||
// // 修改订单
|
||||
// if (existOrder == null) {
|
||||
// return Res.error("订单不存在,操作失败");
|
||||
// }
|
||||
//
|
||||
// orderService.updateOrder(Order);
|
||||
// }
|
||||
// return Res.success(true);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 关闭订单
|
||||
// *
|
||||
// * @param id
|
||||
// * @return
|
||||
// */
|
||||
// @PostMapping("/manage/deleteOrder")
|
||||
// @ResponseBody
|
||||
// public Res deleteOrder(Long id) {
|
||||
// if (id == null || id <= 0) {
|
||||
// return Res.error("订单不存在,删除失败");
|
||||
// }
|
||||
// // 先查询订单是否存在
|
||||
// Order existOrder = orderService.getOrderById(id);
|
||||
// if (existOrder == null) {
|
||||
// return Res.error("订单不存在,删除失败");
|
||||
// }
|
||||
// boolean b = orderService.deleteOrder(existOrder.getId());
|
||||
// return Res.success(b);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 导出订单列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/manage/exportOrderList")
|
||||
@ResponseBody
|
||||
public Res exportOrderList(OrderQuery orderQuery) {
|
||||
List<Order> orderList = orderService.getOrderList(orderQuery);
|
||||
List<OrderVO> orderVOList = OrderVO.convertFrom(orderList);
|
||||
|
||||
// 当前时间
|
||||
Date now = Calendar.getInstance().getTime();
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss");
|
||||
String dateTime = format.format(now);
|
||||
|
||||
HashMap<String, Object> map = new HashMap<>();
|
||||
map.put("list", orderVOList);
|
||||
map.put("sheetName", "订单表-" + System.currentTimeMillis());
|
||||
map.put("fileName", "订单表_导出时间_" + dateTime);
|
||||
|
||||
return Res.success(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -14,6 +14,8 @@ public interface GoodDao {
|
||||
|
||||
List<Good> list(@Param("cateId") Integer cateId, @Param("searchText") String searchText);
|
||||
|
||||
List<Good> listOnSale(@Param("cateId") Integer cateId, @Param("searchText") String searchText);
|
||||
|
||||
Good getById(Long id);
|
||||
|
||||
Good selectById(Long id);
|
||||
@@ -27,13 +29,7 @@ public interface GoodDao {
|
||||
Integer deleteById(Integer id);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Manage
|
||||
public boolean addGood(Good good);
|
||||
|
||||
public boolean updateGood(Good good);
|
||||
|
@@ -2,6 +2,7 @@ package com.cxyxiaomo.epp.shop.dao;
|
||||
|
||||
import com.cxyxiaomo.epp.common.pojo.Order;
|
||||
import com.cxyxiaomo.epp.common.pojo.OrderDetail;
|
||||
import com.cxyxiaomo.epp.common.query.OrderQuery;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -62,6 +63,19 @@ public interface OrderDao {
|
||||
*/
|
||||
int updateOrderStatus(@Param("orderId") Long orderId, @Param("orderStatus") String orderStatus);
|
||||
|
||||
/**
|
||||
* 更新订单发货信息
|
||||
*
|
||||
* @param orderId
|
||||
* @param expressId
|
||||
* @param comment
|
||||
* @return
|
||||
*/
|
||||
int updateOrderShipInfo(@Param("orderId") Long orderId, @Param("expressId") String expressId, @Param("comment") String comment);
|
||||
|
||||
// 根据订单 ID 删除订单信息及订单详情信息
|
||||
int deleteOrderById(Long orderId);
|
||||
boolean deleteOrderById(Long orderId);
|
||||
|
||||
|
||||
public List<Order> getOrderList(OrderQuery orderQuery);
|
||||
}
|
||||
|
@@ -27,6 +27,13 @@ public class GoodService {
|
||||
return goodVOS;
|
||||
}
|
||||
|
||||
// 只列出上架状态的商品
|
||||
public List<GoodVO> listOnSale(Integer cateId, String searchText) {
|
||||
List<Good> list = goodDao.listOnSale(cateId, searchText);
|
||||
List<GoodVO> goodVOS = GoodVO.convertFrom(list);
|
||||
return goodVOS;
|
||||
}
|
||||
|
||||
public GoodVO getById(Long id) {
|
||||
Good good = goodDao.getById(id);
|
||||
GoodVO goodVO = GoodVO.convertFrom(good);
|
||||
|
@@ -4,6 +4,7 @@ import com.cxyxiaomo.epp.common.enums.OrderStatus;
|
||||
import com.cxyxiaomo.epp.common.pojo.Good;
|
||||
import com.cxyxiaomo.epp.common.pojo.Order;
|
||||
import com.cxyxiaomo.epp.common.pojo.OrderDetail;
|
||||
import com.cxyxiaomo.epp.common.query.OrderQuery;
|
||||
import com.cxyxiaomo.epp.common.utils.SnowflakeManager;
|
||||
import com.cxyxiaomo.epp.common.vo.OrderDetailVO;
|
||||
import com.cxyxiaomo.epp.shop.dao.GoodDao;
|
||||
@@ -132,4 +133,15 @@ public class OrderService {
|
||||
int affectRows = orderDao.updateOrderStatus(orderId, orderStatus.getValue());
|
||||
return affectRows > 0;
|
||||
}
|
||||
|
||||
public Boolean updateOrderShipInfo(Long orderId, String expressId, String comment) {
|
||||
int affectRows = orderDao.updateOrderShipInfo(orderId, expressId, comment);
|
||||
return affectRows > 0;
|
||||
}
|
||||
|
||||
|
||||
public List<Order> getOrderList(OrderQuery orderQuery) {
|
||||
return orderDao.getOrderList(orderQuery);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -37,6 +37,20 @@
|
||||
</if>
|
||||
order by `sort_order` asc
|
||||
</select>
|
||||
<select id="listOnSale" resultType="com.cxyxiaomo.epp.common.pojo.Good">
|
||||
SELECT *
|
||||
FROM goods
|
||||
WHERE is_on_sale = 1
|
||||
<if test="cateId != null">
|
||||
AND category_id = #{cateId}
|
||||
</if>
|
||||
<if test="searchText != null">
|
||||
AND
|
||||
( goods_name LIKE concat('%',#{searchText,jdbcType=VARCHAR},'%')
|
||||
OR brief LIKE concat('%',#{searchText,jdbcType=VARCHAR},'%'))
|
||||
</if>
|
||||
order by `sort_order` asc
|
||||
</select>
|
||||
<select id="getById" parameterType="java.lang.Long" resultType="com.cxyxiaomo.epp.common.pojo.Good">
|
||||
SELECT *
|
||||
FROM goods
|
||||
|
@@ -95,7 +95,7 @@
|
||||
</update>
|
||||
|
||||
<!-- 根据订单 ID 删除订单信息及订单详情信息 -->
|
||||
<delete id="deleteOrderById" parameterType="java.lang.Integer">
|
||||
<delete id="deleteOrderById" parameterType="java.lang.Long">
|
||||
DELETE
|
||||
FROM `order`
|
||||
WHERE id = #{orderId};
|
||||
@@ -104,4 +104,39 @@
|
||||
-- FROM goods_order_details
|
||||
-- WHERE order_id = #{orderId};
|
||||
</delete>
|
||||
|
||||
|
||||
<!-- manage 相关的 SQL -->
|
||||
<select id="getOrderList" resultType="com.cxyxiaomo.epp.common.pojo.Order"
|
||||
parameterType="com.cxyxiaomo.epp.common.query.OrderQuery">
|
||||
SELECT * FROM `order`
|
||||
<where>
|
||||
<if test="id != null">
|
||||
AND id = #{id}
|
||||
</if>
|
||||
<if test="userId != null">
|
||||
AND user_id = #{userId}
|
||||
</if>
|
||||
<if test="orderStatusCode != null and orderStatusCode != ''">
|
||||
AND order_status = #{orderStatusCode}
|
||||
</if>
|
||||
<if test="expressId != null and expressId != ''">
|
||||
AND express_id = #{expressId}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!-- 更新发货信息 -->
|
||||
<update id="updateOrderShipInfo" parameterType="com.cxyxiaomo.epp.common.pojo.Order">
|
||||
UPDATE `order`
|
||||
<set>
|
||||
<if test="expressId != null">
|
||||
express_id = #{expressId},
|
||||
</if>
|
||||
<if test="comment != null">
|
||||
`comment` = #{comment},
|
||||
</if>
|
||||
</set>
|
||||
WHERE id = #{orderId}
|
||||
</update>
|
||||
</mapper>
|
||||
|
@@ -11,7 +11,14 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<iframe src="https://epp-prod.only4.work/access/index.html?inElectron=true" frameborder="0"></iframe>
|
||||
<!--
|
||||
FIXME 环境配置
|
||||
|
||||
src
|
||||
- 线上环境:"https://epp.only4.work/guard/index.html?inElectron=true"
|
||||
- 开发环境:"http://localhost/guard/index.html?inElectron=true"
|
||||
-->
|
||||
<iframe src="https://epp.only4.work/guard/index.html?inElectron=true" frameborder="0"></iframe>
|
||||
<script src="./renderer.js" type="module"></script>
|
||||
</body>
|
||||
|
||||
|
1042
database/epp.sql
3
database/备注.md
Normal file
@@ -0,0 +1,3 @@
|
||||
该SQL中包含测试数据,若不需要可以自行删除
|
||||
|
||||
数据表名最后带 1 的为项目未使用到的表,可以直接删除
|
5
frontend/components.d.ts
vendored
@@ -7,13 +7,10 @@ export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
Calender: typeof import('./src/components/calender.vue')['default']
|
||||
ContextMenu: typeof import('./src/components/context-menu.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
@@ -30,7 +27,6 @@ declare module '@vue/runtime-core' {
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
@@ -38,6 +34,7 @@ declare module '@vue/runtime-core' {
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
Header: typeof import('./src/components/header.vue')['default']
|
||||
|
7
frontend/package-lock.json
generated
@@ -24,6 +24,7 @@
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mockjs": "^1.0.7",
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"@vue/compiler-sfc": "^3.1.2",
|
||||
"typescript": "^4.6.4",
|
||||
@@ -223,6 +224,12 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mockjs": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/@types/mockjs/-/mockjs-1.0.7.tgz",
|
||||
"integrity": "sha512-OCxXz6hEaJOVpRwuJMiVY5a6LtJcih+br9gwB/Q8ooOBikvk5FpBQ31OlNimXo3EqKha1Z7PFBni+q9m+8NCWg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.14.0",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-18.14.0.tgz",
|
||||
|
@@ -24,6 +24,7 @@
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mockjs": "^1.0.7",
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"@vue/compiler-sfc": "^3.1.2",
|
||||
"typescript": "^4.6.4",
|
||||
|
63
frontend/src/api/shop-order.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import send_request from '../utils/send_request';
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
* @returns
|
||||
*/
|
||||
export function getOrderList(params) {
|
||||
return send_request({
|
||||
url: '/shop/order/manage/getOrderList',
|
||||
method: 'GET',
|
||||
params: params,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
* @returns
|
||||
*/
|
||||
export function getOrderDetail(params) {
|
||||
return send_request({
|
||||
url: '/shop/order/miniprogram/orderDetail',
|
||||
method: 'GET',
|
||||
params: params,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 订单发货
|
||||
* @returns
|
||||
*/
|
||||
export function deliverOrder(params) {
|
||||
return send_request({
|
||||
url: '/shop/order/manage/deliverOrder',
|
||||
method: 'POST',
|
||||
useQS: true,
|
||||
params: params,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭订单
|
||||
* @returns
|
||||
*/
|
||||
export function withdrawOrder(params) {
|
||||
return send_request({
|
||||
url: '/shop/order/manage/cancelOrder',
|
||||
method: 'POST',
|
||||
useQS: true,
|
||||
params: params,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出订单列表
|
||||
* @returns
|
||||
*/
|
||||
export function exportOrderList(params) {
|
||||
return send_request({
|
||||
url: '/shop/order/manage/exportOrderList',
|
||||
method: 'GET',
|
||||
params: params,
|
||||
});
|
||||
};
|
@@ -24,6 +24,24 @@ export function userLogout() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @returns
|
||||
*/
|
||||
export function updatePwd({ oldpwd, newpwd }) {
|
||||
let userId = localStorage.getItem("ms_user_id")
|
||||
return send_request({
|
||||
url: '/user/updatePwd',
|
||||
method: 'POST',
|
||||
useQS: true,
|
||||
params: {
|
||||
"userId": userId,
|
||||
"oldpwd": oldpwd,
|
||||
"newpwd": newpwd,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @returns
|
||||
|
@@ -13,7 +13,7 @@
|
||||
<div class="header-right">
|
||||
<div class="header-user-con">
|
||||
<!-- 消息中心 -->
|
||||
<div class="btn-bell" @click="router.push('/tabs')">
|
||||
<div class="btn-bell" @click="router.push('/tabs')" v-if="false">
|
||||
<el-tooltip effect="dark" :content="message ? `有${message}条未读消息` : `消息中心`" placement="bottom">
|
||||
<i class="el-icon-lx-notice"></i>
|
||||
</el-tooltip>
|
||||
|
@@ -5,9 +5,9 @@
|
||||
<!-- 筛选 -->
|
||||
<div class="handle-box">
|
||||
<template v-for="field in searchFields">
|
||||
<el-input v-if="field.searchType == 'input'" v-model="query[field.field]"
|
||||
@keyup.enter.native="handleSearch" :placeholder="field.placeholder" :prefix-icon="Filter"
|
||||
class="handle-input mr10"></el-input>
|
||||
<el-input v-if="field.searchType == 'input' || field.searchType == 'input-number'"
|
||||
v-model="query[field.field]" @keyup.enter.native="handleSearch" :placeholder="field.placeholder"
|
||||
:prefix-icon="Filter" class="handle-input mr10"></el-input>
|
||||
<el-select v-else-if="field.searchType == 'select'" v-model="query[field.field]" :clearable="true"
|
||||
@change="handleSearch" :placeholder="field.placeholder" class="handle-select mr10">
|
||||
<template #prefix>
|
||||
@@ -56,16 +56,27 @@
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" align="center" v-if="props.editFunc || props.deleteFunc">
|
||||
<el-table-column label="操作" width="220" align="center"
|
||||
v-if="props.editFunc || props.deleteFunc || props.customEditHandle">
|
||||
<template #default="scope">
|
||||
<el-button text :icon="Edit" @click="handleEdit(scope.$index, scope.row)"
|
||||
v-permiss="props.editPermiss" v-if="props.editFunc">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button text :icon="Delete" class="red" @click="handleDelete(scope.$index, scope.row)"
|
||||
v-permiss="props.editPermiss" v-if="props.deleteFunc">
|
||||
删除
|
||||
</el-button>
|
||||
<template v-if="props.customEditHandle">
|
||||
<el-button text :icon="List"
|
||||
@click="props.customEditHandle?.((scope as any).$index, (scope as any).row, getData)"
|
||||
v-permiss="props.editPermiss">
|
||||
管理
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button text :icon="Edit" @click="handleEdit((scope as any).$index, (scope as any).row)"
|
||||
v-permiss="props.editPermiss" v-if="props.editFunc">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button text :icon="Delete" class="red"
|
||||
@click="handleDelete((scope as any).$index, (scope as any).row)"
|
||||
v-permiss="props.editPermiss" v-if="props.deleteFunc">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -87,6 +98,9 @@
|
||||
<el-input v-if="(formId > 0 ? field.editType : field.addType) == 'input'"
|
||||
:placeholder="formId > 0 ? field.editPlaceholder : field.addPlaceholder" class="popup-item"
|
||||
v-model="form[field.field]"></el-input>
|
||||
<el-input v-else-if="(formId > 0 ? field.editType : field.addType) == 'input-number'" type="number"
|
||||
:placeholder="formId > 0 ? field.editPlaceholder : field.addPlaceholder" class="popup-item"
|
||||
v-model="form[field.field]"></el-input>
|
||||
<el-input v-else-if="(formId > 0 ? field.editType : field.addType) == 'textarea'"
|
||||
:placeholder="formId > 0 ? field.editPlaceholder : field.addPlaceholder" class="popup-item"
|
||||
v-model="form[field.field]" type="textarea" :rows="4"></el-input>
|
||||
@@ -141,7 +155,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { FormInstance, FormRules, ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Delete, Edit, Search, Plus, Filter, Download } from '@element-plus/icons-vue';
|
||||
import { Delete, Edit, Search, Plus, Filter, Download, List } from '@element-plus/icons-vue';
|
||||
import * as xlsx from 'xlsx';
|
||||
import Mock from 'mockjs';
|
||||
import ImageUpload from './image-upload.vue';
|
||||
@@ -176,6 +190,12 @@ const props = defineProps({
|
||||
'editPermiss': {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
// 自定义修改按钮点击事件
|
||||
'customEditHandle': {
|
||||
type: Function,
|
||||
required: false,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -379,7 +399,8 @@ const handleEdit = (index: number, row: any) => {
|
||||
for (let f of dialogFields.value) {
|
||||
switch (f.editType) {
|
||||
case "select":
|
||||
// 下拉框的值要为 string 类型
|
||||
case "input-number":
|
||||
// 下拉框、数字输入框的值要为 string 类型
|
||||
form[f.field] = String(row[f.field]);
|
||||
break;
|
||||
default:
|
||||
@@ -399,7 +420,8 @@ const handleNew = () => {
|
||||
for (let f of dialogFields.value) {
|
||||
switch (f.editType) {
|
||||
case "select":
|
||||
// 下拉框的值要为 string 类型
|
||||
case "input-number":
|
||||
// 下拉框、数字输入框的值要为 string 类型
|
||||
form[f.field] = String(f.default);
|
||||
break;
|
||||
default:
|
||||
@@ -431,10 +453,10 @@ const saveEdit = async (formEl: FormInstance | undefined) => {
|
||||
var result;
|
||||
if (formId > 0) {
|
||||
// 修改记录
|
||||
var result = await props.editFunc(form)
|
||||
var result = await props.editFunc?.(form)
|
||||
} else {
|
||||
// 新增记录
|
||||
var result = await props.addFunc(form)
|
||||
var result = await props.addFunc?.(form)
|
||||
query.pageIndex = Math.ceil((pageTotal.value + 1) / query.pageSize);
|
||||
}
|
||||
console.log("result", result)
|
||||
@@ -460,7 +482,7 @@ const handleDelete = (index: number, row: any) => {
|
||||
// 二次确认删除
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', { type: 'warning' })
|
||||
.then(async () => {
|
||||
var result = await props.deleteFunc({
|
||||
var result = await props.deleteFunc?.({
|
||||
id: row[idFieldName],
|
||||
})
|
||||
if (result) {
|
||||
@@ -540,7 +562,7 @@ const handleExport = async () => {
|
||||
// 数据部分
|
||||
let excelList = dataList.map((row: any) => {
|
||||
// 通过翻译前的 key 拿数据
|
||||
return fieldNameList.map((field: any) => String(row[field]))
|
||||
return fieldNameList.map((field: any) => String(row[field] || ""))
|
||||
})
|
||||
excelList.unshift(firstRow) // 插入表头
|
||||
|
||||
|
@@ -91,6 +91,11 @@ const items = [
|
||||
title: '商品管理',
|
||||
permiss: 'shop-good-setting',
|
||||
},
|
||||
{
|
||||
index: '/shop-order-setting',
|
||||
title: '订单管理',
|
||||
permiss: 'shop-order-setting',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@@ -84,6 +84,15 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
component: () => import('../views/shop-good-setting.vue'),
|
||||
},
|
||||
{
|
||||
path: '/shop-order-setting',
|
||||
name: 'shop-order-setting',
|
||||
meta: {
|
||||
title: '订单管理',
|
||||
permiss: 'shop-order-setting',
|
||||
},
|
||||
component: () => import('../views/shop-order-setting.vue'),
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
|
@@ -15,16 +15,17 @@ export const usePermissStore = defineStore('permiss', {
|
||||
|
||||
"dashboard",
|
||||
|
||||
"access",
|
||||
"access-log",
|
||||
"access-gate-setting",
|
||||
// "access",
|
||||
// "access-log",
|
||||
// "access-gate-setting",
|
||||
|
||||
"report",
|
||||
"report-log",
|
||||
// "report",
|
||||
// "report-log",
|
||||
|
||||
"shop",
|
||||
"shop-cate-setting",
|
||||
"shop-good-setting",
|
||||
// "shop",
|
||||
// "shop-cate-setting",
|
||||
// "shop-good-setting",
|
||||
// "shop-order-setting",
|
||||
|
||||
"privilege",
|
||||
"privilege-user-setting",
|
||||
@@ -46,9 +47,10 @@ export const usePermissStore = defineStore('permiss', {
|
||||
"shop",
|
||||
"shop-cate-setting",
|
||||
"shop-good-setting",
|
||||
"shop-order-setting",
|
||||
|
||||
"privilege",
|
||||
"privilege-user-setting",
|
||||
// "privilege",
|
||||
// "privilege-user-setting",
|
||||
]
|
||||
};
|
||||
},
|
||||
|
@@ -22,6 +22,12 @@ export default {
|
||||
/**
|
||||
* 后端接口请求地址
|
||||
* (结尾加不加 / 都可)
|
||||
*
|
||||
* FIXME 环境配置
|
||||
*
|
||||
* backendHost
|
||||
* - 线上环境:"https://epp.only4.work/"
|
||||
* - 开发环境:"http://localhost/"
|
||||
*/
|
||||
backendHost: "https://epp-prod.only4.work/",
|
||||
backendHost: "http://localhost/",
|
||||
};
|
||||
|
@@ -1,213 +1,26 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="mgb20" style="height: 252px">
|
||||
<div class="user-info">
|
||||
<el-avatar :size="120" :src="imgurl" />
|
||||
<div class="user-info-cont">
|
||||
<div class="user-info-name">{{ name }}</div>
|
||||
<div>{{ role }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-info-list">
|
||||
上次登录时间:
|
||||
<span>2022-10-01</span>
|
||||
</div>
|
||||
<div class="user-info-list">
|
||||
上次登录地点:
|
||||
<span>东莞</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover" style="height: 252px">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>语言详情</span>
|
||||
</div>
|
||||
</template>
|
||||
Vue
|
||||
<el-progress :percentage="79.4" color="#42b983"></el-progress>
|
||||
TypeScript
|
||||
<el-progress :percentage="14" color="#f1e05a"></el-progress>
|
||||
CSS
|
||||
<el-progress :percentage="5.6"></el-progress>
|
||||
HTML
|
||||
<el-progress :percentage="1" color="#f56c6c"></el-progress>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-row :gutter="20" class="mgb20">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||
<div class="grid-content grid-con-1">
|
||||
<el-icon class="grid-con-icon"><User /></el-icon>
|
||||
<div class="grid-cont-right">
|
||||
<div class="grid-num">1234</div>
|
||||
<div>用户访问量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||
<div class="grid-content grid-con-2">
|
||||
<el-icon class="grid-con-icon"><ChatDotRound /></el-icon>
|
||||
<div class="grid-cont-right">
|
||||
<div class="grid-num">321</div>
|
||||
<div>系统消息</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||
<div class="grid-content grid-con-3">
|
||||
<el-icon class="grid-con-icon"><Goods /></el-icon>
|
||||
<div class="grid-cont-right">
|
||||
<div class="grid-num">500</div>
|
||||
<div>商品数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-card shadow="hover" style="height: 403px">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>预警列表</span>
|
||||
<el-button style="float: right; padding: 3px 0" text>添加</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table :show-header="false" :data="todoList" style="width: 100%">
|
||||
<el-table-column width="40">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.status"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column>
|
||||
<template #default="scope">
|
||||
<div
|
||||
class="todo-item"
|
||||
:class="{
|
||||
'todo-item-del': scope.row.status
|
||||
}"
|
||||
>
|
||||
{{ scope.row.title }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="container">
|
||||
<el-card shadow="hover" class="mgb20" style="height: 252px">
|
||||
<div class="user-info">
|
||||
<el-avatar :size="120" :src="imgurl" />
|
||||
<div class="user-info-cont">
|
||||
<div class="user-info-name">{{ name }}</div>
|
||||
<div>{{ role }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="dashboard">
|
||||
import { ref, reactive } from 'vue';
|
||||
import imgurl from '../assets/img/img.jpg';
|
||||
|
||||
const roleMap = {
|
||||
|
||||
}
|
||||
const name = localStorage.getItem('ms_username');
|
||||
const role: string = name === 'admin' ? '超级管理员' : '普通用户';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.grid-cont-right {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.grid-num {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.grid-con-icon {
|
||||
font-size: 50px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grid-con-1 .grid-con-icon {
|
||||
background: rgb(45, 140, 240);
|
||||
}
|
||||
|
||||
.grid-con-1 .grid-num {
|
||||
color: rgb(45, 140, 240);
|
||||
}
|
||||
|
||||
.grid-con-2 .grid-con-icon {
|
||||
background: rgb(100, 213, 114);
|
||||
}
|
||||
|
||||
.grid-con-2 .grid-num {
|
||||
color: rgb(100, 213, 114);
|
||||
}
|
||||
|
||||
.grid-con-3 .grid-con-icon {
|
||||
background: rgb(242, 94, 67);
|
||||
}
|
||||
|
||||
.grid-con-3 .grid-num {
|
||||
color: rgb(242, 94, 67);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #ccc;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-info-cont {
|
||||
padding-left: 50px;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.user-info-cont div:first-child {
|
||||
font-size: 30px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.user-info-list {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.user-info-list span {
|
||||
margin-left: 70px;
|
||||
}
|
||||
|
||||
.mgb20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.todo-item {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.todo-item-del {
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
@@ -67,8 +67,8 @@ interface RoleInfo {
|
||||
|
||||
const router = useRouter();
|
||||
const param = reactive<LoginInfo>({
|
||||
username: 'root',
|
||||
password: 'root'
|
||||
username: 'admin',
|
||||
password: 'admin'
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
@@ -116,7 +116,10 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
||||
if (!data) return;
|
||||
|
||||
console.log("login data", data, data.userInfo);
|
||||
|
||||
if (![1, 2].includes(data.userInfo?.roleId)) {
|
||||
ElMessage.error('您所在用户组无权登录当前系统');
|
||||
return;
|
||||
}
|
||||
ElMessage.success('登录成功');
|
||||
localStorage.setItem('ms_username', data.userInfo?.username);
|
||||
localStorage.setItem('ms_realname', data.userInfo?.realname);
|
||||
|
191
frontend/src/views/shop-order-setting.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<manageList :list-func="shopOrderApi.getOrderList" :custom-edit-handle="editHandle"
|
||||
:export-func="shopOrderApi.exportOrderList" edit-permiss="shop-order-setting" />
|
||||
|
||||
<!-- 新增 / 编辑弹出框 -->
|
||||
<el-dialog title="管理订单" v-model="visible" style="width: 60%; min-width: 280px;">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<p class="line-height">
|
||||
<span class="row-index">订单ID</span>
|
||||
{{ orderDetail.id || "" }}
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">订单状态</span>
|
||||
<el-tag effect="plain" round>{{ orderDetail.orderStatus || "" }}</el-tag>
|
||||
{{ orderDetail.orderStatusCode || "" }}
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">下单用户ID</span>
|
||||
{{ orderDetail.userId || "" }}
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">订单价格</span>
|
||||
{{ orderDetail.orderPrice ? ('¥' + orderDetail.orderPrice) : "-" }}
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">下单时间</span>
|
||||
{{ orderDetail.orderDate || "-" }}
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">订单取消时间</span>
|
||||
{{ orderDetail.cancelDate || "-" }}
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">订单支付时间</span>
|
||||
{{ orderDetail.payDate || "-" }}
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">订单发货时间</span>
|
||||
{{ orderDetail.shipDate || "-" }}
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">订单送达时间</span>
|
||||
{{ orderDetail.deliverDate || "-" }}
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">运单号</span>
|
||||
<template
|
||||
v-if="orderDetail.orderStatusCode != 'Processing' && orderDetail.orderStatusCode != 'Shipped'">
|
||||
{{ orderDetail.expressId || "-" }}
|
||||
</template>
|
||||
<el-input v-else v-model="shippingInfo.expressId" placeholder="Please input" />
|
||||
</p>
|
||||
<p class="line-height">
|
||||
<span class="row-index">发货备注</span>
|
||||
<template
|
||||
v-if="orderDetail.orderStatusCode != 'Processing' && orderDetail.orderStatusCode != 'Shipped'">
|
||||
{{ orderDetail.comment || "-" }}
|
||||
</template>
|
||||
<el-input v-else v-model="shippingInfo.comment" placeholder="Please input" />
|
||||
</p>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<p style="margin-bottom: 20px;">该订单中包含如下商品</p>
|
||||
<div style="max-height:50vh; overflow-y: scroll;">
|
||||
<el-card class="box-card" v-for="i in orderItem" :key="i.goodId"
|
||||
style="--el-card-padding: 10px; margin-bottom: 8px;">
|
||||
<div style="display: grid; grid-template-columns: 50px 1fr 60px; gap: 15px;">
|
||||
<img :src="i.good.picUrl" style="width: 60px; height: 60px;" />
|
||||
<div style="place-self: center left;">
|
||||
<p>{{ i.good.goodsName }}</p>
|
||||
<p style="color: grey; font-size: 12px;">商品ID: {{ i.goodId }}</p>
|
||||
</div>
|
||||
<div style="place-self: center; font-size: 16px;">{{ i.goodCount }} {{ i.good.unit }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 订单状态:PENDING("Pending", "等待确认"), -->
|
||||
<!-- 订单状态:PROCESSING("Processing", "已支付,等待发货中"), -->
|
||||
<!-- 订单状态:SHIPPED("Shipped", "已发货,等待确认收货"), -->
|
||||
<!-- 订单状态:DELIVERED("Delivered", "已送达"), -->
|
||||
<!-- 订单状态:CANCELLED("Cancelled", "已取消"); -->
|
||||
<!-- 该订单已被取消 -->
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button v-if="orderDetail.orderStatusCode == 'Processing'" type="danger"
|
||||
@click="withdrawOrder">取消发货并退款</el-button>
|
||||
<el-button
|
||||
v-if="orderDetail.orderStatusCode == 'Processing' || orderDetail.orderStatusCode == 'Shipped'"
|
||||
type="primary" @click="saveEdit">保存发货信息</el-button>
|
||||
<el-button @click="visible = false">关 闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import manageList from '../components/manage-list.vue';
|
||||
import * as shopOrderApi from '../api/shop-order';
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import { FormInstance, FormRules, ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Delete, Edit, Search, Plus, Filter, Download } from '@element-plus/icons-vue';
|
||||
|
||||
const visible: any = ref(false);
|
||||
|
||||
const orderDetail = ref({} as any);
|
||||
const orderItem = ref([] as any);
|
||||
|
||||
const shippingInfo = ref({} as any); // 发货信息
|
||||
|
||||
let completeCallback: Function = () => { };
|
||||
|
||||
const editHandle = async function (tabIndex: number, row: any, refreshFunc: Function) {
|
||||
console.log("tabIndex", tabIndex)
|
||||
console.log("orderDetail", row)
|
||||
orderDetail.value = row // 订单详情
|
||||
|
||||
shippingInfo.value.expressId = row.expressId
|
||||
shippingInfo.value.comment = row.comment
|
||||
|
||||
completeCallback = refreshFunc
|
||||
visible.value = true
|
||||
|
||||
shopOrderApi.getOrderDetail({
|
||||
orderId: row.id
|
||||
}).then(function (data) {
|
||||
let orderGoods = {}
|
||||
data.goods.forEach((good: any) => {
|
||||
orderGoods[good.id] = good
|
||||
});
|
||||
|
||||
orderItem.value = data.orderItem.map((item: any) => {
|
||||
item.good = orderGoods[item.goodId]
|
||||
return item
|
||||
})
|
||||
|
||||
console.log("orderItem", orderItem.value)
|
||||
})
|
||||
}
|
||||
|
||||
const saveEdit = function () {
|
||||
// 保存发货信息
|
||||
ElMessageBox.confirm('确定要进行发货操作吗?发货后订单将不可撤销。(用户点击收货前仍可以修改发货信息)', '提示', { type: 'warning' })
|
||||
.then(async () => {
|
||||
shopOrderApi.deliverOrder({
|
||||
orderId: orderDetail.value.id,
|
||||
...shippingInfo.value
|
||||
}).then(function (data) {
|
||||
if (data) { // 如果出错则已经出提示了
|
||||
ElMessage.success({ message: data })
|
||||
visible.value = false
|
||||
completeCallback()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => { });
|
||||
}
|
||||
|
||||
const withdrawOrder = function () {
|
||||
// 取消订单
|
||||
ElMessageBox.confirm('确定要取消此订单吗?该操作不可撤销。', '提示', { type: 'warning' })
|
||||
.then(async () => {
|
||||
shopOrderApi.withdrawOrder({
|
||||
orderId: orderDetail.value.id,
|
||||
...shippingInfo.value
|
||||
}).then(function (data) {
|
||||
if (data) { // 如果出错则已经出提示了
|
||||
ElMessage.success({ message: data })
|
||||
visible.value = false
|
||||
completeCallback()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => { });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.line-height {
|
||||
line-height: 2.7em;
|
||||
}
|
||||
|
||||
.row-index {
|
||||
width: 110px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
@@ -1,67 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>基础信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="info">
|
||||
<div class="info-image" @click="showDialog">
|
||||
<el-avatar :size="100" :src="avatarImg" />
|
||||
<span class="info-edit">
|
||||
<i class="el-icon-lx-camerafill"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-name">{{ name }}</div>
|
||||
<!-- <div class="info-desc">不可能!我的代码怎么可能会有bug!</div> -->
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>账户编辑</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="用户名:"> {{ name }} </el-form-item>
|
||||
<el-form-item label="旧密码:">
|
||||
<el-input type="password" v-model="form.old"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码:">
|
||||
<el-input type="password" v-model="form.new"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码:">
|
||||
<el-input type="password" v-model="form.new1"></el-input>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="个人简介:">
|
||||
<el-input v-model="form.desc"></el-input>
|
||||
</el-form-item> -->
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="裁剪图片" v-model="dialogVisible" width="600px">
|
||||
<vue-cropper ref="cropper" :src="imgSrc" :ready="cropImage" :zoom="cropImage" :cropmove="cropImage"
|
||||
style="width: 100%; height: 400px"></vue-cropper>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>基础信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="info">
|
||||
<div class="info-image" @click="showDialog">
|
||||
<el-avatar :size="100" :src="avatarImg" />
|
||||
<span class="info-edit">
|
||||
<i class="el-icon-lx-camerafill"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-name">{{ name }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span>账户编辑</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="用户名:"> {{ name }} </el-form-item>
|
||||
<el-form-item label="旧密码:">
|
||||
<el-input type="password" v-model="form.old"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码:">
|
||||
<el-input type="password" v-model="form.new"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码:">
|
||||
<el-input type="password" v-model="form.new1"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="裁剪图片" v-model="dialogVisible" width="600px">
|
||||
<vue-cropper ref="cropper" :src="imgSrc" :ready="cropImage" :zoom="cropImage" :cropmove="cropImage"
|
||||
style="width: 100%; height: 400px"></vue-cropper>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button class="crop-demo-btn" type="primary">选择图片
|
||||
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
|
||||
</el-button>
|
||||
<el-button type="primary" @click="saveAvatar">上传并保存</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button class="crop-demo-btn" type="primary">选择图片
|
||||
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
|
||||
</el-button>
|
||||
<el-button type="primary" @click="saveAvatar">上传并保存</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="user">
|
||||
@@ -70,65 +66,52 @@ import VueCropper from 'vue-cropperjs';
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
import avatar from '../assets/img/img.jpg';
|
||||
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
|
||||
import send_request from '../utils/send_request';
|
||||
import * as userApi from '../api/user';
|
||||
|
||||
const name = localStorage.getItem('ms_username');
|
||||
const user_id = localStorage.getItem('ms_user_id');
|
||||
const form = reactive({
|
||||
user_id: user_id,
|
||||
old: '',
|
||||
new: '',
|
||||
new1: '',
|
||||
desc: ''
|
||||
user_id: user_id,
|
||||
old: '',
|
||||
new: '',
|
||||
new1: '',
|
||||
desc: ''
|
||||
});
|
||||
const onSubmit = async () => {
|
||||
if (form.old == '' || form.new == '' || form.new1 == '') {
|
||||
// 弹窗
|
||||
ElMessageBox.confirm('输入为空,请检查', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
} else if (form.new != form.new1) {
|
||||
// 弹窗
|
||||
ElMessageBox.confirm('新密码2次输入的不相同', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
} else if (form.new == form.old) {
|
||||
// 弹窗
|
||||
ElMessageBox.confirm('新、旧密码相同', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (form.old == '' || form.new == '' || form.new1 == '') {
|
||||
// 弹窗
|
||||
ElMessageBox.confirm('输入为空,请检查', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
} else if (form.new != form.new1) {
|
||||
// 弹窗
|
||||
ElMessageBox.confirm('新密码2次输入的不相同', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
} else if (form.new == form.old) {
|
||||
// 弹窗
|
||||
ElMessageBox.confirm('新、旧密码相同', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm('确认要修改密码吗?', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
console.log("send_request v1/user/alterPSW")
|
||||
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '请稍候',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
});
|
||||
await send_request('v1/user/alterPSW', "POST", {
|
||||
"userId": form.user_id,
|
||||
"oldPSW": form.old,
|
||||
"newPSW": form.new
|
||||
}, (data: any) => {
|
||||
console.log(data);
|
||||
ElMessage.success('删除成功');
|
||||
// tableData.value.splice(index, 1);
|
||||
})
|
||||
loading.close();
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.success('删除失败');
|
||||
});
|
||||
return;
|
||||
ElMessageBox.confirm('确认要修改密码吗?', '提示', {
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
userApi.updatePwd({
|
||||
oldpwd: form.old,
|
||||
newpwd: form.new,
|
||||
}).then((data) => {
|
||||
data && ElMessage.success(data);
|
||||
})
|
||||
}).catch((err) => {
|
||||
ElMessage.success('删除失败');
|
||||
console.log("err", err)
|
||||
});
|
||||
return;
|
||||
|
||||
};
|
||||
|
||||
@@ -139,92 +122,92 @@ const dialogVisible = ref(false);
|
||||
const cropper: any = ref();
|
||||
|
||||
const showDialog = () => {
|
||||
dialogVisible.value = true;
|
||||
imgSrc.value = avatarImg.value;
|
||||
dialogVisible.value = true;
|
||||
imgSrc.value = avatarImg.value;
|
||||
};
|
||||
|
||||
const setImage = (e: any) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file.type.includes('image/')) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event: any) => {
|
||||
dialogVisible.value = true;
|
||||
imgSrc.value = event.target.result;
|
||||
cropper.value && cropper.value.replace(event.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
const file = e.target.files[0];
|
||||
if (!file.type.includes('image/')) {
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event: any) => {
|
||||
dialogVisible.value = true;
|
||||
imgSrc.value = event.target.result;
|
||||
cropper.value && cropper.value.replace(event.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const cropImage = () => {
|
||||
cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
|
||||
cropImg.value = cropper.value.getCroppedCanvas().toDataURL();
|
||||
};
|
||||
|
||||
const saveAvatar = () => {
|
||||
avatarImg.value = cropImg.value;
|
||||
dialogVisible.value = false;
|
||||
avatarImg.value = cropImg.value;
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.info {
|
||||
text-align: center;
|
||||
padding: 35px 0;
|
||||
text-align: center;
|
||||
padding: 35px 0;
|
||||
}
|
||||
|
||||
.info-image {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: #f8f8f8;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 50px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: #f8f8f8;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info-edit {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.info-edit i {
|
||||
color: #eee;
|
||||
font-size: 25px;
|
||||
color: #eee;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.info-image:hover .info-edit {
|
||||
opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.info-name {
|
||||
margin: 15px 0 10px;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
margin: 15px 0 10px;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.crop-demo-btn {
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.crop-input {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,20 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
import { setGlobalDataPlugin } from '@tarojs/taro'
|
||||
import './app.css'
|
||||
|
||||
const App = createApp({
|
||||
onShow(options) { },
|
||||
// 入口组件不需要实现 render 方法,即使实现了也会被 taro 所覆盖
|
||||
})
|
||||
|
||||
// 全局变量 refer: https://docs.taro.zone/docs/come-from-miniapp#%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F
|
||||
App.use(setGlobalDataPlugin, {
|
||||
globalData: {
|
||||
debugMode: true, // 是否展示调试内容
|
||||
baseUrl: true
|
||||
? "https://epp-prod.only4.work"
|
||||
: "http://localhost", // 不带最后的 /
|
||||
}
|
||||
})
|
||||
|
||||
export default App
|
@@ -1,9 +1,16 @@
|
||||
server
|
||||
{
|
||||
server_name epp-prod.only4.work;
|
||||
server_name epp.only4.work;
|
||||
listen 80;
|
||||
listen 443 ssl http2;
|
||||
|
||||
# 并发限制 限制当前站点最大并发数
|
||||
limit_conn perserver 50;
|
||||
# 单IP限制 限制单个IP访问最大并发数
|
||||
limit_conn perip 10;
|
||||
# 流量限制 限制每个请求的流量上限(单位:KB)
|
||||
limit_rate 8192k;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5203;
|
||||
|
||||
|
@@ -22,9 +22,14 @@ App({
|
||||
},
|
||||
globalData: {
|
||||
// debugMode: true, // 是否展示调试内容
|
||||
baseUrl: true ? // Api 请求域名 不带最后的 /
|
||||
"https://epp-prod.only4.work" :
|
||||
"http://localhost",
|
||||
/**
|
||||
* FIXME 环境配置
|
||||
*
|
||||
* baseUrl
|
||||
* - 线上环境:'https://epp.only4.work'
|
||||
* - 开发环境:'http://localhost'
|
||||
*/
|
||||
baseUrl: "http://localhost", // Api 请求域名 不带最后的 /
|
||||
userInfo: null
|
||||
}
|
||||
})
|
||||
|
@@ -1,97 +1,97 @@
|
||||
var defaultTabbarItem = [
|
||||
"pages/person/person",
|
||||
"pages/residents/report",
|
||||
"pages/index/index"
|
||||
]
|
||||
|
||||
export function toggle(that) {
|
||||
let pages = getCurrentPages()
|
||||
let route = pages[pages.length - 1].route
|
||||
console.log("[CustomTabBar] route", route)
|
||||
|
||||
if (!typeof that.getTabBar === 'function' || !that.getTabBar()) {
|
||||
// wx.showModal({
|
||||
// title: '版本太旧',
|
||||
// content: '您的微信版本太旧,无法使用本小程序',
|
||||
// showCancel: false,
|
||||
// complete: (res) => {
|
||||
// wx.exitMiniProgram()
|
||||
// }
|
||||
// })
|
||||
return
|
||||
}
|
||||
|
||||
// 找出要选择哪一项
|
||||
let tabBar = that.getTabBar()
|
||||
console.log("[CustomTabBar] tabBar", tabBar)
|
||||
|
||||
let data = tabBar.data
|
||||
console.log("[CustomTabBar] data", data)
|
||||
|
||||
let showPagePathList = wx.getStorageSync('tabbarItem') || defaultTabbarItem
|
||||
console.log("[CustomTabBar] showPagePathList", showPagePathList)
|
||||
|
||||
let list = tabBar.data.rawList.filter((page) => showPagePathList.includes(page.pagePath))
|
||||
console.log("[CustomTabBar] tabBarList", list)
|
||||
|
||||
let selected = list ? list.indexOf(list.find(p => p.pagePath == route)) : -1
|
||||
console.log("[CustomTabBar] selected", selected)
|
||||
|
||||
console.log("更新tabbar")
|
||||
|
||||
// 选中这一项
|
||||
tabBar.setData({
|
||||
selected: selected,
|
||||
list: list
|
||||
})
|
||||
}
|
||||
|
||||
export function updateConfig(baseUrl) {
|
||||
// 请求配置文件(用于审核时隐藏部分功能)
|
||||
wx.request({
|
||||
url: baseUrl + '/getConfig',
|
||||
success(result) {
|
||||
let data = result.data
|
||||
if (data.tabbarItem) {
|
||||
console.log("[CustomTabBar] tabbar数据拉取完毕")
|
||||
wx.setStorageSync('tabbarItem', data.tabbarItem)
|
||||
wx.setStorageSync('indexItem', data.indexItem)
|
||||
|
||||
const pages = getCurrentPages();
|
||||
const indexPage = pages[0]
|
||||
const currentPage = pages[pages.length - 1]
|
||||
console.log("[CustomTabBar] indexPage", indexPage)
|
||||
console.log("[CustomTabBar] currentPage", currentPage)
|
||||
|
||||
// // 更新tabbar
|
||||
// toggle(currentPage)
|
||||
// 刷新首页中包含了触发 toggle 的代码 此处不重复触发
|
||||
|
||||
// 刷新首页
|
||||
indexPage && indexPage.onLoad()
|
||||
|
||||
// let tabBar = currentPage.getTabBar()
|
||||
// tabBar.setData({
|
||||
// list: tabBar.data.rawList.filter((page) => itemList.includes(page.pagePath))
|
||||
// })
|
||||
}
|
||||
},
|
||||
fail() {
|
||||
wx.showModal({
|
||||
title: '小程序启动失败',
|
||||
content: '点击确认重试,若多次失败请检查网络连接',
|
||||
complete: (res) => {
|
||||
if (res.cancel) {
|
||||
wx.exitMiniProgram()
|
||||
}
|
||||
|
||||
if (res.confirm) {
|
||||
wx.reLaunch({
|
||||
url: 'pages/index/index',
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
var defaultTabbarItem = [
|
||||
"pages/person/person",
|
||||
"pages/residents/report",
|
||||
"pages/index/index"
|
||||
]
|
||||
|
||||
export function toggle(that) {
|
||||
let pages = getCurrentPages()
|
||||
let route = pages[pages.length - 1].route
|
||||
console.log("[CustomTabBar] route", route)
|
||||
|
||||
if (!typeof that.getTabBar === 'function' || !that.getTabBar()) {
|
||||
// wx.showModal({
|
||||
// title: '版本太旧',
|
||||
// content: '您的微信版本太旧,无法使用本小程序',
|
||||
// showCancel: false,
|
||||
// complete: (res) => {
|
||||
// wx.exitMiniProgram()
|
||||
// }
|
||||
// })
|
||||
return
|
||||
}
|
||||
|
||||
// 找出要选择哪一项
|
||||
let tabBar = that.getTabBar()
|
||||
console.log("[CustomTabBar] tabBar", tabBar)
|
||||
|
||||
let data = tabBar.data
|
||||
console.log("[CustomTabBar] data", data)
|
||||
|
||||
let showPagePathList = wx.getStorageSync('tabbarItem') || defaultTabbarItem
|
||||
console.log("[CustomTabBar] showPagePathList", showPagePathList)
|
||||
|
||||
let list = tabBar.data.rawList.filter((page) => showPagePathList.includes(page.pagePath))
|
||||
console.log("[CustomTabBar] tabBarList", list)
|
||||
|
||||
let selected = list ? list.indexOf(list.find(p => p.pagePath == route)) : -1
|
||||
console.log("[CustomTabBar] selected", selected)
|
||||
|
||||
console.log("更新tabbar")
|
||||
|
||||
// 选中这一项
|
||||
tabBar.setData({
|
||||
selected: selected,
|
||||
list: list
|
||||
})
|
||||
}
|
||||
|
||||
export function updateConfig(baseUrl) {
|
||||
// 请求配置文件(用于审核时隐藏部分功能)
|
||||
wx.request({
|
||||
url: baseUrl + '/getConfig?v=2',
|
||||
success(result) {
|
||||
let data = result.data
|
||||
if (data.tabbarItem) {
|
||||
console.log("[CustomTabBar] tabbar数据拉取完毕")
|
||||
wx.setStorageSync('tabbarItem', data.tabbarItem)
|
||||
wx.setStorageSync('indexItem', data.indexItem)
|
||||
|
||||
const pages = getCurrentPages();
|
||||
const indexPage = pages[0]
|
||||
const currentPage = pages[pages.length - 1]
|
||||
console.log("[CustomTabBar] indexPage", indexPage)
|
||||
console.log("[CustomTabBar] currentPage", currentPage)
|
||||
|
||||
// // 更新tabbar
|
||||
// toggle(currentPage)
|
||||
// 刷新首页中包含了触发 toggle 的代码 此处不重复触发
|
||||
|
||||
// 刷新首页
|
||||
indexPage && indexPage.onLoad()
|
||||
|
||||
// let tabBar = currentPage.getTabBar()
|
||||
// tabBar.setData({
|
||||
// list: tabBar.data.rawList.filter((page) => itemList.includes(page.pagePath))
|
||||
// })
|
||||
}
|
||||
},
|
||||
fail() {
|
||||
wx.showModal({
|
||||
title: '小程序启动失败',
|
||||
content: '点击确认重试,若多次失败请检查网络连接',
|
||||
complete: (res) => {
|
||||
if (res.cancel) {
|
||||
wx.exitMiniProgram()
|
||||
}
|
||||
|
||||
if (res.confirm) {
|
||||
wx.reLaunch({
|
||||
url: 'pages/index/index',
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -7,16 +7,16 @@
|
||||
<view class="good-card-info">
|
||||
<!-- <text class="good-title line-wrap">{{ goodinfo.goodsName }}</text> -->
|
||||
<text class="good-title line-wrap">{{ goodinfo.brief }}</text>
|
||||
<view class="good-price good-price-counter">
|
||||
<!-- 原价 -->
|
||||
<view class="good-price-symbol">¥</view>
|
||||
<view class="good-price-number">{{ goodinfo.counterPrice }}</view>
|
||||
</view>
|
||||
<view class="good-price good-price-retail">
|
||||
<!-- 售价 -->
|
||||
<view style="height:0.12em;" class="good-price-line"></view>
|
||||
<view class="good-price-symbol">¥</view>
|
||||
<view class="good-price-number">{{ goodinfo.retailPrice }}</view>
|
||||
</view>
|
||||
<view class="good-price good-price-counter">
|
||||
<!-- 原价 -->
|
||||
<view style="height:0.12em;" class="good-price-line"></view>
|
||||
<view class="good-price-symbol">¥</view>
|
||||
<view class="good-price-number">{{ goodinfo.counterPrice }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
@@ -52,12 +52,12 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.good-price-counter {
|
||||
.good-price-retail {
|
||||
color: #fa4126;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.good-price-retail {
|
||||
.good-price-counter {
|
||||
color: #bbbbbb;
|
||||
font-size: 24rpx;
|
||||
margin: 0 0 0 8rpx;
|
||||
|
@@ -137,7 +137,7 @@ Page({
|
||||
filterList: filterList,
|
||||
filterActiveName: "全部",
|
||||
orderList: userOrder.orders.map(order => {
|
||||
order.displayDate = order.orderDate.replace("T", " ")
|
||||
order.displayDate = order.orderDate
|
||||
return order
|
||||
})
|
||||
})
|
||||
@@ -153,7 +153,7 @@ Page({
|
||||
console.log("userOrder", userOrder)
|
||||
this.setData({
|
||||
orderList: userOrder.orders.map(order => {
|
||||
order.displayDate = order.orderDate.replace("T", " ")
|
||||
order.displayDate = order.orderDate
|
||||
return order
|
||||
})
|
||||
})
|
||||
|
@@ -259,7 +259,7 @@ Page({
|
||||
this.setData({
|
||||
order: orderDetail.order,
|
||||
orderGoodList: orderGoodList,
|
||||
orderTime: orderDetail.order.orderDate.replace("T", " "),
|
||||
orderTime: orderDetail.order.orderDate,
|
||||
orderStatusCode: orderStatusCode,
|
||||
orderPrice: orderPrice,
|
||||
})
|
||||
|
16
z 论文TODO.txt
@@ -7,20 +7,4 @@ TODO
|
||||
文档中的TODO处理
|
||||
|
||||
|
||||
|
||||
论文可以参考一下:https://blog.csdn.net/WeiXin_DZbishe/article/details/127091024
|
||||
|
||||
|
||||
【中英论文下载】
|
||||
强烈推荐使用谷歌浏览器
|
||||
卡号:356983353,密码:270045
|
||||
打开链接
|
||||
www.mv616.xyz,点击页面顶端【会员登录】
|
||||
输入发给你的卡号卡密,点击【资源列表】然后点击【中文/英文数据库】
|
||||
_________________________________________________________________
|
||||
|
||||
1,每个数据库里面很多入口,选择一个点击进去,搜不到或者不能下载的,换其他入口使用即可!
|
||||
2,三小时内5星带10字以上好评告知客服,免费送第三方查重工具哦!
|
||||
|
||||
|
||||
*不限制下载数量,遇到使用问题请咨询客服或者留言,不满意支持退款的哦!
|
||||
|
3
分支说明.md
Normal file
@@ -0,0 +1,3 @@
|
||||
main: 开发分支 (域名为localhost; 门禁端小程序环境为develop)
|
||||
production: 生产分支 (基于开发分支,域名改为epp.only4.work; 门禁端小程序环境改为release)
|
||||
miniprogramAudit: 小程序提审分支 (基于生产分支,从后端动态隐藏小程序端社区码&小商店功能)
|
167
论文中数据字典.md
@@ -1,26 +1,21 @@
|
||||
> 使用 phpMyAdmin 导出,如果有修改,可以同步修改到论文正文
|
||||
|
||||
## apply1
|
||||
## access_log
|
||||
|
||||
| 字段 | 类型 | 空 | 默认 | 注释 |
|
||||
| :---------- | :---------- | :--- | :----- | :--------------------------- |
|
||||
| id *(主键)* | int | 否 | | |
|
||||
| stu_id | int | 是 | *NULL* | 用户id |
|
||||
| issue | varchar(50) | 是 | *NULL* | 申请事由 |
|
||||
| transport | varchar(20) | 是 | *NULL* | 出行方式 |
|
||||
| place | varchar(50) | 是 | *NULL* | 目的地 |
|
||||
| start_time | datetime | 是 | *NULL* | 开始时间 |
|
||||
| end_time | datetime | 是 | *NULL* | 结束时间 |
|
||||
| state | int | 是 | 0 | 状态(0:审批中,1:通过,2:驳回) |
|
||||
| reason | varchar(20) | 是 | *NULL* | 驳回原因 |
|
||||
表注释: *人员进出记录表*
|
||||
|
||||
### 索引
|
||||
| 字段 | 类型 | 空 | 默认 | 注释 |
|
||||
| :------------- | :---------------- | :--- | :---------------- | :------------------- |
|
||||
| id | bigint | 否 | | 雪花id |
|
||||
| time | datetime | 否 | CURRENT_TIMESTAMP | 进出时间 |
|
||||
| user_id | int | 否 | | 用户id |
|
||||
| user_real_name | varchar(255) | 是 | *NULL* | 用户真实姓名 |
|
||||
| gate_id | bigint | 否 | | 大门id |
|
||||
| type | enum('IN', 'OUT') | 是 | *NULL* | 类型(进门 OR 出门) |
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 10 | A | 否 | |
|
||||
没有已定义的索引!
|
||||
|
||||
## community_gate
|
||||
## gate
|
||||
|
||||
表注释: *社区大门*
|
||||
|
||||
@@ -32,38 +27,6 @@
|
||||
|
||||
### 索引
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 0 | A | 否 | |
|
||||
|
||||
## count1
|
||||
|
||||
| 字段 | 类型 | 空 | 默认 | 注释 |
|
||||
| :------------ | :------- | :--- | :----- | :------- |
|
||||
| date *(主键)* | date | 否 | | 日期 |
|
||||
| time | datetime | 是 | *NULL* | 时间 |
|
||||
| in_num | int | 是 | *NULL* | 入校人数 |
|
||||
| out_num | int | 是 | *NULL* | 出校人数 |
|
||||
|
||||
### 索引
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | date | 0 | A | 否 | |
|
||||
|
||||
## feedback1
|
||||
|
||||
| 字段 | 类型 | 空 | 默认 | 注释 |
|
||||
| :---------- | :----------- | :--- | :----- | :--------- |
|
||||
| id *(主键)* | int | 否 | | |
|
||||
| title | varchar(255) | 是 | *NULL* | 反馈标题 |
|
||||
| content | longtext | 是 | *NULL* | 反馈内容 |
|
||||
| time | datetime | 是 | *NULL* | 反馈时间 |
|
||||
| user_id | int | 是 | *NULL* | 反馈用户id |
|
||||
| reply | varchar(255) | 是 | *NULL* | 回复 |
|
||||
|
||||
### 索引
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 5 | A | 否 | |
|
||||
@@ -94,10 +57,10 @@
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :---------- | :---- | :--- | :--- | :---------- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 87 | A | 否 | |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 89 | A | 否 | |
|
||||
| category_id | BTREE | 否 | 否 | category_id | 8 | A | 是 | |
|
||||
| brand_id | BTREE | 否 | 否 | brand | 67 | A | 是 | |
|
||||
| sort_order | BTREE | 否 | 否 | sort_order | 25 | A | 是 | |
|
||||
| brand_id | BTREE | 否 | 否 | brand | 69 | A | 是 | |
|
||||
| sort_order | BTREE | 否 | 否 | sort_order | 27 | A | 是 | |
|
||||
|
||||
## goods_category
|
||||
|
||||
@@ -111,22 +74,7 @@
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 7 | A | 否 | |
|
||||
|
||||
## notice1
|
||||
|
||||
| 字段 | 类型 | 空 | 默认 | 注释 |
|
||||
| :---------- | :----------- | :--- | :----- | :--- |
|
||||
| id *(主键)* | int | 否 | | |
|
||||
| title | varchar(255) | 是 | *NULL* | 标题 |
|
||||
| content | longtext | 是 | *NULL* | 内容 |
|
||||
| time | date | 是 | *NULL* | 时间 |
|
||||
|
||||
### 索引
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 5 | A | 否 | |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 8 | A | 否 | |
|
||||
|
||||
## order
|
||||
|
||||
@@ -138,13 +86,18 @@
|
||||
| order_status | enum('Pending', 'Processing', 'Shipped', 'Delivered', 'Cancelled') | 否 | | 订单状态 |
|
||||
| order_price | decimal(10,2) | 否 | | 订单总金额 |
|
||||
| pay_date | datetime | 是 | *NULL* | 订单支付时间 |
|
||||
| cancel_date | datetime | 是 | *NULL* | 订单取消时间 |
|
||||
| ship_date | datetime | 是 | *NULL* | 订单发货时间 |
|
||||
| deliver_date | datetime | 是 | *NULL* | 订单送达时间 |
|
||||
| express_id | varchar(500) | 是 | *NULL* | 快递单号 |
|
||||
| comment | text | 是 | *NULL* | 发货备注 |
|
||||
|
||||
### 索引
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :------ | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 4 | A | 否 | |
|
||||
| id | BTREE | 是 | 否 | id | 4 | A | 否 | |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 15 | A | 否 | |
|
||||
| id | BTREE | 是 | 否 | id | 15 | A | 否 | |
|
||||
| user_id | BTREE | 否 | 否 | user_id | 1 | A | 否 | |
|
||||
|
||||
## order_detail
|
||||
@@ -161,9 +114,9 @@
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------- | :---- | :--- | :--- | :------- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 10 | A | 否 | |
|
||||
| order_id | BTREE | 否 | 否 | order_id | 4 | A | 否 | |
|
||||
| good_id | BTREE | 否 | 否 | good_id | 2 | A | 否 | |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 21 | A | 否 | |
|
||||
| order_id | BTREE | 否 | 否 | order_id | 15 | A | 否 | |
|
||||
| good_id | BTREE | 否 | 否 | good_id | 9 | A | 否 | |
|
||||
|
||||
## report
|
||||
|
||||
@@ -180,7 +133,20 @@
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 1 | A | 否 | |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 12 | A | 否 | |
|
||||
|
||||
## role
|
||||
|
||||
| 字段 | 类型 | 空 | 默认 | 注释 |
|
||||
| :---------- | :----------- | :--- | :--- | :------- |
|
||||
| id *(主键)* | int | 否 | | role_id |
|
||||
| role_name | varchar(255) | 否 | | roleName |
|
||||
|
||||
### 索引
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 6 | A | 否 | |
|
||||
|
||||
## setting
|
||||
|
||||
@@ -198,42 +164,27 @@
|
||||
|
||||
## user
|
||||
|
||||
| 字段 | 类型 | 空 | 默认 | 注释 |
|
||||
| :-------------- | :----------- | :--- | :----- | :----------------------------------------------------------- |
|
||||
| id *(主键)* | int | 否 | | |
|
||||
| username | varchar(20) | 否 | | 用户名 |
|
||||
| password | varchar(255) | 否 | | 密码 |
|
||||
| realname | varchar(20) | 是 | *NULL* | 真实姓名 |
|
||||
| id_number | varchar(18) | 是 | *NULL* | 身份证号 |
|
||||
| phone_number | varchar(11) | 是 | *NULL* | 手机号 |
|
||||
| role | int | 否 | | 角色 (0-超级管理员 1-工作人员 2-社区居民_房主 3-社区居民_家庭成员 4-社区居民_租客 5-访客) |
|
||||
| building_id | varchar(255) | 是 | *NULL* | 门栋号+单元号 |
|
||||
| doorplate | varchar(255) | 是 | *NULL* | 门牌号 |
|
||||
| permission | int | 否 | 0 | 进出权限 (0-无 1-继承(普通居民) 2-永久 3-限时) |
|
||||
| permission_time | datetime | 是 | *NULL* | 进出权限失效时间 |
|
||||
|
||||
### 索引
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 1 | A | 否 | |
|
||||
|
||||
## visitor1
|
||||
|
||||
| 字段 | 类型 | 空 | 默认 | 注释 |
|
||||
| :---------- | :----------- | :--- | :----- | :---------------------------------- |
|
||||
| id *(主键)* | int | 否 | | |
|
||||
| time | datetime | 是 | *NULL* | 预约时间 |
|
||||
| phone | varchar(20) | 是 | *NULL* | 预约用户id |
|
||||
| issue | varchar(255) | 是 | *NULL* | 预约事由 |
|
||||
| meet_name | varchar(20) | 是 | *NULL* | 会见人姓名 |
|
||||
| create_time | datetime | 是 | *NULL* | 创建时间 |
|
||||
| state | int | 是 | 0 | 状态(0:审批中,1:成功,2:驳回) |
|
||||
| 字段 | 类型 | 空 | 默认 | 注释 |
|
||||
| :-------------- | :----------- | :--- | :----- | :--------------------------------------------- |
|
||||
| id *(主键)* | int | 否 | | |
|
||||
| username | varchar(20) | 否 | | 用户名 |
|
||||
| password | varchar(255) | 否 | | 密码 |
|
||||
| realname | varchar(20) | 是 | *NULL* | 真实姓名 |
|
||||
| id_number | varchar(18) | 是 | *NULL* | 身份证号 |
|
||||
| phone_number | varchar(20) | 是 | *NULL* | 手机号 |
|
||||
| role_id | int | 否 | | 角色id |
|
||||
| building_id | varchar(255) | 是 | *NULL* | 门栋号+单元号 |
|
||||
| doorplate | varchar(255) | 是 | *NULL* | 门牌号 |
|
||||
| permission | varchar(20) | 否 | 0 | 进出权限 (0-无 1-继承(普通居民) 2-永久 3-限时) |
|
||||
| permission_time | datetime | 是 | *NULL* | 进出权限失效时间 |
|
||||
| wx_code | varchar(255) | 是 | *NULL* | 微信登录授权码 |
|
||||
|
||||
### 索引
|
||||
|
||||
|
||||
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------ | :---- | :--- | :--- | :--- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 1 | A | 否 | |
|
||||
| 键名 | 类型 | 唯一 | 紧凑 | 字段 | 基数 | 排序规则 | 空 | 注释 |
|
||||
| :------- | :---- | :--- | :--- | :------- | :--- | :------- | :--- | :--- |
|
||||
| PRIMARY | BTREE | 是 | 否 | id | 1 | A | 否 | |
|
||||
| username | BTREE | 是 | 否 | username | 1 | A | 否 | |
|
||||
| wx_code | BTREE | 是 | 否 | wx_code | 1 | A | 是 | |
|