Commit 6c32142688766a4a25253b3d1e07a6530bb2907d

Authored by “wangming”
1 parent 051dc19f

Refactor build script and enhance sales order handling

- Updated the build script in package.json to remove unnecessary NODE_OPTIONS.
- Modified index.vue and Form.vue to manage the visibility of inventory fields based on the source of the order.
- Added logic to ensure sales orders from the backend are marked correctly and can be edited based on their status.
- Introduced new methods to handle order source and approval logic in the sales order forms.
- Updated the API service to reflect changes in order status and source handling for sales orders.
Antis.Erp.Plat/antis-ncc-admin/package.json
... ... @@ -7,7 +7,7 @@
7 7 "scripts": {
8 8 "dev": "vue-cli-service serve --open",
9 9 "dev:3000": "vue-cli-service serve --port 3000",
10   - "build": "cross-env NODE_ENV=production NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build",
  10 + "build": "cross-env NODE_ENV=production vue-cli-service build",
11 11 "build:staging": "cross-env NODE_ENV=production vue-cli-service build --mode staging",
12 12 "build:javaBootDev": "cross-env NODE_ENV=production vue-cli-service build --mode javaBootDev",
13 13 "build:javaBootTest": "cross-env NODE_ENV=production vue-cli-service build --mode javaBootTest",
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtSp/index.vue
... ... @@ -45,7 +45,7 @@
45 45 <num-range v-model="query.zg"></num-range>
46 46 </el-form-item>
47 47 </el-col>
48   - <el-col :span="6">
  48 + <el-col :span="6" v-if="false">
49 49 <el-form-item label="库存">
50 50 <num-range v-model="query.kc"></num-range>
51 51 </el-form-item>
... ... @@ -113,7 +113,7 @@
113 113 </el-table-column>
114 114 <el-table-column prop="lsj" label="零售价" align="left" />
115 115 <el-table-column prop="zg" label="限购" align="left" />
116   - <el-table-column prop="kc" label="库存" align="left" />
  116 + <el-table-column prop="kc" label="库存" align="left" v-if="false" />
117 117 <el-table-column prop="shgz" label="售后规则" align="left" />
118 118 <el-table-column prop="yfgz" label="运费规则" align="left" />
119 119 <el-table-column label="销售渠道" prop="xsqd" align="left">
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/Form.vue
... ... @@ -329,6 +329,7 @@
329 329 djlx:undefined,
330 330 djzt: '', // 单据状态
331 331 cbje: undefined, // 主表出库成本合计(详情/已保存单据由后端返回)
  332 + ly: '后台', // 单据来源:后台录入为「后台」;收银台为「门店」,后台不可改
332 333 },
333 334 rules: {
334 335 },
... ... @@ -393,6 +394,12 @@
393 394 mounted() {
394 395 },
395 396 methods: {
  397 + /** ERP 新建/保存销售出库单时固定为「后台」,避免与收银台「门店」混淆 */
  398 + ensureBackendSalesSource() {
  399 + if (this.dataForm.djlx === '销售出库单') {
  400 + this.dataForm.ly = this.dataForm.ly || '后台'
  401 + }
  402 + },
396 403 // 获取商品信息(只通过API获取序列号类型)
397 404 async getProductInfo(productId) {
398 405 const key = String(productId);
... ... @@ -720,6 +727,8 @@
720 727 })
721 728 }
722 729 else{
  730 + _this.dataForm.ly = '后台';
  731 + _this.dataForm.djlx = '销售出库单';
723 732 // 新建时加载默认选项设置
724 733 request({ url: '/api/Extend/WtMrsz', method: 'get' }).then(res => {
725 734 if (res.data) {
... ... @@ -739,6 +748,7 @@
739 748 async saveDraft() {
740 749 // 确保单据类型字段赋值
741 750 this.dataForm.djlx = '销售出库单';
  751 + this.ensureBackendSalesSource();
742 752 this.$refs['elForm'].validate(async (valid) => {
743 753 if (valid) {
744 754 try {
... ... @@ -807,6 +817,7 @@
807 817 this.dataForm.djlx = '销售出库单';
808 818 // 确保单据状态为待审核
809 819 this.dataForm.djzt = '待审核';
  820 + this.ensureBackendSalesSource();
810 821 // 检查是否有明细数据
811 822 if (!this.dataForm.wtXsckdMxList || this.dataForm.wtXsckdMxList.length === 0) {
812 823 console.log('没有明细数据,跳过序列号验证');
... ... @@ -916,6 +927,7 @@
916 927 if (valid) {
917 928 console.log('表单验证通过,继续提交...');
918 929 try {
  930 + this.ensureBackendSalesSource();
919 931 // 格式化所有明细行的金额字段为 2 位小数
920 932 this.dataForm.wtXsckdMxList.forEach(row => {
921 933 if (row.dj) {
... ... @@ -996,6 +1008,7 @@
996 1008 if (valid) {
997 1009 console.log('表单验证通过,继续提交...');
998 1010 try {
  1011 + this.ensureBackendSalesSource();
999 1012 if (!this.dataForm.id) {
1000 1013 const res = await request({
1001 1014 url: `/api/Extend/WtXsckd`,
... ... @@ -1574,13 +1587,13 @@
1574 1587 },
1575 1588 getSpOptionLabel(item) {
1576 1589 const base = ((item.spbm || '') + ' ' + (item.F_Spmc || '')).trim()
1577   - const c = this.productCostPreviewMap[item.F_Id]
1578   - if (c != null && c !== '' && !isNaN(parseFloat(c))) {
1579   - return base + ' (成本¥' + parseFloat(c).toFixed(2) + ')'
1580   - }
1581   - if (!this.costContextCk()) {
1582   - return base + ' (选仓库后显示成本)'
1583   - }
  1590 + // const c = this.productCostPreviewMap[item.F_Id]
  1591 + // if (c != null && c !== '' && !isNaN(parseFloat(c))) {
  1592 + // return base + ' (成本¥' + parseFloat(c).toFixed(2) + ')'
  1593 + // }
  1594 + // if (!this.costContextCk()) {
  1595 + // return base + ' (选仓库后显示成本)'
  1596 + // }
1584 1597 return base
1585 1598 },
1586 1599 async loadProductCostPreviewMap() {
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/detail-view.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + title="销售出库单详情"
  4 + :close-on-click-modal="false"
  5 + :visible.sync="visible"
  6 + class="NCC-dialog NCC-dialog_center xsckd-detail-dialog"
  7 + lock-scroll
  8 + width="1200px"
  9 + top="6vh"
  10 + @close="handleClose"
  11 + >
  12 + <div v-loading="loading" class="xsckd-detail-wrap">
  13 + <template v-if="detail">
  14 + <div class="detail-top">
  15 + <div class="detail-top__id">
  16 + <i class="el-icon-document detail-top__icon detail-top__icon--primary" />
  17 + <span class="cell-nowrap">{{ cellText(detail.id) }}</span>
  18 + </div>
  19 + <el-tag :type="djztTagType" size="medium" effect="plain">{{ djztLabel }}</el-tag>
  20 + </div>
  21 +
  22 + <div class="collect-stat-card">
  23 + <div class="collect-stat-card__inner">
  24 + <i class="el-icon-coin collect-stat-card__coin" />
  25 + <div class="collect-stat-card__text">
  26 + <div class="collect-stat-card__label">收款金额(与列表一致)</div>
  27 + <div class="collect-stat-card__value cell-nowrap">{{ displayCollectionAmount }}</div>
  28 + </div>
  29 + </div>
  30 + <p class="collect-stat-card__hint">
  31 + <i class="el-icon-info" />
  32 + 有原价时收款=原价−优惠;组合支付以 skmx 汇总为准
  33 + </p>
  34 + </div>
  35 +
  36 + <el-descriptions
  37 + title="基本信息"
  38 + :column="2"
  39 + border
  40 + size="small"
  41 + class="detail-descriptions"
  42 + :label-style="{ width: '108px' }"
  43 + >
  44 + <el-descriptions-item label="单据日期">
  45 + <span class="cell-nowrap">
  46 + <i class="el-icon-date desc-icon desc-icon--primary" />
  47 + {{ formatDjrq(detail.djrq) }}
  48 + </span>
  49 + </el-descriptions-item>
  50 + <el-descriptions-item label="单据类型">
  51 + <span class="cell-nowrap">
  52 + <i class="el-icon-tickets desc-icon desc-icon--info" />
  53 + {{ cellText(detail.djlx || '销售出库单') }}
  54 + </span>
  55 + </el-descriptions-item>
  56 + <el-descriptions-item label="单据来源">
  57 + <span class="cell-nowrap">
  58 + <i class="el-icon-s-promotion desc-icon desc-icon--muted" />
  59 + {{ formatLy(detail.ly) }}
  60 + </span>
  61 + </el-descriptions-item>
  62 + <el-descriptions-item label="出库仓库">
  63 + <span class="cell-nowrap">
  64 + <i class="el-icon-s-home desc-icon desc-icon--muted" />
  65 + {{ labelFromOptions(detail.cjckId || detail.cjck, cjckOptions) }}
  66 + </span>
  67 + </el-descriptions-item>
  68 + <el-descriptions-item label="经手人">
  69 + <span class="cell-nowrap">
  70 + <i class="el-icon-user desc-icon desc-icon--primary" />
  71 + {{ cellText(detail.jsr) }}
  72 + </span>
  73 + </el-descriptions-item>
  74 + <el-descriptions-item label="会员手机">
  75 + <span class="cell-nowrap">
  76 + <i class="el-icon-phone desc-icon desc-icon--info" />
  77 + {{ cellText(detail.hysjh) }}
  78 + </span>
  79 + </el-descriptions-item>
  80 + </el-descriptions>
  81 +
  82 + <div class="detail-section-head">
  83 + <i class="el-icon-s-order detail-section-head__icon" />
  84 + <span>出库明细</span>
  85 + <span class="detail-section-head__sub">(列表不换行展示)</span>
  86 + </div>
  87 + <el-table
  88 + :data="detail.wtXsckdMxList || []"
  89 + size="small"
  90 + border
  91 + stripe
  92 + show-summary
  93 + :summary-method="getSummaries"
  94 + class="detail-mx-table"
  95 + :empty-text="'无明细'"
  96 + >
  97 + <el-table-column type="index" width="52" label="#" align="center" />
  98 + <el-table-column label="出库仓库" min-width="118" show-overflow-tooltip>
  99 + <template slot-scope="scope">
  100 + <span class="cell-nowrap mx-cell">
  101 + <i class="el-icon-location-outline mx-cell__icon mx-cell__icon--muted" />
  102 + {{ labelFromOptions(scope.row.ckck, cjckOptions) }}
  103 + </span>
  104 + </template>
  105 + </el-table-column>
  106 + <el-table-column label="商品" min-width="160" show-overflow-tooltip>
  107 + <template slot-scope="scope">
  108 + <span class="cell-nowrap mx-cell">
  109 + <i class="el-icon-goods mx-cell__icon mx-cell__icon--primary" />
  110 + {{ cellText(scope.row.spmc) }}
  111 + </span>
  112 + </template>
  113 + </el-table-column>
  114 + <el-table-column prop="sl" label="数量" width="72" align="right">
  115 + <template slot-scope="scope">
  116 + <span class="cell-nowrap">{{ cellText(scope.row.sl) }}</span>
  117 + </template>
  118 + </el-table-column>
  119 + <el-table-column prop="dj" label="单价" width="96" align="right">
  120 + <template slot-scope="scope">
  121 + <span class="cell-nowrap">{{ formatMoneyCol(scope.row.dj) }}</span>
  122 + </template>
  123 + </el-table-column>
  124 + <el-table-column prop="je" label="金额" width="96" align="right">
  125 + <template slot-scope="scope">
  126 + <span class="cell-nowrap mx-cell__money">{{ formatMoneyCol(scope.row.je) }}</span>
  127 + </template>
  128 + </el-table-column>
  129 + <el-table-column prop="cbje" label="成本" width="96" align="right">
  130 + <template slot-scope="scope">
  131 + <span class="cell-nowrap">{{ formatMoneyCol(scope.row.cbje) }}</span>
  132 + </template>
  133 + </el-table-column>
  134 + <el-table-column label="序列号" min-width="168">
  135 + <template slot-scope="scope">
  136 + <div v-if="scope.row.selectedSerialNumbers && scope.row.selectedSerialNumbers.length" class="sn-tags">
  137 + <el-tag
  138 + v-for="(sn, idx) in scope.row.selectedSerialNumbers"
  139 + :key="idx"
  140 + size="mini"
  141 + type="warning"
  142 + class="sn-tag"
  143 + >{{ sn }}</el-tag>
  144 + </div>
  145 + <span v-else class="text-muted">无</span>
  146 + </template>
  147 + </el-table-column>
  148 + </el-table>
  149 +
  150 + <el-descriptions
  151 + title="收款与审核"
  152 + :column="2"
  153 + border
  154 + size="small"
  155 + class="detail-descriptions detail-descriptions--footer"
  156 + :label-style="{ width: '108px' }"
  157 + >
  158 + <el-descriptions-item label="收款账户">
  159 + <span class="cell-nowrap">
  160 + <i class="el-icon-bank-card desc-icon desc-icon--success" />
  161 + {{ labelFromOptions(detail.skzh, skzhOptions) }}
  162 + </span>
  163 + </el-descriptions-item>
  164 + <el-descriptions-item label="原价">
  165 + <span class="cell-nowrap">
  166 + <i class="el-icon-money desc-icon desc-icon--primary" />
  167 + {{ displayOriginalPrice }}
  168 + </span>
  169 + </el-descriptions-item>
  170 + <el-descriptions-item label="优惠金额">
  171 + <span class="cell-nowrap">
  172 + <i class="el-icon-bottom desc-icon desc-icon--warning" />
  173 + {{ displayDiscountAmount }}
  174 + </span>
  175 + </el-descriptions-item>
  176 + <el-descriptions-item label="出库成本">
  177 + <span class="cell-nowrap">
  178 + <i class="el-icon-s-grid desc-icon desc-icon--info" />
  179 + {{ formatMoneyCol(detail.cbje) }}
  180 + </span>
  181 + </el-descriptions-item>
  182 + <el-descriptions-item label="制单人">
  183 + <span class="cell-nowrap">
  184 + <i class="el-icon-edit-outline desc-icon desc-icon--muted" />
  185 + {{ cellText(detail.zdr) }}
  186 + </span>
  187 + </el-descriptions-item>
  188 + <el-descriptions-item label="审核人">
  189 + <span class="cell-nowrap">
  190 + <i class="el-icon-s-check desc-icon desc-icon--warning" />
  191 + {{ cellText(detail.shr) }}
  192 + </span>
  193 + </el-descriptions-item>
  194 + <el-descriptions-item label="过账人">
  195 + <span class="cell-nowrap">
  196 + <i class="el-icon-s-promotion desc-icon desc-icon--info" />
  197 + {{ cellText(detail.gzr) }}
  198 + </span>
  199 + </el-descriptions-item>
  200 + <el-descriptions-item label="备注" :span="2">
  201 + <span class="detail-bz">{{ cellText(detail.bz) }}</span>
  202 + </el-descriptions-item>
  203 + </el-descriptions>
  204 + </template>
  205 + <div v-else-if="!loading" class="detail-empty">
  206 + <i class="el-icon-warning-outline" />
  207 + <span>暂无数据</span>
  208 + </div>
  209 + </div>
  210 + </el-dialog>
  211 +</template>
  212 +
  213 +<script>
  214 +import request from '@/utils/request'
  215 +import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
  216 +import { previewDataInterface } from '@/api/systemData/dataInterface'
  217 +import { dynamicText } from '@/filters'
  218 +
  219 +export default {
  220 + name: 'WtXsckdDetailView',
  221 + data() {
  222 + return {
  223 + visible: false,
  224 + loading: false,
  225 + detail: null,
  226 + cjckOptions: [],
  227 + skzhOptions: []
  228 + }
  229 + },
  230 + computed: {
  231 + djztLabel() {
  232 + const z = this.detail && this.detail.djzt
  233 + if (!z) return '待审核'
  234 + return z
  235 + },
  236 + djztTagType() {
  237 + const z = this.detail && this.detail.djzt
  238 + if (z === '已审核') return 'success'
  239 + if (z === '一级已审') return ''
  240 + if (z === '草稿') return 'info'
  241 + if (z === '待审核' || !z) return 'warning'
  242 + return 'info'
  243 + },
  244 + displayOriginalPrice() {
  245 + const row = this.detail
  246 + if (!row) return '0.00'
  247 + const ydje = row.ydje != null && row.ydje !== ''
  248 + const val = ydje
  249 + ? parseFloat(row.ydje)
  250 + : (parseFloat(this.displayCollectionRaw) || 0) + (parseFloat(row.ysje) || 0)
  251 + return (typeof val === 'number' && !isNaN(val)) ? val.toFixed(2) : '0.00'
  252 + },
  253 + displayDiscountAmount() {
  254 + const row = this.detail
  255 + if (!row) return '0.00'
  256 + const ysje = parseFloat(row.ysje) || 0
  257 + return ysje.toFixed(2)
  258 + },
  259 + displayCollectionRaw() {
  260 + const row = this.detail
  261 + if (!row) return 0
  262 + const hasYdje = row.ydje != null && row.ydje !== ''
  263 + if (hasYdje) {
  264 + const ydje = parseFloat(row.ydje)
  265 + const ysje = parseFloat(row.ysje) || 0
  266 + return !isNaN(ydje) ? ydje - ysje : 0
  267 + }
  268 + if (row.skmx) {
  269 + try {
  270 + const paymentDetails = JSON.parse(row.skmx)
  271 + if (Array.isArray(paymentDetails) && paymentDetails.length > 0) {
  272 + return paymentDetails.reduce((sum, item) => sum + (parseFloat(item.skje) || 0), 0)
  273 + }
  274 + } catch (e) {
  275 + // ignore
  276 + }
  277 + }
  278 + if (row.skje != null && row.skje !== '') return parseFloat(row.skje)
  279 + return 0
  280 + },
  281 + displayCollectionAmount() {
  282 + const n = this.displayCollectionRaw
  283 + if (typeof n === 'number' && !isNaN(n)) return n.toFixed(2)
  284 + return '0.00'
  285 + }
  286 + },
  287 + methods: {
  288 + labelFromOptions(value, options) {
  289 + if (value === null || value === undefined || value === '') return '无'
  290 + const opts = options || []
  291 + let text = dynamicText(value, opts)
  292 + if (text && text !== value) return text
  293 + const row = opts.find(
  294 + o => o.F_Id === value || String(o.F_Id) === String(value) || o.id === value || String(o.id) === String(value)
  295 + )
  296 + if (row) return row.F_mdmc || row.fullName || value
  297 + return text || String(value)
  298 + },
  299 + cellText(v) {
  300 + if (v === null || v === undefined || v === '') return '无'
  301 + return v
  302 + },
  303 + formatMoneyCol(v) {
  304 + if (v === null || v === undefined || v === '') return '无'
  305 + const n = Number(v)
  306 + return isNaN(n) ? '无' : n.toFixed(2)
  307 + },
  308 + formatLy(ly) {
  309 + if (ly == null || ly === '') return '无'
  310 + if (ly === '门店') return '门店(收银台)'
  311 + if (ly === '抖音订单') return '抖音订单'
  312 + return ly
  313 + },
  314 + formatDjrq(ts) {
  315 + if (ts == null || ts === '') return '无'
  316 + const d = new Date(typeof ts === 'number' ? ts : Number(ts))
  317 + if (isNaN(d.getTime())) return '无'
  318 + const y = d.getFullYear()
  319 + const m = String(d.getMonth() + 1).padStart(2, '0')
  320 + const day = String(d.getDate()).padStart(2, '0')
  321 + return `${y}-${m}-${day}`
  322 + },
  323 + getSummaries(param) {
  324 + const { columns, data } = param
  325 + const sums = []
  326 + columns.forEach((column, index) => {
  327 + if (index === 0) {
  328 + sums[index] = '合计'
  329 + return
  330 + }
  331 + if (column.property === 'sl') {
  332 + sums[index] = data.reduce((t, row) => t + (parseFloat(row.sl) || 0), 0)
  333 + } else if (column.property === 'je') {
  334 + sums[index] = data
  335 + .reduce((t, row) => t + (parseFloat(row.je) || 0), 0)
  336 + .toFixed(2)
  337 + } else if (column.property === 'cbje') {
  338 + sums[index] = data
  339 + .reduce((t, row) => t + (parseFloat(row.cbje) || 0), 0)
  340 + .toFixed(2)
  341 + } else {
  342 + sums[index] = ''
  343 + }
  344 + })
  345 + return sums
  346 + },
  347 + handleClose() {
  348 + this.detail = null
  349 + this.$emit('close')
  350 + },
  351 + init(id) {
  352 + if (!id) return
  353 + this.visible = true
  354 + this.loading = true
  355 + this.detail = null
  356 + request({
  357 + url: '/api/Extend/WtXsckd/' + id,
  358 + method: 'get'
  359 + })
  360 + .then(res => {
  361 + this.detail = res.data || null
  362 + if (this.detail && !this.detail.wtXsckdMxList) {
  363 + this.$set(this.detail, 'wtXsckdMxList', [])
  364 + }
  365 + })
  366 + .catch(() => {
  367 + this.$message.error('加载详情失败')
  368 + this.visible = false
  369 + })
  370 + .finally(() => {
  371 + this.loading = false
  372 + })
  373 + },
  374 + loadOptions() {
  375 + previewDataInterface('681758216954053893').then(res => {
  376 + this.cjckOptions = res.data || []
  377 + })
  378 + getDictionaryDataSelector('681761709836207365').then(res => {
  379 + this.skzhOptions = res.data.list || []
  380 + })
  381 + }
  382 + },
  383 + created() {
  384 + this.loadOptions()
  385 + }
  386 +}
  387 +</script>
  388 +
  389 +<style scoped lang="scss">
  390 +.cell-nowrap {
  391 + white-space: nowrap;
  392 +}
  393 +.text-muted {
  394 + color: #909399;
  395 + font-size: 12px;
  396 +}
  397 +
  398 +.xsckd-detail-wrap {
  399 + min-height: 160px;
  400 + margin-bottom: 30px;
  401 +}
  402 +
  403 +.detail-top {
  404 + display: flex;
  405 + align-items: center;
  406 + justify-content: space-between;
  407 + flex-wrap: wrap;
  408 + gap: 10px;
  409 + margin-bottom: 14px;
  410 + padding-bottom: 12px;
  411 + border-bottom: 1px solid #ebeef5;
  412 +}
  413 +.detail-top__id {
  414 + display: flex;
  415 + align-items: center;
  416 + gap: 8px;
  417 + font-size: 15px;
  418 + font-weight: 600;
  419 + color: #303133;
  420 +}
  421 +.detail-top__icon {
  422 + font-size: 18px;
  423 +}
  424 +.detail-top__icon--primary {
  425 + color: #409eff;
  426 +}
  427 +
  428 +.collect-stat-card {
  429 + height: 100px;
  430 + padding: 12px;
  431 + border-radius: 12px;
  432 + background: linear-gradient(135deg, #ecf5ff 0%, #f5f9ff 100%);
  433 + border: 1px solid #d9ecff;
  434 + margin-bottom: 16px;
  435 + box-sizing: border-box;
  436 + display: flex;
  437 + flex-direction: column;
  438 + justify-content: center;
  439 +}
  440 +.collect-stat-card__inner {
  441 + display: flex;
  442 + align-items: center;
  443 + gap: 14px;
  444 +}
  445 +.collect-stat-card__coin {
  446 + font-size: 36px;
  447 + color: #409eff;
  448 + line-height: 1;
  449 +}
  450 +.collect-stat-card__label {
  451 + font-size: 12px;
  452 + color: #909399;
  453 + margin-bottom: 4px;
  454 +}
  455 +.collect-stat-card__value {
  456 + font-size: 22px;
  457 + font-weight: 600;
  458 + color: #303133;
  459 + letter-spacing: 0.02em;
  460 +}
  461 +.collect-stat-card__hint {
  462 + margin: 10px 0 0;
  463 + font-size: 12px;
  464 + color: #909399;
  465 + line-height: 1.4;
  466 + display: flex;
  467 + align-items: flex-start;
  468 + gap: 4px;
  469 +}
  470 +.collect-stat-card__hint .el-icon-info {
  471 + margin-top: 2px;
  472 + color: #c0c4cc;
  473 +}
  474 +
  475 +.detail-descriptions {
  476 + margin-bottom: 8px;
  477 +}
  478 +.detail-descriptions--footer {
  479 + margin-top: 14px;
  480 +}
  481 +.desc-icon {
  482 + margin-right: 6px;
  483 + font-size: 14px;
  484 + vertical-align: -1px;
  485 +}
  486 +.desc-icon--primary {
  487 + color: #409eff;
  488 +}
  489 +.desc-icon--success {
  490 + color: #67c23a;
  491 +}
  492 +.desc-icon--warning {
  493 + color: #e6a23c;
  494 +}
  495 +.desc-icon--info {
  496 + color: #909399;
  497 +}
  498 +.desc-icon--muted {
  499 + color: #c0c4cc;
  500 +}
  501 +
  502 +.detail-section-head {
  503 + display: flex;
  504 + align-items: center;
  505 + gap: 6px;
  506 + margin: 16px 0 10px;
  507 + font-size: 14px;
  508 + font-weight: 600;
  509 + color: #303133;
  510 +}
  511 +.detail-section-head__icon {
  512 + color: #409eff;
  513 + font-size: 16px;
  514 +}
  515 +.detail-section-head__sub {
  516 + font-size: 12px;
  517 + font-weight: normal;
  518 + color: #909399;
  519 +}
  520 +
  521 +.detail-mx-table {
  522 + width: 100%;
  523 +}
  524 +.mx-cell {
  525 + display: inline-flex;
  526 + align-items: center;
  527 + max-width: 100%;
  528 +}
  529 +.mx-cell__icon {
  530 + margin-right: 4px;
  531 + flex-shrink: 0;
  532 + font-size: 14px;
  533 +}
  534 +.mx-cell__icon--primary {
  535 + color: #409eff;
  536 +}
  537 +.mx-cell__icon--muted {
  538 + color: #909399;
  539 +}
  540 +.mx-cell__money {
  541 + color: #303133;
  542 + font-weight: 500;
  543 +}
  544 +
  545 +.sn-tags {
  546 + display: flex;
  547 + flex-wrap: wrap;
  548 + gap: 4px;
  549 + align-items: center;
  550 +}
  551 +.sn-tag {
  552 + margin: 0 !important;
  553 +}
  554 +
  555 +.detail-bz {
  556 + white-space: pre-wrap;
  557 + word-break: break-all;
  558 + line-height: 1.5;
  559 + color: #606266;
  560 +}
  561 +
  562 +.detail-empty {
  563 + display: flex;
  564 + align-items: center;
  565 + justify-content: center;
  566 + gap: 8px;
  567 + padding: 40px;
  568 + color: #909399;
  569 + font-size: 14px;
  570 +}
  571 +.detail-empty .el-icon-warning-outline {
  572 + font-size: 20px;
  573 + color: #e6a23c;
  574 +}
  575 +
  576 +.xsckd-detail-footer {
  577 + text-align: left;
  578 +}
  579 +</style>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsckd/index.vue
... ... @@ -78,8 +78,9 @@
78 78 <el-col :span="6">
79 79 <el-form-item label="单据来源">
80 80 <el-select v-model="query.ly" placeholder="单据来源" clearable >
81   - <el-option label="门店" value="门店"></el-option>
82   - <el-option label="抖音抓单" value="抖音抓单"></el-option>
  81 + <el-option label="后台" value="后台" />
  82 + <el-option label="门店(收银台)" value="门店" />
  83 + <el-option label="抖音抓单" value="抖音抓单" />
83 84 </el-select>
84 85 </el-form-item>
85 86 </el-col>
... ... @@ -138,20 +139,25 @@
138 139 <el-table-column prop="gzr" label="过账人" align="left" />
139 140 <el-table-column prop="bz" label="备注" align="left" />
140 141 <el-table-column prop="djlx" label="单据类型" align="left" />
  142 + <el-table-column label="单据来源" prop="ly" align="left" min-width="100">
  143 + <template slot-scope="scope">{{ formatLy(scope.row.ly) }}</template>
  144 + </el-table-column>
141 145 <el-table-column prop="djzt" label="审核状态" align="left">
142 146 <template slot-scope="scope">
143 147 <el-tag v-if="scope.row.djzt === '已审核'" type="success">已审核</el-tag>
144 148 <el-tag v-else-if="scope.row.djzt === '一级已审'" type="">一级已审</el-tag>
  149 + <el-tag v-else-if="scope.row.djzt === '草稿'" type="info">草稿</el-tag>
  150 + <el-tag v-else-if="isSkipErpAuditSource(scope.row)" type="success" effect="plain">无需审核</el-tag>
145 151 <el-tag v-else-if="scope.row.djzt === '待审核'" type="warning">待审核</el-tag>
146 152 <el-tag v-else type="warning">{{ scope.row.djzt || '待审核' }}</el-tag>
147 153 </template>
148 154 </el-table-column>
149   - <el-table-column label="操作" fixed="right" width="250">
  155 + <el-table-column label="操作" fixed="right" width="280">
150 156 <template slot-scope="scope">
151   - <el-button type="text" @click="addOrUpdateHandle(scope.row.id, true)">查看</el-button>
152   - <el-button type="text" disabled >编辑</el-button>
153   - <el-button type="text" @click="handleApprove(scope.row.id)" v-if="scope.row.djzt === '待审核' || !scope.row.djzt" style="color:#E6A23C">一级审核</el-button>
154   - <el-button type="text" @click="handleApprove(scope.row.id)" v-if="scope.row.djzt === '一级已审'" style="color:#409EFF">二级审核</el-button>
  157 + <el-button type="text" @click="openDetail(scope.row.id)">查看</el-button>
  158 + <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" v-if="canEditDraft(scope.row)">编辑</el-button>
  159 + <el-button type="text" @click="handleApprove(scope.row.id)" v-if="canFirstApprove(scope.row)" style="color:#E6A23C">一级审核</el-button>
  160 + <el-button type="text" @click="handleApprove(scope.row.id)" v-if="canSecondApprove(scope.row)" style="color:#409EFF">二级审核</el-button>
155 161 <el-button type="text" @click="handleDel(scope.row.id)" class="NCC-table-delBtn" >删除</el-button>
156 162 </template>
157 163 </el-table-column>
... ... @@ -160,6 +166,7 @@
160 166 </div>
161 167 </div>
162 168 <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" />
  169 + <DetailView v-if="detailVisible" ref="NCCDetailView" @close="detailVisible = false" />
163 170 <ExportBox v-if="exportBoxVisible" ref="ExportBox" @download="download" />
164 171 </div>
165 172 </template>
... ... @@ -167,10 +174,11 @@
167 174 import request from '@/utils/request'
168 175 import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
169 176 import NCCForm from './Form'
  177 + import DetailView from './detail-view'
170 178 import ExportBox from './ExportBox'
171 179 import { previewDataInterface } from '@/api/systemData/dataInterface'
172 180 export default {
173   - components: { NCCForm, ExportBox },
  181 + components: { NCCForm, DetailView, ExportBox },
174 182 data() {
175 183 return {
176 184 showAll: false,
... ... @@ -200,6 +208,7 @@
200 208 sidx: "",
201 209 },
202 210 formVisible: false,
  211 + detailVisible: false,
203 212 exportBoxVisible: false,
204 213 columnList: [
205 214 { prop: 'id', label: '单据编号' },
... ... @@ -228,6 +237,33 @@
228 237 this.getskzhOptions();
229 238 },
230 239 methods: {
  240 + /** 收银台/抖音等:不走 ERP 二级审核、仅查看 */
  241 + isSkipErpAuditSource(row) {
  242 + if (!row) return false
  243 + const ly = row.ly
  244 + if (ly === '门店' || ly === '抖音订单' || ly === '抖音抓单') return true
  245 + const bz = row.bz != null && row.bz !== '' ? String(row.bz).trim() : ''
  246 + if (bz.startsWith('抖音订单:')) return true
  247 + return false
  248 + },
  249 + /** 后台来源且草稿可编辑 */
  250 + canEditDraft(row) {
  251 + return !this.isSkipErpAuditSource(row) && row.djzt === '草稿'
  252 + },
  253 + canFirstApprove(row) {
  254 + if (this.isSkipErpAuditSource(row)) return false
  255 + if (row.djzt === '草稿' || row.djzt === '已审核') return false
  256 + return row.djzt === '待审核' || row.djzt === '' || row.djzt == null
  257 + },
  258 + canSecondApprove(row) {
  259 + return !this.isSkipErpAuditSource(row) && row.djzt === '一级已审'
  260 + },
  261 + formatLy(ly) {
  262 + if (ly == null || ly === '') return '无'
  263 + if (ly === '门店') return '门店(收银台)'
  264 + if (ly === '抖音订单') return '抖音订单'
  265 + return ly
  266 + },
231 267 // ✅ 原价:优先使用后端存储的 ydje(与收银台订单原价一致),无则用 收款+优惠 兼容旧数据
232 268 getDisplayOriginalPrice(row) {
233 269 const ydje = row.ydje != null && row.ydje !== '';
... ... @@ -392,10 +428,17 @@
392 428 })
393 429 }).catch(() => { })
394 430 },
395   - addOrUpdateHandle(id, isDetail) {
  431 + openDetail(id) {
  432 + if (!id) return
  433 + this.detailVisible = true
  434 + this.$nextTick(() => {
  435 + if (this.$refs.NCCDetailView) this.$refs.NCCDetailView.init(id)
  436 + })
  437 + },
  438 + addOrUpdateHandle(id) {
396 439 this.formVisible = true
397 440 this.$nextTick(() => {
398   - this.$refs.NCCForm.init(id, isDetail)
  441 + this.$refs.NCCForm.init(id, false)
399 442 })
400 443 },
401 444 exportData() {
... ... @@ -428,6 +471,7 @@
428 471 },
429 472 refresh(isrRefresh) {
430 473 this.formVisible = false
  474 + this.detailVisible = false
431 475 if (isrRefresh) this.reset()
432 476 },
433 477 reset() {
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsthd/Form.vue
1   -<template>
2   - <el-dialog :title="!dataForm.id ? '新建' : isDetail ? '详情':'编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%">
  1 +<template>
  2 + <el-dialog :title="!dataForm.id ? '新建' : '编辑'" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="80%">
3 3 <el-row :gutter="15" class="" >
4   - <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :disabled="!!isDetail" :rules="rules">
  4 + <el-form ref="elForm" :model="dataForm" size="small" label-width="100px" label-position="right" :rules="rules">
5 5 <el-col :span="12">
6 6 <el-form-item label="单据编号" prop="id">
7 7 <el-input v-model="dataForm.id" placeholder="请输入" clearable readonly :style='{"width":"100%"}' >
... ... @@ -215,7 +215,7 @@
215 215 </el-row>
216 216 <span slot="footer" class="dialog-footer">
217 217 <el-button @click="visible = false">取 消</el-button>
218   - <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button>
  218 + <el-button type="primary" @click="dataFormSubmit()">确 定</el-button>
219 219 </span>
220 220 <!-- 序列号选择弹窗 -->
221 221 <SerialNumberSelect ref="serialNumberSelect" @confirm="handleSerialNumberSelect" />
... ... @@ -233,7 +233,6 @@
233 233 return {
234 234 loading: false,
235 235 visible: false,
236   - isDetail: false,
237 236 dataForm: {
238 237 id:'',
239 238 id:undefined,
... ... @@ -324,10 +323,9 @@
324 323 goBack() {
325 324 this.$emit('refresh')
326 325 },
327   - init(id, isDetail) {
  326 + init(id) {
328 327 this.dataForm.id = id || 0;
329 328 this.visible = true;
330   - this.isDetail = isDetail || false;
331 329 this.$nextTick(() => {
332 330 this.$refs['elForm'].resetFields();
333 331 if (this.dataForm.id) {
... ... @@ -358,8 +356,11 @@
358 356  
359 357 // 同步明细表的入库仓库与主表保持一致
360 358 this.syncDetailWarehouses();
361   - // 计算总金额
362   - this.updateTotalAmount();
  359 + // 主表退款金额 skje 以服务端为准(收银台选择的实退金额),不能用明细 je 合计覆盖,
  360 + // 否则会出现列表 320、详情被算成 388 等不一致。
  361 + if (this.dataForm.skje == null || this.dataForm.skje === '') {
  362 + this.updateTotalAmount();
  363 + }
363 364 })
364 365 } else {
365 366 // 新建时确保wtXsckdMxList为空数组
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsthd/detail-view.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + title="销售退货单详情"
  4 + :close-on-click-modal="false"
  5 + :visible.sync="visible"
  6 + class="NCC-dialog NCC-dialog_center xsthd-detail-dialog"
  7 + lock-scroll
  8 + width="1200px"
  9 + top="6vh"
  10 + @close="handleClose"
  11 + >
  12 + <div v-loading="loading" class="xsthd-detail-wrap">
  13 + <template v-if="detail">
  14 + <!-- 顶部:单号 + 状态 -->
  15 + <div class="detail-top">
  16 + <div class="detail-top__id">
  17 + <i class="el-icon-document detail-top__icon detail-top__icon--primary" />
  18 + <span class="cell-nowrap">{{ cellText(detail.id) }}</span>
  19 + </div>
  20 + <el-tag :type="djztTagType" size="medium" effect="plain">{{ djztLabel }}</el-tag>
  21 + </div>
  22 +
  23 + <!-- 退款金额(与列表一致,突出展示) -->
  24 + <div class="refund-stat-card">
  25 + <div class="refund-stat-card__inner">
  26 + <i class="el-icon-coin refund-stat-card__coin" />
  27 + <div class="refund-stat-card__text">
  28 + <div class="refund-stat-card__label">退款金额(收银台实退)</div>
  29 + <div class="refund-stat-card__value cell-nowrap">{{ formatMoneyCol(detail.skje) }}</div>
  30 + </div>
  31 + </div>
  32 + <p class="refund-stat-card__hint">
  33 + <i class="el-icon-info" />
  34 + 与明细「金额」合计可能不一致(议价、部分退等以本金额为准)
  35 + </p>
  36 + </div>
  37 +
  38 + <el-descriptions
  39 + title="基本信息"
  40 + :column="2"
  41 + border
  42 + size="small"
  43 + class="detail-descriptions"
  44 + :label-style="{ width: '108px' }"
  45 + >
  46 + <el-descriptions-item label="单据日期">
  47 + <span class="cell-nowrap">
  48 + <i class="el-icon-date desc-icon desc-icon--primary" />
  49 + {{ formatDjrq(detail.djrq) }}
  50 + </span>
  51 + </el-descriptions-item>
  52 + <el-descriptions-item label="单据类型">
  53 + <span class="cell-nowrap">
  54 + <i class="el-icon-tickets desc-icon desc-icon--info" />
  55 + {{ cellText(detail.djlx || '销售退货单') }}
  56 + </span>
  57 + </el-descriptions-item>
  58 + <el-descriptions-item label="入库仓库">
  59 + <span class="cell-nowrap">
  60 + <i class="el-icon-s-home desc-icon desc-icon--muted" />
  61 + {{ cellText(detail.cjck) }}
  62 + </span>
  63 + </el-descriptions-item>
  64 + <el-descriptions-item label="经手人">
  65 + <span class="cell-nowrap">
  66 + <i class="el-icon-user desc-icon desc-icon--primary" />
  67 + {{ cellText(detail.jsr) }}
  68 + </span>
  69 + </el-descriptions-item>
  70 + </el-descriptions>
  71 +
  72 + <div class="detail-section-head">
  73 + <i class="el-icon-s-order detail-section-head__icon" />
  74 + <span>退货明细</span>
  75 + <span class="detail-section-head__sub">(列表不换行展示)</span>
  76 + </div>
  77 + <el-table
  78 + :data="detail.wtXsckdMxList || []"
  79 + size="small"
  80 + border
  81 + stripe
  82 + show-summary
  83 + :summary-method="getSummaries"
  84 + class="detail-mx-table"
  85 + :empty-text="'无明细'"
  86 + >
  87 + <el-table-column type="index" width="52" label="#" align="center" />
  88 + <el-table-column label="入库仓库" min-width="118" show-overflow-tooltip>
  89 + <template slot-scope="scope">
  90 + <span class="cell-nowrap mx-cell">
  91 + <i class="el-icon-location-outline mx-cell__icon mx-cell__icon--muted" />
  92 + {{ labelFromOptions(scope.row.ckck, cjckOptions) }}
  93 + </span>
  94 + </template>
  95 + </el-table-column>
  96 + <el-table-column label="商品" min-width="160" show-overflow-tooltip>
  97 + <template slot-scope="scope">
  98 + <span class="cell-nowrap mx-cell">
  99 + <i class="el-icon-goods mx-cell__icon mx-cell__icon--primary" />
  100 + {{ cellText(scope.row.spmc) }}
  101 + </span>
  102 + </template>
  103 + </el-table-column>
  104 + <el-table-column prop="sl" label="数量" width="72" align="right">
  105 + <template slot-scope="scope">
  106 + <span class="cell-nowrap">{{ cellText(scope.row.sl) }}</span>
  107 + </template>
  108 + </el-table-column>
  109 + <el-table-column prop="dj" label="单价" width="96" align="right">
  110 + <template slot-scope="scope">
  111 + <span class="cell-nowrap">{{ formatMoneyCol(scope.row.dj) }}</span>
  112 + </template>
  113 + </el-table-column>
  114 + <el-table-column prop="je" label="金额" width="96" align="right">
  115 + <template slot-scope="scope">
  116 + <span class="cell-nowrap mx-cell__money">{{ formatMoneyCol(scope.row.je) }}</span>
  117 + </template>
  118 + </el-table-column>
  119 + <el-table-column prop="description" label="备注" min-width="88" show-overflow-tooltip>
  120 + <template slot-scope="scope">
  121 + <span class="cell-nowrap">{{ cellText(scope.row.description) }}</span>
  122 + </template>
  123 + </el-table-column>
  124 + <el-table-column label="序列号" min-width="168">
  125 + <template slot-scope="scope">
  126 + <div v-if="scope.row.selectedSerialNumbers && scope.row.selectedSerialNumbers.length" class="sn-tags">
  127 + <el-tag
  128 + v-for="(sn, idx) in scope.row.selectedSerialNumbers"
  129 + :key="idx"
  130 + size="mini"
  131 + type="warning"
  132 + class="sn-tag"
  133 + >{{ sn }}</el-tag>
  134 + </div>
  135 + <span v-else class="text-muted">无</span>
  136 + </template>
  137 + </el-table-column>
  138 + </el-table>
  139 +
  140 + <el-descriptions
  141 + title="退款与审核"
  142 + :column="2"
  143 + border
  144 + size="small"
  145 + class="detail-descriptions detail-descriptions--footer"
  146 + :label-style="{ width: '108px' }"
  147 + >
  148 + <el-descriptions-item label="退款账户">
  149 + <span class="cell-nowrap">
  150 + <i class="el-icon-bank-card desc-icon desc-icon--success" />
  151 + {{ labelFromOptions(detail.skzh, skzhOptions) }}
  152 + </span>
  153 + </el-descriptions-item>
  154 + <el-descriptions-item label="审核人">
  155 + <span class="cell-nowrap">
  156 + <i class="el-icon-s-check desc-icon desc-icon--warning" />
  157 + {{ cellText(detail.shr) }}
  158 + </span>
  159 + </el-descriptions-item>
  160 + <el-descriptions-item label="过账人">
  161 + <span class="cell-nowrap">
  162 + <i class="el-icon-s-promotion desc-icon desc-icon--info" />
  163 + {{ cellText(detail.gzr) }}
  164 + </span>
  165 + </el-descriptions-item>
  166 + <el-descriptions-item label="备注" :span="2">
  167 + <span class="detail-bz">{{ cellText(detail.bz) }}</span>
  168 + </el-descriptions-item>
  169 + </el-descriptions>
  170 + </template>
  171 + <div v-else-if="!loading" class="detail-empty">
  172 + <i class="el-icon-warning-outline" />
  173 + <span>暂无数据</span>
  174 + </div>
  175 + </div>
  176 + </el-dialog>
  177 +</template>
  178 +
  179 +<script>
  180 +import request from '@/utils/request'
  181 +import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
  182 +import { previewDataInterface } from '@/api/systemData/dataInterface'
  183 +import { dynamicText } from '@/filters'
  184 +
  185 +export default {
  186 + name: 'WtXsthdDetailView',
  187 + data() {
  188 + return {
  189 + visible: false,
  190 + loading: false,
  191 + detail: null,
  192 + cjckOptions: [],
  193 + skzhOptions: []
  194 + }
  195 + },
  196 + computed: {
  197 + djztLabel() {
  198 + const z = this.detail && this.detail.djzt
  199 + if (!z) return '待审核'
  200 + return z
  201 + },
  202 + djztTagType() {
  203 + const z = this.detail && this.detail.djzt
  204 + if (z === '已审核') return 'success'
  205 + if (z === '一级已审') return ''
  206 + if (z === '待审核' || !z) return 'warning'
  207 + return 'info'
  208 + }
  209 + },
  210 + methods: {
  211 + labelFromOptions(value, options) {
  212 + if (value === null || value === undefined || value === '') return '无'
  213 + const opts = options || []
  214 + let text = dynamicText(value, opts)
  215 + if (text && text !== value) return text
  216 + const row = opts.find(
  217 + o => o.F_Id === value || String(o.F_Id) === String(value) || o.id === value || String(o.id) === String(value)
  218 + )
  219 + if (row) return row.F_mdmc || row.fullName || value
  220 + return text || String(value)
  221 + },
  222 + cellText(v) {
  223 + if (v === null || v === undefined || v === '') return '无'
  224 + return v
  225 + },
  226 + formatMoneyCol(v) {
  227 + if (v === null || v === undefined || v === '') return '无'
  228 + const n = Number(v)
  229 + return isNaN(n) ? '无' : n.toFixed(2)
  230 + },
  231 + formatDjrq(ts) {
  232 + if (ts == null || ts === '') return '无'
  233 + const d = new Date(typeof ts === 'number' ? ts : Number(ts))
  234 + if (isNaN(d.getTime())) return '无'
  235 + const y = d.getFullYear()
  236 + const m = String(d.getMonth() + 1).padStart(2, '0')
  237 + const day = String(d.getDate()).padStart(2, '0')
  238 + return `${y}-${m}-${day}`
  239 + },
  240 + getSummaries(param) {
  241 + const { columns, data } = param
  242 + const sums = []
  243 + columns.forEach((column, index) => {
  244 + if (index === 0) {
  245 + sums[index] = '合计'
  246 + return
  247 + }
  248 + if (column.property === 'sl') {
  249 + sums[index] = data.reduce((t, row) => t + (parseFloat(row.sl) || 0), 0)
  250 + } else if (column.property === 'je') {
  251 + sums[index] = data
  252 + .reduce((t, row) => t + (parseFloat(row.je) || 0), 0)
  253 + .toFixed(2)
  254 + } else {
  255 + sums[index] = ''
  256 + }
  257 + })
  258 + return sums
  259 + },
  260 + handleClose() {
  261 + this.detail = null
  262 + this.$emit('close')
  263 + },
  264 + init(id) {
  265 + if (!id) return
  266 + this.visible = true
  267 + this.loading = true
  268 + this.detail = null
  269 + request({
  270 + url: '/api/Extend/WtXsckd/' + id,
  271 + method: 'get'
  272 + })
  273 + .then(res => {
  274 + this.detail = res.data || null
  275 + if (this.detail && !this.detail.wtXsckdMxList) {
  276 + this.$set(this.detail, 'wtXsckdMxList', [])
  277 + }
  278 + })
  279 + .catch(() => {
  280 + this.$message.error('加载详情失败')
  281 + this.visible = false
  282 + })
  283 + .finally(() => {
  284 + this.loading = false
  285 + })
  286 + },
  287 + loadOptions() {
  288 + previewDataInterface('681758216954053893').then(res => {
  289 + this.cjckOptions = res.data || []
  290 + })
  291 + getDictionaryDataSelector('681761709836207365').then(res => {
  292 + this.skzhOptions = res.data.list || []
  293 + })
  294 + }
  295 + },
  296 + created() {
  297 + this.loadOptions()
  298 + }
  299 +}
  300 +</script>
  301 +
  302 +<style scoped lang="scss">
  303 +.cell-nowrap {
  304 + white-space: nowrap;
  305 +}
  306 +.text-muted {
  307 + color: #909399;
  308 + font-size: 12px;
  309 +}
  310 +
  311 +.xsthd-detail-wrap {
  312 + min-height: 160px;
  313 + margin-bottom: 50px;
  314 +}
  315 +
  316 +.detail-top {
  317 + display: flex;
  318 + align-items: center;
  319 + justify-content: space-between;
  320 + flex-wrap: wrap;
  321 + gap: 10px;
  322 + margin-bottom: 14px;
  323 + padding-bottom: 12px;
  324 + border-bottom: 1px solid #ebeef5;
  325 +}
  326 +.detail-top__id {
  327 + display: flex;
  328 + align-items: center;
  329 + gap: 8px;
  330 + font-size: 15px;
  331 + font-weight: 600;
  332 + color: #303133;
  333 +}
  334 +.detail-top__icon {
  335 + font-size: 18px;
  336 +}
  337 +.detail-top__icon--primary {
  338 + color: #409eff;
  339 +}
  340 +
  341 +/* 统计卡片:高度 100px、内边距 12px、圆角 12px(与项目规范一致) */
  342 +.refund-stat-card {
  343 + height: 100px;
  344 + padding: 12px;
  345 + border-radius: 12px;
  346 + background: linear-gradient(135deg, #ecf5ff 0%, #f5f9ff 100%);
  347 + border: 1px solid #d9ecff;
  348 + margin-bottom: 16px;
  349 + box-sizing: border-box;
  350 + display: flex;
  351 + flex-direction: column;
  352 + justify-content: center;
  353 +}
  354 +.refund-stat-card__inner {
  355 + display: flex;
  356 + align-items: center;
  357 + gap: 14px;
  358 +}
  359 +.refund-stat-card__coin {
  360 + font-size: 36px;
  361 + color: #409eff;
  362 + line-height: 1;
  363 +}
  364 +.refund-stat-card__label {
  365 + font-size: 12px;
  366 + color: #909399;
  367 + margin-bottom: 4px;
  368 +}
  369 +.refund-stat-card__value {
  370 + font-size: 22px;
  371 + font-weight: 600;
  372 + color: #303133;
  373 + letter-spacing: 0.02em;
  374 +}
  375 +.refund-stat-card__hint {
  376 + margin: 10px 0 0;
  377 + font-size: 12px;
  378 + color: #909399;
  379 + line-height: 1.4;
  380 + display: flex;
  381 + align-items: flex-start;
  382 + gap: 4px;
  383 +}
  384 +.refund-stat-card__hint .el-icon-info {
  385 + margin-top: 2px;
  386 + color: #c0c4cc;
  387 +}
  388 +
  389 +.detail-descriptions {
  390 + margin-bottom: 8px;
  391 +}
  392 +.detail-descriptions--footer {
  393 + margin-top: 14px;
  394 +}
  395 +.desc-icon {
  396 + margin-right: 6px;
  397 + font-size: 14px;
  398 + vertical-align: -1px;
  399 +}
  400 +.desc-icon--primary {
  401 + color: #409eff;
  402 +}
  403 +.desc-icon--success {
  404 + color: #67c23a;
  405 +}
  406 +.desc-icon--warning {
  407 + color: #e6a23c;
  408 +}
  409 +.desc-icon--info {
  410 + color: #909399;
  411 +}
  412 +.desc-icon--muted {
  413 + color: #c0c4cc;
  414 +}
  415 +
  416 +.detail-section-head {
  417 + display: flex;
  418 + align-items: center;
  419 + gap: 6px;
  420 + margin: 16px 0 10px;
  421 + font-size: 14px;
  422 + font-weight: 600;
  423 + color: #303133;
  424 +}
  425 +.detail-section-head__icon {
  426 + color: #409eff;
  427 + font-size: 16px;
  428 +}
  429 +.detail-section-head__sub {
  430 + font-size: 12px;
  431 + font-weight: normal;
  432 + color: #909399;
  433 +}
  434 +
  435 +.detail-mx-table {
  436 + width: 100%;
  437 +}
  438 +.mx-cell {
  439 + display: inline-flex;
  440 + align-items: center;
  441 + max-width: 100%;
  442 +}
  443 +.mx-cell__icon {
  444 + margin-right: 4px;
  445 + flex-shrink: 0;
  446 + font-size: 14px;
  447 +}
  448 +.mx-cell__icon--primary {
  449 + color: #409eff;
  450 +}
  451 +.mx-cell__icon--muted {
  452 + color: #909399;
  453 +}
  454 +.mx-cell__money {
  455 + color: #303133;
  456 + font-weight: 500;
  457 +}
  458 +
  459 +.sn-tags {
  460 + display: flex;
  461 + flex-wrap: wrap;
  462 + gap: 4px;
  463 + align-items: center;
  464 +}
  465 +.sn-tag {
  466 + margin: 0 !important;
  467 +}
  468 +
  469 +.detail-bz {
  470 + white-space: pre-wrap;
  471 + word-break: break-all;
  472 + line-height: 1.5;
  473 + color: #606266;
  474 +}
  475 +
  476 +.detail-empty {
  477 + display: flex;
  478 + align-items: center;
  479 + justify-content: center;
  480 + gap: 8px;
  481 + padding: 40px;
  482 + color: #909399;
  483 + font-size: 14px;
  484 +}
  485 +.detail-empty .el-icon-warning-outline {
  486 + font-size: 20px;
  487 + color: #e6a23c;
  488 +}
  489 +
  490 +/* 操作区左对齐 */
  491 +.xsthd-detail-footer {
  492 + text-align: left;
  493 +}
  494 +</style>
... ...
Antis.Erp.Plat/antis-ncc-admin/src/views/wtXsthd/index.vue
... ... @@ -133,6 +133,7 @@
133 133 </div>
134 134 </div>
135 135 <NCC-Form v-if="formVisible" ref="NCCForm" @refresh="refresh" />
  136 + <DetailView v-if="detailVisible" ref="NCCDetailView" @close="detailVisible = false" />
136 137 <ExportBox v-if="exportBoxVisible" ref="ExportBox" @download="download" />
137 138 </div>
138 139 </template>
... ... @@ -140,10 +141,11 @@
140 141 import request from '@/utils/request'
141 142 import { getDictionaryDataSelector } from '@/api/systemData/dictionary'
142 143 import NCCForm from './Form'
  144 + import DetailView from './detail-view'
143 145 import ExportBox from './ExportBox'
144 146 import { previewDataInterface } from '@/api/systemData/dataInterface'
145 147 export default {
146   - components: { NCCForm, ExportBox },
  148 + components: { NCCForm, DetailView, ExportBox },
147 149 data() {
148 150 return {
149 151 showAll: false,
... ... @@ -171,6 +173,7 @@
171 173 sidx: "",
172 174 },
173 175 formVisible: false,
  176 + detailVisible: false,
174 177 exportBoxVisible: false,
175 178 columnList: [
176 179 { prop: 'id', label: '单据编号' },
... ... @@ -325,9 +328,16 @@
325 328 }).catch(() => { })
326 329 },
327 330 addOrUpdateHandle(id, isDetail) {
  331 + if (isDetail) {
  332 + this.detailVisible = true
  333 + this.$nextTick(() => {
  334 + if (this.$refs.NCCDetailView) this.$refs.NCCDetailView.init(id)
  335 + })
  336 + return
  337 + }
328 338 this.formVisible = true
329 339 this.$nextTick(() => {
330   - this.$refs.NCCForm.init(id, isDetail)
  340 + this.$refs.NCCForm.init(id)
331 341 })
332 342 },
333 343 exportData() {
... ... @@ -360,6 +370,7 @@
360 370 },
361 371 refresh(isrRefresh) {
362 372 this.formVisible = false
  373 + this.detailVisible = false
363 374 if (isrRefresh) this.reset()
364 375 },
365 376 reset() {
... ...
Antis.Erp.Plat/douyin/DouyinLogistics.API/Services/OrderService.cs
... ... @@ -1307,7 +1307,8 @@ public class OrderService
1307 1307 djlx = "销售出库单",
1308 1308 kh = defaultKh,
1309 1309 gys = "",
1310   - djzt = "待审核",
  1310 + // 抖音发货带出单不走 ERP 二级审核,与收银台一致视为已生效
  1311 + djzt = "已审核",
1311 1312 ly = "抖音订单",
1312 1313 skmx = "",
1313 1314 wtXsckdMxList = salesOrderDetails
... ...
Antis.Erp.Plat/netcore/src/Modularity/Extend/NCC.Extend/WtXsckdService.cs
... ... @@ -336,7 +336,8 @@ namespace NCC.Extend.WtXsckd
336 336  
337 337 bz = xsckd.Bz,
338 338 djlx = xsckd.Djlx,
339   - djzt = xsckd.Djzt
  339 + djzt = xsckd.Djzt,
  340 + ly = xsckd.Ly
340 341 }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize);
341 342 var pageRows = data.list?.ToList() ?? new List<WtXsckdListOutput>();
342 343 await EnrichWtXsckdListWarehouseDisplayAsync(pageRows);
... ... @@ -973,7 +974,8 @@ namespace NCC.Extend.WtXsckd
973 974 gys = SqlFunc.Subqueryable<WtGysEntity>().Where(u => u.Id == xsckd.Gys).Select(u => u.Gysmc),
974 975 bz = xsckd.Bz,
975 976 djlx = xsckd.Djlx,
976   - djzt = xsckd.Djzt
  977 + djzt = xsckd.Djzt,
  978 + ly = xsckd.Ly
977 979 }).MergeTable().OrderBy(sidx + " " + input.sort).ToListAsync();
978 980 await EnrichWtXsckdListWarehouseDisplayAsync(data);
979 981 return data;
... ... @@ -1137,6 +1139,8 @@ namespace NCC.Extend.WtXsckd
1137 1139 EnsureBjhcbColumn();
1138 1140 var oldHeader = await _db.Queryable<WtXsckdEntity>().FirstAsync(p => p.Id == id);
1139 1141 var oldMxList = await _db.Queryable<WtXsckdMxEntity>().Where(u => u.Djbh == id).ToListAsync();
  1142 + if (IsSalesOutboundSkipErpAudit(oldHeader))
  1143 + throw NCCException.Bah("收银台或抖音等外部来源的销售出库单不允许在此修改");
1140 1144 var entity = input.Adapt<WtXsckdEntity>();
1141 1145 entity.Bjsx = input.bjsx;
1142 1146  
... ... @@ -2129,6 +2133,26 @@ ORDER BY t.`商品编号`;&quot;;
2129 2133 }
2130 2134  
2131 2135 /// <summary>
  2136 + /// 销售出库单是否跳过 ERP 二级审核:收银台「门店」、抖音「抖音订单/抖音抓单」、或备注为抖音发货脚本格式(抖音订单:…)。
  2137 + /// </summary>
  2138 + private static bool IsSalesOutboundSkipErpAudit(WtXsckdEntity entity)
  2139 + {
  2140 + if (entity == null) return false;
  2141 + if (!string.Equals(entity.Djlx, "销售出库单", StringComparison.Ordinal)) return false;
  2142 + var ly = entity.Ly?.Trim();
  2143 + if (!string.IsNullOrEmpty(ly))
  2144 + {
  2145 + if (string.Equals(ly, "门店", StringComparison.Ordinal)) return true;
  2146 + if (string.Equals(ly, "抖音订单", StringComparison.Ordinal)) return true;
  2147 + if (string.Equals(ly, "抖音抓单", StringComparison.Ordinal)) return true;
  2148 + }
  2149 + var bz = entity.Bz?.Trim();
  2150 + if (!string.IsNullOrEmpty(bz) && bz.StartsWith("抖音订单:", StringComparison.Ordinal))
  2151 + return true;
  2152 + return false;
  2153 + }
  2154 +
  2155 + /// <summary>
2132 2156 /// 通用两级审核逻辑:根据 wt_shrysz 配置自动适配单级/两级审批
2133 2157 /// </summary>
2134 2158 [NonAction]
... ... @@ -2272,11 +2296,15 @@ ORDER BY t.`商品编号`;&quot;;
2272 2296 }
2273 2297  
2274 2298 /// <summary>
2275   - /// 审核销售出库单(支持两级审核)
  2299 + /// 审核销售出库单(支持两级审核)。收银台「门店」、抖音「抖音订单」等外部来源无需在此审核。
2276 2300 /// </summary>
  2301 + /// <param name="id">销售出库单主键</param>
2277 2302 [HttpPost("ApproveSalesOutbound/{id}")]
2278 2303 public async Task<dynamic> ApproveSalesOutbound(string id)
2279 2304 {
  2305 + var header = await _db.Queryable<WtXsckdEntity>().FirstAsync(p => p.Id == id);
  2306 + if (header != null && IsSalesOutboundSkipErpAudit(header))
  2307 + return new { success = false, message = "收银台或抖音等外部来源的销售出库单无需在此审核" };
2280 2308 return await ApproveDocument(id, "销售出库单");
2281 2309 }
2282 2310  
... ...
Antis.Erp.Plat/sy/settlement.html
... ... @@ -967,6 +967,10 @@
967 967 "bz": "",
968 968 // 单据类型
969 969 "djlx": "销售出库单",
  970 + // 单据来源:与 ERP 后台「门店(收银台)」一致,用于免审、仅查看
  971 + "ly": "门店",
  972 + // 收银台成交即生效,不走 ERP 二级审核
  973 + "djzt": "已审核",
970 974 // 会员手机号码
971 975 "hysjh": "",
972 976 // 销售出库单明细列表
... ... @@ -2469,6 +2473,8 @@
2469 2473 this.info.zdr = this.selectedCashier;
2470 2474 this.info.djrq = new Date().toISOString(); // 确保日期时间正确
2471 2475 delete this.info.id; // 让后端生成ID
  2476 + this.info.ly = '门店';
  2477 + this.info.djzt = '已审核';
2472 2478  
2473 2479 axios({
2474 2480 url: this.baseUrl + "/api/Extend/wtxsckd",
... ... @@ -2785,6 +2791,9 @@
2785 2791 }
2786 2792  
2787 2793 delete orderData.id; // 让后端生成ID
  2794 + // 与 info 一致:收银台固定来源与状态(避免 spread 遗漏或被覆盖)
  2795 + orderData.ly = '门店';
  2796 + orderData.djzt = '已审核';
2788 2797  
2789 2798 console.log(`✅ 创建${documentType},商品数量: ${items.length},订单金额: ${groupTotalAmount.toFixed(2)}`);
2790 2799  
... ...