1. 优化门店管理员的功能
This commit is contained in:
@@ -67,6 +67,10 @@ export function getLedgerDetail(params) {
|
||||
return clinicRequest({ url: `${PREFIX}-settlement/ledger-detail`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getAccountChangeLogList(params) {
|
||||
return clinicRequest({ url: `${PREFIX}-account-change-log/list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getWithdrawalList(params) {
|
||||
return clinicRequest({ url: `${PREFIX}-withdrawal/list`, method: 'GET', data: params });
|
||||
}
|
||||
@@ -115,6 +119,18 @@ export function getOrderDetail(id) {
|
||||
return clinicRequest({ url: `${PREFIX}-order/detail`, method: 'GET', data: { id } });
|
||||
}
|
||||
|
||||
export function getOrderTrace(params) {
|
||||
return clinicRequest({ url: `${PREFIX}-order/trace`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getOrderLedgerDetail(params) {
|
||||
return clinicRequest({ url: `${PREFIX}-order/ledger-detail`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getOrderReconciliationDetail(params) {
|
||||
return clinicRequest({ url: `${PREFIX}-order/reconciliation-detail`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function refundOrder(data) {
|
||||
return clinicRequest({ url: `${PREFIX}-order/refund`, method: 'POST', data });
|
||||
}
|
||||
|
||||
@@ -412,6 +412,10 @@
|
||||
"path": "withdrawal/settlement/detail",
|
||||
"style": { "navigationBarTitleText": "结算明细", "navigationStyle": "custom" }
|
||||
},
|
||||
{
|
||||
"path": "withdrawal/account-change/index",
|
||||
"style": { "navigationBarTitleText": "资金变动", "navigationStyle": "custom" }
|
||||
},
|
||||
{
|
||||
"path": "withdrawal/apply",
|
||||
"style": { "navigationBarTitleText": "申请提现", "navigationStyle": "custom" }
|
||||
@@ -436,6 +440,10 @@
|
||||
"path": "order/detail",
|
||||
"style": { "navigationBarTitleText": "订单详情", "navigationStyle": "custom" }
|
||||
},
|
||||
{
|
||||
"path": "order/trace/index",
|
||||
"style": { "navigationBarTitleText": "订单追溯", "navigationStyle": "custom" }
|
||||
},
|
||||
{
|
||||
"path": "warehouse/index",
|
||||
"style": { "navigationBarTitleText": "仓库管理", "navigationStyle": "custom" }
|
||||
|
||||
@@ -56,15 +56,22 @@ export function mapWithdrawalRow(item) {
|
||||
/** 结算记录列表行(yii_ledger_log) */
|
||||
export function mapSettlementRow(item) {
|
||||
if (!item) return {}
|
||||
const isIncrease = item.merged
|
||||
? (item.type_txt === '增加' || Number(item.amount) >= 0)
|
||||
: item.type === 1
|
||||
return {
|
||||
id: item.id,
|
||||
orderNo: item.order_no || (item.order_id ? '订单#' + item.order_id : '-'),
|
||||
orderTypeText: item.order_type_txt || '',
|
||||
feeTypeText: item.fee_type_txt || '-',
|
||||
amountText: formatMoney(item.amount),
|
||||
amountClass: ledgerTypeClass(item),
|
||||
amountClass: isIncrease ? 'text-success' : 'text-danger',
|
||||
typeText: item.type_txt || '-',
|
||||
content: item.content || '',
|
||||
createdAt: formatDateTime(item.created_at),
|
||||
merged: !!item.merged,
|
||||
orderId: item.order_id,
|
||||
orderType: item.order_type,
|
||||
_raw: item,
|
||||
}
|
||||
}
|
||||
@@ -445,6 +452,26 @@ export function mapReconciliationStoreRow(item) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 订单追溯对账汇总(summary 字段,对齐 mapReconciliationStoreRow 结构) */
|
||||
export function mapReconciliationOrderSummary(summary) {
|
||||
if (!summary) return null
|
||||
return {
|
||||
totalSales: formatMoney(summary.total_sales_price),
|
||||
totalSupply: formatMoney(summary.total_supply_price),
|
||||
registrationPrice: formatMoney(summary.registration_price),
|
||||
registerOrderNo: summary.register_order_no || '',
|
||||
herbal: mapReconciliationCategory(summary, 'herbal'),
|
||||
medicine: mapReconciliationCategory(summary, 'medicine'),
|
||||
servicePackage: mapReconciliationCategory(summary, 'service_package'),
|
||||
otherFees: {
|
||||
express: formatMoney(summary.express_price),
|
||||
process: formatMoney(summary.process_price),
|
||||
treatment: formatMoney(summary.treatment_price),
|
||||
},
|
||||
_raw: summary,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapReconciliationDrugRow(item) {
|
||||
if (!item) return {}
|
||||
return {
|
||||
|
||||
@@ -122,11 +122,10 @@
|
||||
:show="true"
|
||||
/>
|
||||
|
||||
<!-- 底部悬浮操作栏:移除确认发货,全宽查看处方 -->
|
||||
<view class="actions-bar" v-if="info.id && (canRefund || detailExtra.canViewPrescription)">
|
||||
<!-- 退款按钮为次要操作 -->
|
||||
<!-- 底部悬浮操作栏 -->
|
||||
<view class="actions-bar" v-if="info.id">
|
||||
<button class="btn-outline" @click="goTrace">订单追溯</button>
|
||||
<button v-if="canRefund" class="btn-warn" @click="refund">申请退款</button>
|
||||
<!-- 查看处方按钮为主操作,利用 flex: 1 自动铺满剩余宽度 -->
|
||||
<button v-if="detailExtra.canViewPrescription" class="btn-primary btn-full" @click="openPrescription">查看处方</button>
|
||||
</view>
|
||||
|
||||
@@ -276,6 +275,11 @@ export default {
|
||||
if (!pid) return
|
||||
this.$refs.prescriptionPopup.open(pid)
|
||||
},
|
||||
goTrace() {
|
||||
uni.navigateTo({
|
||||
url: `/subPackages/sub_clinic_admin/order/trace/index?order_id=${this.id}&scene=product`,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -547,6 +551,11 @@ $color-danger: #F53F3F;
|
||||
background: $theme-primary;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-outline {
|
||||
background: #fff;
|
||||
color: $theme-primary;
|
||||
border: 1rpx solid $theme-primary;
|
||||
}
|
||||
|
||||
/* 次要操作(退款)给固定宽度 */
|
||||
.btn-warn {
|
||||
|
||||
482
subPackages/sub_clinic_admin/order/trace/index.vue
Normal file
482
subPackages/sub_clinic_admin/order/trace/index.vue
Normal file
@@ -0,0 +1,482 @@
|
||||
<template>
|
||||
<clinic-page-layout title="订单追溯" :show-back="true">
|
||||
<view class="trace-container">
|
||||
<view v-if="loading" class="loading-tip">加载中...</view>
|
||||
|
||||
<template v-else-if="traceData">
|
||||
<!-- 摘要 -->
|
||||
<view class="summary-card">
|
||||
<view class="row"><text class="label">商品订单</text><text class="value">{{ summary.product_order_no || '—' }}</text></view>
|
||||
<view class="row" v-if="summary.register_order_no"><text class="label">挂号订单</text><text class="value">{{ summary.register_order_no }}</text></view>
|
||||
<view class="row" v-if="summary.prescription_no"><text class="label">处方号</text><text class="value">{{ summary.prescription_no }}</text></view>
|
||||
<view class="row"><text class="label">结算状态</text><text class="value">{{ summary.is_settled === 1 ? '已结算' : '未结算' }}</text></view>
|
||||
</view>
|
||||
|
||||
<!-- 主 Tab -->
|
||||
<view class="main-tabs">
|
||||
<view
|
||||
v-for="tab in mainTabs"
|
||||
:key="tab.key"
|
||||
class="main-tab"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@click="switchMainTab(tab.key)"
|
||||
>{{ tab.label }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间线 -->
|
||||
<view v-show="activeTab === 'timeline'" class="tab-panel">
|
||||
<view v-if="!timeline.length" class="empty">暂无时间线数据</view>
|
||||
<view v-for="(node, idx) in timeline" :key="node.key || idx" class="timeline-item">
|
||||
<view class="tl-dot" />
|
||||
<view class="tl-body">
|
||||
<view class="tl-title">{{ node.title }}</view>
|
||||
<view class="tl-meta">
|
||||
<text v-if="node.time">{{ node.time }}</text>
|
||||
<text v-if="node.status" class="tl-status">{{ node.status }}</text>
|
||||
</view>
|
||||
<view v-if="node.extra" class="tl-extra">{{ node.extra }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分账明细 -->
|
||||
<view v-show="activeTab === 'ledger'" class="tab-panel">
|
||||
<view v-if="ledgerLoading" class="loading-tip">加载分账...</view>
|
||||
<template v-else>
|
||||
<view v-if="hasRegisterLedger" class="sub-tabs">
|
||||
<view
|
||||
class="sub-tab"
|
||||
:class="{ active: ledgerSubTab === 'product' }"
|
||||
@click="switchLedgerSub('product')"
|
||||
>商品订单</view>
|
||||
<view
|
||||
class="sub-tab"
|
||||
:class="{ active: ledgerSubTab === 'register' }"
|
||||
@click="switchLedgerSub('register')"
|
||||
>挂号订单</view>
|
||||
</view>
|
||||
<view v-if="currentLedgerItems.length">
|
||||
<view v-for="row in currentLedgerItems" :key="row._wxKey" class="detail-card">
|
||||
<view class="row"><text class="label">费用类型</text><text class="value">{{ row.fee_type_txt || '-' }}</text></view>
|
||||
<view class="row" v-if="row.drug_name"><text class="label">药品</text><text class="value">{{ row.drug_name }}</text></view>
|
||||
<view class="row" v-if="row.specification"><text class="label">规格</text><text class="value">{{ row.specification }}</text></view>
|
||||
<view class="row"><text class="label">分账金额</text><text class="value amount">{{ formatMoney(row.money) }}</text></view>
|
||||
<view class="row"><text class="label">结算状态</text><text class="value">{{ row.status_txt || '-' }}</text></view>
|
||||
<view class="row" v-if="row.created_at"><text class="label">时间</text><text class="value">{{ row.created_at }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty">暂无分账明细</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- 对账明细 -->
|
||||
<view v-show="activeTab === 'reconciliation'" class="tab-panel">
|
||||
<view v-if="reconciliationLoading" class="loading-tip">加载对账...</view>
|
||||
<template v-else>
|
||||
<view v-if="reconciliationHint" class="hint-box">{{ reconciliationHint }}</view>
|
||||
<template v-if="reconciliationSummary">
|
||||
<view class="recon-summary-card">
|
||||
<view class="recon-head">
|
||||
<text class="recon-title">对账汇总</text>
|
||||
<text v-if="reconciliationSummary.registrationPrice !== '¥0.00'" class="mini-tag">
|
||||
挂号费 {{ reconciliationSummary.registrationPrice }}
|
||||
</text>
|
||||
</view>
|
||||
<view v-if="reconciliationSummary.registerOrderNo" class="recon-sub">
|
||||
挂号单号 {{ reconciliationSummary.registerOrderNo }}
|
||||
</view>
|
||||
<view class="hero-data">
|
||||
<view class="data-box">
|
||||
<text class="data-label">总销售额</text>
|
||||
<text class="data-val text-primary">{{ reconciliationSummary.totalSales }}</text>
|
||||
</view>
|
||||
<view class="data-divider" />
|
||||
<view class="data-box">
|
||||
<text class="data-label">总供货额</text>
|
||||
<text class="data-val">{{ reconciliationSummary.totalSupply }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="dashboard-grid">
|
||||
<view class="dashboard-island">
|
||||
<view class="island-head">
|
||||
<text class="island-title">草药</text>
|
||||
<text class="island-sales">销量 {{ reconciliationSummary.herbal.sales }}</text>
|
||||
</view>
|
||||
<view class="island-line"><text>销售</text><text>{{ reconciliationSummary.herbal.salesPrice }}</text></view>
|
||||
<view class="island-line"><text>供货</text><text>{{ reconciliationSummary.herbal.supplyPrice }}</text></view>
|
||||
</view>
|
||||
<view class="dashboard-island">
|
||||
<view class="island-head">
|
||||
<text class="island-title">西药</text>
|
||||
<text class="island-sales">销量 {{ reconciliationSummary.medicine.sales }}</text>
|
||||
</view>
|
||||
<view class="island-line"><text>销售</text><text>{{ reconciliationSummary.medicine.salesPrice }}</text></view>
|
||||
<view class="island-line"><text>供货</text><text>{{ reconciliationSummary.medicine.supplyPrice }}</text></view>
|
||||
</view>
|
||||
<view class="dashboard-island">
|
||||
<view class="island-head">
|
||||
<text class="island-title">服务包</text>
|
||||
<text class="island-sales">销量 {{ reconciliationSummary.servicePackage.sales }}</text>
|
||||
</view>
|
||||
<view class="island-line"><text>销售</text><text>{{ reconciliationSummary.servicePackage.salesPrice }}</text></view>
|
||||
<view class="island-line"><text>供货</text><text>{{ reconciliationSummary.servicePackage.supplyPrice }}</text></view>
|
||||
</view>
|
||||
<view class="dashboard-island">
|
||||
<view class="island-head">
|
||||
<text class="island-title">其他费用</text>
|
||||
</view>
|
||||
<view class="island-line"><text>诊疗费</text><text>{{ reconciliationSummary.otherFees.treatment }}</text></view>
|
||||
<view class="island-line"><text>加工费</text><text>{{ reconciliationSummary.otherFees.process }}</text></view>
|
||||
<view class="island-line"><text>快递费</text><text>{{ reconciliationSummary.otherFees.express }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<view v-if="reconciliationItems.length" class="recon-drug-section">
|
||||
<view class="section-title">药品明细</view>
|
||||
<view v-for="row in reconciliationItems" :key="row._wxKey" class="detail-card">
|
||||
<view class="row"><text class="label">药品</text><text class="value">{{ row.drug_name || row.drug_id || '-' }}</text></view>
|
||||
<view class="row"><text class="label">数量</text><text class="value">{{ row.number }}</text></view>
|
||||
<view class="row"><text class="label">销售价</text><text class="value">{{ formatMoney(row.total_price) }}</text></view>
|
||||
<view class="row"><text class="label">供货价</text><text class="value">{{ formatMoney(row.total_buy_price) }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="!reconciliationHint && !reconciliationSummary" class="empty">暂无对账明细</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view v-else-if="!loading" class="empty">加载失败,请返回重试</view>
|
||||
</view>
|
||||
</clinic-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClinicPageLayout from '../../components/clinic-page-layout.vue'
|
||||
import { formatMoney } from '../../common/format.js'
|
||||
import { mapReconciliationOrderSummary } from '../../common/display.js'
|
||||
import {
|
||||
getOrderTrace,
|
||||
getOrderLedgerDetail,
|
||||
getOrderReconciliationDetail,
|
||||
} from '@/api/clinicAdmin.js'
|
||||
import { withWxKey } from '@/utils/wxListKey.js'
|
||||
|
||||
export default {
|
||||
components: { ClinicPageLayout },
|
||||
data() {
|
||||
return {
|
||||
orderId: 0,
|
||||
scene: 'product',
|
||||
loading: true,
|
||||
traceData: null,
|
||||
activeTab: 'timeline',
|
||||
ledgerSubTab: 'product',
|
||||
ledgerProduct: null,
|
||||
ledgerRegister: null,
|
||||
ledgerLoading: false,
|
||||
reconciliationData: null,
|
||||
reconciliationLoading: false,
|
||||
loadedTabs: {},
|
||||
mainTabs: [
|
||||
{ key: 'timeline', label: '时间线' },
|
||||
{ key: 'ledger', label: '分账' },
|
||||
{ key: 'reconciliation', label: '对账' },
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
summary() {
|
||||
return this.traceData?.summary || {}
|
||||
},
|
||||
timeline() {
|
||||
return this.traceData?.timeline || []
|
||||
},
|
||||
links() {
|
||||
return this.traceData?.links || {}
|
||||
},
|
||||
hasRegisterLedger() {
|
||||
return !!(this.links.register_id && this.ledgerRegister !== null)
|
||||
},
|
||||
currentLedgerItems() {
|
||||
const data = this.ledgerSubTab === 'register' ? this.ledgerRegister : this.ledgerProduct
|
||||
const items = data?.items || []
|
||||
return withWxKey(items, 'id', 'traceLed')
|
||||
},
|
||||
reconciliationItems() {
|
||||
const items = this.reconciliationData?.items || []
|
||||
return withWxKey(items, 'id', 'traceRec')
|
||||
},
|
||||
reconciliationSummary() {
|
||||
return mapReconciliationOrderSummary(this.reconciliationData?.summary)
|
||||
},
|
||||
reconciliationHint() {
|
||||
return this.reconciliationData?.reconciliation_hint || this.summary.reconciliation_hint || ''
|
||||
},
|
||||
},
|
||||
onLoad(q) {
|
||||
this.orderId = Number(q.order_id || 0)
|
||||
this.scene = q.scene || 'product'
|
||||
this.loadTrace()
|
||||
},
|
||||
methods: {
|
||||
formatMoney,
|
||||
loadTrace() {
|
||||
if (this.orderId <= 0) {
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
getOrderTrace({ scene: this.scene, order_id: this.orderId }).then(w => {
|
||||
if (w.ok) {
|
||||
this.traceData = w.data
|
||||
} else {
|
||||
uni.showToast({ title: (w.res && w.res.message) || '加载失败', icon: 'none' })
|
||||
}
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
switchMainTab(key) {
|
||||
this.activeTab = key
|
||||
if (key === 'ledger' && !this.loadedTabs.ledger) {
|
||||
this.loadLedger()
|
||||
} else if (key === 'reconciliation' && !this.loadedTabs.reconciliation) {
|
||||
this.loadReconciliation()
|
||||
}
|
||||
},
|
||||
switchLedgerSub(key) {
|
||||
this.ledgerSubTab = key
|
||||
},
|
||||
loadLedger() {
|
||||
const productId = this.links.product_order_id
|
||||
const registerId = this.links.register_id
|
||||
if (!productId && !registerId) {
|
||||
this.loadedTabs.ledger = true
|
||||
return
|
||||
}
|
||||
this.ledgerLoading = true
|
||||
const reqs = []
|
||||
if (productId) {
|
||||
reqs.push(
|
||||
getOrderLedgerDetail({ order_id: productId, order_type: 1, scope: 'store' }).then(w => {
|
||||
this.ledgerProduct = w.ok ? w.data : null
|
||||
})
|
||||
)
|
||||
} else {
|
||||
this.ledgerProduct = null
|
||||
}
|
||||
if (registerId) {
|
||||
reqs.push(
|
||||
getOrderLedgerDetail({ order_id: registerId, order_type: 2, scope: 'store' }).then(w => {
|
||||
this.ledgerRegister = w.ok ? w.data : null
|
||||
})
|
||||
)
|
||||
} else {
|
||||
this.ledgerRegister = null
|
||||
}
|
||||
Promise.all(reqs).finally(() => {
|
||||
this.ledgerLoading = false
|
||||
this.loadedTabs.ledger = true
|
||||
})
|
||||
},
|
||||
loadReconciliation() {
|
||||
const productId = this.links.product_order_id || (this.scene === 'product' ? this.orderId : 0)
|
||||
if (!productId) {
|
||||
this.reconciliationData = { items: [], reconciliation_hint: '暂无关联商品订单' }
|
||||
this.loadedTabs.reconciliation = true
|
||||
return
|
||||
}
|
||||
this.reconciliationLoading = true
|
||||
getOrderReconciliationDetail({ product_order_id: productId }).then(w => {
|
||||
if (w.ok) {
|
||||
this.reconciliationData = w.data
|
||||
}
|
||||
}).finally(() => {
|
||||
this.reconciliationLoading = false
|
||||
this.loadedTabs.reconciliation = true
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.trace-container {
|
||||
padding: 24rpx;
|
||||
padding-bottom: 48rpx;
|
||||
}
|
||||
.loading-tip, .empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
padding: 48rpx 0;
|
||||
}
|
||||
.summary-card, .detail-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.summary-card .row, .detail-card .row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 26rpx;
|
||||
padding: 8rpx 0;
|
||||
color: #333;
|
||||
}
|
||||
.label { color: #888; flex-shrink: 0; margin-right: 16rpx; }
|
||||
.value { text-align: right; word-break: break-all; }
|
||||
.amount { color: #1677ff; font-weight: 600; }
|
||||
.main-tabs, .sub-tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.main-tab, .sub-tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
.main-tab.active, .sub-tab.active {
|
||||
color: #1677ff;
|
||||
font-weight: 600;
|
||||
background: #e6f4ff;
|
||||
}
|
||||
.tab-panel { min-height: 200rpx; }
|
||||
.timeline-item {
|
||||
display: flex;
|
||||
margin-bottom: 24rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
.tl-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background: #1677ff;
|
||||
margin-top: 10rpx;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tl-body { flex: 1; background: #fff; border-radius: 12rpx; padding: 20rpx; }
|
||||
.tl-title { font-size: 28rpx; font-weight: 600; color: #333; }
|
||||
.tl-meta { font-size: 24rpx; color: #999; margin-top: 8rpx; }
|
||||
.tl-status { margin-left: 12rpx; color: #1677ff; }
|
||||
.tl-extra { font-size: 24rpx; color: #666; margin-top: 8rpx; }
|
||||
.hint-box {
|
||||
background: #fff7e6;
|
||||
color: #d48806;
|
||||
font-size: 26rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.sum-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
.recon-summary-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.recon-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.recon-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.recon-sub {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.mini-tag {
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
background: rgba(106, 205, 187, 0.1);
|
||||
color: #6ACDBB;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.hero-data {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #F8FBFB;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx 0;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.data-box {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.data-label {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.data-val {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.text-primary { color: #1677ff; }
|
||||
.data-divider {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background: #eee;
|
||||
}
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
.dashboard-island {
|
||||
background: #F8FBFB;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
.island-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.island-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.island-sales {
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
}
|
||||
.island-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
padding: 4rpx 0;
|
||||
}
|
||||
.recon-drug-section { margin-top: 8rpx; }
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
</style>
|
||||
215
subPackages/sub_clinic_admin/withdrawal/account-change/index.vue
Normal file
215
subPackages/sub_clinic_admin/withdrawal/account-change/index.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<clinic-page-layout title="资金变动" :show-back="true">
|
||||
<view class="page-container">
|
||||
<view class="filter-bar">
|
||||
<picker :range="sourceTypeLabels" @change="onSourceTypeChange">
|
||||
<view class="picker-value">{{ currentSourceLabel }}</view>
|
||||
</picker>
|
||||
<input
|
||||
v-model="orderNo"
|
||||
class="search-input"
|
||||
placeholder="订单号"
|
||||
confirm-type="search"
|
||||
@confirm="reload"
|
||||
/>
|
||||
<view class="search-btn" @click="reload">搜索</view>
|
||||
</view>
|
||||
|
||||
<view class="list-wrap">
|
||||
<view v-for="item in list" :key="item.id" class="record-card">
|
||||
<view class="row head">
|
||||
<text class="source">{{ item.source_type_txt || '-' }}</text>
|
||||
<text class="change" :class="Number(item.change_amount) >= 0 ? 'inc' : 'dec'">
|
||||
{{ item.change_amount }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="row"><text class="label">变更字段</text><text class="value">{{ item.field_name_txt || item.field_name }}</text></view>
|
||||
<view class="row"><text class="label">变动前</text><text class="value">{{ item.before_amount }}</text></view>
|
||||
<view class="row"><text class="label">变动后</text><text class="value">{{ item.after_amount }}</text></view>
|
||||
<view class="row" v-if="item.order_no"><text class="label">订单号</text><text class="value">{{ item.order_no }}</text></view>
|
||||
<view class="row"><text class="label">时间</text><text class="value">{{ item.created_at }}</text></view>
|
||||
<view class="row" v-if="item.remark"><text class="label">备注</text><text class="value">{{ item.remark }}</text></view>
|
||||
<view
|
||||
v-if="Number(item.order_type) === 1 && item.order_id"
|
||||
class="order-link"
|
||||
@click="goOrderDetail(item)"
|
||||
>查看订单详情</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!loading && list.length === 0" class="empty-state">暂无资金变动记录</view>
|
||||
</view>
|
||||
</clinic-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClinicPageLayout from '../../components/clinic-page-layout.vue'
|
||||
import { getAccountChangeLogList } from '@/api/clinicAdmin.js'
|
||||
|
||||
export default {
|
||||
components: { ClinicPageLayout },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
page: 1,
|
||||
loading: false,
|
||||
orderNo: '',
|
||||
sourceType: '',
|
||||
sourceTypeOptions: [
|
||||
{ label: '全部来源', value: '' },
|
||||
{ label: '分账', value: 'settle' },
|
||||
{ label: '提现', value: 'withdraw' },
|
||||
{ label: '退款', value: 'refund' },
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sourceTypeLabels() {
|
||||
return this.sourceTypeOptions.map(o => o.label)
|
||||
},
|
||||
currentSourceLabel() {
|
||||
const hit = this.sourceTypeOptions.find(o => o.value === this.sourceType)
|
||||
return hit ? hit.label : '全部来源'
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
this.reload()
|
||||
},
|
||||
onReachBottom() {
|
||||
this.load(this.page + 1, true)
|
||||
},
|
||||
methods: {
|
||||
onSourceTypeChange(e) {
|
||||
const idx = Number(e.detail.value || 0)
|
||||
this.sourceType = this.sourceTypeOptions[idx].value
|
||||
this.reload()
|
||||
},
|
||||
reload() {
|
||||
this.load(1, false)
|
||||
},
|
||||
load(page, append) {
|
||||
this.loading = true
|
||||
const params = { page, pageSize: 15 }
|
||||
if (this.sourceType) params.source_type = this.sourceType
|
||||
if (this.orderNo.trim()) params.order_no = this.orderNo.trim()
|
||||
getAccountChangeLogList(params).then(w => {
|
||||
const items = (w.data && w.data.items) || []
|
||||
this.list = append ? this.list.concat(items) : items
|
||||
this.page = page
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
goOrderDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/sub_clinic_admin/order/detail?id=' + item.order_id,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$color-bg-base: #F5F7F8;
|
||||
$color-text-main: #1D2129;
|
||||
$color-text-muted: #86909C;
|
||||
$theme-primary: #6acdbb;
|
||||
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: $color-bg-base;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 24rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.picker-value,
|
||||
.search-input {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.picker-value {
|
||||
min-width: 160rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: $theme-primary;
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 26rpx;
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
.row.head {
|
||||
border-bottom: 1rpx dashed #E5E6EB;
|
||||
margin-bottom: 12rpx;
|
||||
padding-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.source {
|
||||
font-weight: 600;
|
||||
color: $color-text-main;
|
||||
}
|
||||
|
||||
.change {
|
||||
font-weight: 700;
|
||||
font-family: DIN Alternate, monospace, sans-serif;
|
||||
}
|
||||
|
||||
.inc { color: $theme-primary; }
|
||||
.dec { color: #F53F3F; }
|
||||
|
||||
.label {
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: $color-text-main;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
margin-left: 16rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.order-link {
|
||||
margin-top: 16rpx;
|
||||
text-align: right;
|
||||
color: $theme-primary;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
color: $color-text-muted;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
</style>
|
||||
@@ -91,12 +91,13 @@ export default {
|
||||
},
|
||||
balanceItems: [
|
||||
{ key: 'balance', label: '可提现余额 (元)', display: '0.00' },
|
||||
{ key: 'frozen', label: '冻结金额 (元)', display: '0.00' }
|
||||
{ key: 'reviewing', label: '审核中金额 (元)', display: '0.00' },
|
||||
{ key: 'pending', label: '待结算收益 (元)', display: '0.00' },
|
||||
],
|
||||
actions: [
|
||||
{ key: 'apply', label: '申请提现' },
|
||||
// { key: 'edit', label: '修改账户' },
|
||||
{ key: 'settlement', label: '结算记录' }
|
||||
{ key: 'settlement', label: '结算记录' },
|
||||
{ key: 'accountChange', label: '资金变动' },
|
||||
],
|
||||
withdrawalList: [],
|
||||
page: 1,
|
||||
@@ -129,6 +130,8 @@ export default {
|
||||
uni.navigateTo({ url: '/subPackages/sub_clinic_admin/withdrawal/card-edit' });
|
||||
} else if (key === 'settlement') {
|
||||
uni.navigateTo({ url: '/subPackages/sub_clinic_admin/withdrawal/settlement/index' });
|
||||
} else if (key === 'accountChange') {
|
||||
uni.navigateTo({ url: '/subPackages/sub_clinic_admin/withdrawal/account-change/index' });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -151,9 +154,16 @@ export default {
|
||||
loadBalance() {
|
||||
getClinicAdminBalance().then(w => {
|
||||
if (w.ok && w.data) {
|
||||
// 格式化金额并去除 ¥ 符号以便大字号展示
|
||||
this.balanceItems[0].display = formatMoney(w.data.balance || 0).replace('¥', '');
|
||||
this.balanceItems[1].display = formatMoney(w.data.frozen_balance || 0).replace('¥', '');
|
||||
const fmt = (v) => formatMoney(v || 0).replace('¥', '')
|
||||
const map = {
|
||||
balance: fmt(w.data.balance),
|
||||
reviewing: fmt(w.data.withdrawn_frozen),
|
||||
pending: fmt(w.data.pending_earnings),
|
||||
}
|
||||
this.balanceItems = this.balanceItems.map(item => ({
|
||||
...item,
|
||||
display: map[item.key] ?? '0.00',
|
||||
}))
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -265,7 +275,7 @@ $color-text-muted: #86909C;
|
||||
/* 统计网格 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
ledgerLogId: 0,
|
||||
orderId: 0,
|
||||
orderType: 0,
|
||||
detail: { items: [], sum_money: '0' },
|
||||
ledgerLogSummary: null,
|
||||
ledgerItems: [],
|
||||
@@ -55,19 +57,22 @@ export default {
|
||||
},
|
||||
onLoad(q) {
|
||||
this.ledgerLogId = Number(q.ledger_log_id || 0)
|
||||
this.orderId = Number(q.order_id || 0)
|
||||
this.orderType = Number(q.order_type || 0)
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
formatMoney,
|
||||
load() {
|
||||
this.loading = true
|
||||
getLedgerDetail({ ledger_log_id: this.ledgerLogId }).then(w => {
|
||||
const params = this.orderId > 0 && this.orderType > 0
|
||||
? { order_id: this.orderId, order_type: this.orderType }
|
||||
: { ledger_log_id: this.ledgerLogId }
|
||||
getLedgerDetail(params).then(w => {
|
||||
if (w.ok) {
|
||||
this.detail = w.data || {}
|
||||
const log = this.detail.ledger_log
|
||||
if (log) {
|
||||
this.ledgerLogSummary = log
|
||||
}
|
||||
this.ledgerLogSummary = log || null
|
||||
this.ledgerItems = withWxKey(this.detail.items || [], 'id', 'led')
|
||||
}
|
||||
}).finally(() => { this.loading = false })
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
<template>
|
||||
<clinic-page-layout title="结算记录" :show-back="true">
|
||||
<view class="settlement-container">
|
||||
<view class="filter-bar">
|
||||
<input
|
||||
v-model="orderNo"
|
||||
class="search-input"
|
||||
placeholder="搜索订单号"
|
||||
confirm-type="search"
|
||||
@confirm="reload"
|
||||
/>
|
||||
<view class="search-btn" @click="reload">搜索</view>
|
||||
</view>
|
||||
|
||||
<view class="merge-bar">
|
||||
<text class="merge-label">同订单合并</text>
|
||||
<switch :checked="mergeByOrder" color="#6acdbb" @change="onMergeChange" />
|
||||
</view>
|
||||
|
||||
<view class="list-wrap">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="premium-card"
|
||||
hover-class="card-hover"
|
||||
@click="goDetail(item)"
|
||||
>
|
||||
<view class="card-head">
|
||||
<view class="order-no">
|
||||
@@ -21,6 +34,10 @@
|
||||
</view>
|
||||
|
||||
<view class="card-body">
|
||||
<view class="info-row" v-if="rowDisplay(item).orderTypeText">
|
||||
<text class="label">订单类型</text>
|
||||
<text class="value">{{ rowDisplay(item).orderTypeText }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">费用类型</text>
|
||||
<text class="value">{{ rowDisplay(item).feeTypeText }}</text>
|
||||
@@ -34,6 +51,15 @@
|
||||
<text class="value">{{ rowDisplay(item).content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card-actions">
|
||||
<text class="action-link" @click.stop="goSettlementDetail(item)">结算明细</text>
|
||||
<text
|
||||
v-if="Number(item.order_type) === 1"
|
||||
class="action-link primary"
|
||||
@click.stop="goOrderDetail(item)"
|
||||
>订单详情</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -56,6 +82,8 @@ export default {
|
||||
list: [],
|
||||
page: 1,
|
||||
loading: false,
|
||||
mergeByOrder: true,
|
||||
orderNo: '',
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
@@ -68,22 +96,51 @@ export default {
|
||||
rowDisplay(item) {
|
||||
return mapSettlementRow(item)
|
||||
},
|
||||
onMergeChange(e) {
|
||||
this.mergeByOrder = !!e.detail.value
|
||||
this.reload()
|
||||
},
|
||||
reload() {
|
||||
this.load(1, false)
|
||||
},
|
||||
load(page, append) {
|
||||
this.loading = true
|
||||
getLedgerLogList({ page, pageSize: 15 }).then(w => {
|
||||
const params = {
|
||||
page,
|
||||
pageSize: 15,
|
||||
merge_by_order: this.mergeByOrder ? 1 : 0,
|
||||
exclude_zero_amount: 1,
|
||||
}
|
||||
if (this.orderNo.trim()) {
|
||||
params.order_no = this.orderNo.trim()
|
||||
}
|
||||
getLedgerLogList(params).then(w => {
|
||||
const items = (w.data && w.data.items) || []
|
||||
this.list = append ? this.list.concat(items) : items
|
||||
this.page = page
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
goDetail(item) {
|
||||
goSettlementDetail(item) {
|
||||
if (item.merged) {
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/sub_clinic_admin/withdrawal/settlement/detail?order_id='
|
||||
+ item.order_id + '&order_type=' + item.order_type,
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/sub_clinic_admin/withdrawal/settlement/detail?ledger_log_id=' + item.id,
|
||||
})
|
||||
},
|
||||
goOrderDetail(item) {
|
||||
if (Number(item.order_type) !== 1 || !item.order_id) {
|
||||
uni.showToast({ title: '暂不支持查看该订单详情', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/subPackages/sub_clinic_admin/order/detail?id=' + item.order_id,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -93,6 +150,7 @@ $color-bg-base: #F5F7F8;
|
||||
$color-text-main: #1D2129;
|
||||
$color-text-body: #4E5969;
|
||||
$color-text-muted: #86909C;
|
||||
$theme-primary: #6acdbb;
|
||||
|
||||
.settlement-container {
|
||||
background-color: $color-bg-base;
|
||||
@@ -100,6 +158,45 @@ $color-text-muted: #86909C;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: $theme-primary;
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.merge-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.merge-label {
|
||||
font-size: 28rpx;
|
||||
color: $color-text-main;
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -111,12 +208,6 @@ $color-text-muted: #86909C;
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.card-hover {
|
||||
background-color: #FAFAFA;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.card-head {
|
||||
@@ -155,10 +246,8 @@ $color-text-muted: #86909C;
|
||||
font-family: DIN Alternate, monospace, sans-serif;
|
||||
}
|
||||
|
||||
/* 兼容你原有的类名逻辑 */
|
||||
.text-danger { color: #F53F3F !important; }
|
||||
.text-success { color: #6acdbb !important; }
|
||||
.text-warning { color: #F5A623 !important; }
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
@@ -185,10 +274,29 @@ $color-text-muted: #86909C;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 32rpx;
|
||||
margin-top: 24rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #F2F3F5;
|
||||
}
|
||||
|
||||
.action-link {
|
||||
font-size: 26rpx;
|
||||
color: $color-text-muted;
|
||||
}
|
||||
|
||||
.action-link.primary {
|
||||
color: $theme-primary;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
color: $color-text-muted;
|
||||
padding: 120rpx 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user