Compare commits
8 Commits
3af309d442
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
721543f423 | ||
|
|
324679964c | ||
|
|
9be6fd6ec9 | ||
|
|
98aa066ae4 | ||
|
|
4d833d7882 | ||
|
|
ddae147aef | ||
|
|
2991dd88bb | ||
|
|
dfef98814b |
@@ -1,10 +1,10 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 觉远创智管理系统
|
||||
VUE_APP_TITLE = 觉远创智功能镜片配镜系统
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
# 觉远创智管理系统/开发环境
|
||||
# 觉远创智功能镜片配镜系统/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# 路由懒加载
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 觉远创智管理系统
|
||||
VUE_APP_TITLE = 觉远创智功能镜片配镜系统
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
# 觉远创智管理系统/开发环境
|
||||
# 觉远创智功能镜片配镜系统/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# 路由懒加载
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 觉远创智管理系统
|
||||
VUE_APP_TITLE = 觉远创智功能镜片配镜系统
|
||||
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
|
||||
# 觉远创智管理系统/生产环境
|
||||
# 觉远创智功能镜片配镜系统/生产环境
|
||||
VUE_APP_BASE_API = '/prod-api'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 觉远创智管理系统
|
||||
VUE_APP_TITLE = 觉远创智功能镜片配镜系统
|
||||
|
||||
NODE_ENV = production
|
||||
|
||||
# 测试环境配置
|
||||
ENV = 'staging'
|
||||
|
||||
# 觉远创智管理系统/测试环境
|
||||
# 觉远创智功能镜片配镜系统/测试环境
|
||||
VUE_APP_BASE_API = '/stage-api'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ruoyi",
|
||||
"version": "3.8.8",
|
||||
"description": "觉远创智管理系统",
|
||||
"description": "觉远创智功能镜片配镜系统",
|
||||
"author": "觉远创智",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<!-- <title>11<%= webpackConfig.name %></title> -->
|
||||
<title>觉远创智管理系统</title>
|
||||
<title>觉远创智功能镜片配镜系统</title>
|
||||
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
||||
<style>
|
||||
html,
|
||||
|
||||
@@ -114,23 +114,6 @@ export function rejectExchangeOrder(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 拒绝换货申请订单完成
|
||||
export function completeExchangeRejectedOrder(data) {
|
||||
return request({
|
||||
url: '/jysystem/order/exchangeRejectedComplete',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 批量将拒绝换货申请订单更新为已完成
|
||||
export function completeAllExchangeRejectedOrder() {
|
||||
return request({
|
||||
url: '/jysystem/order/exchangeRejectedCompleteAll',
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
// 变更订单状态
|
||||
export function changeOrderStatus(data) {
|
||||
return request({
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 25 KiB |
BIN
admin-front/src/assets/logo/logo2.png
Normal file
BIN
admin-front/src/assets/logo/logo2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@@ -2,11 +2,9 @@
|
||||
<div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
|
||||
<transition name="sidebarLogoFade">
|
||||
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
||||
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
||||
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
|
||||
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
|
||||
</router-link>
|
||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
||||
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
|
||||
</router-link>
|
||||
</transition>
|
||||
@@ -14,7 +12,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import logoImg from '@/assets/logo/logo.png'
|
||||
import variables from '@/assets/styles/variables.scss'
|
||||
|
||||
export default {
|
||||
@@ -35,9 +32,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// title: process.env.VUE_APP_TITLE,
|
||||
title: '觉远创智管理系统',
|
||||
logo: logoImg
|
||||
title: '觉远创智功能镜片配镜系统'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,13 +61,6 @@ export default {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
& .sidebar-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
& .sidebar-title {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
@@ -84,11 +72,5 @@ export default {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&.collapse {
|
||||
.sidebar-logo {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -172,15 +172,6 @@
|
||||
@click="handleQuickFilter('exchangeRejected')"
|
||||
>拒绝换货申请</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
size="mini"
|
||||
@click="handleBatchCompleteExchangeRejected"
|
||||
v-hasPermi="['jysystem:order:edit']"
|
||||
>更新拒绝换货申请为已完成</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
@@ -200,17 +191,22 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="B价格" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatPriceB(scope.row) }}</span>
|
||||
<span>{{ formatPriceAmount(formatPriceB(scope.row)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="C价格" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatPriceC(scope.row) }}</span>
|
||||
<span>{{ formatPriceAmount(formatPriceC(scope.row)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="物流费用" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatPriceAmount(formatLogisticsFee(scope.row)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="渐进多焦点价格" align="center" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatProgressiveMultifocalPrice(scope.row) }}</span>
|
||||
<span>{{ formatPriceAmount(formatProgressiveMultifocalPrice(scope.row)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" align="center" prop="orderStatus" width="130">
|
||||
@@ -539,17 +535,26 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="球镜S(度)" align="center" min-width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model="scope.row.sphereS" :precision="2" :step="0.25" controls-position="right" style="width: 100%" />
|
||||
<el-input-number v-model="scope.row.sphereS" :precision="2" :step="0.25" controls-position="right" style="width: 100%" @change="val => onSphereSChange(val, scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="柱镜C(度)" align="center" min-width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model="scope.row.cylinderC" :precision="2" :step="0.25" controls-position="right" style="width: 100%" />
|
||||
<el-input-number
|
||||
v-model="scope.row.cylinderC"
|
||||
:precision="2"
|
||||
:step="0.25"
|
||||
:min="getCylinderCMin(scope.row)"
|
||||
:max="getCylinderCMax(scope.row)"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
@change="val => onCylinderCChange(val, scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="轴位A(度)" align="center" min-width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model="scope.row.axisA" :min="0" :max="180" controls-position="right" style="width: 100%" />
|
||||
<el-input-number v-model="scope.row.axisA" :min="0" :max="180" :disabled="!isAxisAEditable(scope.row)" controls-position="right" style="width: 100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="瞳距(mm)" align="center" min-width="90">
|
||||
@@ -557,7 +562,7 @@
|
||||
<el-input-number v-model="scope.row.pd" :precision="2" :min="0" controls-position="right" style="width: 100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="瞳高(mm)" align="center" min-width="80">
|
||||
<el-table-column v-if="form.progressiveMultifocal === 1" label="瞳高(mm)" align="center" min-width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model="scope.row.pupilHeight" :precision="2" :min="0" controls-position="right" style="width: 100%" />
|
||||
</template>
|
||||
@@ -567,9 +572,9 @@
|
||||
<el-input v-model="scope.row.distanceVa" placeholder="远用VA" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="近用ADD(屈光度)" align="center" min-width="100">
|
||||
<el-table-column v-if="form.progressiveMultifocal === 1" label="近用ADD(屈光度)" align="center" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model="scope.row.nearAdd" :precision="2" :min="0" controls-position="right" style="width: 100%" />
|
||||
<el-input-number v-model="scope.row.nearAdd" :precision="2" :step="0.25" :min="0.75" :max="4" controls-position="right" style="width: 100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -850,21 +855,21 @@
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="B合计">
|
||||
<el-tag
|
||||
v-if="isPendingTagText(formatPriceAmount(formatPriceTotal(detail, 'priceB')))"
|
||||
v-if="isPendingTagText(formatPriceAmount(formatDetailPriceTotal(detail, 'priceB')))"
|
||||
type="danger"
|
||||
size="small"
|
||||
:disable-transitions="true"
|
||||
>{{ formatPriceAmount(formatPriceTotal(detail, 'priceB')) }}</el-tag>
|
||||
<span v-else class="price-total-value">{{ formatPriceAmount(formatPriceTotal(detail, 'priceB')) }}</span>
|
||||
>{{ formatPriceAmount(formatDetailPriceTotal(detail, 'priceB')) }}</el-tag>
|
||||
<span v-else class="price-total-value">{{ formatPriceAmount(formatDetailPriceTotal(detail, 'priceB')) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="C合计">
|
||||
<el-tag
|
||||
v-if="isPendingTagText(formatPriceAmount(formatPriceTotal(detail, 'priceC')))"
|
||||
v-if="isPendingTagText(formatPriceAmount(formatDetailPriceTotal(detail, 'priceC')))"
|
||||
type="danger"
|
||||
size="small"
|
||||
:disable-transitions="true"
|
||||
>{{ formatPriceAmount(formatPriceTotal(detail, 'priceC')) }}</el-tag>
|
||||
<span v-else class="price-total-value">{{ formatPriceAmount(formatPriceTotal(detail, 'priceC')) }}</span>
|
||||
>{{ formatPriceAmount(formatDetailPriceTotal(detail, 'priceC')) }}</el-tag>
|
||||
<span v-else class="price-total-value">{{ formatPriceAmount(formatDetailPriceTotal(detail, 'priceC')) }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="detail-logistics-fee-block">
|
||||
@@ -876,26 +881,6 @@
|
||||
<span>{{ formatPriceAmount(formatLogisticsLogFee(scope.row, detail)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应收结算" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag
|
||||
v-if="formatLogSaleSettleStatus(scope.row) != null"
|
||||
:options="dict.type.jy_sale_settle_status"
|
||||
:value="formatLogSaleSettleStatus(scope.row)"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付结算" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag
|
||||
v-if="formatLogPurchaseSettleStatus(scope.row) != null"
|
||||
:options="dict.type.jy_purchase_settle_status"
|
||||
:value="formatLogPurchaseSettleStatus(scope.row)"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="发货日期" align="center" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatShipDate(scope.row.operateTime) }}</span>
|
||||
@@ -987,7 +972,7 @@
|
||||
<span>{{ formatDetailPdValue(scope.row, scope.$index) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="瞳高(mm)" align="center" min-width="80">
|
||||
<el-table-column v-if="detail.progressiveMultifocal === 1" label="瞳高(mm)" align="center" min-width="80">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.pupilHeight != null ? scope.row.pupilHeight : '-' }}</span>
|
||||
</template>
|
||||
@@ -997,7 +982,7 @@
|
||||
<span>{{ scope.row.distanceVa != null && scope.row.distanceVa !== '' ? scope.row.distanceVa : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="近用ADD(屈光度)" align="center" min-width="100">
|
||||
<el-table-column v-if="detail.progressiveMultifocal === 1" label="近用ADD(屈光度)" align="center" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatDetailNearAdd(scope.row.nearAdd) }}</span>
|
||||
</template>
|
||||
@@ -1021,26 +1006,6 @@
|
||||
<dict-tag :options="dict.type.jy_order_status" :value="scope.row.orderStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应收结算" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag
|
||||
v-if="formatLogSaleSettleStatus(scope.row) != null"
|
||||
:options="dict.type.jy_sale_settle_status"
|
||||
:value="formatLogSaleSettleStatus(scope.row)"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付结算" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag
|
||||
v-if="formatLogPurchaseSettleStatus(scope.row) != null"
|
||||
:options="dict.type.jy_purchase_settle_status"
|
||||
:value="formatLogPurchaseSettleStatus(scope.row)"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="操作人" align="center" prop="operator" width="100" />
|
||||
<el-table-column label="操作时间" align="center" prop="operateTime" width="160" />
|
||||
@@ -1124,7 +1089,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-alert
|
||||
title="仅汇总所选年月内已完成状态的订单;品牌方生成应收(AR),镜片厂生成应付(AP)。"
|
||||
title="仅汇总所选年月内已完成或换货完成状态的订单;品牌方生成应收(AR),镜片厂生成应付(AP)。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
@@ -1142,7 +1107,7 @@
|
||||
import axios from 'axios'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
import { listOrder, getOrder, getOrderLogistics, delOrder, addOrder, updateOrder, previewAssignPrice, assignOrder, rejectOrder, closeOrder, completeOrder, agreeExchangeOrder, rejectExchangeOrder, completeExchangeRejectedOrder, completeAllExchangeRejectedOrder, changeOrderStatus } from "@/api/jysystem/order";
|
||||
import { listOrder, getOrder, getOrderLogistics, delOrder, addOrder, updateOrder, previewAssignPrice, assignOrder, rejectOrder, closeOrder, completeOrder, agreeExchangeOrder, rejectExchangeOrder, changeOrderStatus } from "@/api/jysystem/order";
|
||||
import { checkPermi } from "@/utils/permission";
|
||||
import { listBrandTenant, listLensFactoryTenant } from "@/api/jysystem/tenant";
|
||||
import { listLensOptions } from "@/api/jysystem/lens";
|
||||
@@ -1216,6 +1181,9 @@ export default {
|
||||
ORDER_STATUS_SHIPPED: '8',
|
||||
ORDER_STATUS_COMPLETED: '9',
|
||||
ORDER_STATUS_EXCHANGE_APPLY: '12',
|
||||
ORDER_STATUS_EXCHANGE_AGREED: '13',
|
||||
ORDER_STATUS_EXCHANGE_LOGISTICS: '15',
|
||||
ORDER_STATUS_EXCHANGE_COMPLETED: '17',
|
||||
ORDER_STATUS_EXCHANGE_REJECTED: '21',
|
||||
logisticsDialogVisible: false,
|
||||
logisticsBasic: null,
|
||||
@@ -1368,20 +1336,42 @@ export default {
|
||||
},
|
||||
formatPriceB(row) {
|
||||
if (!row) return '-';
|
||||
if (this.isExchangeOrder(row)) return '-';
|
||||
if (this.isPendingAssign(row)) return '未核算';
|
||||
return row.priceB != null ? row.priceB : '-';
|
||||
},
|
||||
formatPriceC(row) {
|
||||
if (!row) return '-';
|
||||
if (this.isExchangeOrder(row)) return '-';
|
||||
if (this.isPendingAssign(row)) return '未核算';
|
||||
return row.priceC != null ? row.priceC : '-';
|
||||
},
|
||||
formatProgressiveMultifocalPrice(row) {
|
||||
if (!row) return '-';
|
||||
if (this.isExchangeOrder(row)) return '-';
|
||||
if (Number(row.progressiveMultifocal) !== 1) return '-';
|
||||
if (this.isPendingAssign(row)) return '未核算';
|
||||
return row.progressiveMultifocalPrice != null ? row.progressiveMultifocalPrice : '-';
|
||||
},
|
||||
isExchangeOrder(row) {
|
||||
if (!row) return false;
|
||||
if (row.orderNo && String(row.orderNo).startsWith('HH')) return true;
|
||||
const status = row.orderStatus != null ? String(row.orderStatus) : '';
|
||||
return status === this.ORDER_STATUS_EXCHANGE_APPLY
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_AGREED
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_LOGISTICS
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_COMPLETED;
|
||||
},
|
||||
formatDetailPriceTotal(detail, priceKey) {
|
||||
if (this.isExchangeOrder(detail)) return this.formatDetailLogisticsFee(detail);
|
||||
return this.formatPriceTotal(detail, priceKey);
|
||||
},
|
||||
formatLogisticsFee(row) {
|
||||
if (!row) return '-';
|
||||
if (!this.isExchangeOrder(row)) return '-';
|
||||
const fee = this.parseLogisticsFeeValue(row.logisticsFee);
|
||||
return fee != null ? fee : '-';
|
||||
},
|
||||
formatPriceBC(row) {
|
||||
return `${this.formatPriceB(row)} / ${this.formatPriceC(row)}`;
|
||||
},
|
||||
@@ -1430,7 +1420,7 @@ export default {
|
||||
hasFee = true;
|
||||
}
|
||||
});
|
||||
if (!hasFee && logs.length > 0 && detail.logisticsFee != null) {
|
||||
if (!hasFee && detail.logisticsFee != null) {
|
||||
const legacyFee = this.parseLogisticsFeeValue(detail.logisticsFee);
|
||||
if (legacyFee != null) return legacyFee;
|
||||
}
|
||||
@@ -1440,24 +1430,6 @@ export default {
|
||||
formatDetailLogisticsFee(detail) {
|
||||
return this.sumDetailLogisticsFee(detail);
|
||||
},
|
||||
formatLogSaleSettleStatus(log) {
|
||||
if (!log || log.expressNo == null || String(log.expressNo).trim() === '') {
|
||||
return null;
|
||||
}
|
||||
if (log.saleSettleStatus != null && log.saleSettleStatus !== '') {
|
||||
return log.saleSettleStatus;
|
||||
}
|
||||
return '0';
|
||||
},
|
||||
formatLogPurchaseSettleStatus(log) {
|
||||
if (!log || log.expressNo == null || String(log.expressNo).trim() === '') {
|
||||
return null;
|
||||
}
|
||||
if (log.purchaseSettleStatus != null && log.purchaseSettleStatus !== '') {
|
||||
return log.purchaseSettleStatus;
|
||||
}
|
||||
return '0';
|
||||
},
|
||||
formatShipDate(time) {
|
||||
if (!time) {
|
||||
return '-';
|
||||
@@ -1508,8 +1480,8 @@ export default {
|
||||
},
|
||||
createDefaultOptometryList() {
|
||||
return [
|
||||
{ eyeSide: 'OS', sphereS: null, cylinderC: null, axisA: null, pd: null, dualPd: null, pupilHeight: null, distanceVa: null, nearAdd: null },
|
||||
{ eyeSide: 'OD', sphereS: null, cylinderC: null, axisA: null, pd: null, dualPd: null, pupilHeight: null, distanceVa: null, nearAdd: null }
|
||||
{ eyeSide: 'OS', sphereS: 0, cylinderC: null, axisA: null, pd: null, dualPd: null, pupilHeight: null, distanceVa: null, nearAdd: null },
|
||||
{ eyeSide: 'OD', sphereS: 0, cylinderC: null, axisA: null, pd: null, dualPd: null, pupilHeight: null, distanceVa: null, nearAdd: null }
|
||||
];
|
||||
},
|
||||
detectPdMode(optometryList) {
|
||||
@@ -1553,6 +1525,45 @@ export default {
|
||||
if (Number.isNaN(num)) return String(value);
|
||||
return '+' + num.toFixed(2);
|
||||
},
|
||||
isAxisAEditable(row) {
|
||||
const val = row && row.cylinderC;
|
||||
if (val == null || val === '') return false;
|
||||
const num = Number(val);
|
||||
return !Number.isNaN(num) && num !== 0;
|
||||
},
|
||||
getSphereSign(sphereS) {
|
||||
if (sphereS == null || sphereS === '') return 0;
|
||||
const num = Number(sphereS);
|
||||
if (Number.isNaN(num) || num === 0) return 0;
|
||||
return num > 0 ? 1 : -1;
|
||||
},
|
||||
getCylinderCMin(row) {
|
||||
return this.getSphereSign(row && row.sphereS) > 0 ? 0 : -10;
|
||||
},
|
||||
getCylinderCMax(row) {
|
||||
return this.getSphereSign(row && row.sphereS) < 0 ? 0 : 10;
|
||||
},
|
||||
alignCylinderSignWithSphere(row) {
|
||||
if (!row || row.cylinderC == null || row.cylinderC === '') return;
|
||||
let cylinder = Number(row.cylinderC);
|
||||
if (Number.isNaN(cylinder) || cylinder === 0) return;
|
||||
const sphereSign = this.getSphereSign(row.sphereS);
|
||||
if (sphereSign > 0 && cylinder < 0) cylinder = Math.abs(cylinder);
|
||||
else if (sphereSign < 0 && cylinder > 0) cylinder = -Math.abs(cylinder);
|
||||
row.cylinderC = Math.min(this.getCylinderCMax(row), Math.max(this.getCylinderCMin(row), cylinder));
|
||||
},
|
||||
onSphereSChange(val, row) {
|
||||
this.alignCylinderSignWithSphere(row);
|
||||
if (!this.isAxisAEditable(row)) row.axisA = null;
|
||||
},
|
||||
onCylinderCChange(val, row) {
|
||||
if (val == null || val === '' || Number(val) === 0) {
|
||||
row.axisA = null;
|
||||
return;
|
||||
}
|
||||
this.alignCylinderSignWithSphere(row);
|
||||
if (!this.isAxisAEditable(row)) row.axisA = null;
|
||||
},
|
||||
cancel() {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
@@ -1560,6 +1571,12 @@ export default {
|
||||
handleProgressiveMultifocalChange(value) {
|
||||
if (Number(value) !== 1) {
|
||||
this.form.progressiveMultifocalPrice = 0;
|
||||
if (this.form.optometryList) {
|
||||
this.form.optometryList.forEach(row => {
|
||||
row.nearAdd = null;
|
||||
row.pupilHeight = null;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!this.form.lensFactoryTenantId) {
|
||||
@@ -1730,13 +1747,17 @@ export default {
|
||||
},
|
||||
canViewLogistics(row) {
|
||||
if (!row || row.orderStatus == null) return false;
|
||||
const status = Number(row.orderStatus);
|
||||
return status === Number(this.ORDER_STATUS_SHIPPED) || status === 9;
|
||||
const status = String(row.orderStatus);
|
||||
return status === this.ORDER_STATUS_SHIPPED
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_LOGISTICS
|
||||
|| status === this.ORDER_STATUS_COMPLETED
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_COMPLETED;
|
||||
},
|
||||
canCompleteOrder(row) {
|
||||
if (!row || row.orderStatus == null) return false;
|
||||
const status = String(row.orderStatus);
|
||||
return status === this.ORDER_STATUS_SHIPPED || status === this.ORDER_STATUS_EXCHANGE_REJECTED;
|
||||
return status === this.ORDER_STATUS_SHIPPED
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_LOGISTICS;
|
||||
},
|
||||
handleViewLogistics(row) {
|
||||
if (!row || !row.id) return;
|
||||
@@ -1864,28 +1885,13 @@ export default {
|
||||
if (!row || !row.id) {
|
||||
return;
|
||||
}
|
||||
const isExchangeRejected = String(row.orderStatus) === this.ORDER_STATUS_EXCHANGE_REJECTED;
|
||||
const confirmMsg = isExchangeRejected
|
||||
? `确认将拒绝换货申请订单"${row.orderNo}"标记为已完成吗?`
|
||||
: `确认完成订单"${row.orderNo}"吗?`;
|
||||
const requestFn = isExchangeRejected
|
||||
? () => completeExchangeRejectedOrder({ orderId: row.id })
|
||||
: () => completeOrder({ orderId: row.id });
|
||||
this.$modal.confirm(confirmMsg).then(() => {
|
||||
return requestFn();
|
||||
this.$modal.confirm(`确认完成订单"${row.orderNo}"吗?`).then(() => {
|
||||
return completeOrder({ orderId: row.id });
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('操作成功');
|
||||
this.getList();
|
||||
}).catch(() => {});
|
||||
},
|
||||
handleBatchCompleteExchangeRejected() {
|
||||
this.$modal.confirm('确认将所有拒绝换货申请状态的订单更新为已完成吗?').then(() => {
|
||||
return completeAllExchangeRejectedOrder();
|
||||
}).then((res) => {
|
||||
this.$modal.msgSuccess((res && res.msg) || '操作成功');
|
||||
this.getList();
|
||||
}).catch(() => {});
|
||||
},
|
||||
handleAgreeExchange(row) {
|
||||
if (!row || !row.id) {
|
||||
return;
|
||||
@@ -2027,6 +2033,19 @@ export default {
|
||||
? this.resolveProgressiveMultifocalPrice()
|
||||
: 0
|
||||
};
|
||||
if (Number(payload.progressiveMultifocal) !== 1 && payload.optometryList) {
|
||||
payload.optometryList.forEach(row => {
|
||||
row.nearAdd = null;
|
||||
row.pupilHeight = null;
|
||||
});
|
||||
}
|
||||
if (payload.optometryList) {
|
||||
payload.optometryList.forEach(row => {
|
||||
if (!this.isAxisAEditable(row)) {
|
||||
row.axisA = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Number(payload.progressiveMultifocal) === 1) {
|
||||
if (!payload.lensFactoryTenantId) {
|
||||
this.$modal.msgWarning("选择渐进多焦点时需先选择镜片厂");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">觉远创智后台管理系统</h3>
|
||||
<h3 class="title">觉远创智功能镜片配镜系统</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="register">
|
||||
<el-form ref="registerForm" :model="registerForm" :rules="registerRules" class="register-form">
|
||||
<h3 class="title">觉远创智后台管理系统</h3>
|
||||
<h3 class="title">觉远创智功能镜片配镜系统</h3>
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="registerForm.username" type="text" auto-complete="off" placeholder="账号">
|
||||
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
|
||||
|
||||
@@ -7,8 +7,8 @@ function resolve(dir) {
|
||||
|
||||
const CompressionPlugin = require('compression-webpack-plugin')
|
||||
|
||||
// const name = process.env.VUE_APP_TITLE || '觉远创智管理系统' // 网页标题
|
||||
const name = '觉远创智管理系统' // 网页标题
|
||||
// const name = process.env.VUE_APP_TITLE || '觉远创智功能镜片配镜系统' // 网页标题
|
||||
const name = '觉远创智功能镜片配镜系统' // 网页标题
|
||||
|
||||
const port = process.env.port || process.env.npm_config_port || 2091 // 端口
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ExpressQueryUtils {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", "APPCODE " + appCode);
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
headers.setContentType(MediaType.parseMediaType("application/x-www-form-urlencoded; charset=UTF-8"));
|
||||
|
||||
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||
body.add("expressNo", expressNo);
|
||||
@@ -75,21 +75,46 @@ public class ExpressQueryUtils {
|
||||
return result;
|
||||
} catch (HttpStatusCodeException e) {
|
||||
int status = e.getRawStatusCode();
|
||||
log.error("调用阿里云物流查询失败 expressNo={} status={}", expressNo, status, e);
|
||||
String caError = e.getResponseHeaders() != null
|
||||
? e.getResponseHeaders().getFirst("X-Ca-Error-Message") : null;
|
||||
log.warn("调用阿里云物流查询失败 expressNo={} status={} caError={}", expressNo, status, caError);
|
||||
result.put("errorCode", "HTTP_" + status);
|
||||
if (status == 403) {
|
||||
result.put("errorMsg", "物流查询服务授权失效(HTTP 403),请在阿里云 API 市场续费快递查询接口并更新 express.ali.app-code");
|
||||
} else {
|
||||
if (status == 403)
|
||||
{
|
||||
result.put("errorMsg", buildExpress403Message(caError));
|
||||
}
|
||||
else
|
||||
{
|
||||
String errorBody = e.getResponseBodyAsString();
|
||||
result.put("errorMsg", StringUtils.isNotEmpty(errorBody) ? errorBody : ("物流查询请求失败,HTTP状态码:" + status));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("调用阿里云物流查询失败 expressNo={}", expressNo, e);
|
||||
result.put("errorCode", "EXCEPTION");
|
||||
result.put("errorMsg", e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private String buildExpress403Message(String caError)
|
||||
{
|
||||
if (StringUtils.isEmpty(caError))
|
||||
{
|
||||
return "物流查询未授权(HTTP 403):请在阿里云 API 市场购买「快递物流查询」接口,"
|
||||
+ "并使用该商品对应的 AppCode 配置 express.ali.app-code(不能与实名认证等其它 API 混用)";
|
||||
}
|
||||
if (caError.contains("Quota") || caError.contains("quota") || caError.contains("exhausted"))
|
||||
{
|
||||
return "物流查询套餐次数已用完(HTTP 403),请在阿里云 API 市场续费快递查询接口";
|
||||
}
|
||||
if (caError.contains("Unauthorized") || caError.contains("Invalid AppCode") || caError.contains("not exists"))
|
||||
{
|
||||
return "物流查询 AppCode 无效或未订阅该 API(HTTP 403),请更新 express.ali.app-code 为快递查询商品的 AppCode";
|
||||
}
|
||||
return "物流查询未授权(HTTP 403):" + caError;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ wechat:
|
||||
|
||||
# 业务短信(template 为短信平台模板 Code,变量由代码传入)
|
||||
# assign-order 模板:您有新分配的制片单(订单号${code}),请立即登录觉远配片系统查看并处理。
|
||||
# ship-order 模板:您的配片单(订单号${code})已发出,由${expressCompany}承运,单号${expressNo},可在官网查询物流进度。
|
||||
# ship-order 模板:您的配片单(订单号${code})已发出,由${expressCompany}承运。
|
||||
sms:
|
||||
sign-name: 金华觉远创智科技
|
||||
assign-order:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE=觉远创智配件系统
|
||||
VUE_APP_TITLE=觉远创智功能镜片配镜系统
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
# 觉远创智管理系统/开发环境
|
||||
# 觉远创智功能镜片配镜系统/开发环境
|
||||
VUE_APP_BASE_API = '/dev-api'
|
||||
|
||||
# 路由懒加载
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE=觉远创智配件系统
|
||||
VUE_APP_TITLE=觉远创智功能镜片配镜系统
|
||||
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
|
||||
# 觉远创智管理系统/生产环境
|
||||
# 觉远创智功能镜片配镜系统/生产环境
|
||||
VUE_APP_BASE_API = '/prod-api'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE=觉远创智配件系统
|
||||
VUE_APP_TITLE=觉远创智功能镜片配镜系统
|
||||
|
||||
NODE_ENV = production
|
||||
|
||||
# 测试环境配置
|
||||
ENV = 'staging'
|
||||
|
||||
# 觉远创智管理系统/测试环境
|
||||
# 觉远创智功能镜片配镜系统/测试环境
|
||||
VUE_APP_BASE_API = '/stage-api'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ruoyi",
|
||||
"version": "3.8.8",
|
||||
"description": "觉远创智配件系统",
|
||||
"description": "觉远创智功能镜片配镜系统",
|
||||
"author": "觉远创智",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
return {
|
||||
title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title,
|
||||
titleTemplate: title => {
|
||||
const appTitle = process.env.VUE_APP_TITLE || '觉远创智配件系统'
|
||||
const appTitle = process.env.VUE_APP_TITLE || '觉远创智功能镜片配镜系统'
|
||||
return title ? `${title} - ${appTitle}` : appTitle
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 22 KiB |
BIN
developer-front/src/assets/images/home/logo.c3029a2f2.png
Normal file
BIN
developer-front/src/assets/images/home/logo.c3029a2f2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -3,21 +3,6 @@ import store from '@/store'
|
||||
import DataDict from '@/utils/dict'
|
||||
import { getDicts as getDicts } from '@/api/system/dict/data'
|
||||
|
||||
function searchDictByKey(dict, key) {
|
||||
if (key == null && key == "") {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
for (let i = 0; i < dict.length; i++) {
|
||||
if (dict[i].key == key) {
|
||||
return dict[i].value
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function install() {
|
||||
Vue.use(DataDict, {
|
||||
metas: {
|
||||
@@ -25,19 +10,14 @@ function install() {
|
||||
labelField: 'dictLabel',
|
||||
valueField: 'dictValue',
|
||||
request(dictMeta) {
|
||||
const storeDict = searchDictByKey(store.getters.dict, dictMeta.type)
|
||||
if (storeDict) {
|
||||
return new Promise(resolve => { resolve(storeDict) })
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
getDicts(dictMeta.type).then(res => {
|
||||
store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data })
|
||||
resolve(res.data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
getDicts(dictMeta.type).then(res => {
|
||||
store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data })
|
||||
resolve(res.data)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-for="(item, index) in options">
|
||||
<template v-if="values.includes(item.value)">
|
||||
<template v-if="values.includes(String(item.value))">
|
||||
<span
|
||||
v-if="(item.raw.listClass == 'default' || item.raw.listClass == '') && (item.raw.cssClass == '' || item.raw.cssClass == null)"
|
||||
:key="item.value"
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
// 传入值为数组
|
||||
let unmatch = false // 添加一个标志来判断是否有未匹配项
|
||||
this.values.forEach(item => {
|
||||
if (!this.options.some(v => v.value === item)) {
|
||||
if (!this.options.some(v => String(v.value) === String(item))) {
|
||||
this.unmatchArray.push(item)
|
||||
unmatch = true // 如果有未匹配项,将标志设置为true
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
// title: process.env.VUE_APP_TITLE,
|
||||
title: '觉远创智配件系统',
|
||||
title: '觉远创智功能镜片配镜系统',
|
||||
logo: logoImg
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,15 @@ const state = {
|
||||
const mutations = {
|
||||
SET_DICT: (state, { key, value }) => {
|
||||
if (key !== null && key !== "") {
|
||||
state.dict.push({
|
||||
key: key,
|
||||
value: value
|
||||
})
|
||||
const index = state.dict.findIndex(item => item.key === key)
|
||||
if (index >= 0) {
|
||||
state.dict.splice(index, 1, { key: key, value: value })
|
||||
} else {
|
||||
state.dict.push({
|
||||
key: key,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
REMOVE_DICT: (state, key) => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<nav class="nav-bar">
|
||||
<div class="logo">
|
||||
<img :src="require('@/assets/images/home/logo.c3029a2f.png')" alt="JUEYUAN" />
|
||||
<h1 class="logo-text">配件系统</h1>
|
||||
<h1 class="logo-text">功能镜片配镜系统</h1>
|
||||
</div>
|
||||
<div class="menu-toggle" @click="toggleMenu">
|
||||
<span class="hamburger" :class="{ active: isMenuOpen }"></span>
|
||||
|
||||
@@ -22,34 +22,32 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="orderList">
|
||||
<el-table-column label="订单编号" align="center" prop="orderNo" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="品牌方" align="center" prop="brandTenantName" min-width="100" />
|
||||
<el-table-column label="通用镜片" align="center" min-width="120">
|
||||
<el-table v-loading="loading" :data="orderList" class="factory-order-table">
|
||||
<el-table-column label="订单编号" align="center" prop="orderNo" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="通用镜片" align="center" min-width="200" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatLens(scope.row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="C价格" align="center" prop="priceC" width="80" />
|
||||
<el-table-column label="渐进多焦点价格" align="center" min-width="100">
|
||||
<el-table-column label="总价" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatProgressiveMultifocalPrice(scope.row) }}</span>
|
||||
<span>{{ formatPriceAmount(formatDetailPriceTotal(scope.row, 'priceC')) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" align="center" prop="orderStatus" width="100">
|
||||
<el-table-column label="订单状态" align="center" prop="orderStatus" width="112">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.jy_order_status" :value="scope.row.orderStatus"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算" align="center" prop="purchaseSettleStatus" width="80">
|
||||
<el-table-column label="结算" align="center" prop="purchaseSettleStatus" width="88">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.jy_purchase_settle_status" :value="scope.row.purchaseSettleStatus"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收货人" align="center" prop="receiverName" width="80" />
|
||||
<el-table-column label="手机号" align="center" prop="receiverPhone" width="110" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="150" />
|
||||
<el-table-column label="操作" align="center" min-width="280">
|
||||
<el-table-column label="收货人" align="center" prop="receiverName" width="100" show-overflow-tooltip />
|
||||
<el-table-column label="手机号" align="center" prop="receiverPhone" width="126" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="168" />
|
||||
<el-table-column label="操作" align="center" min-width="300" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="handleDetail(scope.row)">详情</el-button>
|
||||
<el-button
|
||||
@@ -139,7 +137,6 @@
|
||||
<el-descriptions-item label="订单状态">
|
||||
<dict-tag :options="dict.type.jy_order_status" :value="detail.orderStatus" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="品牌方">{{ detail.brandTenantName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="通用镜片">{{ formatLens(detail) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="渐进多焦点">{{ detail.progressiveMultifocal === 1 ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="附件" :span="2">
|
||||
@@ -187,11 +184,8 @@
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<el-descriptions :column="2" border size="small" class="detail-descriptions detail-descriptions--price">
|
||||
<el-descriptions-item label="C价格">{{ formatPriceAmount(detail.priceC) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="物流费用">{{ formatPriceAmount(formatDetailLogisticsFee(detail)) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="渐进多焦点价格">{{ formatPriceAmount(formatProgressiveMultifocalPrice(detail)) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合计">
|
||||
<span class="price-total-value">{{ formatPriceAmount(formatPriceTotal(detail, 'priceC')) }}</span>
|
||||
<span class="price-total-value">{{ formatPriceAmount(formatDetailPriceTotal(detail, 'priceC')) }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="detail-logistics-fee-block">
|
||||
@@ -203,26 +197,6 @@
|
||||
<span>{{ formatPriceAmount(formatLogisticsLogFee(scope.row, detail)) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应收结算" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag
|
||||
v-if="formatLogSaleSettleStatus(scope.row) != null"
|
||||
:options="dict.type.jy_sale_settle_status"
|
||||
:value="formatLogSaleSettleStatus(scope.row)"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付结算" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag
|
||||
v-if="formatLogPurchaseSettleStatus(scope.row) != null"
|
||||
:options="dict.type.jy_purchase_settle_status"
|
||||
:value="formatLogPurchaseSettleStatus(scope.row)"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="发货日期" align="center" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatShipDate(scope.row.operateTime) }}</span>
|
||||
@@ -277,7 +251,7 @@
|
||||
<span>{{ formatDetailPdValue(scope.row, scope.$index) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="瞳高(mm)" align="center" min-width="80">
|
||||
<el-table-column v-if="detail.progressiveMultifocal === 1" label="瞳高(mm)" align="center" min-width="80">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.pupilHeight != null ? scope.row.pupilHeight : '-' }}</span>
|
||||
</template>
|
||||
@@ -287,7 +261,7 @@
|
||||
<span>{{ scope.row.distanceVa != null && scope.row.distanceVa !== '' ? scope.row.distanceVa : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="近用ADD(屈光度)" align="center" min-width="100">
|
||||
<el-table-column v-if="detail.progressiveMultifocal === 1" label="近用ADD(屈光度)" align="center" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatDetailNearAdd(scope.row.nearAdd) }}</span>
|
||||
</template>
|
||||
@@ -311,26 +285,6 @@
|
||||
<dict-tag :options="dict.type.jy_order_status" :value="scope.row.orderStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应收结算" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag
|
||||
v-if="formatLogSaleSettleStatus(scope.row) != null"
|
||||
:options="dict.type.jy_sale_settle_status"
|
||||
:value="formatLogSaleSettleStatus(scope.row)"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付结算" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag
|
||||
v-if="formatLogPurchaseSettleStatus(scope.row) != null"
|
||||
:options="dict.type.jy_purchase_settle_status"
|
||||
:value="formatLogPurchaseSettleStatus(scope.row)"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="操作人" align="center" prop="operator" width="100" />
|
||||
<el-table-column label="操作时间" align="center" prop="operateTime" width="160" />
|
||||
@@ -574,7 +528,10 @@ export default {
|
||||
ORDER_STATUS_PRODUCING: '7',
|
||||
ORDER_STATUS_SHIPPED: '8',
|
||||
ORDER_STATUS_COMPLETED: '9',
|
||||
ORDER_STATUS_EXCHANGE_APPLY: '12',
|
||||
ORDER_STATUS_EXCHANGE_AGREED: '13',
|
||||
ORDER_STATUS_EXCHANGE_LOGISTICS: '15',
|
||||
ORDER_STATUS_EXCHANGE_COMPLETED: '17',
|
||||
ORDER_STATUS_WITHDRAWN: '20',
|
||||
ORDER_STATUS_SUPPLEMENTED: '19',
|
||||
logisticsDialogVisible: false,
|
||||
@@ -703,8 +660,32 @@ export default {
|
||||
},
|
||||
formatProgressiveMultifocalPrice(row) {
|
||||
if (!row) return '-'
|
||||
if (this.isExchangeOrder(row)) return '-'
|
||||
return row.progressiveMultifocalPrice != null ? row.progressiveMultifocalPrice : '-'
|
||||
},
|
||||
isExchangeOrder(row) {
|
||||
if (!row) return false
|
||||
if (row.orderNo && String(row.orderNo).startsWith('HH')) return true
|
||||
const status = row.orderStatus != null ? String(row.orderStatus) : ''
|
||||
return status === this.ORDER_STATUS_EXCHANGE_APPLY || status === this.ORDER_STATUS_EXCHANGE_AGREED
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_LOGISTICS
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_COMPLETED
|
||||
},
|
||||
formatLogisticsFee(row) {
|
||||
if (!row) return '-'
|
||||
if (!this.isExchangeOrder(row)) return '-'
|
||||
const fee = this.parseLogisticsFeeValue(row.logisticsFee)
|
||||
return fee != null ? fee : '-'
|
||||
},
|
||||
formatPriceC(row) {
|
||||
if (!row) return '-'
|
||||
if (this.isExchangeOrder(row)) return '-'
|
||||
return row.priceC != null ? row.priceC : '-'
|
||||
},
|
||||
formatDetailPriceTotal(detail, priceKey) {
|
||||
if (this.isExchangeOrder(detail)) return this.formatDetailLogisticsFee(detail)
|
||||
return this.formatPriceTotal(detail, priceKey)
|
||||
},
|
||||
formatPriceAmount(value) {
|
||||
if (value == null || value === '' || value === '-') return '-'
|
||||
if (value === '未核算') return '未核算'
|
||||
@@ -750,7 +731,7 @@ export default {
|
||||
hasFee = true
|
||||
}
|
||||
})
|
||||
if (!hasFee && logs.length > 0 && detail.logisticsFee != null) {
|
||||
if (!hasFee && detail.logisticsFee != null) {
|
||||
const legacyFee = this.parseLogisticsFeeValue(detail.logisticsFee)
|
||||
if (legacyFee != null) return legacyFee
|
||||
}
|
||||
@@ -760,24 +741,6 @@ export default {
|
||||
formatDetailLogisticsFee(detail) {
|
||||
return this.sumDetailLogisticsFee(detail)
|
||||
},
|
||||
formatLogSaleSettleStatus(log) {
|
||||
if (!log || log.expressNo == null || String(log.expressNo).trim() === '') {
|
||||
return null
|
||||
}
|
||||
if (log.saleSettleStatus != null && log.saleSettleStatus !== '') {
|
||||
return log.saleSettleStatus
|
||||
}
|
||||
return '0'
|
||||
},
|
||||
formatLogPurchaseSettleStatus(log) {
|
||||
if (!log || log.expressNo == null || String(log.expressNo).trim() === '') {
|
||||
return null
|
||||
}
|
||||
if (log.purchaseSettleStatus != null && log.purchaseSettleStatus !== '') {
|
||||
return log.purchaseSettleStatus
|
||||
}
|
||||
return '0'
|
||||
},
|
||||
formatShipDate(time) {
|
||||
if (!time) {
|
||||
return '-'
|
||||
@@ -915,12 +878,17 @@ export default {
|
||||
},
|
||||
canCompleteOrder(row) {
|
||||
if (!row || row.orderStatus == null) return false
|
||||
return String(row.orderStatus) === this.ORDER_STATUS_SHIPPED
|
||||
const status = String(row.orderStatus)
|
||||
return status === this.ORDER_STATUS_SHIPPED
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_LOGISTICS
|
||||
},
|
||||
canViewLogistics(row) {
|
||||
if (!row || row.orderStatus == null) return false
|
||||
const status = Number(row.orderStatus)
|
||||
return status === Number(this.ORDER_STATUS_SHIPPED) || status === 9
|
||||
const status = String(row.orderStatus)
|
||||
return status === this.ORDER_STATUS_SHIPPED
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_LOGISTICS
|
||||
|| status === this.ORDER_STATUS_COMPLETED
|
||||
|| status === this.ORDER_STATUS_EXCHANGE_COMPLETED
|
||||
},
|
||||
handleViewLogistics(row) {
|
||||
if (!row || !row.id) return
|
||||
@@ -1112,6 +1080,10 @@ export default {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.factory-order-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logistics-basic {
|
||||
margin-bottom: 16px;
|
||||
color: #606266;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ function resolve(dir) {
|
||||
|
||||
const CompressionPlugin = require('compression-webpack-plugin')
|
||||
|
||||
const name = process.env.VUE_APP_TITLE || '觉远创智配件系统'
|
||||
const name = process.env.VUE_APP_TITLE || '觉远创智功能镜片配镜系统'
|
||||
|
||||
const port = process.env.port || process.env.npm_config_port || 2092 // ??
|
||||
|
||||
@@ -77,7 +77,7 @@ module.exports = {
|
||||
},
|
||||
chainWebpack(config) {
|
||||
config.plugin('html').tap(args => {
|
||||
args[0].title = process.env.VUE_APP_TITLE || '觉远创智配件系统'
|
||||
args[0].title = process.env.VUE_APP_TITLE || '觉远创智功能镜片配镜系统'
|
||||
return args
|
||||
})
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ public class SmsProperties
|
||||
/** 配片订单分配至镜片厂(模板变量 code=订单号) */
|
||||
private SceneConfig assignOrder = new SceneConfig();
|
||||
|
||||
/** 配片订单发货通知(模板变量 code、expressCompany、expressNo) */
|
||||
/** 配片订单发货通知(模板变量 code、expressCompany) */
|
||||
private SceneConfig shipOrder = new SceneConfig();
|
||||
|
||||
public String getSignName()
|
||||
|
||||
@@ -199,32 +199,6 @@ public class JySysOrderController extends BaseController
|
||||
rejectDTO.getOrderId(), rejectDTO.getReason().trim(), getUsername()));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('jysystem:order:edit')")
|
||||
@Log(title = "拒绝换货申请订单完成", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/exchangeRejectedComplete")
|
||||
public AjaxResult exchangeRejectedComplete(@RequestBody OrderCloseDTO completeDTO)
|
||||
{
|
||||
if (completeDTO == null || completeDTO.getOrderId() == null)
|
||||
{
|
||||
return error("订单ID不能为空");
|
||||
}
|
||||
return toAjax(jySysOrderService.adminCompleteExchangeRejectedOrder(
|
||||
completeDTO.getOrderId(), getUsername()));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('jysystem:order:edit')")
|
||||
@Log(title = "批量完成拒绝换货申请订单", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/exchangeRejectedCompleteAll")
|
||||
public AjaxResult exchangeRejectedCompleteAll()
|
||||
{
|
||||
int rows = jySysOrderService.adminCompleteAllExchangeRejectedOrders(getUsername());
|
||||
if (rows <= 0)
|
||||
{
|
||||
return success("没有需要更新的订单");
|
||||
}
|
||||
return success("已成功更新 " + rows + " 条订单");
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('jysystem:order:edit')")
|
||||
@Log(title = "订单状态变更", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/changeStatus")
|
||||
|
||||
@@ -210,7 +210,7 @@ public class JySysOrderPortalController extends BaseController
|
||||
{
|
||||
return error("换货申请原因不能为空");
|
||||
}
|
||||
return toAjax(jySysOrderPortalService.exchangeApplyPortalOrder(
|
||||
return success(jySysOrderPortalService.exchangeApplyPortalOrder(
|
||||
exchangeDTO.getOrderId(), exchangeDTO.getReason().trim()));
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ public class JySysOrder extends BaseEntity
|
||||
@Excel(name = "渐进多焦点价格")
|
||||
private BigDecimal progressiveMultifocalPrice;
|
||||
|
||||
@Excel(name = "是否老花", readConverterExp = "0=否,1=是")
|
||||
private Integer presbyopia;
|
||||
|
||||
@Excel(name = "订单状态", dictType = "jy_order_status")
|
||||
private String orderStatus;
|
||||
|
||||
@@ -261,6 +264,16 @@ public class JySysOrder extends BaseEntity
|
||||
this.progressiveMultifocalPrice = progressiveMultifocalPrice;
|
||||
}
|
||||
|
||||
public Integer getPresbyopia()
|
||||
{
|
||||
return presbyopia;
|
||||
}
|
||||
|
||||
public void setPresbyopia(Integer presbyopia)
|
||||
{
|
||||
this.presbyopia = presbyopia;
|
||||
}
|
||||
|
||||
public String getOrderStatus()
|
||||
{
|
||||
return orderStatus;
|
||||
@@ -385,6 +398,7 @@ public class JySysOrder extends BaseEntity
|
||||
.append("priceC", getPriceC())
|
||||
.append("logisticsFee", getLogisticsFee())
|
||||
.append("progressiveMultifocal", getProgressiveMultifocal())
|
||||
.append("presbyopia", getPresbyopia())
|
||||
.append("orderStatus", getOrderStatus())
|
||||
.append("purchaseSettleStatus", getPurchaseSettleStatus())
|
||||
.append("saleSettleStatus", getSaleSettleStatus())
|
||||
|
||||
@@ -25,12 +25,6 @@ public class JySysOrderStatusLog
|
||||
|
||||
private BigDecimal logisticsFee;
|
||||
|
||||
/** 应收结算状态(0未结算 1已结算) */
|
||||
private String saleSettleStatus;
|
||||
|
||||
/** 应付结算状态(0未结算 1已结算) */
|
||||
private String purchaseSettleStatus;
|
||||
|
||||
private String remark;
|
||||
|
||||
private String operator;
|
||||
@@ -98,26 +92,6 @@ public class JySysOrderStatusLog
|
||||
this.logisticsFee = logisticsFee;
|
||||
}
|
||||
|
||||
public String getSaleSettleStatus()
|
||||
{
|
||||
return saleSettleStatus;
|
||||
}
|
||||
|
||||
public void setSaleSettleStatus(String saleSettleStatus)
|
||||
{
|
||||
this.saleSettleStatus = saleSettleStatus;
|
||||
}
|
||||
|
||||
public String getPurchaseSettleStatus()
|
||||
{
|
||||
return purchaseSettleStatus;
|
||||
}
|
||||
|
||||
public void setPurchaseSettleStatus(String purchaseSettleStatus)
|
||||
{
|
||||
this.purchaseSettleStatus = purchaseSettleStatus;
|
||||
}
|
||||
|
||||
public String getRemark()
|
||||
{
|
||||
return remark;
|
||||
@@ -158,8 +132,6 @@ public class JySysOrderStatusLog
|
||||
.append("expressCompany", getExpressCompany())
|
||||
.append("expressNo", getExpressNo())
|
||||
.append("logisticsFee", getLogisticsFee())
|
||||
.append("saleSettleStatus", getSaleSettleStatus())
|
||||
.append("purchaseSettleStatus", getPurchaseSettleStatus())
|
||||
.append("remark", getRemark())
|
||||
.append("operator", getOperator())
|
||||
.append("operateTime", getOperateTime())
|
||||
|
||||
@@ -28,6 +28,8 @@ public interface JySysOrderMapper extends BaseMapper<JySysOrder>
|
||||
|
||||
int countByTenantId(@Param("tenantId") Long tenantId);
|
||||
|
||||
int countByOrderNo(@Param("orderNo") String orderNo);
|
||||
|
||||
List<JySysOrder> selectCompletedOrdersByYearMonth(@Param("settleYearMonth") String settleYearMonth);
|
||||
|
||||
int updatePurchaseSettleStatusByOrderIds(@Param("orderIds") List<Long> orderIds,
|
||||
|
||||
@@ -17,10 +17,4 @@ public interface JySysOrderStatusLogMapper
|
||||
|
||||
List<JySysOrderStatusLog> selectUnsettledShipLogsByOrderIdAndType(@Param("orderId") Long orderId,
|
||||
@Param("settlementType") String settlementType);
|
||||
|
||||
int updateSaleSettleStatusByIds(@Param("ids") List<Long> ids, @Param("settleStatus") String settleStatus);
|
||||
|
||||
int updatePurchaseSettleStatusByIds(@Param("ids") List<Long> ids, @Param("settleStatus") String settleStatus);
|
||||
|
||||
int resetShipLogSettleStatusByOrderIds(@Param("orderIds") List<Long> orderIds);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public interface IJySysOrderPortalService
|
||||
|
||||
int completePortalOrder(Long orderId);
|
||||
|
||||
int exchangeApplyPortalOrder(Long orderId, String reason);
|
||||
JySysOrder exchangeApplyPortalOrder(Long orderId, String reason);
|
||||
|
||||
int receivePortalOrder(Long orderId);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public interface IJySysOrderService extends IService<JySysOrder>
|
||||
|
||||
int brandReceiveExchangeRejectedOrder(Long orderId, Long brandTenantId, String operator);
|
||||
|
||||
int brandExchangeApplyOrder(Long orderId, Long brandTenantId, String reason, String operator);
|
||||
JySysOrder brandExchangeApplyOrder(Long orderId, Long brandTenantId, String reason, String operator);
|
||||
|
||||
int adminRejectOrder(Long orderId, String reason, String operator);
|
||||
|
||||
@@ -64,10 +64,6 @@ public interface IJySysOrderService extends IService<JySysOrder>
|
||||
|
||||
int adminRejectExchangeOrder(Long orderId, String reason, String operator);
|
||||
|
||||
int adminCompleteExchangeRejectedOrder(Long orderId, String operator);
|
||||
|
||||
int adminCompleteAllExchangeRejectedOrders(String operator);
|
||||
|
||||
int brandResubmitOrder(Long orderId, Long brandTenantId, String reason, String operator);
|
||||
|
||||
int changeOrderStatus(Long orderId, String orderStatus, String operator);
|
||||
|
||||
@@ -14,12 +14,11 @@ public interface SmsNotificationService
|
||||
void trySendOrderAssignedNotify(String phone, String orderNo);
|
||||
|
||||
/**
|
||||
* 订单发货后,向收件人手机号发送通知(失败不影响主流程)
|
||||
* 订单发货后发送通知(失败不影响主流程)
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @param phone 手机号(品牌方租户手机号,或收货人手机号)
|
||||
* @param orderNo 订单号,对应模板变量 code
|
||||
* @param expressCompany 快递公司,对应模板变量 expressCompany
|
||||
* @param expressNo 快递单号,对应模板变量 expressNo
|
||||
*/
|
||||
void trySendOrderShippedNotify(String phone, String orderNo, String expressCompany, String expressNo);
|
||||
void trySendOrderShippedNotify(String phone, String orderNo, String expressCompany);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.ruoyi.jysystem.utils.FeeSettlementExcelExporter;
|
||||
import com.ruoyi.jysystem.utils.FeeSettlementExportHelper;
|
||||
import com.ruoyi.jysystem.utils.FeeSettlementPdfExporter;
|
||||
import com.ruoyi.jysystem.utils.IdUtil;
|
||||
import com.ruoyi.jysystem.utils.OrderExchangeHelper;
|
||||
import com.ruoyi.jysystem.vo.FeeSettlementCheckVO;
|
||||
import com.ruoyi.jysystem.vo.FeeSettlementDetailVO;
|
||||
|
||||
@@ -130,10 +131,6 @@ public class JySysFeeSettlementServiceImpl implements IJySysFeeSettlementService
|
||||
{
|
||||
throw new ServiceException("该年月没有已完成状态的订单,无法生成结算单");
|
||||
}
|
||||
if (forceRegenerate && exists)
|
||||
{
|
||||
resetShipLogSettleStatusForOrders(orders);
|
||||
}
|
||||
|
||||
Map<Long, List<JySysOrder>> brandGroups = new LinkedHashMap<>();
|
||||
Map<Long, List<JySysOrder>> factoryGroups = new LinkedHashMap<>();
|
||||
@@ -210,27 +207,9 @@ public class JySysFeeSettlementServiceImpl implements IJySysFeeSettlementService
|
||||
update.setUpdateTime(DateUtils.getNowDate());
|
||||
int rows = feeSettlementMapper.updateSettleStatus(update);
|
||||
updateOrderSettleStatusBySettlement(settlement, operator);
|
||||
updateStatusLogSettleStatusBySettlement(settlement);
|
||||
return rows;
|
||||
}
|
||||
|
||||
private void updateStatusLogSettleStatusBySettlement(JySysFeeSettlement settlement)
|
||||
{
|
||||
List<Long> statusLogIds = collectStatusLogIdsBySettlement(settlement.getId());
|
||||
if (statusLogIds.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (JySysFeeSettlement.TYPE_AP.equals(settlement.getSettlementType()))
|
||||
{
|
||||
jySysOrderStatusLogMapper.updatePurchaseSettleStatusByIds(statusLogIds, "1");
|
||||
}
|
||||
else if (JySysFeeSettlement.TYPE_AR.equals(settlement.getSettlementType()))
|
||||
{
|
||||
jySysOrderStatusLogMapper.updateSaleSettleStatusByIds(statusLogIds, "1");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOrderSettleStatusBySettlement(JySysFeeSettlement settlement, String operator)
|
||||
{
|
||||
List<JySysFeeSettlementItem> items = feeSettlementItemMapper.selectBySettlementId(settlement.getId());
|
||||
@@ -314,10 +293,13 @@ public class JySysFeeSettlementServiceImpl implements IJySysFeeSettlementService
|
||||
|
||||
private JySysFeeSettlementItem buildSettlementItem(Long settlementId, String type, JySysOrder order)
|
||||
{
|
||||
BigDecimal lensPrice = JySysFeeSettlement.TYPE_AR.equals(type)
|
||||
? defaultAmount(order.getPriceC()) : defaultAmount(order.getPriceB());
|
||||
BigDecimal progressivePrice = Integer.valueOf(1).equals(order.getProgressiveMultifocal())
|
||||
? defaultAmount(order.getProgressiveMultifocalPrice()) : BigDecimal.ZERO;
|
||||
boolean exchangeOrder = isExchangeOrder(order);
|
||||
BigDecimal lensPrice = exchangeOrder ? BigDecimal.ZERO
|
||||
: (JySysFeeSettlement.TYPE_AR.equals(type)
|
||||
? defaultAmount(order.getPriceC()) : defaultAmount(order.getPriceB()));
|
||||
BigDecimal progressivePrice = exchangeOrder ? BigDecimal.ZERO
|
||||
: (Integer.valueOf(1).equals(order.getProgressiveMultifocal())
|
||||
? defaultAmount(order.getProgressiveMultifocalPrice()) : BigDecimal.ZERO);
|
||||
|
||||
List<JySysOrderStatusLog> shipLogs = jySysOrderStatusLogMapper
|
||||
.selectUnsettledShipLogsByOrderIdAndType(order.getId(), type);
|
||||
@@ -374,7 +356,6 @@ public class JySysFeeSettlementServiceImpl implements IJySysFeeSettlementService
|
||||
{
|
||||
continue;
|
||||
}
|
||||
revertLogSettleStatusBySettlement(settlement);
|
||||
if ("1".equals(settlement.getSettleStatus()))
|
||||
{
|
||||
revertOrderSettleStatusBySettlement(settlement);
|
||||
@@ -392,24 +373,6 @@ public class JySysFeeSettlementServiceImpl implements IJySysFeeSettlementService
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除结算单时回滚关联发货日志的结算状态,便于重新生成时再次纳入物流费用 */
|
||||
private void revertLogSettleStatusBySettlement(JySysFeeSettlement settlement)
|
||||
{
|
||||
List<Long> statusLogIds = collectStatusLogIdsBySettlement(settlement.getId());
|
||||
if (statusLogIds.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (JySysFeeSettlement.TYPE_AP.equals(settlement.getSettlementType()))
|
||||
{
|
||||
jySysOrderStatusLogMapper.updatePurchaseSettleStatusByIds(statusLogIds, "0");
|
||||
}
|
||||
else if (JySysFeeSettlement.TYPE_AR.equals(settlement.getSettlementType()))
|
||||
{
|
||||
jySysOrderStatusLogMapper.updateSaleSettleStatusByIds(statusLogIds, "0");
|
||||
}
|
||||
}
|
||||
|
||||
private void revertOrderSettleStatusBySettlement(JySysFeeSettlement settlement)
|
||||
{
|
||||
List<JySysFeeSettlementItem> items = feeSettlementItemMapper.selectBySettlementId(settlement.getId());
|
||||
@@ -433,38 +396,6 @@ public class JySysFeeSettlementServiceImpl implements IJySysFeeSettlementService
|
||||
}
|
||||
}
|
||||
|
||||
private void resetShipLogSettleStatusForOrders(List<JySysOrder> orders)
|
||||
{
|
||||
if (orders == null || orders.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<Long> orderIds = orders.stream()
|
||||
.map(JySysOrder::getId)
|
||||
.filter(orderId -> orderId != null)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (!orderIds.isEmpty())
|
||||
{
|
||||
jySysOrderStatusLogMapper.resetShipLogSettleStatusByOrderIds(orderIds);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Long> collectStatusLogIdsBySettlement(Long settlementId)
|
||||
{
|
||||
List<JySysFeeSettlementItem> items = feeSettlementItemMapper.selectBySettlementId(settlementId);
|
||||
if (items == null || items.isEmpty())
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<Long> itemIds = items.stream().map(JySysFeeSettlementItem::getId).collect(Collectors.toList());
|
||||
return feeSettlementLogisticsMapper.selectBySettlementItemIds(itemIds).stream()
|
||||
.map(JySysFeeSettlementLogistics::getStatusLogId)
|
||||
.filter(statusLogId -> statusLogId != null)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private String nextSettlementNo(String type)
|
||||
{
|
||||
String datePart = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
@@ -496,6 +427,11 @@ public class JySysFeeSettlementServiceImpl implements IJySysFeeSettlementService
|
||||
return value != null ? value : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private boolean isExchangeOrder(JySysOrder order)
|
||||
{
|
||||
return OrderExchangeHelper.isExchangeOrder(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportPdf(Long id)
|
||||
{
|
||||
|
||||
@@ -229,7 +229,7 @@ public class JySysOrderPortalServiceImpl implements IJySysOrderPortalService
|
||||
}
|
||||
|
||||
@Override
|
||||
public int exchangeApplyPortalOrder(Long orderId, String reason)
|
||||
public JySysOrder exchangeApplyPortalOrder(Long orderId, String reason)
|
||||
{
|
||||
JySysTenant tenant = requireBrandTenant();
|
||||
return jySysOrderService.brandExchangeApplyOrder(
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.ruoyi.jysystem.mapper.JySysTenantMapper;
|
||||
import com.ruoyi.jysystem.service.IJySysOrderService;
|
||||
import com.ruoyi.jysystem.service.IJySysTenantMessageService;
|
||||
import com.ruoyi.jysystem.utils.JySysOrderListPdfExporter;
|
||||
import com.ruoyi.jysystem.utils.OrderExchangeHelper;
|
||||
import com.ruoyi.jysystem.utils.TenantTypeHelper;
|
||||
import com.ruoyi.jysystem.vo.JySysTenantLensVO;
|
||||
import com.ruoyi.jysystem.vo.OrderAssignPriceVO;
|
||||
@@ -59,6 +60,10 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
|
||||
private static final String ORDER_STATUS_EXCHANGE_AGREED = "13";
|
||||
|
||||
private static final String ORDER_STATUS_EXCHANGE_LOGISTICS = "15";
|
||||
|
||||
private static final String ORDER_STATUS_EXCHANGE_COMPLETED = "17";
|
||||
|
||||
private static final String ORDER_STATUS_EXCHANGE_REJECTED = "21";
|
||||
|
||||
private static final String ORDER_STATUS_CANCELLED = "18";
|
||||
@@ -347,7 +352,7 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
throw new ServiceException("物流费用不能小于0");
|
||||
}
|
||||
|
||||
saveStatusLog(orderId, ORDER_STATUS_SHIPPED, operator, expressCompany, expressNo, null, logisticsFee);
|
||||
saveStatusLog(orderId, resolveShipStatus(order), operator, expressCompany, expressNo, null, logisticsFee);
|
||||
BigDecimal totalLogisticsFee = jySysOrderStatusLogMapper.sumLogisticsFeeByOrderId(orderId);
|
||||
if (totalLogisticsFee == null)
|
||||
{
|
||||
@@ -362,7 +367,7 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
update.setAttachment(order.getAttachment());
|
||||
update.setLensFactoryTenantId(order.getLensFactoryTenantId());
|
||||
update.setLogisticsFee(totalLogisticsFee);
|
||||
update.setOrderStatus(ORDER_STATUS_SHIPPED);
|
||||
update.setOrderStatus(resolveShipStatus(order));
|
||||
update.setUpdateBy(operator);
|
||||
update.setUpdateTime(DateUtils.getNowDate());
|
||||
|
||||
@@ -383,12 +388,13 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
update.setAttachment(order.getAttachment());
|
||||
update.setLensFactoryTenantId(order.getLensFactoryTenantId());
|
||||
update.setLogisticsFee(order.getLogisticsFee());
|
||||
update.setOrderStatus(ORDER_STATUS_COMPLETED);
|
||||
String completeStatus = resolveCompleteStatus(order);
|
||||
update.setOrderStatus(completeStatus);
|
||||
update.setUpdateBy(operator);
|
||||
update.setUpdateTime(DateUtils.getNowDate());
|
||||
|
||||
int rows = jySysOrderMapper.updateJySysOrder(update);
|
||||
saveStatusLog(orderId, ORDER_STATUS_COMPLETED, operator, null, null, null);
|
||||
saveStatusLog(orderId, completeStatus, operator, null, null, null);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@@ -406,12 +412,13 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
update.setAttachment(order.getAttachment());
|
||||
update.setLensFactoryTenantId(order.getLensFactoryTenantId());
|
||||
update.setLogisticsFee(order.getLogisticsFee());
|
||||
update.setOrderStatus(ORDER_STATUS_COMPLETED);
|
||||
String completeStatus = resolveCompleteStatus(order);
|
||||
update.setOrderStatus(completeStatus);
|
||||
update.setUpdateBy(operator);
|
||||
update.setUpdateTime(DateUtils.getNowDate());
|
||||
|
||||
int rows = jySysOrderMapper.updateJySysOrder(update);
|
||||
saveStatusLog(orderId, ORDER_STATUS_COMPLETED, operator, null, null, null);
|
||||
saveStatusLog(orderId, completeStatus, operator, null, null, null);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@@ -440,29 +447,19 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int brandExchangeApplyOrder(Long orderId, Long brandTenantId, String reason, String operator)
|
||||
public JySysOrder brandExchangeApplyOrder(Long orderId, Long brandTenantId, String reason, String operator)
|
||||
{
|
||||
JySysOrder order = requireBrandExchangeAppliableOrder(orderId, brandTenantId);
|
||||
requireBrandExchangeAppliableOrder(orderId, brandTenantId);
|
||||
if (StringUtils.isEmpty(reason))
|
||||
{
|
||||
throw new ServiceException("换货申请原因不能为空");
|
||||
}
|
||||
|
||||
JySysOrder update = new JySysOrder();
|
||||
update.setId(orderId);
|
||||
update.setBrandTenantId(order.getBrandTenantId());
|
||||
update.setLensId(order.getLensId());
|
||||
update.setProductId(order.getProductId());
|
||||
update.setAttachment(order.getAttachment());
|
||||
update.setLensFactoryTenantId(order.getLensFactoryTenantId());
|
||||
update.setLogisticsFee(order.getLogisticsFee());
|
||||
update.setOrderStatus(ORDER_STATUS_EXCHANGE_APPLY);
|
||||
update.setUpdateBy(operator);
|
||||
update.setUpdateTime(DateUtils.getNowDate());
|
||||
|
||||
int rows = jySysOrderMapper.updateJySysOrder(update);
|
||||
saveStatusLog(orderId, ORDER_STATUS_EXCHANGE_APPLY, operator, null, null, reason);
|
||||
return rows;
|
||||
JySysOrder source = selectJySysOrderById(orderId);
|
||||
if (source == null)
|
||||
{
|
||||
throw new ServiceException("订单不存在");
|
||||
}
|
||||
return copyOrderForExchangeApply(source, reason, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -557,11 +554,13 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
{
|
||||
throw new ServiceException("订单不存在");
|
||||
}
|
||||
if (!ORDER_STATUS_SHIPPED.equals(order.getOrderStatus()))
|
||||
if (!ORDER_STATUS_SHIPPED.equals(order.getOrderStatus())
|
||||
&& !ORDER_STATUS_EXCHANGE_LOGISTICS.equals(order.getOrderStatus()))
|
||||
{
|
||||
throw new ServiceException("仅已发货状态的订单可完成");
|
||||
throw new ServiceException("仅已发货或换货物流状态的订单可完成");
|
||||
}
|
||||
|
||||
String completeStatus = resolveCompleteStatus(order);
|
||||
JySysOrder update = new JySysOrder();
|
||||
update.setId(orderId);
|
||||
update.setBrandTenantId(order.getBrandTenantId());
|
||||
@@ -570,12 +569,12 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
update.setAttachment(order.getAttachment());
|
||||
update.setLensFactoryTenantId(order.getLensFactoryTenantId());
|
||||
update.setLogisticsFee(order.getLogisticsFee());
|
||||
update.setOrderStatus(ORDER_STATUS_COMPLETED);
|
||||
update.setOrderStatus(completeStatus);
|
||||
update.setUpdateBy(operator);
|
||||
update.setUpdateTime(DateUtils.getNowDate());
|
||||
|
||||
int rows = jySysOrderMapper.updateJySysOrder(update);
|
||||
saveStatusLog(orderId, ORDER_STATUS_COMPLETED, operator, null, null, null);
|
||||
saveStatusLog(orderId, completeStatus, operator, null, null, null);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@@ -629,33 +628,6 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int adminCompleteExchangeRejectedOrder(Long orderId, String operator)
|
||||
{
|
||||
return completeExchangeRejectedToCompleted(orderId, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int adminCompleteAllExchangeRejectedOrders(String operator)
|
||||
{
|
||||
JySysOrder query = new JySysOrder();
|
||||
query.setOrderStatus(ORDER_STATUS_EXCHANGE_REJECTED);
|
||||
List<JySysOrder> orderList = jySysOrderMapper.selectJySysOrderList(query);
|
||||
if (orderList == null || orderList.isEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (JySysOrder order : orderList)
|
||||
{
|
||||
completeExchangeRejectedToCompleted(order.getId(), operator);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int brandResubmitOrder(Long orderId, Long brandTenantId, String reason, String operator)
|
||||
@@ -801,9 +773,10 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
private JySysOrder requireFactoryCompletableOrder(Long orderId, Long lensFactoryTenantId)
|
||||
{
|
||||
JySysOrder order = requireFactoryOwnedOrder(orderId, lensFactoryTenantId);
|
||||
if (!ORDER_STATUS_SHIPPED.equals(order.getOrderStatus()))
|
||||
if (!ORDER_STATUS_SHIPPED.equals(order.getOrderStatus())
|
||||
&& !ORDER_STATUS_EXCHANGE_LOGISTICS.equals(order.getOrderStatus()))
|
||||
{
|
||||
throw new ServiceException("仅已发货状态的订单可完成");
|
||||
throw new ServiceException("仅已发货或换货物流状态的订单可完成");
|
||||
}
|
||||
return order;
|
||||
}
|
||||
@@ -827,9 +800,10 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
{
|
||||
throw new ServiceException("无权操作该订单");
|
||||
}
|
||||
if (!ORDER_STATUS_SHIPPED.equals(order.getOrderStatus()))
|
||||
if (!ORDER_STATUS_SHIPPED.equals(order.getOrderStatus())
|
||||
&& !ORDER_STATUS_EXCHANGE_LOGISTICS.equals(order.getOrderStatus()))
|
||||
{
|
||||
throw new ServiceException("仅已发货状态的订单可完成");
|
||||
throw new ServiceException("仅已发货或换货物流状态的订单可完成");
|
||||
}
|
||||
return order;
|
||||
}
|
||||
@@ -880,46 +854,14 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
throw new ServiceException("无权操作该订单");
|
||||
}
|
||||
if (!ORDER_STATUS_COMPLETED.equals(order.getOrderStatus())
|
||||
&& !ORDER_STATUS_EXCHANGE_COMPLETED.equals(order.getOrderStatus())
|
||||
&& !ORDER_STATUS_EXCHANGE_REJECTED.equals(order.getOrderStatus()))
|
||||
{
|
||||
throw new ServiceException("仅已完成或拒绝换货申请状态的订单可申请换货");
|
||||
throw new ServiceException("仅已完成、换货完成或拒绝换货申请状态的订单可申请换货");
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
private int completeExchangeRejectedToCompleted(Long orderId, String operator)
|
||||
{
|
||||
if (orderId == null)
|
||||
{
|
||||
throw new ServiceException("订单ID不能为空");
|
||||
}
|
||||
JySysOrder order = jySysOrderMapper.selectJySysOrderById(orderId);
|
||||
if (order == null)
|
||||
{
|
||||
throw new ServiceException("订单不存在");
|
||||
}
|
||||
if (!ORDER_STATUS_EXCHANGE_REJECTED.equals(order.getOrderStatus()))
|
||||
{
|
||||
throw new ServiceException("仅拒绝换货申请状态的订单可完成");
|
||||
}
|
||||
|
||||
JySysOrder update = new JySysOrder();
|
||||
update.setId(orderId);
|
||||
update.setBrandTenantId(order.getBrandTenantId());
|
||||
update.setLensId(order.getLensId());
|
||||
update.setProductId(order.getProductId());
|
||||
update.setAttachment(order.getAttachment());
|
||||
update.setLensFactoryTenantId(order.getLensFactoryTenantId());
|
||||
update.setLogisticsFee(order.getLogisticsFee());
|
||||
update.setOrderStatus(ORDER_STATUS_COMPLETED);
|
||||
update.setUpdateBy(operator);
|
||||
update.setUpdateTime(DateUtils.getNowDate());
|
||||
|
||||
int rows = jySysOrderMapper.updateJySysOrder(update);
|
||||
saveStatusLog(orderId, ORDER_STATUS_COMPLETED, operator, null, null, null);
|
||||
return rows;
|
||||
}
|
||||
|
||||
private JySysOrder requireAdminExchangeAppliableOrder(Long orderId)
|
||||
{
|
||||
if (orderId == null)
|
||||
@@ -1126,6 +1068,50 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveShipStatus(JySysOrder order)
|
||||
{
|
||||
if (order != null && isExchangeOrder(order.getId()))
|
||||
{
|
||||
return ORDER_STATUS_EXCHANGE_LOGISTICS;
|
||||
}
|
||||
return ORDER_STATUS_SHIPPED;
|
||||
}
|
||||
|
||||
private String resolveCompleteStatus(JySysOrder order)
|
||||
{
|
||||
if (order != null && ORDER_STATUS_EXCHANGE_LOGISTICS.equals(order.getOrderStatus()))
|
||||
{
|
||||
return ORDER_STATUS_EXCHANGE_COMPLETED;
|
||||
}
|
||||
return ORDER_STATUS_COMPLETED;
|
||||
}
|
||||
|
||||
private boolean isExchangeOrder(Long orderId)
|
||||
{
|
||||
if (orderId == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
JySysOrder order = jySysOrderMapper.selectJySysOrderById(orderId);
|
||||
if (OrderExchangeHelper.isExchangeOrder(order))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
List<JySysOrderStatusLog> logs = jySysOrderStatusLogMapper.selectJySysOrderStatusLogByOrderId(orderId);
|
||||
if (logs == null || logs.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (JySysOrderStatusLog statusLog : logs)
|
||||
{
|
||||
if (statusLog != null && ORDER_STATUS_EXCHANGE_APPLY.equals(statusLog.getOrderStatus()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void saveOptometryList(JySysOrder jySysOrder)
|
||||
{
|
||||
if (jySysOrder.getId() == null)
|
||||
@@ -1200,12 +1186,6 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
statusLog.setExpressCompany(expressCompany);
|
||||
statusLog.setExpressNo(expressNo);
|
||||
statusLog.setLogisticsFee(logisticsFee);
|
||||
if (ORDER_STATUS_SHIPPED.equals(orderStatus)
|
||||
&& StringUtils.isNotEmpty(expressNo))
|
||||
{
|
||||
statusLog.setSaleSettleStatus("0");
|
||||
statusLog.setPurchaseSettleStatus("0");
|
||||
}
|
||||
statusLog.setRemark(remark);
|
||||
statusLog.setOperator(operator);
|
||||
statusLog.setOperateTime(DateUtils.getNowDate());
|
||||
@@ -1219,4 +1199,94 @@ public class JySysOrderServiceImpl extends ServiceImpl<JySysOrderMapper, JySysOr
|
||||
int randomPart = ThreadLocalRandom.current().nextInt(1000, 10000);
|
||||
return "JY" + timePart + randomPart;
|
||||
}
|
||||
|
||||
private JySysOrder copyOrderForExchangeApply(JySysOrder source, String reason, String operator)
|
||||
{
|
||||
Date now = DateUtils.getNowDate();
|
||||
JySysOrder newOrder = new JySysOrder();
|
||||
newOrder.setOrderNo(buildExchangeOrderNo(source.getOrderNo()));
|
||||
newOrder.setBrandTenantId(source.getBrandTenantId());
|
||||
newOrder.setLensFactoryTenantId(source.getLensFactoryTenantId());
|
||||
newOrder.setProductId(source.getProductId());
|
||||
newOrder.setLensId(source.getLensId());
|
||||
newOrder.setPriceB(source.getPriceB());
|
||||
newOrder.setPriceC(source.getPriceC());
|
||||
newOrder.setLogisticsFee(BigDecimal.ZERO);
|
||||
newOrder.setProgressiveMultifocal(source.getProgressiveMultifocal());
|
||||
newOrder.setProgressiveMultifocalPrice(source.getProgressiveMultifocalPrice());
|
||||
newOrder.setOrderStatus(ORDER_STATUS_EXCHANGE_APPLY);
|
||||
newOrder.setPurchaseSettleStatus("0");
|
||||
newOrder.setSaleSettleStatus("0");
|
||||
newOrder.setReceiverName(source.getReceiverName());
|
||||
newOrder.setReceiverPhone(source.getReceiverPhone());
|
||||
newOrder.setReceiverAddress(source.getReceiverAddress());
|
||||
newOrder.setAttachment(source.getAttachment());
|
||||
newOrder.setRemark(source.getRemark());
|
||||
newOrder.setDelFlag(0);
|
||||
newOrder.setCreateBy(operator);
|
||||
newOrder.setUpdateBy(operator);
|
||||
newOrder.setCreateTime(now);
|
||||
newOrder.setUpdateTime(now);
|
||||
newOrder.setOptometryList(copyOptometryList(source.getOptometryList()));
|
||||
|
||||
fillDefaultValue(newOrder);
|
||||
jySysOrderMapper.insertJySysOrder(newOrder);
|
||||
saveOptometryList(newOrder);
|
||||
saveStatusLog(newOrder.getId(), ORDER_STATUS_EXCHANGE_APPLY, operator, null, null, reason);
|
||||
return selectJySysOrderById(newOrder.getId());
|
||||
}
|
||||
|
||||
private List<JySysOrderOptometry> copyOptometryList(List<JySysOrderOptometry> sourceList)
|
||||
{
|
||||
List<JySysOrderOptometry> copiedList = new ArrayList<>();
|
||||
if (sourceList == null || sourceList.isEmpty())
|
||||
{
|
||||
return copiedList;
|
||||
}
|
||||
for (JySysOrderOptometry source : sourceList)
|
||||
{
|
||||
if (source == null || StringUtils.isEmpty(source.getEyeSide()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
JySysOrderOptometry copy = new JySysOrderOptometry();
|
||||
copy.setEyeSide(source.getEyeSide());
|
||||
copy.setSphereS(source.getSphereS());
|
||||
copy.setCylinderC(source.getCylinderC());
|
||||
copy.setAxisA(source.getAxisA());
|
||||
copy.setPd(source.getPd());
|
||||
copy.setDualPd(source.getDualPd());
|
||||
copy.setPdType(source.getPdType());
|
||||
copy.setPupilHeight(source.getPupilHeight());
|
||||
copy.setDistanceVa(source.getDistanceVa());
|
||||
copy.setNearAdd(source.getNearAdd());
|
||||
copiedList.add(copy);
|
||||
}
|
||||
return copiedList;
|
||||
}
|
||||
|
||||
private String buildExchangeOrderNo(String sourceOrderNo)
|
||||
{
|
||||
String numericPart = StringUtils.isEmpty(sourceOrderNo)
|
||||
? new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())
|
||||
+ ThreadLocalRandom.current().nextInt(1000, 10000)
|
||||
: sourceOrderNo.replaceAll("\\D", "");
|
||||
if (StringUtils.isEmpty(numericPart))
|
||||
{
|
||||
numericPart = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())
|
||||
+ ThreadLocalRandom.current().nextInt(1000, 10000);
|
||||
}
|
||||
String orderNo = "HH" + numericPart;
|
||||
int retry = 0;
|
||||
while (jySysOrderMapper.countByOrderNo(orderNo) > 0 && retry < 10)
|
||||
{
|
||||
orderNo = "HH" + numericPart + ThreadLocalRandom.current().nextInt(1000, 10000);
|
||||
retry++;
|
||||
}
|
||||
if (jySysOrderMapper.countByOrderNo(orderNo) > 0)
|
||||
{
|
||||
throw new ServiceException("生成换货订单号失败,请稍后重试");
|
||||
}
|
||||
return orderNo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.ruoyi.jysystem.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
@@ -15,6 +17,7 @@ import com.ruoyi.jysystem.mapper.JySysTenantMessageMapper;
|
||||
import com.ruoyi.jysystem.service.IJySysTenantMessageService;
|
||||
import com.ruoyi.jysystem.service.IJySysTenantService;
|
||||
import com.ruoyi.jysystem.service.SmsNotificationService;
|
||||
import com.ruoyi.jysystem.utils.OrderExchangeHelper;
|
||||
import com.ruoyi.jysystem.utils.TenantTypeHelper;
|
||||
|
||||
/**
|
||||
@@ -23,12 +26,12 @@ import com.ruoyi.jysystem.utils.TenantTypeHelper;
|
||||
@Service
|
||||
public class JySysTenantMessageServiceImpl implements IJySysTenantMessageService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(JySysTenantMessageServiceImpl.class);
|
||||
|
||||
private static final String ORDER_STATUS_DRAFT = "1";
|
||||
|
||||
private static final String ORDER_STATUS_ASSIGNED = "3";
|
||||
|
||||
private static final String ORDER_STATUS_SHIPPED = "8";
|
||||
|
||||
@Resource
|
||||
private JySysTenantMessageMapper jySysTenantMessageMapper;
|
||||
|
||||
@@ -102,17 +105,19 @@ public class JySysTenantMessageServiceImpl implements IJySysTenantMessageService
|
||||
}
|
||||
insertMessage(order.getBrandTenantId(), order.getId(), orderNo,
|
||||
JySysTenantMessage.TYPE_STATUS_CHANGE, "订单状态变更", content, orderStatus);
|
||||
if (ORDER_STATUS_SHIPPED.equals(orderStatus))
|
||||
if (shouldSendShipNotifySms(orderStatus, order))
|
||||
{
|
||||
String smsPhone = StringUtils.isNotEmpty(order.getReceiverPhone())
|
||||
? order.getReceiverPhone()
|
||||
: brandTenant.getPhone();
|
||||
if (StringUtils.isNotEmpty(smsPhone))
|
||||
{
|
||||
smsNotificationService.trySendOrderShippedNotify(smsPhone, orderNo, expressCompany, expressNo);
|
||||
}
|
||||
log.info("[发货短信] 触发检查: orderId={}, orderNo={}, orderStatus={}, exchange={}, expressCompany={}, brandTenantId={}, brandPhone={}, receiverPhone={}",
|
||||
orderId, orderNo, orderStatus, OrderExchangeHelper.isExchangeOrder(order), expressCompany,
|
||||
brandTenant.getId(), brandTenant.getPhone(), order.getReceiverPhone());
|
||||
sendShipNotifySms(orderNo, expressCompany, brandTenant.getPhone(), order.getReceiverPhone());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn("[发货短信] 跳过: 品牌方租户无效, orderId={}, brandTenantId={}, tenantType={}",
|
||||
orderId, order.getBrandTenantId(), brandTenant != null ? brandTenant.getTenantType() : null);
|
||||
}
|
||||
}
|
||||
|
||||
if (order.getLensFactoryTenantId() != null)
|
||||
@@ -170,4 +175,59 @@ public class JySysTenantMessageServiceImpl implements IJySysTenantMessageService
|
||||
}
|
||||
return tenant;
|
||||
}
|
||||
|
||||
private void sendShipNotifySms(String orderNo, String expressCompany, String brandPhone, String receiverPhone)
|
||||
{
|
||||
String smsPhone = resolveShipNotifyPhone(brandPhone, receiverPhone);
|
||||
if (StringUtils.isNotEmpty(smsPhone))
|
||||
{
|
||||
log.info("[发货短信] 准备发送: orderNo={}, smsPhone={}, expressCompany={}", orderNo, smsPhone, expressCompany);
|
||||
smsNotificationService.trySendOrderShippedNotify(smsPhone, orderNo, expressCompany);
|
||||
return;
|
||||
}
|
||||
log.warn("[发货短信] 跳过: 无有效手机号, orderNo={}, brandPhone={}, receiverPhone={}",
|
||||
orderNo, brandPhone, receiverPhone);
|
||||
}
|
||||
|
||||
private boolean shouldSendShipNotifySms(String orderStatus, JySysOrder order)
|
||||
{
|
||||
return OrderExchangeHelper.isShipNotifyStatus(orderStatus);
|
||||
}
|
||||
|
||||
private String resolveShipNotifyPhone(String brandPhone, String receiverPhone)
|
||||
{
|
||||
if (isValidMobile(brandPhone))
|
||||
{
|
||||
return normalizeMobile(brandPhone);
|
||||
}
|
||||
if (isValidMobile(receiverPhone))
|
||||
{
|
||||
return normalizeMobile(receiverPhone);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isValidMobile(String phone)
|
||||
{
|
||||
return normalizeMobile(phone) != null;
|
||||
}
|
||||
|
||||
private String normalizeMobile(String phone)
|
||||
{
|
||||
if (StringUtils.isEmpty(phone))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String digits = phone.replaceAll("\\D", "");
|
||||
if (digits.length() >= 11)
|
||||
{
|
||||
String mobile = digits.substring(digits.length() - 11);
|
||||
if (mobile.matches("^1[3-9]\\d{9}$"))
|
||||
{
|
||||
return mobile;
|
||||
}
|
||||
}
|
||||
String trimmed = phone.trim();
|
||||
return trimmed.matches("^1[3-9]\\d{9}$") ? trimmed : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,56 +35,67 @@ public class SmsNotificationServiceImpl implements SmsNotificationService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trySendOrderShippedNotify(String phone, String orderNo, String expressCompany, String expressNo)
|
||||
public void trySendOrderShippedNotify(String phone, String orderNo, String expressCompany)
|
||||
{
|
||||
Map<String, String> params = new HashMap<>(3);
|
||||
Map<String, String> params = new HashMap<>(2);
|
||||
params.put("code", nullToEmpty(orderNo));
|
||||
params.put("expressCompany", nullToEmpty(expressCompany));
|
||||
params.put("expressNo", nullToEmpty(expressNo));
|
||||
trySendScene("配片订单发货", smsProperties.getShipOrder(), phone, params);
|
||||
}
|
||||
|
||||
private void trySendScene(String sceneLabel, SmsProperties.SceneConfig scene, String phone, Map<String, String> templateParams)
|
||||
{
|
||||
if (scene == null || !scene.isEnabled())
|
||||
if (scene == null)
|
||||
{
|
||||
log.warn("[发货短信] 跳过: {}场景配置为空", sceneLabel);
|
||||
return;
|
||||
}
|
||||
if (!scene.isEnabled())
|
||||
{
|
||||
log.warn("[发货短信] 跳过: {}短信未启用, enabled=false", sceneLabel);
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isEmpty(phone))
|
||||
{
|
||||
log.warn("[发货短信] 跳过: {}手机号为空", sceneLabel);
|
||||
return;
|
||||
}
|
||||
String templateCode = StringUtils.isNotEmpty(scene.getTemplate()) ? scene.getTemplate().trim() : null;
|
||||
if (StringUtils.isEmpty(templateCode))
|
||||
{
|
||||
log.warn("未配置{}短信模板,请在 application.yml 的 sms.{} 中填写 template",
|
||||
log.warn("[发货短信] 跳过: 未配置{}短信模板, 请在 application.yml 的 sms.{} 中填写 template",
|
||||
sceneLabel, sceneLabel.contains("分配") ? "assign-order" : "ship-order");
|
||||
return;
|
||||
}
|
||||
String mobile = phone.trim();
|
||||
if (!MOBILE_PATTERN.matcher(mobile).matches())
|
||||
String mobile = normalizeMobile(phone);
|
||||
if (mobile == null)
|
||||
{
|
||||
log.warn("{}短信手机号格式无效,跳过通知: {}", sceneLabel, phone);
|
||||
log.warn("[发货短信] 跳过: {}手机号格式无效, rawPhone={}", sceneLabel, phone);
|
||||
return;
|
||||
}
|
||||
String signName = smsProperties.getSignName();
|
||||
String templateParam = JSON.toJSONString(templateParams);
|
||||
log.info("[发货短信] 调用阿里云: scene={}, phone={}, signName={}, template={}, params={}",
|
||||
sceneLabel, mobile, signName, templateCode, templateParam);
|
||||
try
|
||||
{
|
||||
String templateParam = JSON.toJSONString(templateParams);
|
||||
Map<String, Object> result = SmsUtils.sendNotifyMsg(mobile, smsProperties.getSignName(),
|
||||
templateCode, templateParam);
|
||||
Map<String, Object> result = SmsUtils.sendNotifyMsg(mobile, signName, templateCode, templateParam);
|
||||
Integer statusCode = (Integer) result.get("statusCode");
|
||||
String bodyCode = (String) result.get("bodyCode");
|
||||
String bodyMessage = (String) result.get("bodyMessage");
|
||||
String bizId = (String) result.get("bizId");
|
||||
String requestId = (String) result.get("requestId");
|
||||
if (statusCode == null || statusCode != 200 || !"OK".equalsIgnoreCase(bodyCode))
|
||||
{
|
||||
log.warn("{}短信发送失败: phone={}, template={}, params={}, statusCode={}, bodyCode={}",
|
||||
sceneLabel, mobile, templateCode, templateParam, statusCode, bodyCode);
|
||||
log.warn("[发货短信] 发送失败: scene={}, phone={}, template={}, params={}, statusCode={}, bodyCode={}, bodyMessage={}, bizId={}, requestId={}",
|
||||
sceneLabel, mobile, templateCode, templateParam, statusCode, bodyCode, bodyMessage, bizId, requestId);
|
||||
return;
|
||||
}
|
||||
log.info("{}短信已发送: {}", sceneLabel, mobile);
|
||||
log.info("[发货短信] 发送成功: scene={}, phone={}, bizId={}, requestId={}", sceneLabel, mobile, bizId, requestId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("{}短信发送异常: phone={}, err={}", sceneLabel, mobile, e.getMessage());
|
||||
log.warn("[发货短信] 发送异常: scene={}, phone={}, err={}", sceneLabel, mobile, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,4 +103,22 @@ public class SmsNotificationServiceImpl implements SmsNotificationService
|
||||
{
|
||||
return value != null ? value : "";
|
||||
}
|
||||
|
||||
private String normalizeMobile(String phone)
|
||||
{
|
||||
if (StringUtils.isEmpty(phone))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String digits = phone.replaceAll("\\D", "");
|
||||
if (digits.length() >= 11)
|
||||
{
|
||||
String mobile = digits.substring(digits.length() - 11);
|
||||
if (MOBILE_PATTERN.matcher(mobile).matches())
|
||||
{
|
||||
return mobile;
|
||||
}
|
||||
}
|
||||
return MOBILE_PATTERN.matcher(phone.trim()).matches() ? phone.trim() : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.ruoyi.jysystem.utils;
|
||||
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.jysystem.domain.JySysOrder;
|
||||
|
||||
/**
|
||||
* 换货单识别(与前端 isExchangeOrder 一致)
|
||||
*/
|
||||
public final class OrderExchangeHelper
|
||||
{
|
||||
private static final String ORDER_STATUS_EXCHANGE_APPLY = "12";
|
||||
|
||||
private static final String ORDER_STATUS_EXCHANGE_AGREED = "13";
|
||||
|
||||
private static final String ORDER_STATUS_EXCHANGE_LOGISTICS = "15";
|
||||
|
||||
private static final String ORDER_STATUS_EXCHANGE_COMPLETED = "17";
|
||||
|
||||
private static final String ORDER_STATUS_SHIPPED = "8";
|
||||
|
||||
private OrderExchangeHelper()
|
||||
{
|
||||
}
|
||||
|
||||
public static boolean isExchangeOrder(JySysOrder order)
|
||||
{
|
||||
if (order == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (StringUtils.isNotEmpty(order.getOrderNo()) && order.getOrderNo().startsWith("HH"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
String status = order.getOrderStatus();
|
||||
return ORDER_STATUS_EXCHANGE_APPLY.equals(status)
|
||||
|| ORDER_STATUS_EXCHANGE_AGREED.equals(status)
|
||||
|| ORDER_STATUS_EXCHANGE_LOGISTICS.equals(status)
|
||||
|| ORDER_STATUS_EXCHANGE_COMPLETED.equals(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单进入发货/换货物流状态时发送短信
|
||||
*/
|
||||
public static boolean isShipNotifyStatus(String orderStatus)
|
||||
{
|
||||
return ORDER_STATUS_SHIPPED.equals(orderStatus)
|
||||
|| ORDER_STATUS_EXCHANGE_LOGISTICS.equals(orderStatus);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="logisticsFee" column="logistics_fee" />
|
||||
<result property="progressiveMultifocal" column="progressive_multifocal" />
|
||||
<result property="progressiveMultifocalPrice" column="progressive_multifocal_price" />
|
||||
<result property="presbyopia" column="presbyopia" />
|
||||
<result property="orderStatus" column="order_status" />
|
||||
<result property="purchaseSettleStatus" column="purchase_settle_status" />
|
||||
<result property="saleSettleStatus" column="sale_settle_status" />
|
||||
@@ -42,7 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
o.lens_factory_tenant_id, lt.name as lens_factory_tenant_name,
|
||||
o.product_id, p.product_model, p.product_name,
|
||||
o.lens_id, l.product_name as lens_product_name, l.material as lens_material,
|
||||
o.price_b, o.price_c, o.logistics_fee, o.progressive_multifocal, o.progressive_multifocal_price, o.order_status, o.purchase_settle_status, o.sale_settle_status,
|
||||
o.price_b, o.price_c, o.logistics_fee, o.progressive_multifocal, o.progressive_multifocal_price, o.presbyopia, o.order_status, o.purchase_settle_status, o.sale_settle_status,
|
||||
o.receiver_name, o.receiver_phone, o.receiver_address, o.attachment,
|
||||
o.remark, o.del_flag, o.create_by, o.create_time, o.update_by, o.update_time
|
||||
from jy_sys_order o
|
||||
@@ -98,6 +99,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="logisticsFee != null">logistics_fee,</if>
|
||||
<if test="progressiveMultifocal != null">progressive_multifocal,</if>
|
||||
<if test="progressiveMultifocalPrice != null">progressive_multifocal_price,</if>
|
||||
<if test="presbyopia != null">presbyopia,</if>
|
||||
<if test="orderStatus != null">order_status,</if>
|
||||
<if test="purchaseSettleStatus != null">purchase_settle_status,</if>
|
||||
<if test="saleSettleStatus != null">sale_settle_status,</if>
|
||||
@@ -123,6 +125,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="logisticsFee != null">#{logisticsFee},</if>
|
||||
<if test="progressiveMultifocal != null">#{progressiveMultifocal},</if>
|
||||
<if test="progressiveMultifocalPrice != null">#{progressiveMultifocalPrice},</if>
|
||||
<if test="presbyopia != null">#{presbyopia},</if>
|
||||
<if test="orderStatus != null">#{orderStatus},</if>
|
||||
<if test="purchaseSettleStatus != null">#{purchaseSettleStatus},</if>
|
||||
<if test="saleSettleStatus != null">#{saleSettleStatus},</if>
|
||||
@@ -152,6 +155,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="logisticsFee != null">logistics_fee = #{logisticsFee},</if>
|
||||
<if test="progressiveMultifocal != null">progressive_multifocal = #{progressiveMultifocal},</if>
|
||||
<if test="progressiveMultifocalPrice != null">progressive_multifocal_price = #{progressiveMultifocalPrice},</if>
|
||||
<if test="presbyopia != null">presbyopia = #{presbyopia},</if>
|
||||
<if test="orderStatus != null">order_status = #{orderStatus},</if>
|
||||
<if test="purchaseSettleStatus != null">purchase_settle_status = #{purchaseSettleStatus},</if>
|
||||
<if test="saleSettleStatus != null">sale_settle_status = #{saleSettleStatus},</if>
|
||||
@@ -185,11 +189,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and (brand_tenant_id = #{tenantId} or lens_factory_tenant_id = #{tenantId})
|
||||
</select>
|
||||
|
||||
<select id="countByOrderNo" resultType="int">
|
||||
select count(1)
|
||||
from jy_sys_order
|
||||
where del_flag = '0'
|
||||
and order_no = #{orderNo}
|
||||
</select>
|
||||
|
||||
<select id="selectCompletedOrdersByYearMonth" resultMap="JySysOrderResult">
|
||||
select o.id, o.order_no, o.brand_tenant_id, bt.name as brand_tenant_name,
|
||||
o.lens_factory_tenant_id, lf.name as lens_factory_tenant_name,
|
||||
o.product_id, o.lens_id, o.price_b, o.price_c, o.logistics_fee,
|
||||
o.progressive_multifocal, o.progressive_multifocal_price, o.order_status,
|
||||
o.progressive_multifocal, o.progressive_multifocal_price, o.presbyopia, o.order_status,
|
||||
o.purchase_settle_status, o.sale_settle_status,
|
||||
o.receiver_name, o.receiver_phone, o.receiver_address, o.attachment, o.remark,
|
||||
o.del_flag, o.create_by, o.create_time, o.update_by, o.update_time
|
||||
@@ -197,11 +208,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
left join jy_sys_tenant bt on o.brand_tenant_id = bt.id
|
||||
left join jy_sys_tenant lf on o.lens_factory_tenant_id = lf.id
|
||||
where o.del_flag = '0'
|
||||
and o.order_status = '9'
|
||||
and o.order_status in ('9', '17')
|
||||
and exists (
|
||||
select 1 from jy_sys_order_status_log sl
|
||||
where sl.order_id = o.id
|
||||
and sl.order_status = '9'
|
||||
and sl.order_status in ('9', '17')
|
||||
and DATE_FORMAT(sl.operate_time, '%Y-%m') = #{settleYearMonth}
|
||||
)
|
||||
order by o.id asc
|
||||
|
||||
@@ -11,8 +11,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="expressCompany" column="express_company" />
|
||||
<result property="expressNo" column="express_no" />
|
||||
<result property="logisticsFee" column="logistics_fee" />
|
||||
<result property="saleSettleStatus" column="sale_settle_status" />
|
||||
<result property="purchaseSettleStatus" column="purchase_settle_status" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="operator" column="operator" />
|
||||
<result property="operateTime" column="operate_time" />
|
||||
@@ -20,7 +18,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<select id="selectJySysOrderStatusLogByOrderId" parameterType="Long" resultMap="JySysOrderStatusLogResult">
|
||||
select id, order_id, order_status, express_company, express_no, logistics_fee,
|
||||
sale_settle_status, purchase_settle_status, remark, operator, operate_time
|
||||
remark, operator, operate_time
|
||||
from jy_sys_order_status_log
|
||||
where order_id = #{orderId}
|
||||
order by operate_time desc, id desc
|
||||
@@ -43,8 +41,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="expressCompany != null">express_company,</if>
|
||||
<if test="expressNo != null">express_no,</if>
|
||||
<if test="logisticsFee != null">logistics_fee,</if>
|
||||
<if test="saleSettleStatus != null">sale_settle_status,</if>
|
||||
<if test="purchaseSettleStatus != null">purchase_settle_status,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="operator != null">operator,</if>
|
||||
<if test="operateTime != null">operate_time,</if>
|
||||
@@ -55,8 +51,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="expressCompany != null">#{expressCompany},</if>
|
||||
<if test="expressNo != null">#{expressNo},</if>
|
||||
<if test="logisticsFee != null">#{logisticsFee},</if>
|
||||
<if test="saleSettleStatus != null">#{saleSettleStatus},</if>
|
||||
<if test="purchaseSettleStatus != null">#{purchaseSettleStatus},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="operator != null">#{operator},</if>
|
||||
<if test="operateTime != null">#{operateTime},</if>
|
||||
@@ -64,52 +58,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</insert>
|
||||
|
||||
<select id="selectUnsettledShipLogsByOrderIdAndType" resultMap="JySysOrderStatusLogResult">
|
||||
select id, order_id, order_status, express_company, express_no, logistics_fee,
|
||||
sale_settle_status, purchase_settle_status, remark, operator, operate_time
|
||||
from jy_sys_order_status_log
|
||||
where order_id = #{orderId}
|
||||
and order_status = '8'
|
||||
and express_no is not null
|
||||
and TRIM(express_no) != ''
|
||||
<choose>
|
||||
<when test="settlementType == 'AR'">
|
||||
and (sale_settle_status is null or sale_settle_status = '0')
|
||||
</when>
|
||||
<when test="settlementType == 'AP'">
|
||||
and (purchase_settle_status is null or purchase_settle_status = '0')
|
||||
</when>
|
||||
</choose>
|
||||
order by operate_time asc, id asc
|
||||
select l.id, l.order_id, l.order_status, l.express_company, l.express_no, l.logistics_fee,
|
||||
l.remark, l.operator, l.operate_time
|
||||
from jy_sys_order_status_log l
|
||||
where l.order_id = #{orderId}
|
||||
and l.order_status in ('8', '15')
|
||||
and l.express_no is not null
|
||||
and TRIM(l.express_no) != ''
|
||||
and l.id not in (
|
||||
select fl.status_log_id
|
||||
from jy_sys_fee_settlement_logistics fl
|
||||
inner join jy_sys_fee_settlement_item fi on fl.settlement_item_id = fi.id
|
||||
inner join jy_sys_fee_settlement fs on fi.settlement_id = fs.id
|
||||
where fl.status_log_id is not null
|
||||
and fs.settlement_type = #{settlementType}
|
||||
)
|
||||
order by l.operate_time asc, l.id asc
|
||||
</select>
|
||||
|
||||
<update id="updateSaleSettleStatusByIds">
|
||||
update jy_sys_order_status_log
|
||||
set sale_settle_status = #{settleStatus}
|
||||
where id in
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<update id="updatePurchaseSettleStatusByIds">
|
||||
update jy_sys_order_status_log
|
||||
set purchase_settle_status = #{settleStatus}
|
||||
where id in
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<update id="resetShipLogSettleStatusByOrderIds">
|
||||
update jy_sys_order_status_log
|
||||
set sale_settle_status = '0',
|
||||
purchase_settle_status = '0'
|
||||
where order_id in
|
||||
<foreach collection="orderIds" item="orderId" open="(" separator="," close=")">
|
||||
#{orderId}
|
||||
</foreach>
|
||||
and order_status = '8'
|
||||
and express_no is not null
|
||||
and TRIM(express_no) != ''
|
||||
</update>
|
||||
</mapper>
|
||||
|
||||
@@ -16,6 +16,7 @@ CREATE TABLE `jy_sys_order` (
|
||||
`logistics_fee` decimal(10, 2) NULL DEFAULT NULL COMMENT '物流费用',
|
||||
`progressive_multifocal` tinyint(4) NULL DEFAULT 0 COMMENT '是否渐进多焦点(0否 1是)',
|
||||
`progressive_multifocal_price` decimal(10, 2) NULL DEFAULT 0 COMMENT '渐进多焦点价格',
|
||||
`presbyopia` tinyint(4) NULL DEFAULT 0 COMMENT '是否老花(0否 1是)',
|
||||
`order_status` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '订单状态(字典 jy_order_status)',
|
||||
`purchase_settle_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '采购结算状态(0未结算 1已结算)',
|
||||
`sale_settle_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '销售结算状态(0未结算 1已结算)',
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- 订单表增加:是否老花
|
||||
ALTER TABLE `jy_sys_order`
|
||||
ADD COLUMN `presbyopia` tinyint(4) NULL DEFAULT 0 COMMENT '是否老花(0否 1是)' AFTER `progressive_multifocal_price`;
|
||||
@@ -51,7 +51,7 @@ INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value
|
||||
SELECT 166, 14, '退货物流', '14', 'jy_order_status', NULL, 'primary', 'N', '0', 'admin', NOW(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'jy_order_status' AND `dict_value` = '14');
|
||||
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`)
|
||||
SELECT 167, 15, '换货已发货', '15', 'jy_order_status', NULL, 'primary', 'N', '0', 'admin', NOW(), NULL
|
||||
SELECT 167, 15, '换货物流', '15', 'jy_order_status', NULL, 'primary', 'N', '0', 'admin', NOW(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'jy_order_status' AND `dict_value` = '15');
|
||||
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`)
|
||||
SELECT 168, 16, '退货完成', '16', 'jy_order_status', NULL, 'success', 'N', '0', 'admin', NOW(), NULL
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 订单状态字典:恢复/确保换货物流、换货完成标签
|
||||
UPDATE `sys_dict_data`
|
||||
SET `dict_label` = '换货物流'
|
||||
WHERE `dict_type` = 'jy_order_status' AND `dict_value` = '15';
|
||||
|
||||
UPDATE `sys_dict_data`
|
||||
SET `dict_label` = '换货完成'
|
||||
WHERE `dict_type` = 'jy_order_status' AND `dict_value` = '17';
|
||||
|
||||
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`)
|
||||
SELECT 167, 15, '换货物流', '15', 'jy_order_status', NULL, 'primary', 'N', '0', 'admin', NOW(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'jy_order_status' AND `dict_value` = '15');
|
||||
|
||||
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `remark`)
|
||||
SELECT 169, 17, '换货完成', '17', 'jy_order_status', NULL, 'success', 'N', '0', 'admin', NOW(), NULL
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_dict_data` WHERE `dict_type` = 'jy_order_status' AND `dict_value` = '17');
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 订单状态日志:移除物流应收/应付结算状态字段(改由费用结算单物流明细关联发货日志)
|
||||
ALTER TABLE `jy_sys_order_status_log`
|
||||
DROP COLUMN `sale_settle_status`,
|
||||
DROP COLUMN `purchase_settle_status`;
|
||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "OpticalSystem",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
@@ -3,16 +3,16 @@ package com.ruoyi.common.utils;
|
||||
import com.aliyun.dysmsapi20170525.Client;
|
||||
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
|
||||
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
|
||||
import com.aliyun.tea.*;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.aliyun.teautil.models.RuntimeOptions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.aliyun.teautil.Common.assertAsString;
|
||||
|
||||
public class SmsUtils {
|
||||
private static final Logger log = LoggerFactory.getLogger(SmsUtils.class);
|
||||
public static Client createClient() throws Exception {
|
||||
Config config = new Config()
|
||||
// 配置 AccessKey ID,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
|
||||
@@ -43,6 +43,8 @@ public class SmsUtils {
|
||||
*/
|
||||
public static Map<String, Object> sendNotifyMsg(String phone, String signName, String templateCode, String templateParam) throws Exception {
|
||||
HashMap<String, Object> sendSmsResponseMap = new HashMap<>();
|
||||
log.info("[发货短信] SmsUtils 请求: phone={}, signName={}, templateCode={}, templateParam={}",
|
||||
phone, signName, templateCode, templateParam);
|
||||
try {
|
||||
Client client = SmsUtils.createClient();
|
||||
SendSmsRequest sendSmsRequest = new SendSmsRequest()
|
||||
@@ -54,19 +56,26 @@ public class SmsUtils {
|
||||
SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, new RuntimeOptions());
|
||||
|
||||
Integer statusCode = sendSmsResponse.getStatusCode();
|
||||
String responseCode = sendSmsResponse.getBody().getCode();
|
||||
String responseCode = sendSmsResponse.getBody() != null ? sendSmsResponse.getBody().getCode() : null;
|
||||
String responseMessage = sendSmsResponse.getBody() != null ? sendSmsResponse.getBody().getMessage() : null;
|
||||
String bizId = sendSmsResponse.getBody() != null ? sendSmsResponse.getBody().getBizId() : null;
|
||||
String requestId = sendSmsResponse.getBody() != null ? sendSmsResponse.getBody().getRequestId() : null;
|
||||
|
||||
sendSmsResponseMap.put("statusCode", statusCode);
|
||||
sendSmsResponseMap.put("bodyCode", responseCode);
|
||||
} catch (TeaException error) {
|
||||
System.out.println(error.getMessage());
|
||||
System.out.println(error.getData().get("Recommend"));
|
||||
assertAsString(error.message);
|
||||
sendSmsResponseMap.put("bodyMessage", responseMessage);
|
||||
sendSmsResponseMap.put("bizId", bizId);
|
||||
sendSmsResponseMap.put("requestId", requestId);
|
||||
log.info("[发货短信] SmsUtils 响应: phone={}, statusCode={}, bodyCode={}, bodyMessage={}, bizId={}, requestId={}",
|
||||
phone, statusCode, responseCode, responseMessage, bizId, requestId);
|
||||
} catch (com.aliyun.tea.TeaException error) {
|
||||
log.warn("[发货短信] SmsUtils TeaException: phone={}, code={}, message={}",
|
||||
phone, error.getCode(), error.getMessage());
|
||||
sendSmsResponseMap.put("bodyCode", error.getCode());
|
||||
sendSmsResponseMap.put("bodyMessage", error.getMessage());
|
||||
} catch (Exception _error) {
|
||||
TeaException error = new TeaException(_error.getMessage(), _error);
|
||||
System.out.println(error.getMessage());
|
||||
System.out.println("诊断地址" + error.getData().get("Recommend"));
|
||||
assertAsString(error.message);
|
||||
log.warn("[发货短信] SmsUtils 异常: phone={}, err={}", phone, _error.getMessage(), _error);
|
||||
sendSmsResponseMap.put("bodyMessage", _error.getMessage());
|
||||
}
|
||||
return sendSmsResponseMap;
|
||||
}
|
||||
|
||||
2
uniapp/.env.development
Normal file
2
uniapp/.env.development
Normal file
@@ -0,0 +1,2 @@
|
||||
# H5 开发环境(走 vite 代理)
|
||||
VITE_APP_BASE_API=/dev-api
|
||||
3
uniapp/.env.production
Normal file
3
uniapp/.env.production
Normal file
@@ -0,0 +1,3 @@
|
||||
# App 打包:改为手机能访问的后端地址(不要用 localhost)
|
||||
# 示例:局域网 IP 或正式域名
|
||||
VITE_APP_BASE_API=http://8.133.20.230:2092/prod-api
|
||||
6
uniapp/.gitignore
vendored
Normal file
6
uniapp/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
unpackage/
|
||||
.hbuilderx/
|
||||
.DS_Store
|
||||
*.log
|
||||
86
uniapp/README.md
Normal file
86
uniapp/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# 功能镜片配镜系统 - UniApp 移动端
|
||||
|
||||
基于 `developer-front` 门户端的 UniApp 实现,覆盖认证、配片单/制片单、消息通知、账户中心等核心业务。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
uniapp/
|
||||
├── src/
|
||||
│ ├── api/ # 接口封装
|
||||
│ ├── components/ # NavBar、DictTag
|
||||
│ ├── config/ # API 配置
|
||||
│ ├── pages/ # 页面
|
||||
│ ├── store/ # Pinia 状态
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── App.vue
|
||||
│ ├── main.js
|
||||
│ ├── pages.json
|
||||
│ └── manifest.json
|
||||
├── index.html
|
||||
├── vite.config.js
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
cd uniapp
|
||||
npm install
|
||||
npm run dev:h5
|
||||
```
|
||||
|
||||
默认端口 `2093`,API 代理至 `http://localhost:8082/dev-api`。
|
||||
|
||||
## 与 developer-front 的对应关系
|
||||
|
||||
| Web 端 | UniApp |
|
||||
|--------|--------|
|
||||
| Cookie Developer-Token | uni.setStorageSync |
|
||||
| axios + Element UI | uni.request + 原生组件 |
|
||||
| vue-router | pages.json |
|
||||
| Vuex | Pinia |
|
||||
| NavBar.vue | components/NavBar |
|
||||
| lens-order.vue | pages/order/lens-order |
|
||||
| factory-order.vue | pages/order/factory-order |
|
||||
|
||||
## Android 编译器/SDK 版本不匹配
|
||||
|
||||
若 APK 提示「HBuilderX 4.15 编译,SDK 5.07 不匹配」:
|
||||
|
||||
- **原因:** CLI 项目编译器在 `package.json`,不随 HBuilderX 升级;云打包 SDK 已是 5.x。
|
||||
- **处理:** HBuilderX 升级到 **5.07** → 执行 `npm run update:uni` → `npm install` → 删除 `unpackage` → 重新云打包。
|
||||
- **验证后可选:** `manifest.json` 的 `app-plus.compatible.ignoreVersion` 设为 `true` 关闭弹窗。
|
||||
|
||||
## APK 卡在 Logo 启动页
|
||||
|
||||
1. 已在 `App.vue` 中调用 `plus.navigator.closeSplashscreen()` 手动关闭启动页
|
||||
2. CLI 构建产物里 `autoclose` 可能为 `false`,需重新云打包
|
||||
3. **adb 包名过滤错误**:AppID 为 `__UNI__C17C074`,应使用:
|
||||
```powershell
|
||||
adb logcat -c
|
||||
adb logcat | findstr /i "UNIC17C074 DCloud console uni-app JS"
|
||||
adb logcat *:E | findstr /i "uni dcloud"
|
||||
```
|
||||
4. 查看已安装包名:`adb shell pm list packages | findstr uni`
|
||||
|
||||
## AppID 配置(打包 Android/iOS 必做)
|
||||
|
||||
CLI 创建的项目不会自动生成 AppID,需在 **HBuilderX** 中获取并写入 `src/manifest.json`:
|
||||
|
||||
1. 安装 [HBuilderX](https://www.dcloud.io/hbuilderx.html),使用 DCloud 账号登录
|
||||
2. **文件 → 打开目录**,选择本项目的 `uniapp` 文件夹
|
||||
3. 打开 `src/manifest.json`,切换到 **「基础配置」** 可视化界面
|
||||
4. 点击 **「应用标识(AppID)」** 右侧的 **「重新获取」**
|
||||
5. 保存后,`appid` 会自动写入(格式类似 `__UNI__1A2B3C4`)
|
||||
|
||||
也可在 [DCloud 开发者中心](https://dev.dcloud.net.cn) 手动创建应用,将生成的 AppID 粘贴到 `manifest.json` 的 `"appid"` 字段。
|
||||
|
||||
> AppID 与 DCloud 账号绑定,请勿使用他人 AppID 或随意编造(如 `__UNI__OPTICAL_SYSTEM`)。
|
||||
|
||||
## 待完善项
|
||||
|
||||
- 收货地址 CRUD
|
||||
- 商城模块
|
||||
- 配片单完整验光表单与附件上传
|
||||
- 小程序/App 支付
|
||||
17
uniapp/index.html
Normal file
17
uniapp/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||
document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<title>功能镜片配镜系统</title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
4
uniapp/nativeResources/android/AndroidManifest.xml
Normal file
4
uniapp/nativeResources/android/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application android:usesCleartextTraffic="true" />
|
||||
</manifest>
|
||||
12590
uniapp/package-lock.json
generated
Normal file
12590
uniapp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
uniapp/package.json
Normal file
45
uniapp/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "optical-system-uniapp",
|
||||
"version": "1.0.0",
|
||||
"description": "觉远创智功能镜片配镜系统 - UniApp 移动端",
|
||||
"scripts": {
|
||||
"dev:h5": "uni",
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"dev:app": "uni -p app",
|
||||
"build:h5": "uni build",
|
||||
"build:mp-weixin": "uni build -p mp-weixin",
|
||||
"build:app": "uni build -p app && node scripts/patch-app-manifest.js && node scripts/patch-app-config.js",
|
||||
"update:uni": "npx @dcloudio/uvm@latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-app-harmony": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-components": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-h5": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-alipay": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-baidu": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-harmony": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-jd": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-kuaishou": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-lark": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-qq": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-toutiao": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-5000720260410001",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "3.5.39",
|
||||
"vue-i18n": "9.14.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "3.4.28",
|
||||
"@dcloudio/uni-automator": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-5000720260410001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-5000720260410001",
|
||||
"@vue/runtime-core": "3.5.39",
|
||||
"sass": "^1.77.0",
|
||||
"vite": "5.2.8"
|
||||
}
|
||||
}
|
||||
11
uniapp/scripts/adb-debug.ps1
Normal file
11
uniapp/scripts/adb-debug.ps1
Normal file
@@ -0,0 +1,11 @@
|
||||
# 用法:先启动 App,再运行此脚本
|
||||
adb devices
|
||||
Write-Host "`n=== 已安装的 uni 包 ==="
|
||||
adb shell pm list packages | findstr /i uni
|
||||
|
||||
Write-Host "`n=== 清空日志,请手动点开 App ==="
|
||||
adb logcat -c
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
Write-Host "`n=== 抓取 DCloud / 崩溃 / WebView 日志(30秒)==="
|
||||
adb logcat -v time -t 800 | findstr /i "dcloud uni jyuan UNIOPTICAL NIOPTICAL AndroidRuntime FATAL chromium bootstrap splash WebView loadUrl"
|
||||
26
uniapp/scripts/patch-app-config.js
Normal file
26
uniapp/scripts/patch-app-config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 强制 App 使用 webview 渲染,避免 renderer:auto 在部分设备上卡启动页。
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const root = path.resolve(__dirname, '..')
|
||||
|
||||
const targets = [
|
||||
path.join(root, 'dist/build/app/app-config-service.js'),
|
||||
path.join(root, 'dist/build/app-plus/app-config-service.js')
|
||||
]
|
||||
|
||||
for (const file of targets) {
|
||||
if (!fs.existsSync(file)) continue
|
||||
let content = fs.readFileSync(file, 'utf8')
|
||||
const next = content
|
||||
.replace(/"renderer":"auto"/g, '"renderer":"webview"')
|
||||
.replace(/"waiting":true/g, '"waiting":false')
|
||||
if (next !== content) {
|
||||
fs.writeFileSync(file, next, 'utf8')
|
||||
console.log('[patch-app-config] patched', path.relative(root, file))
|
||||
}
|
||||
}
|
||||
40
uniapp/scripts/patch-app-manifest.js
Normal file
40
uniapp/scripts/patch-app-manifest.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* CLI 构建 app manifest 中 splashscreen.autoclose 恒为 false,打包后补丁修正。
|
||||
* HBuilderX 云打包会读取 dist/build/app 或 app-plus 下的 manifest.json。
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const root = path.resolve(__dirname, '..')
|
||||
|
||||
const targets = [
|
||||
path.join(root, 'dist/build/app/manifest.json'),
|
||||
path.join(root, 'dist/build/app-plus/manifest.json')
|
||||
]
|
||||
|
||||
for (const file of targets) {
|
||||
if (!fs.existsSync(file)) continue
|
||||
const manifest = JSON.parse(fs.readFileSync(file, 'utf8'))
|
||||
if (manifest.plus) {
|
||||
manifest.plus.splashscreen = {
|
||||
...(manifest.plus.splashscreen || {}),
|
||||
autoclose: true,
|
||||
waiting: false,
|
||||
delay: 0
|
||||
}
|
||||
manifest.plus.renderer = 'webview'
|
||||
manifest.plus.compatible = { ignoreVersion: true }
|
||||
manifest.plus.distribute = manifest.plus.distribute || {}
|
||||
manifest.plus.distribute.google = {
|
||||
...(manifest.plus.distribute.google || {}),
|
||||
packagename: 'com.jyuan.optical'
|
||||
}
|
||||
if (manifest.plus['uni-app']) {
|
||||
manifest.plus['uni-app'].renderer = 'webview'
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(file, JSON.stringify(manifest, null, 2), 'utf8')
|
||||
console.log('[patch-app-manifest] patched', path.relative(root, file))
|
||||
}
|
||||
33
uniapp/scripts/verify-apk.ps1
Normal file
33
uniapp/scripts/verify-apk.ps1
Normal file
@@ -0,0 +1,33 @@
|
||||
# 用法:.\scripts\verify-apk.ps1 "D:\path\to\app.apk"
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ApkPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path $ApkPath)) {
|
||||
Write-Error "APK 不存在: $ApkPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$sdk = $env:ANDROID_HOME
|
||||
if (-not $sdk) { $sdk = "$env:LOCALAPPDATA\Android\Sdk" }
|
||||
|
||||
$aapt = Get-ChildItem -Path "$sdk\build-tools" -Recurse -Filter "aapt.exe" -ErrorAction SilentlyContinue |
|
||||
Sort-Object FullName -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
Write-Host "=== APK 文件 ==="
|
||||
Write-Host $ApkPath
|
||||
Write-Host ""
|
||||
|
||||
if ($aapt) {
|
||||
Write-Host "=== 包名(aapt dump badging)==="
|
||||
& $aapt.FullName dump badging $ApkPath | Select-String "package:|launchable-activity"
|
||||
} else {
|
||||
Write-Host "未找到 aapt.exe(Android SDK build-tools)。"
|
||||
Write-Host "请在 HBuilderX 中打开 src/manifest.json -> 基础配置 -> Android包名,确认是否为 com.jyuan.optical"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== 手机已安装的相关包 ==="
|
||||
adb shell pm list packages | findstr /i "uni jyuan optical"
|
||||
179
uniapp/src/App.vue
Normal file
179
uniapp/src/App.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<script>
|
||||
|
||||
import { closeSplashscreen } from '@/utils/splash.js'
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
onLaunch() {
|
||||
|
||||
closeSplashscreen()
|
||||
|
||||
},
|
||||
|
||||
onShow() {
|
||||
|
||||
closeSplashscreen()
|
||||
|
||||
},
|
||||
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
onError(err) {
|
||||
|
||||
console.error('[App] onError', err)
|
||||
|
||||
closeSplashscreen()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
page {
|
||||
|
||||
background-color: #f5f7fa;
|
||||
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
|
||||
font-size: 28rpx;
|
||||
|
||||
color: #333;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.container {
|
||||
|
||||
padding: 24rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.btn-primary {
|
||||
|
||||
background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
|
||||
|
||||
color: #fff;
|
||||
|
||||
border: none;
|
||||
|
||||
border-radius: 12rpx;
|
||||
|
||||
font-size: 30rpx;
|
||||
|
||||
height: 88rpx;
|
||||
|
||||
line-height: 88rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.btn-primary[disabled] {
|
||||
|
||||
opacity: 0.6;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.card {
|
||||
|
||||
background: #fff;
|
||||
|
||||
border-radius: 16rpx;
|
||||
|
||||
padding: 24rpx;
|
||||
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.text-muted {
|
||||
|
||||
color: #94a3b8;
|
||||
|
||||
font-size: 24rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.text-primary {
|
||||
|
||||
color: #2563eb;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.text-danger {
|
||||
|
||||
color: #ef4444;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.text-success {
|
||||
|
||||
color: #22c55e;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.flex-row {
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.flex-between {
|
||||
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.empty-tip {
|
||||
|
||||
text-align: center;
|
||||
|
||||
padding: 80rpx 0;
|
||||
|
||||
color: #94a3b8;
|
||||
|
||||
font-size: 28rpx;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
9
uniapp/src/api/dict.js
Normal file
9
uniapp/src/api/dict.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
export function getDicts(dictType) {
|
||||
return request({ url: '/system/dict/data/type/' + dictType, method: 'GET' })
|
||||
}
|
||||
|
||||
export function listLensOptions() {
|
||||
return request({ url: '/jysystem/lens/options', method: 'GET' })
|
||||
}
|
||||
64
uniapp/src/api/login.js
Normal file
64
uniapp/src/api/login.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
export function login(username, password, code, uuid, directLogin) {
|
||||
return request({
|
||||
url: '/login',
|
||||
method: 'POST',
|
||||
isToken: false,
|
||||
data: { username, password, code, uuid, directLogin }
|
||||
})
|
||||
}
|
||||
|
||||
export function register(data) {
|
||||
return request({ url: '/register', method: 'POST', isToken: false, data })
|
||||
}
|
||||
|
||||
export function registerByCode(data) {
|
||||
return request({ url: '/register/code', method: 'POST', isToken: false, data })
|
||||
}
|
||||
|
||||
export function getInfo() {
|
||||
return request({ url: '/getInfo', method: 'GET' })
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
return request({ url: '/logout', method: 'POST' })
|
||||
}
|
||||
|
||||
export function getCodeImg() {
|
||||
return request({ url: '/captchaImage', method: 'GET', isToken: false, timeout: 20000 })
|
||||
}
|
||||
|
||||
export function sendSmsCode(mobile) {
|
||||
return request({ url: '/sms/code', method: 'POST', isToken: false, data: { mobile } })
|
||||
}
|
||||
|
||||
export function sendEmailCode(email) {
|
||||
return request({ url: '/email/code', method: 'POST', isToken: false, data: { email } })
|
||||
}
|
||||
|
||||
export function emailLogin(email, emailCode, uuid, directLogin) {
|
||||
return request({
|
||||
url: '/email/login',
|
||||
method: 'POST',
|
||||
isToken: false,
|
||||
data: { email, emailCode, uuid, directLogin }
|
||||
})
|
||||
}
|
||||
|
||||
export function smsLogin(mobile, smsCode, uuid, directLogin) {
|
||||
return request({
|
||||
url: '/sms/login',
|
||||
method: 'POST',
|
||||
isToken: false,
|
||||
data: { mobile, smsCode, uuid, directLogin }
|
||||
})
|
||||
}
|
||||
|
||||
export function resetPasswordByAccount(data) {
|
||||
return request({ url: '/resetPassword/account', method: 'POST', isToken: false, data })
|
||||
}
|
||||
|
||||
export function resetPasswordByCode(data) {
|
||||
return request({ url: '/resetPassword/code', method: 'POST', isToken: false, data })
|
||||
}
|
||||
17
uniapp/src/api/message.js
Normal file
17
uniapp/src/api/message.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
export function listPortalMessage(query) {
|
||||
return request({ url: '/jysystem/message/portal/list', method: 'GET', params: query })
|
||||
}
|
||||
|
||||
export function getPortalUnreadCount() {
|
||||
return request({ url: '/jysystem/message/portal/unreadCount', method: 'GET' })
|
||||
}
|
||||
|
||||
export function markPortalMessageRead(id) {
|
||||
return request({ url: '/jysystem/message/portal/read/' + id, method: 'PUT' })
|
||||
}
|
||||
|
||||
export function markPortalMessageAllRead() {
|
||||
return request({ url: '/jysystem/message/portal/readAll', method: 'PUT' })
|
||||
}
|
||||
73
uniapp/src/api/order.js
Normal file
73
uniapp/src/api/order.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
export function listPortalOrder(query) {
|
||||
return request({ url: '/jysystem/order/portal/list', method: 'GET', params: query })
|
||||
}
|
||||
|
||||
export function getPortalOrderContext() {
|
||||
return request({ url: '/jysystem/order/portal/context', method: 'GET' })
|
||||
}
|
||||
|
||||
export function listPortalProductOptions() {
|
||||
return request({ url: '/jysystem/order/portal/productOptions', method: 'GET' })
|
||||
}
|
||||
|
||||
export function getPortalOrder(id) {
|
||||
return request({ url: '/jysystem/order/portal/' + id, method: 'GET' })
|
||||
}
|
||||
|
||||
export function addPortalOrder(data) {
|
||||
return request({ url: '/jysystem/order/portal', method: 'POST', data })
|
||||
}
|
||||
|
||||
export function updatePortalOrder(data) {
|
||||
return request({ url: '/jysystem/order/portal', method: 'PUT', data })
|
||||
}
|
||||
|
||||
export function delPortalOrder(id) {
|
||||
return request({ url: '/jysystem/order/portal/' + id, method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function supplementPortalOrder(data) {
|
||||
return request({ url: '/jysystem/order/portal/supplement', method: 'PUT', data })
|
||||
}
|
||||
|
||||
export function acceptPortalOrder(id) {
|
||||
return request({ url: '/jysystem/order/portal/accept/' + id, method: 'PUT' })
|
||||
}
|
||||
|
||||
export function producePortalOrder(id) {
|
||||
return request({ url: '/jysystem/order/portal/produce/' + id, method: 'PUT' })
|
||||
}
|
||||
|
||||
export function withdrawPortalOrder(data) {
|
||||
return request({ url: '/jysystem/order/portal/withdraw', method: 'PUT', data })
|
||||
}
|
||||
|
||||
export function getPortalOrderLogistics(id) {
|
||||
return request({ url: '/jysystem/order/portal/logistics/' + id, method: 'GET' })
|
||||
}
|
||||
|
||||
export function shipPortalOrder(data) {
|
||||
return request({ url: '/jysystem/order/portal/ship', method: 'PUT', data })
|
||||
}
|
||||
|
||||
export function completePortalOrder(id) {
|
||||
return request({ url: '/jysystem/order/portal/complete/' + id, method: 'PUT' })
|
||||
}
|
||||
|
||||
export function exchangeApplyPortalOrder(data) {
|
||||
return request({ url: '/jysystem/order/portal/exchangeApply', method: 'PUT', data })
|
||||
}
|
||||
|
||||
export function receivePortalOrder(id) {
|
||||
return request({ url: '/jysystem/order/portal/receive/' + id, method: 'PUT' })
|
||||
}
|
||||
|
||||
export function resubmitPortalOrder(data) {
|
||||
return request({ url: '/jysystem/order/portal/resubmit', method: 'PUT', data })
|
||||
}
|
||||
|
||||
export function rejectPortalOrder(data) {
|
||||
return request({ url: '/jysystem/order/portal/reject', method: 'PUT', data })
|
||||
}
|
||||
9
uniapp/src/api/tenant.js
Normal file
9
uniapp/src/api/tenant.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
export function getCurrentTenant() {
|
||||
return request({ url: '/jysystem/tenant/current', method: 'GET' })
|
||||
}
|
||||
|
||||
export function listLensFactoryTenant() {
|
||||
return request({ url: '/jysystem/tenant/lensFactoryList', method: 'GET' })
|
||||
}
|
||||
43
uniapp/src/components/DictTag/DictTag.vue
Normal file
43
uniapp/src/components/DictTag/DictTag.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<text :class="['dict-tag', tagClass]">{{ label }}</text>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
options: { type: Array, default: () => [] },
|
||||
value: { type: [String, Number], default: '' }
|
||||
})
|
||||
|
||||
const label = computed(() => {
|
||||
const item = props.options.find(d => String(d.dictValue) === String(props.value))
|
||||
return item ? item.dictLabel : (props.value || '-')
|
||||
})
|
||||
|
||||
const tagClass = computed(() => {
|
||||
const v = String(props.value)
|
||||
if (['1'].includes(v)) return 'tag-info'
|
||||
if (['2', '3', '6', '7'].includes(v)) return 'tag-primary'
|
||||
if (['8', '9', '17'].includes(v)) return 'tag-success'
|
||||
if (['4', '5', '12'].includes(v)) return 'tag-warning'
|
||||
if (['10', '11', '21'].includes(v)) return 'tag-danger'
|
||||
return 'tag-default'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dict-tag {
|
||||
display: inline-block;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.tag-default { background: #f1f5f9; color: #64748b; }
|
||||
.tag-info { background: #e0f2fe; color: #0284c7; }
|
||||
.tag-primary { background: #dbeafe; color: #2563eb; }
|
||||
.tag-success { background: #dcfce7; color: #16a34a; }
|
||||
.tag-warning { background: #fef3c7; color: #d97706; }
|
||||
.tag-danger { background: #fee2e2; color: #dc2626; }
|
||||
</style>
|
||||
346
uniapp/src/components/NavBar/NavBar.vue
Normal file
346
uniapp/src/components/NavBar/NavBar.vue
Normal file
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<view class="nav-bar-wrap">
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-inner">
|
||||
<view class="logo" @click="goHome">
|
||||
<text class="logo-text">功能镜片配镜系统</text>
|
||||
</view>
|
||||
<view class="nav-right">
|
||||
<view
|
||||
v-if="showMessageBell"
|
||||
class="message-bell"
|
||||
@click="goMessage"
|
||||
>
|
||||
<text class="bell-icon">🔔</text>
|
||||
<view v-if="unreadCount > 0" class="message-badge">
|
||||
{{ unreadCount > 99 ? '99+' : unreadCount }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isLoggedIn" class="user-info" @click="toggleUserMenu">
|
||||
<image v-if="userStore.avatar" :src="userStore.avatar" class="user-avatar" mode="aspectFill" />
|
||||
<view v-else class="avatar-placeholder">{{ avatarText }}</view>
|
||||
<text class="user-name">{{ userStore.name || '用户' }}</text>
|
||||
</view>
|
||||
<view v-else class="auth-buttons">
|
||||
<text class="login-link" @click="goLogin">登录</text>
|
||||
<text class="register-link" @click="goRegister">注册</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷菜单 -->
|
||||
<scroll-view v-if="showMenu" scroll-x class="nav-menu" show-scrollbar="false">
|
||||
<view
|
||||
v-for="item in menuItems"
|
||||
:key="item.path"
|
||||
class="menu-item"
|
||||
:class="{ active: currentPath === item.path }"
|
||||
@click="navigateTo(item.path)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 用户下拉菜单 -->
|
||||
<view v-if="isUserMenuOpen" class="user-dropdown-mask" @click="closeUserMenu">
|
||||
<view class="user-dropdown" @click.stop>
|
||||
<view class="dropdown-item" @click="goRealNameAuth">实名认证</view>
|
||||
<view class="dropdown-item" @click="goAccount">账户中心</view>
|
||||
<view v-if="showLensOrderMenu" class="dropdown-item" @click="navigateTo('/pages/order/lens-order')">我的配片单</view>
|
||||
<view v-if="showFactoryOrderMenu" class="dropdown-item" @click="navigateTo('/pages/order/factory-order')">我的制片单</view>
|
||||
<view class="dropdown-item logout" @click="handleLogout">退出登录</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="nav-placeholder" :style="{ height: navHeight + 'px' }" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useUserStore } from '@/store/index.js'
|
||||
import { getPortalUnreadCount } from '@/api/message.js'
|
||||
import { canAccessLensOrder, isBrandTenant, isLensFactoryTenant } from '@/utils/tenantType.js'
|
||||
|
||||
const props = defineProps({
|
||||
showMenu: { type: Boolean, default: true }
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const statusBarHeight = ref(0)
|
||||
const navHeight = ref(88)
|
||||
const isUserMenuOpen = ref(false)
|
||||
const unreadCount = ref(0)
|
||||
const currentPath = ref('')
|
||||
let pollTimer = null
|
||||
|
||||
const isLoggedIn = computed(() => userStore.isLoggedIn)
|
||||
const showLensOrderMenu = computed(() => isLoggedIn.value && userStore.tenantId && canAccessLensOrder(userStore.tenantType))
|
||||
const showFactoryOrderMenu = computed(() => isLoggedIn.value && userStore.tenantId && isLensFactoryTenant(userStore.tenantType))
|
||||
const showMessageBell = computed(() => isLoggedIn.value && userStore.tenantId && (isBrandTenant(userStore.tenantType) || isLensFactoryTenant(userStore.tenantType)))
|
||||
const avatarText = computed(() => (userStore.name ? userStore.name.charAt(0) : 'U'))
|
||||
|
||||
const menuItems = computed(() => {
|
||||
const items = [{ label: '首页', path: '/pages/index/index' }]
|
||||
if (showLensOrderMenu.value) items.push({ label: '配片单', path: '/pages/order/lens-order' })
|
||||
if (showFactoryOrderMenu.value) items.push({ label: '制片单', path: '/pages/order/factory-order' })
|
||||
items.push({ label: '关于', path: '/pages/about/about' })
|
||||
return items
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const sys = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = sys.statusBarHeight || 0
|
||||
navHeight.value = (props.showMenu ? 160 : 88) + statusBarHeight.value
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length) {
|
||||
currentPath.value = '/' + pages[pages.length - 1].route
|
||||
}
|
||||
ensureTenantContext()
|
||||
startUnreadPolling()
|
||||
})
|
||||
|
||||
onUnmounted(() => stopUnreadPolling())
|
||||
|
||||
function ensureTenantContext() {
|
||||
if (userStore.token && (!userStore.tenantId || !userStore.tenantType)) {
|
||||
userStore.getInfo().then(() => fetchUnreadCount()).catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
function startUnreadPolling() {
|
||||
stopUnreadPolling()
|
||||
if (!showMessageBell.value) return
|
||||
fetchUnreadCount()
|
||||
pollTimer = setInterval(fetchUnreadCount, 60000)
|
||||
}
|
||||
|
||||
function stopUnreadPolling() {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function fetchUnreadCount() {
|
||||
if (!showMessageBell.value) return
|
||||
getPortalUnreadCount().then(res => {
|
||||
unreadCount.value = Number(res.data || 0)
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function toggleUserMenu() {
|
||||
isUserMenuOpen.value = !isUserMenuOpen.value
|
||||
}
|
||||
|
||||
function closeUserMenu() {
|
||||
isUserMenuOpen.value = false
|
||||
}
|
||||
|
||||
function navigateTo(path) {
|
||||
closeUserMenu()
|
||||
if (currentPath.value === path) return
|
||||
uni.navigateTo({ url: path, fail: () => uni.redirectTo({ url: path }) })
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
uni.reLaunch({ url: '/pages/index/index' })
|
||||
}
|
||||
|
||||
function goLogin() {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
|
||||
function goRegister() {
|
||||
uni.navigateTo({ url: '/pages/register/register' })
|
||||
}
|
||||
|
||||
function goMessage() {
|
||||
uni.navigateTo({ url: '/pages/message/message' })
|
||||
}
|
||||
|
||||
function goAccount() {
|
||||
closeUserMenu()
|
||||
uni.navigateTo({ url: '/pages/account/index' })
|
||||
}
|
||||
|
||||
function goRealNameAuth() {
|
||||
closeUserMenu()
|
||||
uni.navigateTo({ url: '/pages/account/realNameAuth' })
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
closeUserMenu()
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
userStore.logout().finally(() => {
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-bar-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.nav-inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.message-bell {
|
||||
position: relative;
|
||||
padding: 8rpx;
|
||||
}
|
||||
|
||||
.bell-icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.message-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
font-size: 18rpx;
|
||||
min-width: 28rpx;
|
||||
height: 28rpx;
|
||||
line-height: 28rpx;
|
||||
text-align: center;
|
||||
border-radius: 14rpx;
|
||||
padding: 0 6rpx;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.user-avatar, .avatar-placeholder {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
background: linear-gradient(135deg, #2563eb, #7c3aed);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 26rpx;
|
||||
color: #334155;
|
||||
max-width: 120rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.auth-buttons {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.login-link {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.register-link {
|
||||
color: #2563eb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
white-space: nowrap;
|
||||
padding: 0 16rpx 12rpx;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: inline-block;
|
||||
padding: 8rpx 24rpx;
|
||||
margin: 0 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #64748b;
|
||||
border-radius: 32rpx;
|
||||
|
||||
&.active {
|
||||
background: rgba(37, 99, 235, 0.1);
|
||||
color: #2563eb;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.user-dropdown-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.user-dropdown {
|
||||
position: absolute;
|
||||
top: 160rpx;
|
||||
right: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||||
min-width: 240rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 24rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
color: #334155;
|
||||
border-bottom: 1rpx solid #f1f5f9;
|
||||
|
||||
&.logout {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
16
uniapp/src/config/index.js
Normal file
16
uniapp/src/config/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/** API 地址:打包前请改为可访问的后端地址(手机无法访问 localhost) */
|
||||
const API_BASE = import.meta.env.VITE_APP_BASE_API || ''
|
||||
|
||||
// #ifdef H5
|
||||
const baseUrl = API_BASE || (import.meta.env.DEV ? '/dev-api' : '/prod-api')
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
const baseUrl = API_BASE || 'http://192.168.1.100:8082'
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
baseUrl,
|
||||
timeout: 10000,
|
||||
tokenKey: 'Developer-Token'
|
||||
}
|
||||
15
uniapp/src/main.js
Normal file
15
uniapp/src/main.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { closeSplashscreen } from '@/utils/splash.js'
|
||||
import { createSSRApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import pinia from './store/pinia.js'
|
||||
|
||||
export function createApp() {
|
||||
closeSplashscreen()
|
||||
const app = createSSRApp(App)
|
||||
app.use(pinia)
|
||||
app.config.errorHandler = (err, instance, info) => {
|
||||
console.error('[App Error]', info, err)
|
||||
closeSplashscreen()
|
||||
}
|
||||
return { app }
|
||||
}
|
||||
77
uniapp/src/manifest.json
Normal file
77
uniapp/src/manifest.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"name" : "功能镜片配镜系统",
|
||||
"appid" : "__UNI__C17C074",
|
||||
"description" : "觉远创智功能镜片配镜系统 UniApp 移动端",
|
||||
"versionName" : "1.0.2",
|
||||
"versionCode" : "102",
|
||||
"transformPx" : false,
|
||||
"app-plus" : {
|
||||
"usingComponents" : true,
|
||||
"renderer" : "webview",
|
||||
"compatible" : {
|
||||
"ignoreVersion" : true
|
||||
},
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : false,
|
||||
"autoclose" : true,
|
||||
"waiting" : false,
|
||||
"delay" : 0,
|
||||
"target" : "id:1"
|
||||
},
|
||||
"modules" : {},
|
||||
"distribute" : {
|
||||
"android" : {
|
||||
"packagename" : "com.jyuan.optical",
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>"
|
||||
],
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
|
||||
},
|
||||
"ios" : {
|
||||
"dSYMs" : false
|
||||
},
|
||||
"sdkConfigs" : {}
|
||||
}
|
||||
},
|
||||
"quickapp" : {},
|
||||
"mp-weixin" : {
|
||||
"appid" : "",
|
||||
"setting" : {
|
||||
"urlCheck" : false,
|
||||
"es6" : true,
|
||||
"minified" : true
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"h5" : {
|
||||
"title" : "功能镜片配镜系统",
|
||||
"router" : {
|
||||
"mode" : "hash"
|
||||
},
|
||||
"devServer" : {
|
||||
"port" : 2093,
|
||||
"proxy" : {
|
||||
"/dev-api" : {
|
||||
"target" : "http://localhost:8082",
|
||||
"changeOrigin" : true,
|
||||
"pathRewrite" : {
|
||||
"^/dev-api" : ""
|
||||
}
|
||||
},
|
||||
"/prod-api" : {
|
||||
"target" : "http://localhost:8082",
|
||||
"changeOrigin" : true,
|
||||
"pathRewrite" : {
|
||||
"^/prod-api" : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vueVersion" : "3"
|
||||
}
|
||||
115
uniapp/src/pages.json
Normal file
115
uniapp/src/pages.json
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/register/register",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/resetPassword/resetPassword",
|
||||
"style": {
|
||||
"navigationBarTitleText": "重置密码"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/lens-order",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的配片单",
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/factory-order",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的制片单",
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/lens-order-form",
|
||||
"style": {
|
||||
"navigationBarTitleText": "配片单"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/order-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/logistics",
|
||||
"style": {
|
||||
"navigationBarTitleText": "物流信息"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/message/message",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息通知",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/account/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "账户中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/account/address",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收货地址"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/account/realNameAuth",
|
||||
"style": {
|
||||
"navigationBarTitleText": "实名认证"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/about/about",
|
||||
"style": {
|
||||
"navigationBarTitleText": "关于我们"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/profile",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "功能镜片配镜系统",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#f5f7fa"
|
||||
},
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^NavBar$": "@/components/NavBar/NavBar.vue",
|
||||
"^DictTag$": "@/components/DictTag/DictTag.vue"
|
||||
}
|
||||
}
|
||||
}
|
||||
24
uniapp/src/pages/about/about.vue
Normal file
24
uniapp/src/pages/about/about.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view class="card">
|
||||
<text class="company-name">金华觉远创智科技有限公司</text>
|
||||
<text class="desc">觉远创智功能镜片配镜系统,为镜片行业提供配片单、制片单全流程数字化管理,助力品牌租户与镜片厂高效协作。</text>
|
||||
</view>
|
||||
<view class="card">
|
||||
<text class="section-title">联系我们</text>
|
||||
<text class="contact-item">官网:www.jyuan.com</text>
|
||||
<text class="contact-item">邮箱:support@jyuan.com</text>
|
||||
</view>
|
||||
<view class="footer">
|
||||
<text>© 2025 金华觉远创智科技有限公司 版权所有</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.company-name { display: block; font-size: 36rpx; font-weight: bold; margin-bottom: 16rpx; }
|
||||
.desc { font-size: 28rpx; color: #64748b; line-height: 1.6; }
|
||||
.section-title { display: block; font-size: 30rpx; font-weight: 600; margin-bottom: 16rpx; }
|
||||
.contact-item { display: block; font-size: 28rpx; color: #64748b; padding: 8rpx 0; }
|
||||
.footer { text-align: center; padding: 48rpx; color: #94a3b8; font-size: 24rpx; }
|
||||
</style>
|
||||
10
uniapp/src/pages/account/address.vue
Normal file
10
uniapp/src/pages/account/address.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view class="empty-tip">收货地址管理功能与 Web 端 API 一致,请对接 /shop/userAddr 相关接口后完善。</view>
|
||||
<button class="btn-primary" @click="goBack">返回</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
function goBack() { uni.navigateBack() }
|
||||
</script>
|
||||
83
uniapp/src/pages/account/index.vue
Normal file
83
uniapp/src/pages/account/index.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<NavBar />
|
||||
<view class="container">
|
||||
<view v-if="userStore.isLoggedIn" class="user-card card">
|
||||
<image v-if="userStore.avatar" :src="userStore.avatar" class="avatar" mode="aspectFill" />
|
||||
<view v-else class="avatar-placeholder">{{ avatarText }}</view>
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ userStore.name || '用户' }}</text>
|
||||
<text class="tenant">{{ userStore.tenantName || '未关联租户' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-list card">
|
||||
<view v-if="showLensOrder" class="menu-item" @click="go('/pages/order/lens-order')">
|
||||
<text>我的配片单</text><text class="arrow">›</text>
|
||||
</view>
|
||||
<view v-if="showFactoryOrder" class="menu-item" @click="go('/pages/order/factory-order')">
|
||||
<text>我的制片单</text><text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" @click="go('/pages/message/message')">
|
||||
<text>消息通知</text><text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" @click="go('/pages/account/realNameAuth')">
|
||||
<text>实名认证</text><text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" @click="go('/pages/account/address')">
|
||||
<text>收货地址</text><text class="arrow">›</text>
|
||||
</view>
|
||||
<view class="menu-item" @click="go('/pages/about/about')">
|
||||
<text>关于我们</text><text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button v-if="userStore.isLoggedIn" class="logout-btn" @click="handleLogout">退出登录</button>
|
||||
<button v-else class="btn-primary" @click="go('/pages/login/login')">去登录</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/store/index.js'
|
||||
import { canAccessLensOrder, isLensFactoryTenant } from '@/utils/tenantType.js'
|
||||
import NavBar from '@/components/NavBar/NavBar.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const avatarText = computed(() => (userStore.name ? userStore.name.charAt(0) : 'U'))
|
||||
const showLensOrder = computed(() => userStore.isLoggedIn && canAccessLensOrder(userStore.tenantType))
|
||||
const showFactoryOrder = computed(() => userStore.isLoggedIn && isLensFactoryTenant(userStore.tenantType))
|
||||
|
||||
onMounted(() => {
|
||||
if (userStore.token && !userStore.tenantId) {
|
||||
userStore.getInfo().catch(() => {})
|
||||
}
|
||||
})
|
||||
|
||||
function go(url) {
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
uni.showModal({
|
||||
title: '提示', content: '确定退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
userStore.logout().finally(() => uni.reLaunch({ url: '/pages/login/login' }))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-card { display: flex; align-items: center; gap: 24rpx; margin-bottom: 24rpx; }
|
||||
.avatar, .avatar-placeholder { width: 100rpx; height: 100rpx; border-radius: 50%; }
|
||||
.avatar-placeholder { background: linear-gradient(135deg, #2563eb, #7c3aed); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 40rpx; }
|
||||
.user-name { display: block; font-size: 32rpx; font-weight: 600; }
|
||||
.tenant { font-size: 24rpx; color: #64748b; }
|
||||
.menu-item { display: flex; justify-content: space-between; padding: 28rpx 0; border-bottom: 1rpx solid #f1f5f9; font-size: 30rpx; .arrow { color: #94a3b8; } }
|
||||
.logout-btn { margin-top: 48rpx; background: #fff; color: #ef4444; border: 2rpx solid #fee2e2; border-radius: 12rpx; }
|
||||
.btn-primary { width: 100%; margin-top: 48rpx; }
|
||||
</style>
|
||||
69
uniapp/src/pages/account/realNameAuth.vue
Normal file
69
uniapp/src/pages/account/realNameAuth.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view v-if="loading" class="empty-tip">加载中...</view>
|
||||
<template v-else-if="authInfo">
|
||||
<view class="card">
|
||||
<view class="detail-row"><text class="label">认证状态</text><text>{{ statusLabel }}</text></view>
|
||||
<view class="detail-row"><text class="label">认证类型</text><text>{{ authInfo.authType === 'enterprise' ? '企业' : '个人' }}</text></view>
|
||||
<view class="detail-row"><text class="label">姓名</text><text>{{ authInfo.realName || '-' }}</text></view>
|
||||
<view class="detail-row"><text class="label">证件号</text><text>{{ maskId(authInfo.idNumber) }}</text></view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="card">
|
||||
<text class="tip">您尚未完成实名认证,请在 Web 端完成认证或在此提交(需对接 /system/realNameAuth 接口)。</text>
|
||||
<view class="form-item"><input v-model="form.realName" placeholder="真实姓名" /></view>
|
||||
<view class="form-item"><input v-model="form.idNumber" placeholder="身份证号" /></view>
|
||||
<button class="btn-primary" @click="handleSubmit">提交认证</button>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import request from '@/utils/request.js'
|
||||
|
||||
const loading = ref(true)
|
||||
const authInfo = ref(null)
|
||||
const form = ref({ realName: '', idNumber: '' })
|
||||
|
||||
const statusLabel = computed(() => {
|
||||
const s = authInfo.value?.status
|
||||
if (s === '1') return '已认证'
|
||||
if (s === '2') return '审核中'
|
||||
if (s === '3') return '已驳回'
|
||||
return '未认证'
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await request({ url: '/system/realNameAuth/my', method: 'GET' })
|
||||
authInfo.value = res.data
|
||||
} catch {
|
||||
authInfo.value = null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function maskId(id) {
|
||||
if (!id || id.length < 8) return id || '-'
|
||||
return id.slice(0, 4) + '****' + id.slice(-4)
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.value.realName || !form.value.idNumber) {
|
||||
return uni.showToast({ title: '请填写完整信息', icon: 'none' })
|
||||
}
|
||||
await request({ url: '/system/realNameAuth/submit', method: 'POST', data: form.value })
|
||||
uni.showToast({ title: '提交成功', icon: 'success' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-row { display: flex; justify-content: space-between; padding: 16rpx 0; border-bottom: 1rpx solid #f1f5f9; .label { color: #64748b; } }
|
||||
.tip { display: block; font-size: 26rpx; color: #64748b; margin-bottom: 24rpx; }
|
||||
.form-item { background: #f8fafc; border-radius: 8rpx; padding: 0 20rpx; margin-bottom: 16rpx; input { height: 80rpx; font-size: 28rpx; } }
|
||||
.btn-primary { width: 100%; margin-top: 24rpx; }
|
||||
</style>
|
||||
230
uniapp/src/pages/index/index.vue
Normal file
230
uniapp/src/pages/index/index.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<NavBar />
|
||||
<view class="hero">
|
||||
<text class="hero-title">下一代智能眼镜</text>
|
||||
<text class="hero-subtitle">开放平台</text>
|
||||
<text class="hero-desc">为开发者提供完整的智能眼镜解决方案,包括硬件接入、SDK、API 文档和技术支持。</text>
|
||||
<view class="hero-actions">
|
||||
<button v-if="showFactoryOrderEntry" class="btn-primary action-btn" @click="goFactoryOrder">我的制片单</button>
|
||||
<button v-if="showLensOrderEntry" class="btn-primary action-btn" @click="goLensOrder">我的配片单</button>
|
||||
<button class="btn-outline action-btn" @click="goAbout">关于我们</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<text class="section-title">平台概览</text>
|
||||
<view class="feature-grid">
|
||||
<view v-for="item in features" :key="item.title" class="feature-card card">
|
||||
<text class="feature-icon">{{ item.icon }}</text>
|
||||
<text class="feature-name">{{ item.title }}</text>
|
||||
<text class="feature-desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<text class="section-title">快速上手</text>
|
||||
<view class="steps">
|
||||
<view v-for="(step, i) in steps" :key="i" class="step-card card">
|
||||
<text class="step-num">{{ i + 1 }}</text>
|
||||
<view class="step-content">
|
||||
<text class="step-title">{{ step.title }}</text>
|
||||
<text class="step-desc">{{ step.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<text>© 2025 金华觉远创智科技有限公司</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { closeSplashscreen } from '@/utils/splash.js'
|
||||
import { useUserStore } from '@/store/index.js'
|
||||
import { canAccessLensOrder, isLensFactoryTenant } from '@/utils/tenantType.js'
|
||||
import NavBar from '@/components/NavBar/NavBar.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const showLensOrderEntry = computed(() => userStore.isLoggedIn && userStore.tenantId && canAccessLensOrder(userStore.tenantType))
|
||||
const showFactoryOrderEntry = computed(() => userStore.isLoggedIn && userStore.tenantId && isLensFactoryTenant(userStore.tenantType))
|
||||
|
||||
const features = [
|
||||
{ icon: '⚙️', title: '是什么', desc: '面向智能眼镜的开发平台,提供 SDK、API 和工具链。' },
|
||||
{ icon: '🚀', title: '能做什么', desc: '快速接入设备,构建 AR 叠加、手势与语音交互等能力。' },
|
||||
{ icon: '👥', title: '为谁而建', desc: '面向开发者与企业,支持多场景应用。' }
|
||||
]
|
||||
|
||||
const steps = [
|
||||
{ title: '注册并创建应用', desc: '获取 API Key 与项目配置。' },
|
||||
{ title: '接入 SDK', desc: '按文档完成设备接入与调试。' },
|
||||
{ title: '发布应用', desc: '提交审核并上线您的智能眼镜应用。' }
|
||||
]
|
||||
|
||||
onLoad(() => {
|
||||
closeSplashscreen()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (userStore.token && !userStore.tenantId) {
|
||||
userStore.getInfo().catch(() => {})
|
||||
}
|
||||
})
|
||||
|
||||
function goLensOrder() {
|
||||
uni.navigateTo({ url: '/pages/order/lens-order' })
|
||||
}
|
||||
|
||||
function goFactoryOrder() {
|
||||
uni.navigateTo({ url: '/pages/order/factory-order' })
|
||||
}
|
||||
|
||||
function goAbout() {
|
||||
uni.navigateTo({ url: '/pages/about/about' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #eff6ff 0%, #f5f7fa 40%);
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding: 48rpx 32rpx 64rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
display: block;
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #2563eb;
|
||||
margin: 12rpx 0 24rpx;
|
||||
}
|
||||
|
||||
.hero-desc {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #64748b;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: #fff;
|
||||
color: #334155;
|
||||
border: 2rpx solid #e2e8f0;
|
||||
border-radius: 12rpx;
|
||||
font-size: 30rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
text-align: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 48rpx;
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.feature-name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
font-size: 24rpx;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.step-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.step-num {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background: linear-gradient(135deg, #2563eb, #7c3aed);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 48rpx;
|
||||
font-size: 24rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
font-size: 24rpx;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 48rpx;
|
||||
color: #94a3b8;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
358
uniapp/src/pages/login/login.vue
Normal file
358
uniapp/src/pages/login/login.vue
Normal file
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<NavBar :show-menu="false" />
|
||||
<view class="login-container">
|
||||
<view class="login-header">
|
||||
<text class="login-title">欢迎登录</text>
|
||||
<text class="login-subtitle">Welcome Back</text>
|
||||
</view>
|
||||
|
||||
<view class="tabs">
|
||||
<view :class="['tab', activeTab === 'password' && 'active']" @click="activeTab = 'password'">密码登录</view>
|
||||
<view :class="['tab', activeTab === 'code' && 'active']" @click="activeTab = 'code'">验证码登录</view>
|
||||
</view>
|
||||
|
||||
<!-- 密码登录 -->
|
||||
<view v-if="activeTab === 'password'" class="form">
|
||||
<view class="form-item">
|
||||
<input v-model="passwordForm.username" placeholder="请输入手机号或邮箱" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<input v-model="passwordForm.password" password placeholder="请输入密码" />
|
||||
</view>
|
||||
<view v-if="captchaEnabled" class="form-item code-row">
|
||||
<input v-model="passwordForm.code" placeholder="验证码" class="code-input" />
|
||||
<image :src="codeUrl" class="code-img" @click="getCode" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="form-options">
|
||||
<label class="remember">
|
||||
<checkbox :checked="passwordForm.rememberMe" @click="passwordForm.rememberMe = !passwordForm.rememberMe" />
|
||||
<text>记住密码</text>
|
||||
</label>
|
||||
<text class="forgot" @click="goReset">忘记密码?</text>
|
||||
</view>
|
||||
<button class="btn-primary login-btn" :loading="loading" @click="handlePasswordLogin">登 录</button>
|
||||
</view>
|
||||
|
||||
<!-- 验证码登录 -->
|
||||
<view v-else class="form">
|
||||
<view class="form-item">
|
||||
<input v-model="codeForm.username" placeholder="请输入手机号或邮箱" />
|
||||
</view>
|
||||
<view class="form-item code-row">
|
||||
<input v-model="codeForm.code" placeholder="验证码" class="code-input" />
|
||||
<button class="send-code-btn" :disabled="codeCountdown > 0 || sendingCode" @click="sendCode">
|
||||
{{ codeCountdown > 0 ? codeCountdown + 's' : '发送验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
<button class="btn-primary login-btn" :loading="loading" @click="handleCodeLogin">登 录</button>
|
||||
</view>
|
||||
|
||||
<view class="login-links">
|
||||
<text>还没有账户?</text>
|
||||
<text class="link" @click="goRegister">立即注册</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { getCodeImg, sendSmsCode, sendEmailCode, emailLogin, smsLogin, login } from '@/api/login.js'
|
||||
import { useUserStore } from '@/store/index.js'
|
||||
import NavBar from '@/components/NavBar/NavBar.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const activeTab = ref('password')
|
||||
const loading = ref(false)
|
||||
const captchaEnabled = ref(true)
|
||||
const codeUrl = ref('')
|
||||
const codeCountdown = ref(0)
|
||||
const sendingCode = ref(false)
|
||||
let countdownTimer = null
|
||||
|
||||
const passwordForm = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
code: '',
|
||||
uuid: '',
|
||||
rememberMe: false,
|
||||
directLogin: false
|
||||
})
|
||||
|
||||
const codeForm = ref({
|
||||
username: '',
|
||||
code: '',
|
||||
uuid: '',
|
||||
directLogin: false
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getCode()
|
||||
const saved = uni.getStorageSync('login_remember')
|
||||
if (saved) {
|
||||
passwordForm.value.username = saved.username || ''
|
||||
passwordForm.value.password = saved.password || ''
|
||||
passwordForm.value.rememberMe = true
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (countdownTimer) clearInterval(countdownTimer)
|
||||
})
|
||||
|
||||
function getCode() {
|
||||
getCodeImg().then(res => {
|
||||
captchaEnabled.value = res.captchaEnabled !== false
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = 'data:image/gif;base64,' + res.img
|
||||
passwordForm.value.uuid = res.uuid
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function isPhone(value) {
|
||||
return /^1[3-9]\d{9}$/.test(value)
|
||||
}
|
||||
|
||||
function sendCode() {
|
||||
const username = codeForm.value.username
|
||||
if (!username) {
|
||||
uni.showToast({ title: '请输入手机号或邮箱', icon: 'none' })
|
||||
return
|
||||
}
|
||||
sendingCode.value = true
|
||||
const promise = isPhone(username) ? sendSmsCode(username) : sendEmailCode(username)
|
||||
promise.then(res => {
|
||||
codeForm.value.uuid = res.data?.uuid || res.uuid
|
||||
uni.showToast({ title: '验证码已发送', icon: 'success' })
|
||||
codeCountdown.value = 60
|
||||
countdownTimer = setInterval(() => {
|
||||
codeCountdown.value--
|
||||
if (codeCountdown.value <= 0) clearInterval(countdownTimer)
|
||||
}, 1000)
|
||||
}).finally(() => { sendingCode.value = false })
|
||||
}
|
||||
|
||||
async function handlePasswordLogin() {
|
||||
if (!passwordForm.value.username || !passwordForm.value.password) {
|
||||
uni.showToast({ title: '请填写账号和密码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
await doLogin(() => login(
|
||||
passwordForm.value.username,
|
||||
passwordForm.value.password,
|
||||
passwordForm.value.code,
|
||||
passwordForm.value.uuid,
|
||||
passwordForm.value.directLogin
|
||||
))
|
||||
if (passwordForm.value.rememberMe) {
|
||||
uni.setStorageSync('login_remember', {
|
||||
username: passwordForm.value.username,
|
||||
password: passwordForm.value.password
|
||||
})
|
||||
} else {
|
||||
uni.removeStorageSync('login_remember')
|
||||
}
|
||||
} catch (e) {
|
||||
if (e && e.code === 201) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: e.msg || '该账号已在其他设备登录,是否继续?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
passwordForm.value.directLogin = true
|
||||
handlePasswordLogin()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (captchaEnabled.value) getCode()
|
||||
} finally {
|
||||
loading.value = false
|
||||
passwordForm.value.directLogin = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCodeLogin() {
|
||||
const username = codeForm.value.username
|
||||
if (!username || !codeForm.value.code) {
|
||||
uni.showToast({ title: '请填写完整信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const promise = isPhone(username)
|
||||
? smsLogin(username, codeForm.value.code, codeForm.value.uuid, codeForm.value.directLogin)
|
||||
: emailLogin(username, codeForm.value.code, codeForm.value.uuid, codeForm.value.directLogin)
|
||||
await doLogin(() => promise)
|
||||
} catch (e) {
|
||||
if (e && e.code === 201) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: e.msg || '该账号已在其他设备登录,是否继续?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
codeForm.value.directLogin = true
|
||||
handleCodeLogin()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
codeForm.value.directLogin = false
|
||||
}
|
||||
}
|
||||
|
||||
async function doLogin(loginFn) {
|
||||
const res = await loginFn()
|
||||
await userStore.loginWithToken(res.token)
|
||||
await userStore.getInfo()
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => uni.reLaunch({ url: '/pages/index/index' }), 500)
|
||||
}
|
||||
|
||||
function goRegister() {
|
||||
uni.navigateTo({ url: '/pages/register/register' })
|
||||
}
|
||||
|
||||
function goReset() {
|
||||
uni.navigateTo({ url: '/pages/resetPassword/resetPassword' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #eff6ff, #fff);
|
||||
}
|
||||
|
||||
.login-container {
|
||||
padding: 48rpx 40rpx;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 48rpx;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #94a3b8;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin-bottom: 32rpx;
|
||||
background: #f1f5f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 6rpx;
|
||||
}
|
||||
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #64748b;
|
||||
border-radius: 8rpx;
|
||||
|
||||
&.active {
|
||||
background: #fff;
|
||||
color: #2563eb;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
.form-item {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border: 2rpx solid #e2e8f0;
|
||||
|
||||
input {
|
||||
height: 88rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.code-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-img {
|
||||
width: 200rpx;
|
||||
height: 72rpx;
|
||||
}
|
||||
|
||||
.send-code-btn {
|
||||
font-size: 24rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
border-radius: 8rpx;
|
||||
white-space: nowrap;
|
||||
|
||||
&[disabled] {
|
||||
background: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.form-options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.remember {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.forgot {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.login-links {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #64748b;
|
||||
|
||||
.link {
|
||||
color: #2563eb;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
110
uniapp/src/pages/message/message.vue
Normal file
110
uniapp/src/pages/message/message.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view class="header flex-between">
|
||||
<text class="title">消息通知</text>
|
||||
<text v-if="unreadCount > 0" class="read-all" @click="handleReadAll">全部已读</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading && !messageList.length" class="empty-tip">加载中...</view>
|
||||
<view v-else-if="!messageList.length" class="empty-tip">暂无消息</view>
|
||||
<view
|
||||
v-for="item in messageList"
|
||||
:key="item.id"
|
||||
class="message-card card"
|
||||
:class="{ unread: item.readFlag === '0' }"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<view class="flex-between">
|
||||
<text class="msg-title">{{ item.title }}</text>
|
||||
<view v-if="item.readFlag === '0'" class="unread-dot" />
|
||||
</view>
|
||||
<text class="msg-content">{{ item.content }}</text>
|
||||
<text class="msg-time">{{ item.createTime }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="hasMore" class="load-more" @click="loadMore">加载更多</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { useUserStore } from '@/store/index.js'
|
||||
import { isLensFactoryTenant } from '@/utils/tenantType.js'
|
||||
import { listPortalMessage, getPortalUnreadCount, markPortalMessageRead, markPortalMessageAllRead } from '@/api/message.js'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const loading = ref(false)
|
||||
const messageList = ref([])
|
||||
const total = ref(0)
|
||||
const unreadCount = ref(0)
|
||||
const query = ref({ pageNum: 1, pageSize: 20 })
|
||||
const hasMore = computed(() => messageList.value.length < total.value)
|
||||
|
||||
onMounted(() => {
|
||||
if (!userStore.isLoggedIn) {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
return
|
||||
}
|
||||
fetchUnread()
|
||||
getList()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
query.value.pageNum = 1
|
||||
getList().finally(() => uni.stopPullDownRefresh())
|
||||
})
|
||||
|
||||
onReachBottom(() => loadMore())
|
||||
|
||||
function fetchUnread() {
|
||||
getPortalUnreadCount().then(res => { unreadCount.value = Number(res.data || 0) })
|
||||
}
|
||||
|
||||
function getList(append = false) {
|
||||
loading.value = true
|
||||
return listPortalMessage(query.value).then(res => {
|
||||
messageList.value = append ? [...messageList.value, ...(res.rows || [])] : (res.rows || [])
|
||||
total.value = res.total || 0
|
||||
}).finally(() => { loading.value = false })
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
if (!hasMore.value || loading.value) return
|
||||
query.value.pageNum++
|
||||
getList(true)
|
||||
}
|
||||
|
||||
function handleClick(item) {
|
||||
if (item.readFlag === '0') {
|
||||
markPortalMessageRead(item.id).then(() => {
|
||||
item.readFlag = '1'
|
||||
fetchUnread()
|
||||
})
|
||||
}
|
||||
const path = isLensFactoryTenant(userStore.tenantType)
|
||||
? '/pages/order/factory-order'
|
||||
: '/pages/order/lens-order'
|
||||
uni.navigateTo({ url: path })
|
||||
}
|
||||
|
||||
function handleReadAll() {
|
||||
markPortalMessageAllRead().then(() => {
|
||||
messageList.value.forEach(i => { i.readFlag = '1' })
|
||||
unreadCount.value = 0
|
||||
uni.showToast({ title: '已全部标记已读', icon: 'success' })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header { margin-bottom: 24rpx; }
|
||||
.title { font-size: 36rpx; font-weight: bold; }
|
||||
.read-all { font-size: 26rpx; color: #2563eb; }
|
||||
.message-card { margin-bottom: 16rpx; &.unread { border-left: 6rpx solid #2563eb; } }
|
||||
.msg-title { font-size: 28rpx; font-weight: 600; }
|
||||
.unread-dot { width: 16rpx; height: 16rpx; background: #ef4444; border-radius: 50%; }
|
||||
.msg-content { display: block; font-size: 26rpx; color: #64748b; margin: 12rpx 0; }
|
||||
.msg-time { font-size: 22rpx; color: #94a3b8; }
|
||||
.load-more { text-align: center; padding: 24rpx; color: #2563eb; }
|
||||
</style>
|
||||
322
uniapp/src/pages/order/factory-order.vue
Normal file
322
uniapp/src/pages/order/factory-order.vue
Normal file
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<NavBar />
|
||||
<view class="container">
|
||||
<view class="page-header">
|
||||
<text class="page-title">我的制片单</text>
|
||||
<text v-if="userStore.tenantName" class="tenant-name">{{ userStore.tenantName }}</text>
|
||||
</view>
|
||||
|
||||
<view class="search-bar card">
|
||||
<input v-model="queryParams.orderNo" placeholder="订单编号" class="search-input" />
|
||||
<picker :range="statusLabels" :value="statusIndex" @change="onStatusChange">
|
||||
<view class="status-picker">{{ statusLabels[statusIndex] || '全部状态' }}</view>
|
||||
</picker>
|
||||
<button size="mini" class="search-btn" @click="handleQuery">搜索</button>
|
||||
</view>
|
||||
|
||||
<view v-if="loading && !orderList.length" class="empty-tip">加载中...</view>
|
||||
<view v-else-if="!orderList.length" class="empty-tip">暂无制片单</view>
|
||||
<view v-for="item in orderList" :key="item.id" class="order-card card" @click="handleDetail(item)">
|
||||
<view class="flex-between order-head">
|
||||
<text class="order-no">{{ item.orderNo }}</text>
|
||||
<DictTag :options="orderStatusDict" :value="item.orderStatus" />
|
||||
</view>
|
||||
<view class="order-info">
|
||||
<text>镜片:{{ formatLens(item) }}</text>
|
||||
<text>收货人:{{ item.receiverName }} {{ item.receiverPhone }}</text>
|
||||
<text>创建:{{ item.createTime }}</text>
|
||||
</view>
|
||||
<view class="order-actions" @click.stop>
|
||||
<text class="action-link" @click="handleDetail(item)">详情</text>
|
||||
<text v-if="canViewLogistics(item)" class="action-link" @click="viewLogistics(item)">物流</text>
|
||||
<text v-if="canAcceptOrder(item)" class="action-link success" @click="handleAccept(item)">接单</text>
|
||||
<text v-if="canSupplementOrder(item)" class="action-link" @click="handleSupplement(item)">需补充</text>
|
||||
<text v-if="isAccepted(item)" class="action-link primary" @click="handleProduce(item)">制作中</text>
|
||||
<text v-if="isProducing(item)" class="action-link warning" @click="handleShip(item)">发货</text>
|
||||
<text v-if="canCompleteOrder(item)" class="action-link success" @click="handleComplete(item)">完成</text>
|
||||
<text v-if="canWithdrawOrder(item)" class="action-link" @click="handleWithdraw(item)">撤单</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="hasMore" class="load-more" @click="loadMore">加载更多</view>
|
||||
</view>
|
||||
|
||||
<!-- 发货弹窗 -->
|
||||
<view v-if="shipVisible" class="modal-mask" @click="shipVisible = false">
|
||||
<view class="modal-content" @click.stop>
|
||||
<text class="modal-title">发货信息</text>
|
||||
<view class="form-item"><input v-model="shipForm.expressCompany" placeholder="物流公司" /></view>
|
||||
<view class="form-item"><input v-model="shipForm.expressNo" placeholder="物流单号" /></view>
|
||||
<view class="form-item"><input v-model="shipForm.logisticsFee" type="digit" placeholder="物流费用" /></view>
|
||||
<view class="modal-actions">
|
||||
<button @click="shipVisible = false">取消</button>
|
||||
<button class="btn-primary" @click="submitShip">确定发货</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 需补充弹窗 -->
|
||||
<view v-if="supplementVisible" class="modal-mask" @click="supplementVisible = false">
|
||||
<view class="modal-content" @click.stop>
|
||||
<text class="modal-title">需补充说明</text>
|
||||
<text class="modal-subtitle">订单:{{ supplementForm.orderNo }}</text>
|
||||
<textarea v-model="supplementForm.remark" placeholder="请输入需要品牌方补充的内容" class="remark-input" />
|
||||
<view class="modal-actions">
|
||||
<button @click="supplementVisible = false">取消</button>
|
||||
<button class="btn-primary" @click="submitSupplement">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 撤单弹窗 -->
|
||||
<view v-if="withdrawVisible" class="modal-mask" @click="withdrawVisible = false">
|
||||
<view class="modal-content" @click.stop>
|
||||
<text class="modal-title">撤单</text>
|
||||
<text class="modal-subtitle">订单:{{ withdrawForm.orderNo }}</text>
|
||||
<textarea v-model="withdrawForm.reason" placeholder="请输入撤单理由" class="remark-input" />
|
||||
<view class="modal-actions">
|
||||
<button @click="withdrawVisible = false">取消</button>
|
||||
<button class="btn-primary" @click="submitWithdraw">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { useUserStore, useDictStore } from '@/store/index.js'
|
||||
import {
|
||||
ORDER_STATUS,
|
||||
FACTORY_ACCEPTABLE_STATUSES,
|
||||
normalizeOrderStatus,
|
||||
formatLens
|
||||
} from '@/utils/orderHelper.js'
|
||||
import {
|
||||
listPortalOrder, acceptPortalOrder, producePortalOrder, shipPortalOrder,
|
||||
completePortalOrder, withdrawPortalOrder, supplementPortalOrder
|
||||
} from '@/api/order.js'
|
||||
import NavBar from '@/components/NavBar/NavBar.vue'
|
||||
import DictTag from '@/components/DictTag/DictTag.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const dictStore = useDictStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const orderList = ref([])
|
||||
const total = ref(0)
|
||||
const orderStatusDict = ref([])
|
||||
const statusIndex = ref(0)
|
||||
const shipVisible = ref(false)
|
||||
const supplementVisible = ref(false)
|
||||
const withdrawVisible = ref(false)
|
||||
const shipForm = ref({ orderId: null, expressCompany: '', expressNo: '', logisticsFee: '' })
|
||||
const supplementForm = ref({ orderId: null, orderNo: '', remark: '' })
|
||||
const withdrawForm = ref({ orderId: null, orderNo: '', reason: '' })
|
||||
|
||||
const queryParams = ref({ pageNum: 1, pageSize: 10, orderNo: null, orderStatus: null })
|
||||
const statusLabels = computed(() => ['全部状态', ...orderStatusDict.value.map(d => d.dictLabel)])
|
||||
const hasMore = computed(() => orderList.value.length < total.value)
|
||||
|
||||
onMounted(async () => {
|
||||
orderStatusDict.value = await dictStore.getDict('jy_order_status')
|
||||
getList()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
queryParams.value.pageNum = 1
|
||||
getList().finally(() => uni.stopPullDownRefresh())
|
||||
})
|
||||
|
||||
onReachBottom(() => loadMore())
|
||||
|
||||
function onStatusChange(e) {
|
||||
statusIndex.value = Number(e.detail.value)
|
||||
queryParams.value.orderStatus = statusIndex.value === 0 ? null : orderStatusDict.value[statusIndex.value - 1]?.dictValue
|
||||
}
|
||||
|
||||
function getList(append = false) {
|
||||
loading.value = true
|
||||
return listPortalOrder(queryParams.value).then(res => {
|
||||
orderList.value = append ? [...orderList.value, ...(res.rows || [])] : (res.rows || [])
|
||||
total.value = res.total || 0
|
||||
}).finally(() => { loading.value = false })
|
||||
}
|
||||
|
||||
function handleQuery() { queryParams.value.pageNum = 1; getList() }
|
||||
function loadMore() { if (!hasMore.value || loading.value) return; queryParams.value.pageNum++; getList(true) }
|
||||
|
||||
function canAcceptOrder(row) {
|
||||
return FACTORY_ACCEPTABLE_STATUSES.includes(normalizeOrderStatus(row?.orderStatus))
|
||||
}
|
||||
|
||||
function canSupplementOrder(row) {
|
||||
const status = normalizeOrderStatus(row?.orderStatus)
|
||||
return status === ORDER_STATUS.ASSIGNED || status === ORDER_STATUS.SUPPLEMENTED
|
||||
}
|
||||
|
||||
function isAccepted(row) {
|
||||
return normalizeOrderStatus(row?.orderStatus) === ORDER_STATUS.ACCEPTED
|
||||
}
|
||||
|
||||
function isProducing(row) {
|
||||
return normalizeOrderStatus(row?.orderStatus) === ORDER_STATUS.PRODUCING
|
||||
}
|
||||
|
||||
function canViewLogistics(row) {
|
||||
const status = normalizeOrderStatus(row?.orderStatus)
|
||||
return [
|
||||
ORDER_STATUS.SHIPPED,
|
||||
ORDER_STATUS.EXCHANGE_LOGISTICS,
|
||||
ORDER_STATUS.COMPLETED,
|
||||
ORDER_STATUS.EXCHANGE_COMPLETED
|
||||
].includes(status)
|
||||
}
|
||||
|
||||
function canCompleteOrder(row) {
|
||||
const status = normalizeOrderStatus(row?.orderStatus)
|
||||
return status === ORDER_STATUS.SHIPPED || status === ORDER_STATUS.EXCHANGE_LOGISTICS
|
||||
}
|
||||
|
||||
function canWithdrawOrder(row) {
|
||||
const status = normalizeOrderStatus(row?.orderStatus)
|
||||
return [
|
||||
ORDER_STATUS.ASSIGNED,
|
||||
ORDER_STATUS.ACCEPTED,
|
||||
ORDER_STATUS.PRODUCING
|
||||
].includes(status)
|
||||
}
|
||||
|
||||
function handleDetail(row) {
|
||||
uni.navigateTo({ url: '/pages/order/order-detail?id=' + row.id + '&type=factory' })
|
||||
}
|
||||
|
||||
function viewLogistics(row) {
|
||||
uni.navigateTo({ url: '/pages/order/logistics?id=' + row.id })
|
||||
}
|
||||
|
||||
function handleAccept(row) {
|
||||
uni.showModal({
|
||||
title: '确认接单',
|
||||
content: '确定接受该制片单吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
acceptPortalOrder(row.id).then(() => {
|
||||
uni.showToast({ title: '接单成功', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleSupplement(row) {
|
||||
supplementForm.value = { orderId: row.id, orderNo: row.orderNo, remark: '' }
|
||||
supplementVisible.value = true
|
||||
}
|
||||
|
||||
function submitSupplement() {
|
||||
if (!supplementForm.value.remark?.trim()) {
|
||||
return uni.showToast({ title: '请输入补充内容', icon: 'none' })
|
||||
}
|
||||
supplementPortalOrder({
|
||||
orderId: supplementForm.value.orderId,
|
||||
remark: supplementForm.value.remark.trim()
|
||||
}).then(() => {
|
||||
supplementVisible.value = false
|
||||
uni.showToast({ title: '补充成功', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
|
||||
function handleProduce(row) {
|
||||
uni.showModal({
|
||||
title: '确认操作',
|
||||
content: '确定将该订单标记为制作中吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
producePortalOrder(row.id).then(() => {
|
||||
uni.showToast({ title: '已标记制作中', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleShip(row) {
|
||||
shipForm.value = { orderId: row.id, expressCompany: '', expressNo: '', logisticsFee: '' }
|
||||
shipVisible.value = true
|
||||
}
|
||||
|
||||
function submitShip() {
|
||||
if (!shipForm.value.expressCompany || !shipForm.value.expressNo) {
|
||||
return uni.showToast({ title: '请填写物流信息', icon: 'none' })
|
||||
}
|
||||
shipPortalOrder(shipForm.value).then(() => {
|
||||
shipVisible.value = false
|
||||
uni.showToast({ title: '发货成功', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
|
||||
function handleComplete(row) {
|
||||
uni.showModal({
|
||||
title: '确认完成',
|
||||
content: '确定标记该订单为已完成吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
completePortalOrder(row.id).then(() => {
|
||||
uni.showToast({ title: '操作成功', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleWithdraw(row) {
|
||||
withdrawForm.value = { orderId: row.id, orderNo: row.orderNo, reason: '' }
|
||||
withdrawVisible.value = true
|
||||
}
|
||||
|
||||
function submitWithdraw() {
|
||||
if (!withdrawForm.value.reason?.trim()) {
|
||||
return uni.showToast({ title: '请输入撤单理由', icon: 'none' })
|
||||
}
|
||||
withdrawPortalOrder({
|
||||
orderId: withdrawForm.value.orderId,
|
||||
reason: withdrawForm.value.reason.trim()
|
||||
}).then(() => {
|
||||
withdrawVisible.value = false
|
||||
uni.showToast({ title: '撤单成功', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-header { margin-bottom: 24rpx; }
|
||||
.page-title { display: block; font-size: 36rpx; font-weight: bold; }
|
||||
.tenant-name { font-size: 24rpx; color: #64748b; }
|
||||
.search-bar { display: flex; gap: 12rpx; align-items: center; margin-bottom: 20rpx; }
|
||||
.search-input { flex: 1; font-size: 26rpx; }
|
||||
.status-picker { font-size: 26rpx; color: #64748b; padding: 8rpx 16rpx; background: #f1f5f9; border-radius: 8rpx; }
|
||||
.search-btn { background: #2563eb; color: #fff; }
|
||||
.order-card { margin-bottom: 20rpx; }
|
||||
.order-head { margin-bottom: 12rpx; }
|
||||
.order-no { font-size: 28rpx; font-weight: 600; }
|
||||
.order-info { display: flex; flex-direction: column; gap: 8rpx; font-size: 24rpx; color: #64748b; margin-bottom: 16rpx; }
|
||||
.order-actions { display: flex; flex-wrap: wrap; gap: 16rpx; border-top: 1rpx solid #f1f5f9; padding-top: 16rpx; }
|
||||
.action-link { font-size: 26rpx; color: #64748b; padding: 4rpx 0; &.primary { color: #2563eb; } &.success { color: #22c55e; } &.warning { color: #f59e0b; } }
|
||||
.load-more { text-align: center; padding: 24rpx; color: #2563eb; font-size: 26rpx; }
|
||||
.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 999; }
|
||||
.modal-content { background: #fff; border-radius: 16rpx; padding: 32rpx; width: 80%; }
|
||||
.modal-title { display: block; font-size: 32rpx; font-weight: bold; margin-bottom: 12rpx; }
|
||||
.modal-subtitle { display: block; font-size: 24rpx; color: #64748b; margin-bottom: 20rpx; }
|
||||
.form-item { background: #f8fafc; border-radius: 8rpx; padding: 0 20rpx; margin-bottom: 16rpx; input { height: 80rpx; font-size: 28rpx; } }
|
||||
.remark-input { width: 100%; height: 200rpx; background: #f8fafc; border-radius: 8rpx; padding: 16rpx; font-size: 28rpx; margin-bottom: 16rpx; box-sizing: border-box; }
|
||||
.modal-actions { display: flex; gap: 16rpx; margin-top: 24rpx; button { flex: 1; } }
|
||||
</style>
|
||||
391
uniapp/src/pages/order/lens-order-form.vue
Normal file
391
uniapp/src/pages/order/lens-order-form.vue
Normal file
@@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view v-if="!pageReady" class="empty-tip">加载中...</view>
|
||||
<template v-else>
|
||||
<view class="section card">
|
||||
<text class="section-title">产品与镜片</text>
|
||||
<view class="form-item">
|
||||
<text class="label">产品</text>
|
||||
<radio-group @change="onProductChange">
|
||||
<label v-for="item in normalizedProductOptions" :key="item.id" class="radio-item">
|
||||
<radio :value="String(item.id)" :checked="form.productId === item.id" />
|
||||
<view class="option-text">
|
||||
<text class="option-main">{{ formatProductOption(item) }}</text>
|
||||
</view>
|
||||
</label>
|
||||
</radio-group>
|
||||
<text v-if="!normalizedProductOptions.length" class="text-muted">暂无可选产品</text>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">通用镜片</text>
|
||||
<radio-group @change="onLensChange">
|
||||
<label v-for="item in filteredLensOptions" :key="item.id" class="radio-item">
|
||||
<radio :value="String(item.id)" :checked="form.lensId === item.id" />
|
||||
<view class="option-text">
|
||||
<text class="option-main">{{ formatLensOption(item) }}</text>
|
||||
</view>
|
||||
</label>
|
||||
</radio-group>
|
||||
<text v-if="!filteredLensOptions.length" class="text-muted">
|
||||
{{ form.productId ? '当前产品暂无可选镜片' : '请先选择产品' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="form-item switch-row">
|
||||
<text class="label">渐进多焦点</text>
|
||||
<switch :checked="form.progressiveMultifocal === 1" @change="onProgressiveMultifocalChange" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section card">
|
||||
<text class="section-title">收货信息</text>
|
||||
<view class="form-item"><input v-model="form.receiverName" placeholder="收货人姓名" /></view>
|
||||
<view class="form-item"><input v-model="form.receiverPhone" placeholder="收货人手机号" maxlength="11" /></view>
|
||||
<view class="form-item"><input v-model="form.receiverAddress" placeholder="收货地址" /></view>
|
||||
</view>
|
||||
|
||||
<view class="section card">
|
||||
<text class="section-title">验光参数</text>
|
||||
<view class="pd-mode-row">
|
||||
<text class="label">瞳距类型</text>
|
||||
<radio-group :disabled="form.progressiveMultifocal === 1" @change="onPdModeChange">
|
||||
<label class="pd-mode-item">
|
||||
<radio value="single" :checked="pdMode === 'single'" :disabled="form.progressiveMultifocal === 1" />
|
||||
<text>单瞳距</text>
|
||||
</label>
|
||||
<label class="pd-mode-item">
|
||||
<radio value="dual" :checked="pdMode === 'dual'" :disabled="form.progressiveMultifocal === 1" />
|
||||
<text>双瞳距</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
<text class="pd-mode-tip">{{ pdModeTip }}</text>
|
||||
</view>
|
||||
<view v-for="(eye, idx) in form.optometryList" :key="idx" class="eye-block">
|
||||
<text class="eye-label">{{ eye.eyeSide === 'OS' ? '左眼(OS)' : '右眼(OD)' }}</text>
|
||||
<view class="eye-grid">
|
||||
<view class="grid-item"><text>球镜S</text><input v-model="eye.sphereS" type="digit" @blur="onSphereSChange(eye)" /></view>
|
||||
<view class="grid-item"><text>柱镜C</text><input v-model="eye.cylinderC" type="digit" @blur="onCylinderCChange(eye)" /></view>
|
||||
<view class="grid-item" :class="{ 'is-error': isAxisAInvalid(eye) }">
|
||||
<text>轴位A</text>
|
||||
<input v-model="eye.axisA" type="number" :disabled="!isAxisAEditable(eye.cylinderC)" @blur="onAxisAChange(eye)" />
|
||||
<text v-if="isAxisAInvalid(eye)" class="field-error-tip">轴位不能为0</text>
|
||||
</view>
|
||||
<view v-if="pdMode === 'dual'" class="grid-item"><text>双瞳距</text><input v-model="eye.dualPd" type="digit" /></view>
|
||||
<view v-else-if="idx === 0" class="grid-item grid-item--span2"><text>单瞳距</text><input v-model="singlePd" type="digit" @blur="onSinglePdChange" /></view>
|
||||
<view v-if="form.progressiveMultifocal === 1" class="grid-item"><text>瞳高</text><input v-model="eye.pupilHeight" type="digit" /></view>
|
||||
<view
|
||||
v-if="form.progressiveMultifocal === 1"
|
||||
class="grid-item"
|
||||
:class="{ 'is-error': isNearAddInvalid(eye) }"
|
||||
>
|
||||
<text>近用ADD</text>
|
||||
<input v-model="eye.nearAdd" type="digit" placeholder="+0.75~+4.00" @blur="onNearAddChange(eye)" />
|
||||
<text v-if="isNearAddInvalid(eye)" class="field-error-tip">ADD不能为0</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section card">
|
||||
<text class="section-title">备注</text>
|
||||
<textarea v-model="form.remark" placeholder="备注信息" class="remark-input" />
|
||||
</view>
|
||||
|
||||
<button class="btn-primary submit-btn" :loading="submitting" @click="handleSubmit">保 存</button>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import {
|
||||
createEmptyForm,
|
||||
formatProductOption,
|
||||
formatLensOption,
|
||||
filterLensOptionsByProduct,
|
||||
isAxisAEditable,
|
||||
applyProgressiveMultifocalState,
|
||||
alignCylinderWithSphere,
|
||||
validateCylinderSignWithSphere,
|
||||
clampSinglePd,
|
||||
clampDualPd,
|
||||
SINGLE_PD_DEFAULT,
|
||||
DUAL_PD_DEFAULT,
|
||||
normalizeOrderForm,
|
||||
unwrapOrderResponse
|
||||
} from '@/utils/orderHelper.js'
|
||||
import { listPortalProductOptions, getPortalOrder, addPortalOrder, updatePortalOrder } from '@/api/order.js'
|
||||
import { listLensOptions } from '@/api/dict.js'
|
||||
|
||||
const mode = ref('create')
|
||||
const orderId = ref(null)
|
||||
const pageReady = ref(false)
|
||||
const submitting = ref(false)
|
||||
const form = ref(createEmptyForm())
|
||||
const pdMode = ref('single')
|
||||
const singlePd = ref(SINGLE_PD_DEFAULT)
|
||||
const productOptions = ref([])
|
||||
const lensOptions = ref([])
|
||||
|
||||
const pdModeTip = computed(() => {
|
||||
return pdMode.value === 'dual'
|
||||
? '双瞳距范围:20.00 ~ 40.00 mm(左右眼分别填写)'
|
||||
: '单瞳距范围:40.00 ~ 80.00 mm(左右眼共用一个值)'
|
||||
})
|
||||
|
||||
const normalizedProductOptions = computed(() => {
|
||||
return (productOptions.value || []).map(item => ({
|
||||
...item,
|
||||
id: item.productId != null ? Number(item.productId) : (item.id != null ? Number(item.id) : null)
|
||||
})).filter(item => item.id != null && !Number.isNaN(item.id))
|
||||
})
|
||||
|
||||
const filteredLensOptions = computed(() => {
|
||||
return filterLensOptionsByProduct(
|
||||
lensOptions.value,
|
||||
normalizedProductOptions.value,
|
||||
form.value.productId,
|
||||
form.value.lensId,
|
||||
{
|
||||
productName: form.value.lensProductName,
|
||||
material: form.value.lensMaterial
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
watch(() => form.value.productId, (val, oldVal) => {
|
||||
if (val === oldVal) return
|
||||
const allowedIds = filteredLensOptions.value.map(item => item.id)
|
||||
if (form.value.lensId != null && !allowedIds.includes(Number(form.value.lensId))) {
|
||||
form.value.lensId = null
|
||||
}
|
||||
})
|
||||
|
||||
function syncPdModeFromForm() {
|
||||
const state = applyProgressiveMultifocalState(form.value)
|
||||
pdMode.value = state.pdMode
|
||||
singlePd.value = state.singlePd != null ? clampSinglePd(state.singlePd) : SINGLE_PD_DEFAULT
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
mode.value = options?.mode || 'create'
|
||||
orderId.value = options?.id || null
|
||||
if (mode.value === 'edit') {
|
||||
uni.setNavigationBarTitle({ title: '修改配片单' })
|
||||
}
|
||||
loadPageData()
|
||||
})
|
||||
|
||||
async function loadPageData() {
|
||||
try {
|
||||
const [products, lenses] = await Promise.all([
|
||||
listPortalProductOptions().then(r => r.data || r.rows || []),
|
||||
listLensOptions().then(r => r.data || r.rows || [])
|
||||
])
|
||||
productOptions.value = products
|
||||
lensOptions.value = (lenses || []).map(item => ({
|
||||
...item,
|
||||
id: item && item.id != null ? Number(item.id) : null
|
||||
}))
|
||||
|
||||
if (orderId.value) {
|
||||
const res = await getPortalOrder(orderId.value)
|
||||
form.value = normalizeOrderForm(unwrapOrderResponse(res))
|
||||
syncPdModeFromForm()
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e?.message || '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
pageReady.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function onProductChange(e) {
|
||||
form.value.productId = Number(e.detail.value)
|
||||
}
|
||||
|
||||
function onLensChange(e) {
|
||||
form.value.lensId = Number(e.detail.value)
|
||||
}
|
||||
|
||||
function onProgressiveMultifocalChange(e) {
|
||||
form.value.progressiveMultifocal = e.detail.value ? 1 : 0
|
||||
syncPdModeFromForm()
|
||||
}
|
||||
|
||||
function onPdModeChange(e) {
|
||||
if (form.value.progressiveMultifocal === 1) return
|
||||
pdMode.value = e.detail.value
|
||||
if (pdMode.value === 'dual') {
|
||||
form.value.optometryList.forEach(eye => {
|
||||
eye.dualPd = clampDualPd(eye.dualPd != null ? eye.dualPd : DUAL_PD_DEFAULT)
|
||||
eye.pd = null
|
||||
eye.pdValue = null
|
||||
eye.pdType = '2'
|
||||
})
|
||||
form.value.pdType = '2'
|
||||
} else {
|
||||
const pd = clampSinglePd(singlePd.value)
|
||||
singlePd.value = pd
|
||||
form.value.optometryList.forEach(eye => {
|
||||
eye.pd = pd
|
||||
eye.pdValue = pd
|
||||
eye.dualPd = null
|
||||
eye.pdType = '1'
|
||||
})
|
||||
form.value.pdType = '1'
|
||||
}
|
||||
}
|
||||
|
||||
function onSinglePdChange() {
|
||||
if (pdMode.value !== 'single') return
|
||||
const pd = clampSinglePd(singlePd.value)
|
||||
singlePd.value = pd
|
||||
form.value.optometryList.forEach(eye => {
|
||||
eye.pd = pd
|
||||
eye.pdValue = pd
|
||||
})
|
||||
}
|
||||
|
||||
function onSphereSChange(eye) {
|
||||
eye.cylinderC = alignCylinderWithSphere(eye.sphereS, eye.cylinderC)
|
||||
if (!isAxisAEditable(eye.cylinderC)) {
|
||||
eye.axisA = null
|
||||
}
|
||||
}
|
||||
|
||||
function onCylinderCChange(eye) {
|
||||
eye.cylinderC = alignCylinderWithSphere(eye.sphereS, eye.cylinderC)
|
||||
if (!isAxisAEditable(eye.cylinderC)) {
|
||||
eye.axisA = null
|
||||
}
|
||||
}
|
||||
|
||||
function isAxisAInvalid(eye) {
|
||||
if (!isAxisAEditable(eye.cylinderC)) return false
|
||||
if (eye.axisA == null || eye.axisA === '') return true
|
||||
const axis = Number(eye.axisA)
|
||||
return Number.isNaN(axis) || axis === 0
|
||||
}
|
||||
|
||||
function onAxisAChange(eye) {
|
||||
if (!isAxisAEditable(eye.cylinderC)) {
|
||||
eye.axisA = null
|
||||
return
|
||||
}
|
||||
if (eye.axisA == null || eye.axisA === '') {
|
||||
eye.axisA = 0
|
||||
return
|
||||
}
|
||||
const axis = Number(eye.axisA)
|
||||
if (Number.isNaN(axis)) {
|
||||
eye.axisA = 0
|
||||
return
|
||||
}
|
||||
eye.axisA = Math.min(180, Math.max(0, axis))
|
||||
}
|
||||
|
||||
function validate() {
|
||||
if (!form.value.productId) return '请选择产品'
|
||||
if (!form.value.lensId) return '请选择通用镜片'
|
||||
if (!form.value.receiverName) return '请输入收货人姓名'
|
||||
if (!/^1[3-9]\d{9}$/.test(form.value.receiverPhone)) return '请输入正确的手机号'
|
||||
if (!form.value.receiverAddress) return '请输入收货地址'
|
||||
const cylinderErr = validateCylinderSignWithSphere(form.value.optometryList)
|
||||
if (cylinderErr) return cylinderErr
|
||||
if (pdMode.value === 'dual') {
|
||||
for (const eye of form.value.optometryList || []) {
|
||||
if (eye.dualPd == null || eye.dualPd === '') continue
|
||||
const num = Number(eye.dualPd)
|
||||
if (Number.isNaN(num) || num < 20 || num > 40) {
|
||||
return '双瞳距范围应为20.00~40.00'
|
||||
}
|
||||
}
|
||||
} else if (singlePd.value != null && singlePd.value !== '') {
|
||||
const num = Number(singlePd.value)
|
||||
if (Number.isNaN(num) || num < 40 || num > 80) {
|
||||
return '单瞳距范围应为40.00~80.00'
|
||||
}
|
||||
}
|
||||
for (const eye of form.value.optometryList || []) {
|
||||
if (isAxisAInvalid(eye)) {
|
||||
const eyeLabel = eye.eyeSide === 'OS' ? '左眼' : '右眼'
|
||||
return `${eyeLabel}:有散光时轴位不能为0,请填写1~180`
|
||||
}
|
||||
}
|
||||
if (form.value.progressiveMultifocal === 1) {
|
||||
for (const eye of form.value.optometryList || []) {
|
||||
if (eye.nearAdd == null || eye.nearAdd === '' || Number(eye.nearAdd) === 0) {
|
||||
const eyeLabel = eye.eyeSide === 'OS' ? '左眼' : '右眼'
|
||||
return `${eyeLabel}:选择渐进多焦点时ADD不能为0,请填写+0.75~+4.00`
|
||||
}
|
||||
const num = Number(eye.nearAdd)
|
||||
if (Number.isNaN(num) || num < 0.75 || num > 4) {
|
||||
return '近用ADD范围应在+0.75~+4.00之间'
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function isNearAddInvalid(eye) {
|
||||
if (form.value.progressiveMultifocal !== 1) return false
|
||||
if (eye.nearAdd == null || eye.nearAdd === '') return true
|
||||
const num = Number(eye.nearAdd)
|
||||
return Number.isNaN(num) || num === 0 || num < 0.75 || num > 4
|
||||
}
|
||||
|
||||
function onNearAddChange(eye) {
|
||||
if (form.value.progressiveMultifocal !== 1) {
|
||||
eye.nearAdd = null
|
||||
return
|
||||
}
|
||||
if (eye.nearAdd == null || eye.nearAdd === '') return
|
||||
const num = Number(eye.nearAdd)
|
||||
if (Number.isNaN(num) || num === 0) {
|
||||
eye.nearAdd = null
|
||||
return
|
||||
}
|
||||
eye.nearAdd = Math.min(4, Math.max(0.75, num))
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
const err = validate()
|
||||
if (err) return uni.showToast({ title: err, icon: 'none' })
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const payload = normalizeOrderForm(form.value)
|
||||
const api = mode.value === 'edit' ? updatePortalOrder : addPortalOrder
|
||||
await api(payload)
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 800)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.section { margin-bottom: 24rpx; }
|
||||
.section-title { display: block; font-size: 30rpx; font-weight: 600; margin-bottom: 20rpx; color: #2563eb; }
|
||||
.form-item { margin-bottom: 16rpx; input { height: 80rpx; background: #f8fafc; border-radius: 8rpx; padding: 0 20rpx; font-size: 28rpx; } }
|
||||
.label { display: block; font-size: 26rpx; color: #64748b; margin-bottom: 12rpx; }
|
||||
.radio-item { display: flex; align-items: flex-start; gap: 12rpx; padding: 16rpx 0; border-bottom: 1rpx solid #f1f5f9; }
|
||||
.option-text { flex: 1; }
|
||||
.option-main { display: block; font-size: 28rpx; color: #1e293b; line-height: 1.5; }
|
||||
.switch-row { display: flex; justify-content: space-between; align-items: center; }
|
||||
.pd-mode-row { display: flex; align-items: center; gap: 24rpx; margin-bottom: 16rpx; flex-wrap: wrap; }
|
||||
.pd-mode-item { display: inline-flex; align-items: center; gap: 8rpx; margin-right: 24rpx; font-size: 26rpx; color: #334155; }
|
||||
.pd-mode-tip { width: 100%; font-size: 22rpx; color: #94a3b8; }
|
||||
.eye-block { margin-bottom: 24rpx; padding: 16rpx; background: #f8fafc; border-radius: 12rpx; }
|
||||
.eye-label { display: block; font-weight: 600; margin-bottom: 12rpx; }
|
||||
.eye-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; }
|
||||
.grid-item { text { display: block; font-size: 22rpx; color: #64748b; margin-bottom: 4rpx; } input { height: 64rpx; background: #fff; border-radius: 8rpx; padding: 0 12rpx; font-size: 26rpx; border: 1rpx solid transparent; box-sizing: border-box; } input:disabled { background: #f1f5f9; color: #94a3b8; } }
|
||||
.grid-item.is-error input { border-color: #f56c6c; background: #fef0f0; }
|
||||
.field-error-tip { display: block; margin-top: 4rpx; font-size: 20rpx; color: #f56c6c; }
|
||||
.grid-item--span2 { grid-column: span 2; }
|
||||
.remark-input { width: 100%; height: 160rpx; background: #f8fafc; border-radius: 8rpx; padding: 16rpx; font-size: 28rpx; }
|
||||
.submit-btn { width: 100%; margin: 32rpx 0; }
|
||||
.text-muted { font-size: 24rpx; color: #94a3b8; }
|
||||
</style>
|
||||
228
uniapp/src/pages/order/lens-order.vue
Normal file
228
uniapp/src/pages/order/lens-order.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<NavBar />
|
||||
<view class="container">
|
||||
<view class="page-header flex-between">
|
||||
<view>
|
||||
<text class="page-title">我的配片单</text>
|
||||
<text v-if="userStore.tenantName" class="tenant-name">{{ userStore.tenantName }}</text>
|
||||
</view>
|
||||
<button v-if="canManage" class="add-btn" size="mini" @click="handleAdd">+ 新增</button>
|
||||
</view>
|
||||
|
||||
<!-- 搜索 -->
|
||||
<view class="search-bar card">
|
||||
<input v-model="queryParams.orderNo" placeholder="订单编号" class="search-input" />
|
||||
<picker :range="statusLabels" :value="statusIndex" @change="onStatusChange">
|
||||
<view class="status-picker">{{ statusLabels[statusIndex] || '全部状态' }}</view>
|
||||
</picker>
|
||||
<button size="mini" class="search-btn" @click="handleQuery">搜索</button>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<view v-if="loading && !orderList.length" class="empty-tip">加载中...</view>
|
||||
<view v-else-if="!orderList.length" class="empty-tip">暂无配片单</view>
|
||||
<view v-for="item in orderList" :key="item.id" class="order-card card" @click="handleDetail(item)">
|
||||
<view class="flex-between order-head">
|
||||
<text class="order-no">{{ item.orderNo }}</text>
|
||||
<DictTag :options="orderStatusDict" :value="item.orderStatus" />
|
||||
</view>
|
||||
<view class="order-info">
|
||||
<text>镜片:{{ formatLens(item) }}</text>
|
||||
<text>收货人:{{ item.receiverName }} {{ item.receiverPhone }}</text>
|
||||
<text>创建:{{ item.createTime }}</text>
|
||||
</view>
|
||||
<view class="order-actions" @click.stop>
|
||||
<text class="action-link" @click="handleDetail(item)">详情</text>
|
||||
<text v-if="canViewLogistics(item)" class="action-link" @click="viewLogistics(item)">物流</text>
|
||||
<text v-if="canCompleteOrder(item)" class="action-link success" @click="handleComplete(item)">完成</text>
|
||||
<text v-if="canReceiveOrder(item)" class="action-link success" @click="handleReceive(item)">接收</text>
|
||||
<text v-if="canManage && canEditOrder(item)" class="action-link" @click="handleEdit(item)">修改</text>
|
||||
<text v-if="canManage && canEditOrder(item)" class="action-link primary" @click="handleSubmitDraft(item)">提交</text>
|
||||
<text v-if="canManage && canDeleteOrder(item)" class="action-link danger" @click="handleDelete(item)">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="hasMore" class="load-more" @click="loadMore">加载更多</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { useUserStore, useDictStore } from '@/store/index.js'
|
||||
import { isBrandTenant } from '@/utils/tenantType.js'
|
||||
import { ORDER_STATUS, formatLens } from '@/utils/orderHelper.js'
|
||||
import {
|
||||
listPortalOrder, delPortalOrder, completePortalOrder, receivePortalOrder, updatePortalOrder
|
||||
} from '@/api/order.js'
|
||||
import NavBar from '@/components/NavBar/NavBar.vue'
|
||||
import DictTag from '@/components/DictTag/DictTag.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const dictStore = useDictStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const orderList = ref([])
|
||||
const total = ref(0)
|
||||
const orderStatusDict = ref([])
|
||||
const statusIndex = ref(0)
|
||||
|
||||
const queryParams = ref({ pageNum: 1, pageSize: 10, orderNo: null, orderStatus: null })
|
||||
|
||||
const canManage = computed(() => isBrandTenant(userStore.tenantType))
|
||||
const statusLabels = computed(() => ['全部状态', ...orderStatusDict.value.map(d => d.dictLabel)])
|
||||
const hasMore = computed(() => orderList.value.length < total.value)
|
||||
|
||||
onMounted(async () => {
|
||||
orderStatusDict.value = await dictStore.getDict('jy_order_status')
|
||||
getList()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
queryParams.value.pageNum = 1
|
||||
getList().finally(() => uni.stopPullDownRefresh())
|
||||
})
|
||||
|
||||
onReachBottom(() => loadMore())
|
||||
|
||||
function onStatusChange(e) {
|
||||
statusIndex.value = Number(e.detail.value)
|
||||
queryParams.value.orderStatus = statusIndex.value === 0 ? null : orderStatusDict.value[statusIndex.value - 1]?.dictValue
|
||||
}
|
||||
|
||||
function getList(append = false) {
|
||||
loading.value = true
|
||||
return listPortalOrder(queryParams.value).then(res => {
|
||||
orderList.value = append ? [...orderList.value, ...(res.rows || [])] : (res.rows || [])
|
||||
total.value = res.total || 0
|
||||
}).finally(() => { loading.value = false })
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
if (!hasMore.value || loading.value) return
|
||||
queryParams.value.pageNum++
|
||||
getList(true)
|
||||
}
|
||||
|
||||
function canEditOrder(row) {
|
||||
return String(row.orderStatus) === ORDER_STATUS.DRAFT
|
||||
}
|
||||
|
||||
function canDeleteOrder(row) {
|
||||
return String(row.orderStatus) === ORDER_STATUS.DRAFT
|
||||
}
|
||||
|
||||
function canViewLogistics(row) {
|
||||
const s = String(row.orderStatus)
|
||||
return [ORDER_STATUS.SHIPPED, ORDER_STATUS.COMPLETED, ORDER_STATUS.EXCHANGE_LOGISTICS, ORDER_STATUS.EXCHANGE_COMPLETED].includes(s)
|
||||
}
|
||||
|
||||
function canCompleteOrder(row) {
|
||||
return String(row.orderStatus) === ORDER_STATUS.SHIPPED
|
||||
}
|
||||
|
||||
function canReceiveOrder(row) {
|
||||
return String(row.orderStatus) === ORDER_STATUS.EXCHANGE_REJECTED
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
uni.navigateTo({ url: '/pages/order/lens-order-form?mode=create' })
|
||||
}
|
||||
|
||||
function handleEdit(row) {
|
||||
uni.navigateTo({ url: '/pages/order/lens-order-form?mode=edit&id=' + row.id })
|
||||
}
|
||||
|
||||
function handleDetail(row) {
|
||||
uni.navigateTo({ url: '/pages/order/order-detail?id=' + row.id + '&type=lens' })
|
||||
}
|
||||
|
||||
function viewLogistics(row) {
|
||||
uni.navigateTo({ url: '/pages/order/logistics?id=' + row.id })
|
||||
}
|
||||
|
||||
function handleSubmitDraft(row) {
|
||||
uni.showModal({
|
||||
title: '确认提交',
|
||||
content: '确定提交该配片单吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
updatePortalOrder({ ...row, orderStatus: ORDER_STATUS.SUBMIT }).then(() => {
|
||||
uni.showToast({ title: '提交成功', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleComplete(row) {
|
||||
uni.showModal({
|
||||
title: '确认完成',
|
||||
content: '确定标记该订单为已完成吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
completePortalOrder(row.id).then(() => {
|
||||
uni.showToast({ title: '操作成功', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleReceive(row) {
|
||||
uni.showModal({
|
||||
title: '确认接收',
|
||||
content: '确定接收该订单吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
receivePortalOrder(row.id).then(() => {
|
||||
uni.showToast({ title: '操作成功', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定删除该配片单吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
delPortalOrder(row.id).then(() => {
|
||||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||||
handleQuery()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-header { margin-bottom: 24rpx; }
|
||||
.page-title { display: block; font-size: 36rpx; font-weight: bold; }
|
||||
.tenant-name { font-size: 24rpx; color: #64748b; }
|
||||
.add-btn { background: #2563eb; color: #fff; font-size: 24rpx; border-radius: 8rpx; }
|
||||
.search-bar { display: flex; gap: 12rpx; align-items: center; margin-bottom: 20rpx; }
|
||||
.search-input { flex: 1; font-size: 26rpx; }
|
||||
.status-picker { font-size: 26rpx; color: #64748b; padding: 8rpx 16rpx; background: #f1f5f9; border-radius: 8rpx; }
|
||||
.search-btn { background: #2563eb; color: #fff; }
|
||||
.order-card { margin-bottom: 20rpx; }
|
||||
.order-head { margin-bottom: 12rpx; }
|
||||
.order-no { font-size: 28rpx; font-weight: 600; }
|
||||
.order-info { display: flex; flex-direction: column; gap: 8rpx; font-size: 24rpx; color: #64748b; margin-bottom: 16rpx; }
|
||||
.order-actions { display: flex; flex-wrap: wrap; gap: 16rpx; border-top: 1rpx solid #f1f5f9; padding-top: 16rpx; }
|
||||
.action-link { font-size: 26rpx; color: #64748b; &.primary { color: #2563eb; } &.success { color: #22c55e; } &.danger { color: #ef4444; } }
|
||||
.load-more { text-align: center; padding: 24rpx; color: #2563eb; font-size: 26rpx; }
|
||||
</style>
|
||||
69
uniapp/src/pages/order/logistics.vue
Normal file
69
uniapp/src/pages/order/logistics.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view v-if="loading" class="empty-tip">加载中...</view>
|
||||
<view v-else-if="loadError" class="empty-tip">{{ loadError }}</view>
|
||||
<template v-else>
|
||||
<view v-if="basic" class="card">
|
||||
<text class="section-title">物流概要</text>
|
||||
<view class="detail-row"><text class="label">订单编号</text><text>{{ basic.orderNo }}</text></view>
|
||||
<view class="detail-row"><text class="label">物流公司</text><text>{{ basic.expressCompany || '-' }}</text></view>
|
||||
<view class="detail-row"><text class="label">物流单号</text><text>{{ basic.expressNo || '-' }}</text></view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="section-title">物流轨迹</text>
|
||||
<view v-if="traces.length" class="timeline">
|
||||
<view v-for="(item, idx) in traces" :key="idx" class="timeline-item">
|
||||
<view class="dot" :class="{ first: idx === 0 }" />
|
||||
<view class="timeline-content">
|
||||
<text class="timeline-text">{{ item.line }}</text>
|
||||
<text class="timeline-time">{{ item.timeDesc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty-tip">未查到物流轨迹</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getPortalOrderLogistics } from '@/api/order.js'
|
||||
import { unwrapOrderResponse } from '@/utils/orderHelper.js'
|
||||
|
||||
const loading = ref(true)
|
||||
const loadError = ref('')
|
||||
const basic = ref(null)
|
||||
const traces = ref([])
|
||||
|
||||
onLoad((options) => {
|
||||
const id = options?.id
|
||||
if (!id) {
|
||||
loading.value = false
|
||||
loadError.value = '缺少订单ID'
|
||||
return
|
||||
}
|
||||
getPortalOrderLogistics(id).then(res => {
|
||||
const data = unwrapOrderResponse(res) || {}
|
||||
basic.value = data.basic || data
|
||||
traces.value = (data.detail && data.detail.traces) || data.traces || []
|
||||
}).catch((e) => {
|
||||
loadError.value = e?.message || '加载失败'
|
||||
}).finally(() => { loading.value = false })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.section-title { display: block; font-size: 30rpx; font-weight: 600; margin-bottom: 16rpx; color: #2563eb; }
|
||||
.detail-row { display: flex; justify-content: space-between; padding: 16rpx 0; border-bottom: 1rpx solid #f1f5f9; font-size: 28rpx; .label { color: #64748b; } }
|
||||
.timeline { padding-left: 24rpx; }
|
||||
.timeline-item { display: flex; gap: 20rpx; padding-bottom: 32rpx; position: relative;
|
||||
&::before { content: ''; position: absolute; left: 11rpx; top: 24rpx; bottom: 0; width: 2rpx; background: #e2e8f0; }
|
||||
&:last-child::before { display: none; }
|
||||
}
|
||||
.dot { width: 24rpx; height: 24rpx; border-radius: 50%; background: #e2e8f0; flex-shrink: 0; margin-top: 4rpx; &.first { background: #2563eb; } }
|
||||
.timeline-text { display: block; font-size: 28rpx; color: #334155; }
|
||||
.timeline-time { display: block; font-size: 24rpx; color: #94a3b8; margin-top: 8rpx; }
|
||||
</style>
|
||||
105
uniapp/src/pages/order/order-detail.vue
Normal file
105
uniapp/src/pages/order/order-detail.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view v-if="loading" class="empty-tip">加载中...</view>
|
||||
<view v-else-if="loadError" class="empty-tip">{{ loadError }}</view>
|
||||
<template v-else>
|
||||
<view class="card">
|
||||
<view class="detail-row"><text class="label">订单编号</text><text>{{ detail.orderNo }}</text></view>
|
||||
<view class="detail-row"><text class="label">订单状态</text><DictTag :options="orderStatusDict" :value="detail.orderStatus" /></view>
|
||||
<view class="detail-row"><text class="label">通用镜片</text><text>{{ formatLens(detail) }}</text></view>
|
||||
<view class="detail-row"><text class="label">渐进多焦点</text><text>{{ detail.progressiveMultifocal === 1 ? '是' : '否' }}</text></view>
|
||||
<view class="detail-row"><text class="label">备注</text><text>{{ detail.remark || '-' }}</text></view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="section-title">收货信息</text>
|
||||
<view class="detail-row"><text class="label">收货人</text><text>{{ detail.receiverName }}</text></view>
|
||||
<view class="detail-row"><text class="label">手机号</text><text>{{ detail.receiverPhone }}</text></view>
|
||||
<view class="detail-row"><text class="label">地址</text><text>{{ detail.receiverAddress }}</text></view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<text class="section-title">验光参数</text>
|
||||
<view v-for="(eye, idx) in detailOptometryList" :key="idx" class="eye-row">
|
||||
<text class="eye-side">{{ eye.eyeSide === 'OS' ? '左眼' : '右眼' }}</text>
|
||||
<text>S: {{ eye.sphereS ?? '-' }} | C: {{ eye.cylinderC ?? '-' }} | A: {{ eye.axisA ?? '-' }}</text>
|
||||
<text>瞳距: {{ formatDetailPd(eye) }}</text>
|
||||
<text v-if="detail.progressiveMultifocal === 1">瞳高: {{ eye.pupilHeight ?? '-' }}</text>
|
||||
<text v-if="detail.progressiveMultifocal === 1 && eye.nearAdd != null && eye.nearAdd !== ''">近用ADD: {{ formatNearAdd(eye.nearAdd) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="detail.statusLogList && detail.statusLogList.length" class="card">
|
||||
<text class="section-title">状态记录</text>
|
||||
<view v-for="(log, idx) in detail.statusLogList" :key="idx" class="log-row">
|
||||
<DictTag :options="orderStatusDict" :value="log.orderStatus" />
|
||||
<text class="log-time">{{ log.operateTime }}</text>
|
||||
<text v-if="log.remark" class="log-remark">{{ log.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getPortalOrder } from '@/api/order.js'
|
||||
import { useDictStore } from '@/store/index.js'
|
||||
import { formatLens, formatNearAdd, unwrapOrderResponse } from '@/utils/orderHelper.js'
|
||||
import DictTag from '@/components/DictTag/DictTag.vue'
|
||||
|
||||
const loading = ref(true)
|
||||
const loadError = ref('')
|
||||
const detail = ref({})
|
||||
const orderStatusDict = ref([])
|
||||
const dictStore = useDictStore()
|
||||
|
||||
const detailOptometryList = computed(() => {
|
||||
const list = detail.value.optometryList || []
|
||||
return [...list].sort((a, b) => (a.eyeSide === 'OS' ? -1 : 1))
|
||||
})
|
||||
|
||||
function formatDetailPd(eye) {
|
||||
if (eye.dualPd != null && eye.dualPd !== '') return eye.dualPd
|
||||
if (eye.pd != null && eye.pd !== '') return eye.pd
|
||||
if (eye.pdValue != null && eye.pdValue !== '') return eye.pdValue
|
||||
return '-'
|
||||
}
|
||||
|
||||
async function loadDetail(id) {
|
||||
loading.value = true
|
||||
loadError.value = ''
|
||||
try {
|
||||
orderStatusDict.value = await dictStore.getDict('jy_order_status')
|
||||
const res = await getPortalOrder(id)
|
||||
const data = unwrapOrderResponse(res)
|
||||
if (!data || !data.id) {
|
||||
loadError.value = '未获取到订单详情'
|
||||
return
|
||||
}
|
||||
detail.value = data
|
||||
} catch (e) {
|
||||
loadError.value = e?.message || '加载失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
const id = options?.id
|
||||
if (!id) {
|
||||
loading.value = false
|
||||
loadError.value = '缺少订单ID'
|
||||
return
|
||||
}
|
||||
loadDetail(id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-row { display: flex; justify-content: space-between; padding: 16rpx 0; border-bottom: 1rpx solid #f1f5f9; font-size: 28rpx; .label { color: #64748b; flex-shrink: 0; margin-right: 24rpx; } }
|
||||
.section-title { display: block; font-size: 30rpx; font-weight: 600; margin-bottom: 16rpx; color: #2563eb; }
|
||||
.eye-row { padding: 16rpx 0; border-bottom: 1rpx solid #f1f5f9; font-size: 26rpx; color: #64748b; .eye-side { display: block; font-weight: 600; color: #334155; margin-bottom: 8rpx; } }
|
||||
.log-row { padding: 16rpx 0; border-bottom: 1rpx solid #f1f5f9; .log-time { display: block; font-size: 24rpx; color: #94a3b8; margin-top: 8rpx; } .log-remark { font-size: 26rpx; color: #64748b; } }
|
||||
</style>
|
||||
134
uniapp/src/pages/register/register.vue
Normal file
134
uniapp/src/pages/register/register.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view class="tabs">
|
||||
<view :class="['tab', activeTab === 'password' && 'active']" @click="activeTab = 'password'">密码注册</view>
|
||||
<view :class="['tab', activeTab === 'code' && 'active']" @click="activeTab = 'code'">验证码注册</view>
|
||||
</view>
|
||||
|
||||
<view v-if="activeTab === 'password'" class="form">
|
||||
<view class="form-item"><input v-model="form.username" placeholder="用户名" /></view>
|
||||
<view class="form-item"><input v-model="form.password" password placeholder="密码" /></view>
|
||||
<view class="form-item"><input v-model="form.confirmPassword" password placeholder="确认密码" /></view>
|
||||
<view v-if="captchaEnabled" class="form-item code-row">
|
||||
<input v-model="form.code" placeholder="验证码" class="code-input" />
|
||||
<image :src="codeUrl" class="code-img" @click="getCode" mode="aspectFit" />
|
||||
</view>
|
||||
<button class="btn-primary" :loading="loading" @click="handleRegister">注 册</button>
|
||||
</view>
|
||||
|
||||
<view v-else class="form">
|
||||
<view class="form-item"><input v-model="codeForm.username" placeholder="手机号或邮箱" /></view>
|
||||
<view class="form-item"><input v-model="codeForm.password" password placeholder="设置密码" /></view>
|
||||
<view class="form-item code-row">
|
||||
<input v-model="codeForm.code" placeholder="验证码" class="code-input" />
|
||||
<button class="send-code-btn" :disabled="countdown > 0" @click="sendCode">{{ countdown > 0 ? countdown + 's' : '发送' }}</button>
|
||||
</view>
|
||||
<button class="btn-primary" :loading="loading" @click="handleCodeRegister">注 册</button>
|
||||
</view>
|
||||
|
||||
<view class="links">
|
||||
<text>已有账户?</text>
|
||||
<text class="link" @click="goLogin">立即登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { register, registerByCode, getCodeImg, sendSmsCode, sendEmailCode } from '@/api/login.js'
|
||||
|
||||
const activeTab = ref('password')
|
||||
const loading = ref(false)
|
||||
const captchaEnabled = ref(true)
|
||||
const codeUrl = ref('')
|
||||
const countdown = ref(0)
|
||||
let timer = null
|
||||
|
||||
const form = ref({ username: '', password: '', confirmPassword: '', code: '', uuid: '' })
|
||||
const codeForm = ref({ username: '', password: '', code: '', uuid: '' })
|
||||
|
||||
onMounted(() => getCode())
|
||||
|
||||
function getCode() {
|
||||
getCodeImg().then(res => {
|
||||
captchaEnabled.value = res.captchaEnabled !== false
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = 'data:image/gif;base64,' + res.img
|
||||
form.value.uuid = res.uuid
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function isPhone(v) { return /^1[3-9]\d{9}$/.test(v) }
|
||||
|
||||
function sendCode() {
|
||||
if (!codeForm.value.username) return uni.showToast({ title: '请输入手机号或邮箱', icon: 'none' })
|
||||
const p = isPhone(codeForm.value.username) ? sendSmsCode(codeForm.value.username) : sendEmailCode(codeForm.value.username)
|
||||
p.then(res => {
|
||||
codeForm.value.uuid = res.data?.uuid || res.uuid
|
||||
countdown.value = 60
|
||||
timer = setInterval(() => { countdown.value--; if (countdown.value <= 0) clearInterval(timer) }, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
async function handleRegister() {
|
||||
if (form.value.password !== form.value.confirmPassword) {
|
||||
return uni.showToast({ title: '两次密码不一致', icon: 'none' })
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
await register(form.value)
|
||||
uni.showToast({ title: '注册成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 1000)
|
||||
} finally {
|
||||
loading.value = false
|
||||
getCode()
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCodeRegister() {
|
||||
loading.value = true
|
||||
try {
|
||||
await registerByCode(codeForm.value)
|
||||
uni.showToast({ title: '注册成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 1000)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goLogin() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin-bottom: 32rpx;
|
||||
background: #f1f5f9;
|
||||
border-radius: 12rpx;
|
||||
padding: 6rpx;
|
||||
}
|
||||
.tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 16rpx;
|
||||
font-size: 28rpx;
|
||||
&.active { background: #fff; color: #2563eb; font-weight: 600; border-radius: 8rpx; }
|
||||
}
|
||||
.form-item {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border: 2rpx solid #e2e8f0;
|
||||
input { height: 88rpx; font-size: 28rpx; }
|
||||
}
|
||||
.code-row { display: flex; align-items: center; gap: 16rpx; padding: 12rpx 24rpx; }
|
||||
.code-input { flex: 1; }
|
||||
.code-img { width: 200rpx; height: 72rpx; }
|
||||
.send-code-btn { font-size: 24rpx; background: #2563eb; color: #fff; border-radius: 8rpx; padding: 0 20rpx; }
|
||||
.btn-primary { width: 100%; margin-top: 16rpx; }
|
||||
.links { text-align: center; margin-top: 32rpx; font-size: 26rpx; .link { color: #2563eb; margin-left: 8rpx; } }
|
||||
</style>
|
||||
82
uniapp/src/pages/resetPassword/resetPassword.vue
Normal file
82
uniapp/src/pages/resetPassword/resetPassword.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view class="tabs">
|
||||
<view :class="['tab', activeTab === 'account' && 'active']" @click="activeTab = 'account'">账号重置</view>
|
||||
<view :class="['tab', activeTab === 'code' && 'active']" @click="activeTab = 'code'">验证码重置</view>
|
||||
</view>
|
||||
|
||||
<view v-if="activeTab === 'account'" class="form">
|
||||
<view class="form-item"><input v-model="accountForm.username" placeholder="用户名" /></view>
|
||||
<view class="form-item"><input v-model="accountForm.oldPassword" password placeholder="旧密码" /></view>
|
||||
<view class="form-item"><input v-model="accountForm.newPassword" password placeholder="新密码" /></view>
|
||||
<view class="form-item"><input v-model="accountForm.confirmPassword" password placeholder="确认新密码" /></view>
|
||||
<button class="btn-primary" :loading="loading" @click="handleAccountReset">重置密码</button>
|
||||
</view>
|
||||
|
||||
<view v-else class="form">
|
||||
<view class="form-item"><input v-model="codeForm.username" placeholder="手机号或邮箱" /></view>
|
||||
<view class="form-item"><input v-model="codeForm.newPassword" password placeholder="新密码" /></view>
|
||||
<view class="form-item code-row">
|
||||
<input v-model="codeForm.code" placeholder="验证码" class="code-input" />
|
||||
<button class="send-code-btn" :disabled="countdown > 0" @click="sendCode">{{ countdown > 0 ? countdown + 's' : '发送' }}</button>
|
||||
</view>
|
||||
<button class="btn-primary" :loading="loading" @click="handleCodeReset">重置密码</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { resetPasswordByAccount, resetPasswordByCode, sendSmsCode, sendEmailCode } from '@/api/login.js'
|
||||
|
||||
const activeTab = ref('account')
|
||||
const loading = ref(false)
|
||||
const countdown = ref(0)
|
||||
let timer = null
|
||||
|
||||
const accountForm = ref({ username: '', oldPassword: '', newPassword: '', confirmPassword: '' })
|
||||
const codeForm = ref({ username: '', newPassword: '', code: '', uuid: '' })
|
||||
|
||||
function isPhone(v) { return /^1[3-9]\d{9}$/.test(v) }
|
||||
|
||||
function sendCode() {
|
||||
if (!codeForm.value.username) return uni.showToast({ title: '请输入手机号或邮箱', icon: 'none' })
|
||||
const p = isPhone(codeForm.value.username) ? sendSmsCode(codeForm.value.username) : sendEmailCode(codeForm.value.username)
|
||||
p.then(res => {
|
||||
codeForm.value.uuid = res.data?.uuid || res.uuid
|
||||
countdown.value = 60
|
||||
timer = setInterval(() => { countdown.value--; if (countdown.value <= 0) clearInterval(timer) }, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
async function handleAccountReset() {
|
||||
if (accountForm.value.newPassword !== accountForm.value.confirmPassword) {
|
||||
return uni.showToast({ title: '两次密码不一致', icon: 'none' })
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
await resetPasswordByAccount(accountForm.value)
|
||||
uni.showToast({ title: '密码重置成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 1000)
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
async function handleCodeReset() {
|
||||
loading.value = true
|
||||
try {
|
||||
await resetPasswordByCode(codeForm.value)
|
||||
uni.showToast({ title: '密码重置成功', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 1000)
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tabs { display: flex; margin-bottom: 32rpx; background: #f1f5f9; border-radius: 12rpx; padding: 6rpx; }
|
||||
.tab { flex: 1; text-align: center; padding: 16rpx; font-size: 28rpx; &.active { background: #fff; color: #2563eb; font-weight: 600; border-radius: 8rpx; } }
|
||||
.form-item { background: #fff; border-radius: 12rpx; padding: 0 24rpx; margin-bottom: 20rpx; border: 2rpx solid #e2e8f0; input { height: 88rpx; font-size: 28rpx; } }
|
||||
.code-row { display: flex; align-items: center; gap: 16rpx; padding: 12rpx 24rpx; }
|
||||
.code-input { flex: 1; }
|
||||
.send-code-btn { font-size: 24rpx; background: #2563eb; color: #fff; border-radius: 8rpx; padding: 0 20rpx; }
|
||||
.btn-primary { width: 100%; margin-top: 16rpx; }
|
||||
</style>
|
||||
28
uniapp/src/pages/user/profile.vue
Normal file
28
uniapp/src/pages/user/profile.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="page container">
|
||||
<view class="card">
|
||||
<view class="detail-row"><text class="label">用户名</text><text>{{ userStore.name }}</text></view>
|
||||
<view class="detail-row"><text class="label">租户</text><text>{{ userStore.tenantName || '-' }}</text></view>
|
||||
<view class="detail-row"><text class="label">租户类型</text><text>{{ tenantTypeLabel }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useUserStore } from '@/store/index.js'
|
||||
import { isBrandTenant, isConsumerTenant, isLensFactoryTenant } from '@/utils/tenantType.js'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const tenantTypeLabel = computed(() => {
|
||||
const t = userStore.tenantType
|
||||
if (isBrandTenant(t)) return '品牌租户'
|
||||
if (isConsumerTenant(t)) return '消费者'
|
||||
if (isLensFactoryTenant(t)) return '镜片厂'
|
||||
return t || '-'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-row { display: flex; justify-content: space-between; padding: 20rpx 0; border-bottom: 1rpx solid #f1f5f9; font-size: 28rpx; .label { color: #64748b; } }
|
||||
</style>
|
||||
0
uniapp/src/static/images/.gitkeep
Normal file
0
uniapp/src/static/images/.gitkeep
Normal file
131
uniapp/src/store/index.js
Normal file
131
uniapp/src/store/index.js
Normal file
@@ -0,0 +1,131 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { login as loginApi, logout as logoutApi, getInfo } from '@/api/login.js'
|
||||
import { getCurrentTenant } from '@/api/tenant.js'
|
||||
import { getDicts } from '@/api/dict.js'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth.js'
|
||||
import config from '@/config/index.js'
|
||||
|
||||
function commitTenant(state, tenant, user) {
|
||||
if (tenant && tenant.id) {
|
||||
state.tenantId = tenant.id
|
||||
state.tenantType = tenant.tenantType != null ? String(tenant.tenantType) : ''
|
||||
state.tenantName = tenant.name || ''
|
||||
state.tenantCode = tenant.code || (user && user.tenantCode) || ''
|
||||
} else {
|
||||
state.tenantId = null
|
||||
state.tenantType = ''
|
||||
state.tenantName = ''
|
||||
state.tenantCode = ''
|
||||
}
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
token: getToken(),
|
||||
id: '',
|
||||
name: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
permissions: [],
|
||||
tenantId: null,
|
||||
tenantType: '',
|
||||
tenantName: '',
|
||||
tenantCode: ''
|
||||
}),
|
||||
|
||||
getters: {
|
||||
isLoggedIn: (state) => !!state.token
|
||||
},
|
||||
|
||||
actions: {
|
||||
async login(userInfo) {
|
||||
const res = await loginApi(
|
||||
userInfo.username.trim(),
|
||||
userInfo.password,
|
||||
userInfo.code,
|
||||
userInfo.uuid,
|
||||
userInfo.directLogin
|
||||
)
|
||||
setToken(res.token)
|
||||
this.token = res.token
|
||||
},
|
||||
|
||||
async loginWithToken(token) {
|
||||
setToken(token)
|
||||
this.token = token
|
||||
},
|
||||
|
||||
async getInfo() {
|
||||
const res = await getInfo()
|
||||
const user = res.user
|
||||
const avatar = (!user.avatar)
|
||||
? '/static/images/profile.jpg'
|
||||
: (user.avatar.startsWith('http') ? user.avatar : config.baseUrl + user.avatar)
|
||||
|
||||
if (res.roles && res.roles.length > 0) {
|
||||
this.roles = res.roles
|
||||
this.permissions = res.permissions
|
||||
} else {
|
||||
this.roles = ['ROLE_DEFAULT']
|
||||
}
|
||||
this.id = user.userId
|
||||
this.name = user.userName
|
||||
this.avatar = avatar
|
||||
|
||||
if (res.tenant && res.tenant.id) {
|
||||
commitTenant(this, res.tenant, user)
|
||||
return res
|
||||
}
|
||||
try {
|
||||
const tenantRes = await getCurrentTenant()
|
||||
commitTenant(this, tenantRes.data || {}, user)
|
||||
} catch {
|
||||
commitTenant(this, null, user)
|
||||
}
|
||||
return res
|
||||
},
|
||||
|
||||
async logout() {
|
||||
try {
|
||||
await logoutApi()
|
||||
} finally {
|
||||
this.token = ''
|
||||
this.roles = []
|
||||
this.permissions = []
|
||||
this.tenantId = null
|
||||
this.tenantType = ''
|
||||
this.tenantName = ''
|
||||
this.tenantCode = ''
|
||||
removeToken()
|
||||
}
|
||||
},
|
||||
|
||||
fedLogout() {
|
||||
this.token = ''
|
||||
removeToken()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const useDictStore = defineStore('dict', {
|
||||
state: () => ({
|
||||
cache: {}
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async getDict(dictType) {
|
||||
if (this.cache[dictType]) {
|
||||
return this.cache[dictType]
|
||||
}
|
||||
const res = await getDicts(dictType)
|
||||
this.cache[dictType] = res.data || []
|
||||
return this.cache[dictType]
|
||||
},
|
||||
|
||||
getDictLabel(dictType, value) {
|
||||
const list = this.cache[dictType] || []
|
||||
const item = list.find(d => String(d.dictValue) === String(value))
|
||||
return item ? item.dictLabel : value
|
||||
}
|
||||
}
|
||||
})
|
||||
4
uniapp/src/store/pinia.js
Normal file
4
uniapp/src/store/pinia.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
const pinia = createPinia()
|
||||
export default pinia
|
||||
10
uniapp/src/uni.scss
Normal file
10
uniapp/src/uni.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
$uni-color-primary: #2563eb;
|
||||
$uni-color-success: #22c55e;
|
||||
$uni-color-warning: #f59e0b;
|
||||
$uni-color-error: #ef4444;
|
||||
|
||||
$page-bg: #f5f7fa;
|
||||
$card-bg: #ffffff;
|
||||
$text-primary: #1e293b;
|
||||
$text-secondary: #64748b;
|
||||
$border-color: rgba(37, 99, 235, 0.12);
|
||||
13
uniapp/src/utils/auth.js
Normal file
13
uniapp/src/utils/auth.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import config from '@/config/index.js'
|
||||
|
||||
export function getToken() {
|
||||
return uni.getStorageSync(config.tokenKey) || ''
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
uni.setStorageSync(config.tokenKey, token)
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
uni.removeStorageSync(config.tokenKey)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user