index.vue 73 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148
  1. <template>
  2. <div class="page-container">
  3. <div class="page-head">
  4. <h1>商家结算管理</h1>
  5. <p class="page-desc">按区域管理待结算订单与结算单记录,支持结算、查看和编辑结算单。</p>
  6. </div>
  7. <div class="region-tabs region-tab-strip">
  8. <div class="tab-button-group region-tab-group">
  9. <el-button
  10. v-for="r in regionList"
  11. :key="r.id"
  12. class="region-tab-btn"
  13. :type="activeRegionId === r.id ? 'primary' : 'default'"
  14. @click="selectRegion(r.id)"
  15. >
  16. {{ r.name }}
  17. </el-button>
  18. </div>
  19. </div>
  20. <el-tabs v-model="activeTab" class="settlement-page-tabs" @tab-change="onTabChange">
  21. <el-tab-pane label="待结算订单" name="pending">
  22. <section class="panel">
  23. <div class="panel-top">
  24. <h2 class="title-bar-heading">待结算订单</h2>
  25. </div>
  26. <el-card shadow="never" class="pending-main-card">
  27. <div class="filter-block">
  28. <el-form :model="searchForm" class="filter-form" label-position="top">
  29. <div class="filter-grid">
  30. <el-form-item label="供应商">
  31. <el-select v-model="searchForm.supplierId" filterable clearable placeholder="请选择供应商">
  32. <el-option
  33. v-for="u in supplierOptions"
  34. :key="u.id"
  35. :label="u.realName || u.username"
  36. :value="u.id"
  37. />
  38. </el-select>
  39. </el-form-item>
  40. <el-form-item label="订单号">
  41. <el-input v-model="searchForm.keyword" clearable placeholder="请输入订单号" />
  42. </el-form-item>
  43. <el-form-item label="日期区间">
  44. <el-date-picker
  45. v-model="dateRange"
  46. type="daterange"
  47. value-format="YYYY-MM-DDTHH:mm:ss"
  48. range-separator="至"
  49. start-placeholder="开始日期"
  50. end-placeholder="结束日期"
  51. />
  52. </el-form-item>
  53. </div>
  54. <div class="filter-actions-row">
  55. <div class="filter-actions-end">
  56. <el-button class="btn-ghost" @click="handleReset">重置</el-button>
  57. <el-button class="btn-main" type="primary" @click="handleSearch">搜索</el-button>
  58. </div>
  59. </div>
  60. </el-form>
  61. </div>
  62. <div class="toolbar">
  63. <span class="list-toolbar-selected" aria-live="polite"
  64. >已选<span class="sel-num">{{ selectedCount }}</span
  65. >项</span
  66. >
  67. <el-button class="btn-ghost" @click="handleSelectAllFiltered">全选可结算订单(已完结·跨分页)</el-button>
  68. <el-button class="btn-main" :disabled="!selectedCount" @click="openSettlementDialog">结算</el-button>
  69. <div class="toolbar-inline-stats" role="status">
  70. <template v-if="selectedCount">
  71. <span class="toolbar-stat"
  72. >本次结算金额 <strong class="stat-money">¥{{ formatAmount(selectedAmount) }}</strong></span
  73. >
  74. <span class="toolbar-stat">本次结算订单数 <strong>{{ selectedCount }}</strong></span>
  75. </template>
  76. <span class="toolbar-stat"
  77. >历史结算金额 <strong class="stat-money">¥{{ formatAmount(historySummary.amount) }}</strong></span
  78. >
  79. <span class="toolbar-stat">历史结算订单数 <strong>{{ historySummary.orderCount }}</strong></span>
  80. </div>
  81. <el-tag v-if="selectionMode === 'allFiltered'" type="warning">已启用跨分页全选,可取消少量订单</el-tag>
  82. </div>
  83. <el-table
  84. ref="tableRef"
  85. class="supplier-settlement-table"
  86. :data="tableData"
  87. row-key="id"
  88. stripe
  89. v-loading="loading"
  90. max-height="calc(100vh - 440px)"
  91. @selection-change="handleSelectionChange"
  92. @expand-change="handleRowExpand"
  93. >
  94. <el-table-column type="expand" width="48">
  95. <template #default="{ row }">
  96. <div class="order-row-expand-wrap">
  97. <div
  98. v-if="getExpandedItems(row.id).length"
  99. class="settlement-row-expand-table-wrap sqxp-subtable-wrap"
  100. >
  101. <el-table
  102. :data="getExpandedItems(row.id)"
  103. stripe
  104. size="small"
  105. class="order-row-expand-table sqxp-subtable"
  106. @row-dblclick="openOrderProductView"
  107. >
  108. <el-table-column prop="productName" label="商品" min-width="220">
  109. <template #default="{ row: line }">
  110. <div class="expand-product-cell">
  111. <div class="expand-product-thumb-wrap">
  112. <el-image
  113. v-if="line.imageUrl"
  114. :src="line.imageUrl"
  115. fit="cover"
  116. class="expand-product-thumb"
  117. />
  118. <span v-else class="expand-product-thumb placeholder">无图</span>
  119. </div>
  120. <span class="expand-product-name">{{ line.productName || '-' }}</span>
  121. </div>
  122. </template>
  123. </el-table-column>
  124. <el-table-column prop="supplierName" label="供货商" min-width="120" show-overflow-tooltip>
  125. <template #default="{ row: line }">{{ line.supplierName || '-' }}</template>
  126. </el-table-column>
  127. <el-table-column label="计量" width="88" align="center">
  128. <template #default="{ row: line }">{{ formatMeasureCell(line.measureValue) }}</template>
  129. </el-table-column>
  130. <el-table-column prop="unitName" label="单位" width="70" align="center" />
  131. <el-table-column prop="quantity" label="数量" width="80" align="right" />
  132. <el-table-column label="供货价" min-width="110" align="right">
  133. <template #default="{ row: line }">¥{{ formatAmount(line.supplierUnitPrice) }}</template>
  134. </el-table-column>
  135. <el-table-column label="供货金额" min-width="110" align="right">
  136. <template #default="{ row: line }">¥{{ formatAmount(line.supplierAmount) }}</template>
  137. </el-table-column>
  138. <el-table-column label="商品售价" min-width="110" align="right">
  139. <template #default="{ row: line }"
  140. >¥{{ formatAmount(line.sellingUnitPrice ?? line.unitPrice) }}</template
  141. >
  142. </el-table-column>
  143. <el-table-column label="商品金额" min-width="110" align="right">
  144. <template #default="{ row: line }"
  145. >¥{{ formatAmount(line.sellingAmount ?? line.amount) }}</template
  146. >
  147. </el-table-column>
  148. </el-table>
  149. <div class="order-row-expand-summary sqxp-subtable-summary">
  150. <span
  151. >供货总金额(元):<span class="sqxp-subtable-summary__amount"
  152. >¥{{ formatAmount(row.settlementAmount) }}</span
  153. ></span
  154. >
  155. <span
  156. >售出总金额(元):<span class="sqxp-subtable-summary__amount"
  157. >¥{{ formatAmount(row.clientSellingTotal) }}</span
  158. ></span
  159. >
  160. </div>
  161. </div>
  162. <el-empty v-else description="暂无商品明细" :image-size="72" />
  163. </div>
  164. </template>
  165. </el-table-column>
  166. <el-table-column type="selection" width="48" :selectable="rowSelectable" />
  167. <el-table-column label="订单编号" min-width="180" show-overflow-tooltip>
  168. <template #default="{ row }">
  169. <el-button link type="primary" @click="toggleRowExpand(row)">{{ row.orderNo }}</el-button>
  170. </template>
  171. </el-table-column>
  172. <el-table-column prop="supplierName" label="供应商名称" min-width="150" show-overflow-tooltip />
  173. <el-table-column prop="regionName" label="区域" min-width="120" show-overflow-tooltip />
  174. <el-table-column prop="settlementAmount" label="供货总金额(元)" min-width="140" align="right">
  175. <template #default="{ row }">
  176. <span class="amount-text">¥{{ formatAmount(row.settlementAmount) }}</span>
  177. </template>
  178. </el-table-column>
  179. <el-table-column prop="totalAmount" label="订单金额(元)" min-width="130" align="right">
  180. <template #default="{ row }">
  181. <span class="amount-text">¥{{ formatAmount(orderTotalAmount(row)) }}</span>
  182. </template>
  183. </el-table-column>
  184. <el-table-column label="订单状态" min-width="120">
  185. <template #default="{ row }">
  186. <el-tag :type="orderStatusTagType(resolveOrderStatus(row))" size="small">
  187. {{ orderStatusLabel(resolveOrderStatus(row)) }}
  188. </el-tag>
  189. </template>
  190. </el-table-column>
  191. <el-table-column label="配送状态" min-width="120">
  192. <template #default="{ row }">
  193. <el-tag :type="deliveryStatusTagType(resolveDeliveryStatus(row))" size="small">
  194. {{ deliveryStatusLabel(resolveDeliveryStatus(row)) }}
  195. </el-tag>
  196. </template>
  197. </el-table-column>
  198. <el-table-column label="支付状态" min-width="120">
  199. <template #default="{ row }">
  200. <el-tag :type="payStatusTagType(resolvePayStatus(row))" size="small">
  201. {{ payStatusLabel(resolvePayStatus(row)) }}
  202. </el-tag>
  203. </template>
  204. </el-table-column>
  205. <el-table-column label="支付方式" min-width="110" align="center">
  206. <template #default="{ row }">
  207. <el-tag :type="resolvePaymentMethod(row) === 'offline' ? 'warning' : 'primary'" size="small">
  208. {{ paymentMethodLabel(resolvePaymentMethod(row)) }}
  209. </el-tag>
  210. </template>
  211. </el-table-column>
  212. <el-table-column prop="createAt" label="下单时间" min-width="170" />
  213. <el-table-column prop="supplierSettlementStatus" label="商家结算" width="100">
  214. <template #default="{ row }">
  215. <el-tag :type="row.supplierSettlementStatus === 'settled' ? 'success' : 'warning'" size="small">
  216. {{ row.supplierSettlementStatus === 'settled' ? '已结算' : '待结算' }}
  217. </el-tag>
  218. </template>
  219. </el-table-column>
  220. <el-table-column label="操作" min-width="120" fixed="right" align="center" header-align="center">
  221. <template #default="{ row }">
  222. <el-button class="btn-op-item primary" @click="openOrderOperate(row)">查看/运营</el-button>
  223. </template>
  224. </el-table-column>
  225. </el-table>
  226. <div class="pagination list-pager-bar">
  227. <el-pagination
  228. :current-page="pagination.page"
  229. :page-size="pagination.pageSize"
  230. :page-sizes="[10, 20, 50, 100]"
  231. :total="pagination.total"
  232. layout="total, sizes, prev, pager, next, jumper"
  233. @size-change="
  234. (val) => {
  235. pagination.pageSize = val
  236. loadData()
  237. }
  238. "
  239. @current-change="
  240. (val) => {
  241. pagination.page = val
  242. loadData()
  243. }
  244. "
  245. />
  246. </div>
  247. </el-card>
  248. </section>
  249. </el-tab-pane>
  250. <el-tab-pane label="结算单记录" name="sheets">
  251. <section class="panel">
  252. <div class="panel-top">
  253. <h2 class="title-bar-heading">结算单记录</h2>
  254. </div>
  255. <el-card shadow="never" class="sheet-main-card" v-loading="sheetLoading">
  256. <div class="filter-block">
  257. <el-form :model="sheetSearchForm" class="filter-form" label-position="top">
  258. <div class="filter-grid">
  259. <el-form-item label="供应商">
  260. <el-select v-model="sheetSearchForm.supplierId" filterable clearable placeholder="全部">
  261. <el-option
  262. v-for="u in supplierOptions"
  263. :key="u.id"
  264. :label="u.realName || u.username"
  265. :value="u.id"
  266. />
  267. </el-select>
  268. </el-form-item>
  269. <el-form-item label="结算单号">
  270. <el-input v-model="sheetSearchForm.settlementNo" clearable placeholder="模糊查询" />
  271. </el-form-item>
  272. <el-form-item label="结算时间">
  273. <el-date-picker
  274. v-model="sheetDateRange"
  275. type="daterange"
  276. value-format="YYYY-MM-DDTHH:mm:ss"
  277. range-separator="至"
  278. start-placeholder="开始"
  279. end-placeholder="结束"
  280. />
  281. </el-form-item>
  282. </div>
  283. <div class="filter-actions-row">
  284. <div class="filter-actions-end">
  285. <el-button class="btn-ghost" @click="handleSheetReset">重置</el-button>
  286. <el-button class="btn-main" type="primary" @click="handleSheetSearch">搜索</el-button>
  287. </div>
  288. </div>
  289. </el-form>
  290. </div>
  291. <div class="sheet-summary-bar" role="status">
  292. <div class="sheet-summary-group">
  293. <span class="sheet-summary-label">历史结算总金额</span>
  294. <span class="sheet-summary-value amount-text">¥{{ formatAmount(sheetHistorySummary.amount) }}</span>
  295. </div>
  296. <div class="sheet-summary-group">
  297. <span class="sheet-summary-label">历史结算总订单数</span>
  298. <span class="sheet-summary-value amount-text">{{ sheetHistorySummary.orderCount }}</span>
  299. </div>
  300. <div class="sheet-summary-divider" aria-hidden="true"></div>
  301. <div class="sheet-summary-group">
  302. <span class="sheet-summary-label">当前检索结算总金额</span>
  303. <span class="sheet-summary-value amount-text">¥{{ formatAmount(sheetCurrentSummary.amount) }}</span>
  304. </div>
  305. <div class="sheet-summary-group">
  306. <span class="sheet-summary-label">当前检索结算总订单数</span>
  307. <span class="sheet-summary-value amount-text">{{ sheetCurrentSummary.orderCount }}</span>
  308. </div>
  309. </div>
  310. <el-table
  311. class="supplier-settlement-sheet-table"
  312. :data="sheetTableData"
  313. stripe
  314. @expand-change="handleSheetRowExpand"
  315. >
  316. <el-table-column type="expand" width="48">
  317. <template #default="{ row }">
  318. <div class="sheet-row-expand-wrap" v-loading="isSheetRowLoading(row.id)">
  319. <el-table
  320. v-if="getSheetOrders(row.id).length"
  321. :data="getSheetOrders(row.id)"
  322. row-key="id"
  323. stripe
  324. size="small"
  325. class="sheet-orders-table"
  326. @expand-change="handleSheetOrderExpand(row.id)"
  327. >
  328. <el-table-column type="expand" width="48">
  329. <template #default="{ row: ord }">
  330. <div class="order-row-expand-wrap">
  331. <div
  332. v-if="Array.isArray(ord.children) && ord.children.length"
  333. class="settlement-row-expand-table-wrap sqxp-subtable-wrap"
  334. >
  335. <el-table
  336. :data="ord.children"
  337. stripe
  338. size="small"
  339. class="order-row-expand-table sqxp-subtable"
  340. @row-dblclick="openOrderProductView"
  341. >
  342. <el-table-column prop="productName" label="商品" min-width="220">
  343. <template #default="{ row: line }">
  344. <div class="expand-product-cell">
  345. <div class="expand-product-thumb-wrap">
  346. <el-image
  347. v-if="line.imageUrl"
  348. :src="line.imageUrl"
  349. fit="cover"
  350. class="expand-product-thumb"
  351. />
  352. <span v-else class="expand-product-thumb placeholder">无图</span>
  353. </div>
  354. <span class="expand-product-name">{{ line.productName || '-' }}</span>
  355. </div>
  356. </template>
  357. </el-table-column>
  358. <el-table-column prop="supplierName" label="供货商" min-width="120" show-overflow-tooltip>
  359. <template #default="{ row: line }">{{ line.supplierName || '-' }}</template>
  360. </el-table-column>
  361. <el-table-column label="计量" width="88" align="center">
  362. <template #default="{ row: line }">{{
  363. formatMeasureCell(line.measureValue)
  364. }}</template>
  365. </el-table-column>
  366. <el-table-column prop="unitName" label="单位" width="70" align="center" />
  367. <el-table-column prop="quantity" label="数量" width="80" align="right" />
  368. <el-table-column label="供货价" min-width="110" align="right">
  369. <template #default="{ row: line }"
  370. >¥{{ formatAmount(line.supplierUnitPrice) }}</template
  371. >
  372. </el-table-column>
  373. <el-table-column label="供货金额" min-width="110" align="right">
  374. <template #default="{ row: line }"
  375. >¥{{ formatAmount(line.supplierAmount) }}</template
  376. >
  377. </el-table-column>
  378. <el-table-column label="商品售价" min-width="110" align="right">
  379. <template #default="{ row: line }"
  380. >¥{{ formatAmount(line.sellingUnitPrice ?? line.unitPrice) }}</template
  381. >
  382. </el-table-column>
  383. <el-table-column label="商品金额" min-width="110" align="right">
  384. <template #default="{ row: line }"
  385. >¥{{ formatAmount(line.sellingAmount ?? line.amount) }}</template
  386. >
  387. </el-table-column>
  388. </el-table>
  389. <div class="order-row-expand-summary sqxp-subtable-summary">
  390. <span
  391. >供货总金额(元):<span class="sqxp-subtable-summary__amount"
  392. >¥{{ formatAmount(sheetOrderSupplierTotal(ord)) }}</span
  393. ></span
  394. >
  395. <span
  396. >售出总金额(元):<span class="sqxp-subtable-summary__amount"
  397. >¥{{ formatAmount(sheetOrderSellingTotal(ord)) }}</span
  398. ></span
  399. >
  400. </div>
  401. </div>
  402. <el-empty v-else description="暂无商品明细" :image-size="72" />
  403. </div>
  404. </template>
  405. </el-table-column>
  406. <el-table-column label="订单编号" min-width="180" show-overflow-tooltip>
  407. <template #default="{ row: ord }">
  408. <strong>{{ ord.orderNo }}</strong>
  409. </template>
  410. </el-table-column>
  411. <el-table-column prop="clientName" label="客户名称" min-width="150" show-overflow-tooltip />
  412. <el-table-column prop="regionName" label="区域" min-width="110" show-overflow-tooltip />
  413. <el-table-column label="供货总金额(元)" min-width="140" align="right">
  414. <template #default="{ row: ord }">
  415. <span class="amount-text">¥{{ formatAmount(sheetOrderSupplierTotal(ord)) }}</span>
  416. </template>
  417. </el-table-column>
  418. <el-table-column label="订单金额(元)" min-width="130" align="right">
  419. <template #default="{ row: ord }">
  420. <span class="amount-text">¥{{ formatAmount(orderTotalAmount(ord)) }}</span>
  421. </template>
  422. </el-table-column>
  423. <el-table-column label="订单状态" min-width="120">
  424. <template #default="{ row: ord }">
  425. <el-tag :type="orderStatusTagType(resolveOrderStatus(ord))" size="small">
  426. {{ orderStatusLabel(resolveOrderStatus(ord)) }}
  427. </el-tag>
  428. </template>
  429. </el-table-column>
  430. <el-table-column label="配送状态" min-width="120">
  431. <template #default="{ row: ord }">
  432. <el-tag :type="deliveryStatusTagType(resolveDeliveryStatus(ord))" size="small">
  433. {{ deliveryStatusLabel(resolveDeliveryStatus(ord)) }}
  434. </el-tag>
  435. </template>
  436. </el-table-column>
  437. <el-table-column label="支付状态" min-width="120">
  438. <template #default="{ row: ord }">
  439. <el-tag :type="payStatusTagType(resolvePayStatus(ord))" size="small">
  440. {{ payStatusLabel(resolvePayStatus(ord)) }}
  441. </el-tag>
  442. </template>
  443. </el-table-column>
  444. <el-table-column label="支付方式" min-width="110" align="center">
  445. <template #default="{ row: ord }">
  446. <el-tag
  447. :type="resolvePaymentMethod(ord) === 'offline' ? 'warning' : 'primary'"
  448. size="small"
  449. >
  450. {{ paymentMethodLabel(resolvePaymentMethod(ord)) }}
  451. </el-tag>
  452. </template>
  453. </el-table-column>
  454. <el-table-column prop="orderCreateAt" label="下单时间" min-width="170" />
  455. </el-table>
  456. <el-empty v-else description="暂无明细" :image-size="72" />
  457. </div>
  458. </template>
  459. </el-table-column>
  460. <el-table-column prop="settlementNo" label="结算单号" min-width="200" />
  461. <el-table-column prop="supplierName" label="供应商" min-width="120" />
  462. <el-table-column prop="orderCount" label="订单数" width="90" align="center" />
  463. <el-table-column prop="totalAmount" label="结算金额(元)" min-width="120" align="right">
  464. <template #default="{ row }">
  465. <span class="amount-text">¥{{ formatAmount(row.totalAmount) }}</span>
  466. </template>
  467. </el-table-column>
  468. <el-table-column prop="settlementAt" label="结算时间" min-width="170" />
  469. <el-table-column prop="createName" label="操作人" width="100" />
  470. <el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
  471. <template #default="{ row }">
  472. <el-button class="btn-op-item primary" @click="openSheetDetail(row.id)">详情</el-button>
  473. <el-button class="btn-op-item" @click="openSheetEdit(row)">编辑</el-button>
  474. </template>
  475. </el-table-column>
  476. </el-table>
  477. <div class="pagination list-pager-bar">
  478. <el-pagination
  479. :current-page="sheetPagination.page"
  480. :page-size="sheetPagination.pageSize"
  481. :page-sizes="[10, 20, 50]"
  482. :total="sheetPagination.total"
  483. layout="total, sizes, prev, pager, next, jumper"
  484. @size-change="
  485. (val) => {
  486. sheetPagination.pageSize = val
  487. loadSheetList()
  488. }
  489. "
  490. @current-change="
  491. (val) => {
  492. sheetPagination.page = val
  493. loadSheetList()
  494. }
  495. "
  496. />
  497. </div>
  498. </el-card>
  499. </section>
  500. </el-tab-pane>
  501. </el-tabs>
  502. <el-dialog
  503. v-model="settlementDialogVisible"
  504. title="确认商家结算"
  505. width="720px"
  506. destroy-on-close
  507. @closed="resetSettlementDialog"
  508. >
  509. <el-alert type="info" :closable="false" class="mb-3">
  510. 已选 {{ selectedCount }} 笔订单,合计
  511. <strong>¥{{ formatAmount(selectedAmount) }}</strong>
  512. </el-alert>
  513. <VoucherMediaUploader v-model="settlementVoucherUrls" business-type="supplier_settlement" />
  514. <el-form label-width="100px" class="settlement-remark-form">
  515. <el-form-item label="结算备注">
  516. <el-input
  517. v-model="settlementRemark"
  518. type="textarea"
  519. :rows="3"
  520. maxlength="500"
  521. show-word-limit
  522. placeholder="选填"
  523. />
  524. </el-form-item>
  525. </el-form>
  526. <template #footer>
  527. <el-button class="btn-ghost" @click="settlementDialogVisible = false"><el-icon><Close /></el-icon>取消</el-button>
  528. <el-button class="btn-main" :loading="submitLoading" @click="confirmSettlementSubmit">
  529. <el-icon><Check /></el-icon>确认结算
  530. </el-button>
  531. </template>
  532. </el-dialog>
  533. <el-dialog
  534. v-model="sheetDetailVisible"
  535. title="结算单详情"
  536. width="1240px"
  537. destroy-on-close
  538. class="supplier-settlement-dialog sheet-detail-dialog"
  539. modal-class="supplier-settlement-dialog-mask"
  540. >
  541. <div class="dialog-scroll sheet-detail-dialog-scroll">
  542. <SettlementSheetDetailPanel
  543. v-if="sheetDetail"
  544. :detail="sheetDetail"
  545. counterparty-label="供应商"
  546. :counterparty-name="sheetDetail.supplierName"
  547. />
  548. </div>
  549. </el-dialog>
  550. <el-dialog
  551. v-model="sheetEditVisible"
  552. title="编辑结算单"
  553. width="720px"
  554. destroy-on-close
  555. @closed="resetSheetEdit"
  556. class="supplier-settlement-dialog sheet-edit-dialog"
  557. modal-class="supplier-settlement-dialog-mask"
  558. >
  559. <div class="dialog-scroll sheet-edit-dialog-scroll">
  560. <div class="form-section">
  561. <h4>结算信息</h4>
  562. <el-form label-position="top">
  563. <div class="form-grid-2">
  564. <el-form-item label="结算备注" class="form-col-span-2">
  565. <el-input
  566. v-model="sheetEditForm.remark"
  567. type="textarea"
  568. :rows="4"
  569. maxlength="500"
  570. show-word-limit
  571. placeholder="请输入结算备注(选填)"
  572. />
  573. </el-form-item>
  574. </div>
  575. </el-form>
  576. </div>
  577. <div class="form-section">
  578. <h4>结算凭证</h4>
  579. <VoucherMediaUploader
  580. v-model="sheetEditForm.voucherUrls"
  581. business-type="supplier_settlement"
  582. :business-id="sheetEditId || 'temp'"
  583. />
  584. </div>
  585. </div>
  586. <template #footer>
  587. <el-button class="btn-ghost" @click="sheetEditVisible = false"><el-icon><Close /></el-icon>取消</el-button>
  588. <el-button class="btn-main" :loading="sheetEditSaving" @click="saveSheetEdit">
  589. <el-icon><Check /></el-icon>保存
  590. </el-button>
  591. </template>
  592. </el-dialog>
  593. <ProductViewDialog v-model="productViewDialogVisible" :product-id="productViewTargetId" />
  594. <OrderOperateLocalDialog v-model="orderOperateDialogVisible" :order-id="orderOperateTargetId" />
  595. </div>
  596. </template>
  597. <script setup lang="ts">
  598. import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
  599. import { ElMessage, ElMessageBox } from 'element-plus'
  600. import { Check, Close } from '@element-plus/icons-vue'
  601. import {
  602. fetchCreateSupplierSettlement,
  603. fetchSupplierSettlementOrderList,
  604. fetchSupplierSettlementHistorySummary,
  605. fetchSupplierSettlementSheetList,
  606. fetchSupplierSettlementSheetDetail,
  607. fetchUpdateSupplierSettlementSheet
  608. } from '@/api/business'
  609. import { fetchUserList, fetchActiveRegions } from '@/api/admin'
  610. import { useUserStore } from '@/store/modules/user'
  611. import ProductViewDialog from '@/components/business/product-view-dialog/index.vue'
  612. import OrderOperateLocalDialog from '@/components/business/order-operate-local-dialog/index.vue'
  613. import VoucherMediaUploader from '@/components/settlement/VoucherMediaUploader.vue'
  614. import SettlementSheetDetailPanel from '@/components/settlement/SettlementSheetDetailPanel.vue'
  615. defineOptions({ name: 'SupplierSettlement' })
  616. const userStore = useUserStore()
  617. const regionList = ref<any[]>([])
  618. const activeRegionId = ref('')
  619. const activeTab = ref<'pending' | 'sheets'>('pending')
  620. const loading = ref(false)
  621. const submitLoading = ref(false)
  622. const tableRef = ref()
  623. const skipSelectionSync = ref(false)
  624. const tableData = ref<any[]>([])
  625. const expandedItemsMap = ref<Record<string, any[]>>({})
  626. const productViewDialogVisible = ref(false)
  627. const productViewTargetId = ref('')
  628. const orderOperateDialogVisible = ref(false)
  629. const orderOperateTargetId = ref('')
  630. const supplierOptions = ref<any[]>([])
  631. const currentPageIds = ref<string[]>([])
  632. const selectionMode = ref<'ids' | 'allFiltered'>('ids')
  633. const selectedIds = ref<Set<string>>(new Set())
  634. const excludedIds = ref<Set<string>>(new Set())
  635. const amountMap = ref<Record<string, number>>({})
  636. const settlementDialogVisible = ref(false)
  637. const settlementVoucherUrls = ref<string[]>([])
  638. const settlementRemark = ref('')
  639. const dateRange = ref<string[]>([])
  640. const searchForm = reactive({
  641. supplierId: '',
  642. keyword: ''
  643. })
  644. const pagination = reactive({
  645. page: 1,
  646. pageSize: 20,
  647. total: 0
  648. })
  649. const summary = reactive({
  650. pendingAmount: 0,
  651. currentPageAmount: 0,
  652. settleableOrderCount: 0
  653. })
  654. /** 当前区域+供应商下,已生成结算单的历史汇总(无勾选时展示) */
  655. const historySummary = reactive({
  656. amount: 0,
  657. orderCount: 0
  658. })
  659. const sheetLoading = ref(false)
  660. const sheetTableData = ref<any[]>([])
  661. const sheetSearchForm = reactive({
  662. supplierId: '',
  663. settlementNo: ''
  664. })
  665. const skipSheetSupplierAutoSearch = ref(false)
  666. const sheetHistorySummary = reactive({
  667. amount: 0,
  668. orderCount: 0
  669. })
  670. const sheetCurrentSummary = reactive({
  671. amount: 0,
  672. orderCount: 0
  673. })
  674. const sheetDateRange = ref<string[]>([])
  675. const sheetPagination = reactive({
  676. page: 1,
  677. pageSize: 20,
  678. total: 0
  679. })
  680. const sheetDetailVisible = ref(false)
  681. const sheetDetail = ref<any>(null)
  682. const sheetExpandedTreeMap = ref<Record<string, any[]>>({})
  683. const sheetRowLoadingMap = ref<Record<string, boolean>>({})
  684. const sheetEditVisible = ref(false)
  685. const sheetEditSaving = ref(false)
  686. const sheetEditId = ref('')
  687. const sheetEditForm = reactive({
  688. remark: '',
  689. voucherUrls: [] as string[]
  690. })
  691. const selectedCount = computed(() => {
  692. if (selectionMode.value === 'allFiltered')
  693. return Math.max(0, summary.settleableOrderCount - excludedIds.value.size)
  694. return selectedIds.value.size
  695. })
  696. const selectedAmount = computed(() => {
  697. if (selectionMode.value === 'allFiltered') {
  698. const excludedAmount = Array.from(excludedIds.value).reduce(
  699. (acc, id) => acc + (amountMap.value[id] || 0),
  700. 0
  701. )
  702. return Math.max(0, summary.pendingAmount - excludedAmount)
  703. }
  704. return Array.from(selectedIds.value).reduce((acc, id) => acc + (amountMap.value[id] || 0), 0)
  705. })
  706. const formatAmount = (val: number) => (Number(val || 0) || 0).toFixed(2)
  707. const formatMeasureCell = (v: unknown) => {
  708. if (v == null || v === '') return '-'
  709. const n = Number(v)
  710. if (Number.isNaN(n)) return '-'
  711. return n.toFixed(3).replace(/\.?0+$/, '')
  712. }
  713. const formatQty = (v: any) => (v == null || v === '' ? '—' : String(v))
  714. const formatSheetMeasure = (row: any) => {
  715. const u = row.unitName || ''
  716. const m = row.measureValue
  717. if (m != null && m !== '') return `${m} · ${u || '—'}`
  718. return u || '—'
  719. }
  720. const sheetOrderSupplierTotal = (orderRow: any) => {
  721. const children = Array.isArray(orderRow?.children) ? orderRow.children : []
  722. return children.reduce((acc: number, ln: any) => acc + Number(ln?.supplierAmount || 0), 0)
  723. }
  724. const sheetOrderSellingTotal = (orderRow: any) => {
  725. const children = Array.isArray(orderRow?.children) ? orderRow.children : []
  726. return children.reduce((acc: number, ln: any) => acc + Number(ln?.sellingAmount || 0), 0)
  727. }
  728. const getSheetOrders = (sheetId: string) => {
  729. const items = sheetExpandedTreeMap.value[sheetId] || []
  730. return items.filter((x: any) => x && x.rowType === 'order')
  731. }
  732. const handleSheetOrderExpand = (_sheetId: string) => (_row: any, _expandedRows: any[]) => {
  733. // 明细数据已在结算单详情接口一次性带回;此处仅占位以统一交互(可后续做只允许展开一行等优化)
  734. }
  735. const getSheetTreeItems = (sheetId: string) => sheetExpandedTreeMap.value[sheetId] || []
  736. const isSheetRowLoading = (sheetId: string) => !!sheetRowLoadingMap.value[sheetId]
  737. const handleSheetRowExpand = async (row: any, expandedRows: any[]) => {
  738. const expanded = expandedRows.some((r) => r.id === row.id)
  739. if (!expanded) return
  740. if (sheetExpandedTreeMap.value[row.id]) return
  741. sheetRowLoadingMap.value = { ...sheetRowLoadingMap.value, [row.id]: true }
  742. try {
  743. const detail: any = await fetchSupplierSettlementSheetDetail(row.id)
  744. sheetExpandedTreeMap.value = { ...sheetExpandedTreeMap.value, [row.id]: detail?.items || [] }
  745. } finally {
  746. sheetRowLoadingMap.value = { ...sheetRowLoadingMap.value, [row.id]: false }
  747. }
  748. }
  749. const ORDER_STATUS_MAP: Record<string, string> = {
  750. pending_payment: '待支付',
  751. pending_review_cancel: '待审核取消',
  752. pending_delivery: '待分配配送',
  753. delivering: '配送中',
  754. partial_delivered: '部分送达',
  755. pending_confirm: '待确认收货',
  756. completed: '已完结',
  757. cancelled: '已取消'
  758. }
  759. const orderStatusLabel = (s: string) => (s ? ORDER_STATUS_MAP[s] || s : '—')
  760. const resolveOrderStatus = (row: any) => {
  761. if (row?.cancelStatus === 'pending_review') return 'pending_review_cancel'
  762. return row?.orderStatus || row?.status || ''
  763. }
  764. const orderStatusTagType = (s: string) => {
  765. if (s === 'completed') return 'success'
  766. if (s === 'cancelled') return 'info'
  767. if (s === 'pending_payment' || s === 'partial_delivered') return 'warning'
  768. return 'primary'
  769. }
  770. const DELIVERY_STATUS_MAP: Record<string, string> = {
  771. pending_dispatch: '待分配配送',
  772. pending_delivery: '待配送',
  773. delivering: '配送中',
  774. partial_delivered: '部分送达',
  775. delivered: '配送完成',
  776. pending: '待取货',
  777. picking: '取货中',
  778. completed: '已完成'
  779. }
  780. const resolveDeliveryStatus = (row: any) =>
  781. row?.deliveryStatus ||
  782. (['pending_payment', 'pending_dispatch', 'pending_delivery', 'pending_review_cancel'].includes(
  783. resolveOrderStatus(row)
  784. )
  785. ? 'pending_dispatch'
  786. : resolveOrderStatus(row) === 'delivering'
  787. ? 'delivering'
  788. : resolveOrderStatus(row) === 'partial_delivered'
  789. ? 'partial_delivered'
  790. : ['pending_confirm', 'completed'].includes(resolveOrderStatus(row))
  791. ? 'delivered'
  792. : 'pending_dispatch')
  793. const deliveryStatusLabel = (s: string) => (s ? DELIVERY_STATUS_MAP[s] || s : '—')
  794. const deliveryStatusTagType = (s: string) => {
  795. if (s === 'delivered' || s === 'completed') return 'success'
  796. if (s === 'delivering') return 'info'
  797. if (s === 'partial_delivered' || s === 'pending_dispatch' || s === 'pending_delivery') return 'warning'
  798. if (s === 'picking') return 'primary'
  799. return 'info'
  800. }
  801. const resolvePayStatus = (row: any) =>
  802. row?.payStatus || (Number(row?.paidAmount || 0) > 0 ? 'paid' : 'pending_payment')
  803. const payStatusLabel = (s: string) => (s === 'paid' ? '已支付' : s === 'pending_payment' ? '待支付' : s || '—')
  804. const payStatusTagType = (s: string) => (s === 'paid' ? 'success' : s === 'pending_payment' ? 'warning' : 'info')
  805. const resolvePaymentMethod = (row: any) => String(row?.paymentMethod || '').toLowerCase()
  806. const paymentMethodLabel = (m: string) => {
  807. if (['online', 'wechat', 'wx'].includes(m)) return '线上支付'
  808. if (m === 'offline') return '线下支付'
  809. return m || '-'
  810. }
  811. const orderTotalAmount = (row: any) =>
  812. Number(row?.totalAmount ?? row?.sellingTotalAmount ?? row?.clientSellingTotal ?? row?.settlementAmount ?? 0)
  813. const orderReceivableAmount = (row: any) => Number(row?.receivableAmount ?? orderTotalAmount(row))
  814. // 线上/线下支付订单均支持商家结算,不在前端按 settlementSelectable 进行勾选限制。
  815. const rowSelectable = (row: any) => row.rowType === 'order'
  816. const getExpandedItems = (orderId: string) => expandedItemsMap.value[orderId] || []
  817. const handleRowExpand = (row: any, expandedRows: any[]) => {
  818. const expanded = expandedRows.some((r) => r.id === row.id)
  819. if (!expanded) return
  820. expandedItemsMap.value = {
  821. ...expandedItemsMap.value,
  822. [row.id]: Array.isArray(row.children) ? row.children : []
  823. }
  824. }
  825. const toggleRowExpand = (row: any) => {
  826. tableRef.value?.toggleRowExpansion?.(row)
  827. if (!expandedItemsMap.value[row.id]) {
  828. expandedItemsMap.value = {
  829. ...expandedItemsMap.value,
  830. [row.id]: Array.isArray(row.children) ? row.children : []
  831. }
  832. }
  833. }
  834. const openOrderProductView = (line: any) => {
  835. const pid = line?.productId
  836. if (!pid) return
  837. productViewTargetId.value = String(pid)
  838. productViewDialogVisible.value = true
  839. }
  840. const openOrderOperate = (row: any) => {
  841. const oid = row?.id
  842. if (!oid) return
  843. orderOperateTargetId.value = String(oid)
  844. orderOperateDialogVisible.value = true
  845. }
  846. const buildFilters = () => ({
  847. regionId: activeRegionId.value || undefined,
  848. supplierId: searchForm.supplierId || undefined,
  849. keyword: searchForm.keyword || undefined,
  850. startDate: dateRange.value?.[0] || undefined,
  851. endDate: dateRange.value?.[1] || undefined
  852. })
  853. const loadRegions = async () => {
  854. const res: any = await fetchActiveRegions()
  855. const all = res || []
  856. const bindId = userStore.getUserInfo?.regionId
  857. if (bindId) {
  858. regionList.value = all.filter((r: any) => r.id === bindId)
  859. if (!regionList.value.length && userStore.getUserInfo?.regionName) {
  860. regionList.value = [{ id: bindId, name: userStore.getUserInfo.regionName }]
  861. }
  862. } else {
  863. regionList.value = all
  864. }
  865. if (!activeRegionId.value && regionList.value.length > 0) {
  866. activeRegionId.value = regionList.value[0].id
  867. }
  868. }
  869. const selectRegion = async (id: string) => {
  870. if (!id || activeRegionId.value === id) return
  871. activeRegionId.value = id
  872. selectionMode.value = 'ids'
  873. selectedIds.value = new Set()
  874. excludedIds.value = new Set()
  875. pagination.page = 1
  876. sheetPagination.page = 1
  877. await loadSupplierOptions()
  878. applyDefaultPendingSupplierFilter()
  879. if (activeTab.value === 'pending') {
  880. await loadData()
  881. } else {
  882. await loadSheetList()
  883. }
  884. await nextTick()
  885. tableRef.value?.clearSelection?.()
  886. }
  887. const loadSupplierOptions = async () => {
  888. const pageSize = 100
  889. const maxPages = 100
  890. const all: any[] = []
  891. let page = 1
  892. let total = 0
  893. while (page <= maxPages) {
  894. const res: any = await fetchUserList({
  895. page,
  896. pageSize,
  897. role: 'supplier',
  898. isActive: true,
  899. regionId: activeRegionId.value || undefined
  900. })
  901. const list = res?.list || []
  902. total = Number(res?.total || 0)
  903. all.push(...list)
  904. if (list.length < pageSize || all.length >= total) break
  905. page++
  906. }
  907. supplierOptions.value = all
  908. }
  909. /** 待结算列表:默认选中当前区域下第一个供应商,保证进入页面即按单一供应商筛选 */
  910. const skipPendingSupplierAutoSearch = ref(false)
  911. const applyDefaultPendingSupplierFilter = () => {
  912. const first = supplierOptions.value[0]
  913. skipPendingSupplierAutoSearch.value = true
  914. searchForm.supplierId = first?.id ? String(first.id) : ''
  915. }
  916. const loadHistorySummary = async () => {
  917. if (activeTab.value !== 'pending') return
  918. try {
  919. const res: any = await fetchSupplierSettlementHistorySummary({
  920. regionId: activeRegionId.value || undefined,
  921. supplierId: searchForm.supplierId || undefined
  922. })
  923. historySummary.amount = Number(res?.historicalSettledAmount ?? 0)
  924. historySummary.orderCount = Number(res?.historicalSettledOrderCount ?? 0)
  925. } catch {
  926. historySummary.amount = 0
  927. historySummary.orderCount = 0
  928. }
  929. }
  930. const loadSheetHistorySummary = async () => {
  931. if (activeTab.value !== 'sheets') return
  932. try {
  933. const res: any = await fetchSupplierSettlementHistorySummary({
  934. regionId: activeRegionId.value || undefined,
  935. supplierId: sheetSearchForm.supplierId || undefined
  936. })
  937. sheetHistorySummary.amount = Number(res?.historicalSettledAmount ?? 0)
  938. sheetHistorySummary.orderCount = Number(res?.historicalSettledOrderCount ?? 0)
  939. } catch {
  940. sheetHistorySummary.amount = 0
  941. sheetHistorySummary.orderCount = 0
  942. }
  943. }
  944. const loadData = async () => {
  945. loading.value = true
  946. try {
  947. const res: any = await fetchSupplierSettlementOrderList({
  948. page: pagination.page,
  949. pageSize: pagination.pageSize,
  950. ...buildFilters()
  951. })
  952. tableData.value = res?.list || []
  953. expandedItemsMap.value = {}
  954. pagination.total = res?.total || 0
  955. summary.pendingAmount = Number(res?.summary?.pendingAmount || 0)
  956. summary.currentPageAmount = Number(res?.summary?.currentPageAmount || 0)
  957. summary.settleableOrderCount = Number(res?.summary?.settleableOrderCount || 0)
  958. currentPageIds.value = tableData.value
  959. .filter((x: any) => x.rowType === 'order')
  960. .map((x: any) => x.id)
  961. tableData.value.forEach((x: any) => {
  962. if (x.rowType === 'order') {
  963. amountMap.value[x.id] = Number(x.settlementAmount ?? 0)
  964. }
  965. })
  966. await nextTick()
  967. skipSelectionSync.value = true
  968. tableRef.value?.clearSelection?.()
  969. await nextTick()
  970. if (selectionMode.value === 'ids') {
  971. tableData.value.forEach((row: any) => {
  972. if (row.rowType === 'order' && selectedIds.value.has(row.id)) {
  973. tableRef.value?.toggleRowSelection?.(row, true)
  974. }
  975. })
  976. } else {
  977. tableData.value.forEach((row: any) => {
  978. if (row.rowType === 'order' && !excludedIds.value.has(row.id)) {
  979. tableRef.value?.toggleRowSelection?.(row, true)
  980. }
  981. })
  982. }
  983. await nextTick()
  984. skipSelectionSync.value = false
  985. await loadHistorySummary()
  986. } finally {
  987. loading.value = false
  988. }
  989. }
  990. const loadSheetList = async () => {
  991. sheetLoading.value = true
  992. try {
  993. const res: any = await fetchSupplierSettlementSheetList({
  994. page: sheetPagination.page,
  995. pageSize: sheetPagination.pageSize,
  996. regionId: activeRegionId.value || undefined,
  997. supplierId: sheetSearchForm.supplierId || undefined,
  998. settlementNo: sheetSearchForm.settlementNo || undefined,
  999. startDate: sheetDateRange.value?.[0] || undefined,
  1000. endDate: sheetDateRange.value?.[1] || undefined
  1001. })
  1002. sheetTableData.value = res?.list || []
  1003. const list = Array.isArray(sheetTableData.value) ? sheetTableData.value : []
  1004. sheetCurrentSummary.amount = Number(
  1005. res?.summary?.totalAmount ??
  1006. list.reduce((acc: number, x: any) => acc + Number(x?.totalAmount || 0), 0)
  1007. )
  1008. sheetCurrentSummary.orderCount = Number(
  1009. res?.summary?.totalOrderCount ??
  1010. list.reduce((acc: number, x: any) => acc + Number(x?.orderCount || 0), 0)
  1011. )
  1012. sheetExpandedTreeMap.value = {}
  1013. sheetRowLoadingMap.value = {}
  1014. sheetPagination.total = res?.total || 0
  1015. await loadSheetHistorySummary()
  1016. } finally {
  1017. sheetLoading.value = false
  1018. }
  1019. }
  1020. const onTabChange = (name: string | number) => {
  1021. if (name === 'sheets') {
  1022. skipSheetSupplierAutoSearch.value = true
  1023. sheetSearchForm.supplierId = searchForm.supplierId || ''
  1024. sheetPagination.page = 1
  1025. loadSheetList()
  1026. }
  1027. if (name === 'pending') {
  1028. // 结算单记录 tab 中切换过供应商后,回到待结算订单时保持同一筛选条件
  1029. skipPendingSupplierAutoSearch.value = true
  1030. searchForm.supplierId = sheetSearchForm.supplierId || ''
  1031. pagination.page = 1
  1032. loadData()
  1033. }
  1034. }
  1035. const handleSearch = () => {
  1036. pagination.page = 1
  1037. selectionMode.value = 'ids'
  1038. selectedIds.value = new Set()
  1039. excludedIds.value = new Set()
  1040. loadData()
  1041. }
  1042. const handleReset = () => {
  1043. searchForm.keyword = ''
  1044. dateRange.value = []
  1045. applyDefaultPendingSupplierFilter()
  1046. handleSearch()
  1047. }
  1048. const handleSheetSearch = () => {
  1049. sheetPagination.page = 1
  1050. loadSheetList()
  1051. }
  1052. const handleSheetReset = () => {
  1053. skipSheetSupplierAutoSearch.value = true
  1054. sheetSearchForm.supplierId = ''
  1055. sheetSearchForm.settlementNo = ''
  1056. sheetDateRange.value = []
  1057. handleSheetSearch()
  1058. }
  1059. const handleSelectionChange = (rows: any[]) => {
  1060. if (skipSelectionSync.value) return
  1061. const orderRows = rows.filter((x: any) => x.rowType === 'order')
  1062. const selectedOnPage = new Set(orderRows.map((x: any) => x.id))
  1063. if (selectionMode.value === 'allFiltered') {
  1064. currentPageIds.value.forEach((id) => {
  1065. if (selectedOnPage.has(id)) excludedIds.value.delete(id)
  1066. else excludedIds.value.add(id)
  1067. })
  1068. return
  1069. }
  1070. currentPageIds.value.forEach((id) => selectedIds.value.delete(id))
  1071. selectedOnPage.forEach((id) => selectedIds.value.add(id))
  1072. }
  1073. const handleSelectAllFiltered = async () => {
  1074. if (!summary.settleableOrderCount) {
  1075. ElMessage.warning('当前检索条件下没有可结算的已完结订单')
  1076. return
  1077. }
  1078. await ElMessageBox.confirm(
  1079. `将全选当前检索条件下的全部已完结订单(共 ${summary.settleableOrderCount} 笔,可结算供货合计约 ¥${formatAmount(summary.pendingAmount)})。未完成订单不会参与结算。是否继续?`,
  1080. '跨分页全选',
  1081. { type: 'warning' }
  1082. )
  1083. selectionMode.value = 'allFiltered'
  1084. selectedIds.value = new Set()
  1085. excludedIds.value = new Set()
  1086. ElMessage.success('已启用跨分页全选')
  1087. await nextTick()
  1088. skipSelectionSync.value = true
  1089. tableRef.value?.clearSelection?.()
  1090. await nextTick()
  1091. tableData.value.forEach((row: any) => {
  1092. if (row.rowType === 'order' && !excludedIds.value.has(row.id)) {
  1093. tableRef.value?.toggleRowSelection?.(row, true)
  1094. }
  1095. })
  1096. await nextTick()
  1097. skipSelectionSync.value = false
  1098. }
  1099. const openSettlementDialog = () => {
  1100. if (!selectedCount.value) {
  1101. ElMessage.warning('请先选择要结算的订单')
  1102. return
  1103. }
  1104. if (selectionMode.value === 'ids') {
  1105. const picked = tableData.value.filter(
  1106. (x: any) => x.rowType === 'order' && selectedIds.value.has(x.id)
  1107. )
  1108. const sids = [...new Set(picked.map((x: any) => x.supplierId).filter(Boolean))]
  1109. if (sids.length > 1) {
  1110. ElMessage.warning('请选择同一供应商的订单后再结算')
  1111. return
  1112. }
  1113. }
  1114. settlementVoucherUrls.value = []
  1115. settlementRemark.value = ''
  1116. settlementDialogVisible.value = true
  1117. }
  1118. const resetSettlementDialog = () => {
  1119. settlementVoucherUrls.value = []
  1120. settlementRemark.value = ''
  1121. }
  1122. const confirmSettlementSubmit = async () => {
  1123. if (!selectedCount.value) return ElMessage.warning('请先选择要结算的订单')
  1124. if (!settlementVoucherUrls.value.length) return ElMessage.warning('请先上传结算凭证')
  1125. submitLoading.value = true
  1126. try {
  1127. await fetchCreateSupplierSettlement({
  1128. selectionMode: selectionMode.value,
  1129. orderIds: Array.from(selectedIds.value),
  1130. filters: buildFilters(),
  1131. excludedOrderIds: Array.from(excludedIds.value),
  1132. voucherUrls: settlementVoucherUrls.value,
  1133. remark: settlementRemark.value || undefined
  1134. })
  1135. ElMessage.success('商家结算完成')
  1136. settlementDialogVisible.value = false
  1137. selectionMode.value = 'ids'
  1138. selectedIds.value = new Set()
  1139. excludedIds.value = new Set()
  1140. loadData()
  1141. if (activeTab.value === 'sheets') loadSheetList()
  1142. await nextTick()
  1143. tableRef.value?.clearSelection?.()
  1144. } finally {
  1145. submitLoading.value = false
  1146. }
  1147. }
  1148. const openSheetDetail = async (id: string) => {
  1149. const res: any = await fetchSupplierSettlementSheetDetail(id)
  1150. sheetDetail.value = res
  1151. sheetDetailVisible.value = true
  1152. }
  1153. const openSheetEdit = async (row: any) => {
  1154. sheetEditId.value = row.id
  1155. const res: any = await fetchSupplierSettlementSheetDetail(row.id)
  1156. sheetEditForm.remark = res?.remark || ''
  1157. sheetEditForm.voucherUrls = Array.isArray(res?.voucherUrls) ? [...res.voucherUrls] : []
  1158. sheetEditVisible.value = true
  1159. }
  1160. const resetSheetEdit = () => {
  1161. sheetEditId.value = ''
  1162. sheetEditForm.remark = ''
  1163. sheetEditForm.voucherUrls = []
  1164. }
  1165. const saveSheetEdit = async () => {
  1166. if (!sheetEditId.value) return
  1167. sheetEditSaving.value = true
  1168. try {
  1169. await fetchUpdateSupplierSettlementSheet(sheetEditId.value, {
  1170. remark: sheetEditForm.remark,
  1171. voucherUrls: sheetEditForm.voucherUrls
  1172. })
  1173. ElMessage.success('已保存')
  1174. sheetEditVisible.value = false
  1175. loadSheetList()
  1176. } finally {
  1177. sheetEditSaving.value = false
  1178. }
  1179. }
  1180. onMounted(async () => {
  1181. await loadRegions()
  1182. await loadSupplierOptions()
  1183. applyDefaultPendingSupplierFilter()
  1184. await loadData()
  1185. })
  1186. watch(
  1187. () => searchForm.supplierId,
  1188. async (val, oldVal) => {
  1189. // 初始化/重置时会程序化赋值一次:跳过自动检索,避免重复请求
  1190. if (skipPendingSupplierAutoSearch.value) {
  1191. skipPendingSupplierAutoSearch.value = false
  1192. return
  1193. }
  1194. if (val === oldVal) return
  1195. if (activeTab.value !== 'pending') return
  1196. handleSearch()
  1197. }
  1198. )
  1199. watch(
  1200. () => sheetSearchForm.supplierId,
  1201. (val, oldVal) => {
  1202. if (skipSheetSupplierAutoSearch.value) {
  1203. skipSheetSupplierAutoSearch.value = false
  1204. return
  1205. }
  1206. if (val === oldVal) return
  1207. if (activeTab.value !== 'sheets') return
  1208. handleSheetSearch()
  1209. }
  1210. )
  1211. </script>
  1212. <style lang="scss" scoped>
  1213. .page-container {
  1214. display: flex;
  1215. flex-direction: column;
  1216. gap: 16px;
  1217. }
  1218. /* 卡片风格:对齐 delivery-demo(白底 + 薄边框 + 10px 圆角 + 轻阴影) */
  1219. .search-card,
  1220. .stat-card,
  1221. .list-table-card,
  1222. .pending-main-card,
  1223. .sheet-list-card,
  1224. .sheet-main-card {
  1225. border: 1px solid var(--mint-border, #d9e8e2) !important;
  1226. border-radius: 10px !important;
  1227. background: var(--mint-surface, #fff) !important;
  1228. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important;
  1229. overflow: hidden;
  1230. }
  1231. /* 商家结算管理 - 结算单记录主卡片:按需求去掉外边框 */
  1232. .sheet-main-card {
  1233. border: 0 !important;
  1234. }
  1235. .search-card :deep(.el-card__body) {
  1236. padding: 12px 16px 12px;
  1237. }
  1238. .pending-main-card :deep(.el-card__body) {
  1239. padding: 0;
  1240. }
  1241. .sheet-main-card :deep(.el-card__body) {
  1242. padding: 0;
  1243. }
  1244. .stat-card :deep(.el-card__body) {
  1245. padding: 12px 14px;
  1246. min-height: 112px;
  1247. display: flex;
  1248. flex-direction: column;
  1249. justify-content: center;
  1250. }
  1251. .list-table-card :deep(.el-card__body),
  1252. .sheet-list-card :deep(.el-card__body) {
  1253. padding: 0;
  1254. }
  1255. /* 结算单记录:三级展开子表按 fresh-mint-green.html(§8)对齐 */
  1256. .supplier-settlement-sheet-table :deep(td.el-table__expanded-cell),
  1257. .sheet-orders-table :deep(td.el-table__expanded-cell),
  1258. .supplier-settlement-sheet-table :deep(.el-table__body td.el-table__expanded-cell),
  1259. .sheet-orders-table :deep(.el-table__body td.el-table__expanded-cell) {
  1260. /* 关键:在 table-cell 上做四周留白,避免 margin 被超宽子表“顶穿” */
  1261. padding: 12px !important;
  1262. text-align: left !important;
  1263. vertical-align: top;
  1264. background: #f8fafc;
  1265. box-sizing: border-box;
  1266. overflow: hidden;
  1267. }
  1268. .supplier-settlement-sheet-table :deep(td.el-table__expanded-cell > .cell),
  1269. .sheet-orders-table :deep(td.el-table__expanded-cell > .cell) {
  1270. display: block !important;
  1271. padding: 0 !important;
  1272. text-align: left !important;
  1273. }
  1274. .sheet-row-expand-wrap,
  1275. .order-row-expand-wrap {
  1276. padding: 0;
  1277. background: #f8fafc;
  1278. }
  1279. /* 第二层:结算单展开后的“订单列表”容器 */
  1280. .sheet-row-expand-wrap {
  1281. /* 外层留白交给 td.el-table__expanded-cell 的 padding,这里不再用 margin */
  1282. margin: 0;
  1283. border-radius: 10px;
  1284. max-width: 100%;
  1285. box-sizing: border-box;
  1286. overflow: hidden;
  1287. overflow-x: auto;
  1288. }
  1289. .sheet-row-expand-wrap :deep(.sheet-orders-table) {
  1290. width: 100%;
  1291. }
  1292. .sheet-row-expand-wrap::after {
  1293. content: '';
  1294. position: absolute;
  1295. left: 0;
  1296. top: 0;
  1297. bottom: 0;
  1298. width: 4px;
  1299. background: #10b981;
  1300. z-index: 2;
  1301. pointer-events: none;
  1302. }
  1303. .sheet-row-expand-wrap {
  1304. position: relative;
  1305. padding: 12px 12px 14px 18px;
  1306. }
  1307. .sheet-row-expand-wrap :deep(.sheet-orders-table) {
  1308. border: 1px solid var(--mint-border, #d9e8e2);
  1309. border-radius: 8px;
  1310. overflow: hidden;
  1311. background: #fff;
  1312. position: relative;
  1313. z-index: 1;
  1314. }
  1315. .sheet-row-expand-wrap :deep(.sheet-orders-table th.el-table__cell) {
  1316. background: #e2e8f0 !important;
  1317. color: var(--mint-text-muted, #64748b);
  1318. font-size: 11px;
  1319. font-weight: 600;
  1320. }
  1321. .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__cell) {
  1322. padding: 8px 10px;
  1323. }
  1324. /* 第二级子表宽度:按内容撑开,外层横向滚动(避免被压成很窄) */
  1325. .sheet-row-expand-wrap {
  1326. width: 100%;
  1327. max-width: 100%;
  1328. }
  1329. .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__inner-wrapper),
  1330. .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__header-wrapper),
  1331. .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__body-wrapper) {
  1332. width: max-content !important;
  1333. min-width: 100% !important;
  1334. }
  1335. .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table__body-wrapper) {
  1336. overflow-x: auto !important;
  1337. }
  1338. .sheet-row-expand-wrap :deep(.sheet-orders-table .el-scrollbar__wrap) {
  1339. overflow-x: auto !important;
  1340. }
  1341. .sheet-row-expand-wrap :deep(.sheet-orders-table.el-table),
  1342. .sheet-row-expand-wrap :deep(.sheet-orders-table .el-table) {
  1343. width: max-content !important;
  1344. min-width: 100% !important;
  1345. }
  1346. /* 第三级(商品明细表)同样按内容撑开并可横向滚动 */
  1347. .supplier-settlement-sheet-table :deep(.sheet-orders-table .settlement-row-expand-table-wrap) {
  1348. overflow: visible;
  1349. width: 100%;
  1350. max-width: 100%;
  1351. }
  1352. /* 第三层容器再缩进一档,形成清晰层级 */
  1353. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-wrap) {
  1354. /* 外层留白交给“二级表格的 expanded-cell padding”,这里不再用 margin */
  1355. margin: 0;
  1356. border-radius: 10px;
  1357. max-width: 100%;
  1358. box-sizing: border-box;
  1359. overflow: hidden;
  1360. overflow-x: auto;
  1361. }
  1362. /* 第三层外层(琥珀)也与二级内容保持四周留白 */
  1363. .supplier-settlement-sheet-table :deep(.sheet-orders-table .settlement-row-expand-table-wrap) {
  1364. /* 外层留白交给 expanded-cell padding,这里不再用 margin */
  1365. margin: 0;
  1366. border-radius: 10px;
  1367. max-width: 100%;
  1368. box-sizing: border-box;
  1369. overflow: hidden;
  1370. overflow-x: auto;
  1371. }
  1372. /* 二级展开/收起 icon 与一级完全一致(不受 size="small" 影响) */
  1373. .supplier-settlement-sheet-table :deep(.sheet-orders-table .el-table__expand-icon) {
  1374. width: 28px !important;
  1375. height: 28px !important;
  1376. min-width: 28px !important;
  1377. min-height: 28px !important;
  1378. border-radius: 6px;
  1379. }
  1380. .supplier-settlement-sheet-table :deep(.sheet-orders-table .el-table__expand-icon svg) {
  1381. width: 14px !important;
  1382. height: 14px !important;
  1383. }
  1384. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table .el-table__inner-wrapper),
  1385. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table .el-table__header-wrapper),
  1386. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table .el-table__body-wrapper) {
  1387. width: max-content !important;
  1388. min-width: 100% !important;
  1389. }
  1390. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table .el-table__body-wrapper) {
  1391. overflow-x: auto !important;
  1392. }
  1393. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table .el-scrollbar__wrap) {
  1394. overflow-x: auto !important;
  1395. }
  1396. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table.el-table),
  1397. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table .el-table) {
  1398. width: max-content !important;
  1399. min-width: 100% !important;
  1400. }
  1401. .search-card {
  1402. margin-bottom: 16px;
  1403. }
  1404. .filter-form :deep(.el-form-item) {
  1405. margin-bottom: 0;
  1406. }
  1407. .filter-form :deep(.el-form-item__label) {
  1408. font-size: 13px;
  1409. font-weight: 500;
  1410. color: var(--mint-text-muted, #64748b);
  1411. padding-bottom: 4px;
  1412. line-height: 1.2;
  1413. }
  1414. .filter-grid {
  1415. display: grid;
  1416. grid-template-columns: repeat(3, minmax(0, 1fr));
  1417. gap: 10px 14px;
  1418. }
  1419. .filter-actions-row {
  1420. display: flex;
  1421. justify-content: flex-end;
  1422. margin-top: 10px;
  1423. }
  1424. .filter-actions-end {
  1425. display: inline-flex;
  1426. align-items: center;
  1427. gap: 8px;
  1428. }
  1429. .region-tab-strip {
  1430. margin-bottom: 0;
  1431. }
  1432. .region-tab-group.tab-button-group {
  1433. gap: 8px;
  1434. }
  1435. .region-tab-btn {
  1436. min-width: 96px;
  1437. }
  1438. .settlement-page-tabs :deep(.el-tabs__header) {
  1439. margin-bottom: 12px;
  1440. }
  1441. .stats-row {
  1442. display: grid;
  1443. grid-template-columns: repeat(4, minmax(0, 1fr));
  1444. gap: 12px;
  1445. margin-bottom: 16px;
  1446. }
  1447. .stats-row--compact {
  1448. max-width: 980px;
  1449. margin-left: auto;
  1450. margin-right: auto;
  1451. }
  1452. /* 待结算订单:顶部 4 个统计卡去边框,并保持居中展示 */
  1453. .stats-row--compact .stat-card {
  1454. border: 0 !important;
  1455. }
  1456. .stat-title {
  1457. color: #64748b;
  1458. font-size: 13px;
  1459. font-weight: 600;
  1460. line-height: 1.35;
  1461. }
  1462. .stat-value {
  1463. margin-top: 6px;
  1464. font-size: 24px;
  1465. font-weight: 700;
  1466. line-height: 1.1;
  1467. }
  1468. .stat-value.amount {
  1469. color: #dc2626;
  1470. }
  1471. .toolbar {
  1472. display: flex;
  1473. align-items: center;
  1474. gap: 12px;
  1475. padding: 8px 16px;
  1476. margin-bottom: 0;
  1477. border-bottom: 1px solid var(--mint-border, #d9e8e2);
  1478. background: #f8fafc;
  1479. }
  1480. .toolbar :deep(.btn-main.el-button:not(.is-disabled)) {
  1481. color: #fff !important;
  1482. }
  1483. .toolbar :deep(.btn-main.el-button:not(.is-disabled):hover),
  1484. .toolbar :deep(.btn-main.el-button:not(.is-disabled):active),
  1485. .toolbar :deep(.btn-main.el-button:not(.is-disabled):focus-visible) {
  1486. color: #fff !important;
  1487. }
  1488. .toolbar :deep(.btn-main.el-button.is-disabled) {
  1489. color: var(--el-text-color-placeholder, #a8abb2) !important;
  1490. }
  1491. .toolbar-inline-stats {
  1492. display: inline-flex;
  1493. align-items: center;
  1494. flex-wrap: wrap;
  1495. gap: 16px 20px;
  1496. margin-left: 4px;
  1497. font-size: 13px;
  1498. color: var(--mint-text-muted, #64748b);
  1499. }
  1500. .toolbar-stat .stat-money {
  1501. color: #dc2626;
  1502. font-weight: 700;
  1503. }
  1504. .toolbar-stat strong:not(.stat-money) {
  1505. color: #dc2626;
  1506. font-weight: 700;
  1507. }
  1508. .filter-block {
  1509. padding: 12px 16px 10px;
  1510. border-bottom: 1px solid #eef4f0;
  1511. background: #fff;
  1512. }
  1513. .amount-text {
  1514. color: #dc2626;
  1515. font-weight: 600;
  1516. }
  1517. .sheet-summary-bar {
  1518. display: flex;
  1519. align-items: center;
  1520. flex-wrap: wrap;
  1521. gap: 10px 18px;
  1522. padding: 10px 16px;
  1523. border-bottom: 1px solid var(--mint-border, #d9e8e2);
  1524. background: #f8fafc;
  1525. }
  1526. .sheet-summary-group {
  1527. display: inline-flex;
  1528. align-items: baseline;
  1529. gap: 8px;
  1530. font-size: 13px;
  1531. }
  1532. .sheet-summary-label {
  1533. color: var(--mint-text-muted, #64748b);
  1534. font-weight: 500;
  1535. }
  1536. .sheet-summary-value {
  1537. font-weight: 700;
  1538. }
  1539. .sheet-summary-divider {
  1540. width: 1px;
  1541. height: 18px;
  1542. background: var(--mint-border, #d9e8e2);
  1543. margin: 0 2px;
  1544. }
  1545. .pagination {
  1546. display: flex;
  1547. justify-content: flex-end;
  1548. margin-top: 0;
  1549. padding: 10px 16px 12px;
  1550. border-top: 1px solid var(--mint-border, #d9e8e2);
  1551. background: #fff;
  1552. }
  1553. .tab-button-group {
  1554. display: flex;
  1555. flex-wrap: wrap;
  1556. align-items: center;
  1557. gap: 10px;
  1558. }
  1559. .region-tabs {
  1560. margin-bottom: 16px;
  1561. }
  1562. .mb-3 {
  1563. margin-bottom: 12px;
  1564. }
  1565. .settlement-remark-form {
  1566. margin-top: 16px;
  1567. }
  1568. .voucher-list {
  1569. display: inline-flex;
  1570. align-items: center;
  1571. gap: 8px;
  1572. margin-left: 12px;
  1573. }
  1574. .voucher-list--block {
  1575. display: flex;
  1576. flex-wrap: wrap;
  1577. margin-left: 0;
  1578. margin-top: 8px;
  1579. }
  1580. .text-muted {
  1581. color: #909399;
  1582. font-size: 12px;
  1583. margin-top: 2px;
  1584. }
  1585. .ml-1 {
  1586. margin-left: 6px;
  1587. }
  1588. .line-cell {
  1589. display: inline-flex;
  1590. align-items: center;
  1591. gap: 8px;
  1592. }
  1593. .line-img {
  1594. width: 36px;
  1595. height: 36px;
  1596. border-radius: 6px;
  1597. flex-shrink: 0;
  1598. }
  1599. .order-row-expand-wrap {
  1600. padding: 0;
  1601. background: #f8fafc;
  1602. border-radius: 0;
  1603. }
  1604. :deep(td.el-table__expanded-cell) {
  1605. padding: 0 !important;
  1606. text-align: left !important;
  1607. }
  1608. :deep(td.el-table__expanded-cell > .cell) {
  1609. display: block !important;
  1610. padding: 0 !important;
  1611. text-align: left !important;
  1612. }
  1613. /* 展开列单元格兜底:避免出现意外的“..”文本占位 */
  1614. .supplier-settlement-table :deep(td.el-table__cell.el-table__expand-column > .cell),
  1615. .supplier-settlement-sheet-table :deep(td.el-table__cell.el-table__expand-column > .cell),
  1616. .supplier-settlement-sheet-table :deep(.sheet-orders-table td.el-table__cell.el-table__expand-column > .cell) {
  1617. display: flex;
  1618. align-items: center;
  1619. justify-content: center;
  1620. font-size: 0;
  1621. line-height: 0;
  1622. overflow: hidden;
  1623. }
  1624. /* 展开按钮:对齐【订单列表】树形列表展开/收起区域样式 */
  1625. .supplier-settlement-table :deep(.el-table__expand-icon),
  1626. .supplier-settlement-sheet-table :deep(.el-table__expand-icon),
  1627. .supplier-settlement-sheet-table :deep(.sheet-orders-table .el-table__expand-icon) {
  1628. width: 28px;
  1629. height: 28px;
  1630. border-radius: 6px;
  1631. border: 1px solid var(--mint-border, #d9e8e2);
  1632. display: inline-flex;
  1633. align-items: center;
  1634. justify-content: center;
  1635. transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
  1636. color: var(--mint-text-muted, #64748b);
  1637. background: var(--mint-surface, #fff);
  1638. font-size: 0;
  1639. line-height: 0;
  1640. }
  1641. .supplier-settlement-table :deep(.el-table__expand-icon:hover),
  1642. .supplier-settlement-sheet-table :deep(.el-table__expand-icon:hover),
  1643. .supplier-settlement-sheet-table :deep(.sheet-orders-table .el-table__expand-icon:hover) {
  1644. background: var(--mint-primary-bg, #ecfdf5);
  1645. border-color: #10b981;
  1646. color: var(--mint-primary-dark, #059669);
  1647. }
  1648. .supplier-settlement-table :deep(.el-table__expand-icon--expanded),
  1649. .supplier-settlement-sheet-table :deep(.el-table__expand-icon--expanded),
  1650. .supplier-settlement-sheet-table :deep(.sheet-orders-table .el-table__expand-icon--expanded) {
  1651. background: var(--mint-primary-bg, #ecfdf5);
  1652. border-color: #10b981;
  1653. color: var(--mint-primary-dark, #059669);
  1654. }
  1655. .supplier-settlement-table :deep(.el-table__expand-icon svg),
  1656. .supplier-settlement-sheet-table :deep(.el-table__expand-icon svg),
  1657. .supplier-settlement-sheet-table :deep(.sheet-orders-table .el-table__expand-icon svg) {
  1658. transition: transform 0.15s ease;
  1659. width: 14px;
  1660. height: 14px;
  1661. }
  1662. .supplier-settlement-table :deep(.el-table__expand-icon--expanded svg),
  1663. .supplier-settlement-sheet-table :deep(.el-table__expand-icon--expanded svg),
  1664. .supplier-settlement-sheet-table :deep(.sheet-orders-table .el-table__expand-icon--expanded svg) {
  1665. transform: rotate(180deg);
  1666. }
  1667. .order-row-expand-table {
  1668. width: 100%;
  1669. max-width: 100%;
  1670. border: 1px solid var(--mint-border, #d9e8e2);
  1671. border-radius: 8px;
  1672. overflow: hidden;
  1673. background: #fff;
  1674. }
  1675. .order-row-expand-table :deep(th.el-table__cell) {
  1676. background: #e2e8f0 !important;
  1677. color: var(--mint-text-muted, #64748b);
  1678. font-size: 11px;
  1679. font-weight: 600;
  1680. }
  1681. .order-row-expand-table :deep(.el-table__cell) {
  1682. padding: 8px 10px;
  1683. }
  1684. .settlement-row-expand-table-wrap {
  1685. display: block;
  1686. width: 100%;
  1687. max-width: 100%;
  1688. overflow: hidden;
  1689. border-radius: 0;
  1690. text-align: left;
  1691. position: relative;
  1692. background: #f8fafc;
  1693. padding: 12px 12px 14px 16px;
  1694. :deep(.el-table__inner-wrapper),
  1695. :deep(.el-table__header-wrapper),
  1696. :deep(.el-table__body-wrapper) {
  1697. width: 100%;
  1698. }
  1699. }
  1700. .settlement-row-expand-table-wrap::before {
  1701. content: '';
  1702. position: absolute;
  1703. left: 0;
  1704. top: 0;
  1705. bottom: 0;
  1706. width: 3px;
  1707. background: #10b981;
  1708. }
  1709. /* 第三层(结算单→订单→商品明细):在结算单记录 Tab 内按 demo 的“孙列表”风格(琥珀)呈现 */
  1710. .supplier-settlement-sheet-table :deep(.sheet-orders-table .settlement-row-expand-table-wrap) {
  1711. margin-top: 4px;
  1712. padding-left: 14px;
  1713. padding-top: 10px;
  1714. padding-right: 0;
  1715. padding-bottom: 0;
  1716. border-left: 3px solid #f59e0b;
  1717. background: linear-gradient(90deg, rgba(254, 243, 199, 0.35) 0%, transparent 12px);
  1718. }
  1719. .supplier-settlement-sheet-table :deep(.sheet-orders-table .settlement-row-expand-table-wrap::before) {
  1720. background: #f59e0b;
  1721. }
  1722. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table) {
  1723. border-color: #fcd34d !important;
  1724. }
  1725. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table th.el-table__cell) {
  1726. background: #fef3c7 !important;
  1727. color: #92400e !important;
  1728. font-size: 10px !important;
  1729. border-bottom: 1px solid #fcd34d !important;
  1730. }
  1731. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table td.el-table__cell) {
  1732. font-size: 12px;
  1733. border-bottom: 1px solid #fef3c7 !important;
  1734. }
  1735. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table .el-table__cell) {
  1736. padding: 6px 8px !important;
  1737. }
  1738. .supplier-settlement-sheet-table :deep(.sheet-orders-table .order-row-expand-table tr:hover td.el-table__cell) {
  1739. background: #fffbeb !important;
  1740. }
  1741. .order-row-expand-summary {
  1742. margin-top: 10px;
  1743. display: flex;
  1744. gap: 20px;
  1745. justify-content: flex-end;
  1746. color: #606266;
  1747. font-size: 13px;
  1748. }
  1749. .expand-product-cell {
  1750. display: inline-flex;
  1751. align-items: center;
  1752. gap: 10px;
  1753. max-width: 100%;
  1754. min-width: 0;
  1755. }
  1756. .expand-product-thumb-wrap {
  1757. flex: 0 0 auto;
  1758. }
  1759. .expand-product-thumb {
  1760. width: 34px;
  1761. height: 34px;
  1762. border-radius: 6px;
  1763. display: block;
  1764. }
  1765. .expand-product-thumb.placeholder {
  1766. width: 34px;
  1767. height: 34px;
  1768. border-radius: 6px;
  1769. display: flex;
  1770. align-items: center;
  1771. justify-content: center;
  1772. font-size: 11px;
  1773. color: #909399;
  1774. background: #f5f7fa;
  1775. border: 1px dashed #dcdfe6;
  1776. }
  1777. .expand-product-name {
  1778. min-width: 0;
  1779. overflow: hidden;
  1780. text-overflow: ellipsis;
  1781. white-space: nowrap;
  1782. }
  1783. .sheet-row-expand-wrap {
  1784. padding: 0;
  1785. background: #f8fafc;
  1786. border-radius: 0;
  1787. }
  1788. :deep(.el-dialog__footer .el-button) {
  1789. display: inline-flex;
  1790. align-items: center;
  1791. justify-content: center;
  1792. }
  1793. :deep(.el-dialog__footer .el-button .el-icon) {
  1794. margin-right: 6px;
  1795. }
  1796. .sheet-order-tree-table {
  1797. :deep(.el-table__body-wrapper) {
  1798. overflow-x: auto;
  1799. }
  1800. }
  1801. .sheet-orders-table {
  1802. :deep(.el-table__body-wrapper) {
  1803. overflow-x: auto;
  1804. }
  1805. }
  1806. .order-row-head {
  1807. display: flex;
  1808. align-items: center;
  1809. gap: 8px;
  1810. flex-wrap: wrap;
  1811. margin-bottom: 4px;
  1812. }
  1813. .order-row-meta {
  1814. font-size: 12px;
  1815. line-height: 1.5;
  1816. color: #606266;
  1817. &.muted {
  1818. color: #909399;
  1819. }
  1820. }
  1821. .line-product-cell {
  1822. display: flex;
  1823. align-items: flex-start;
  1824. gap: 10px;
  1825. }
  1826. .line-thumb {
  1827. width: 44px;
  1828. height: 44px;
  1829. border-radius: 8px;
  1830. flex-shrink: 0;
  1831. }
  1832. .line-thumb--ph {
  1833. display: flex;
  1834. align-items: center;
  1835. justify-content: center;
  1836. background: #ebeef5;
  1837. font-size: 11px;
  1838. color: #909399;
  1839. }
  1840. .line-product-text {
  1841. display: flex;
  1842. flex-direction: column;
  1843. gap: 4px;
  1844. min-width: 0;
  1845. font-size: 13px;
  1846. }
  1847. .line-note {
  1848. font-size: 12px;
  1849. color: #909399;
  1850. }
  1851. .cell-muted {
  1852. color: #c0c4cc;
  1853. }
  1854. </style>
  1855. <!-- Teleport 到 body:弹窗内容需非 scoped 才能稳定命中 -->
  1856. <style lang="scss">
  1857. .supplier-settlement-dialog.el-dialog {
  1858. display: flex;
  1859. flex-direction: column;
  1860. max-height: calc(100vh - 64px);
  1861. }
  1862. .supplier-settlement-dialog .el-dialog__header {
  1863. flex-shrink: 0;
  1864. background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 70%);
  1865. border-bottom: 1px solid var(--mint-border, #d9e8e2);
  1866. }
  1867. .supplier-settlement-dialog .el-dialog__body {
  1868. flex: 1 1 auto;
  1869. min-height: 0;
  1870. display: flex;
  1871. flex-direction: column;
  1872. padding: 0 !important;
  1873. overflow: hidden;
  1874. }
  1875. .supplier-settlement-dialog .dialog-scroll {
  1876. overflow-y: auto;
  1877. overflow-x: hidden;
  1878. padding: 20px 22px;
  1879. flex: 1;
  1880. }
  1881. .supplier-settlement-dialog.sheet-detail-dialog .el-dialog__body {
  1882. padding: 0 !important;
  1883. }
  1884. .supplier-settlement-dialog.sheet-edit-dialog .el-dialog__body {
  1885. padding: 0 !important;
  1886. }
  1887. .supplier-settlement-dialog .el-dialog__footer {
  1888. padding: 12px 22px !important;
  1889. border-top: 1px solid var(--mint-border, #d9e8e2);
  1890. background: #f8fafc;
  1891. }
  1892. .supplier-settlement-dialog .el-dialog__footer .el-button {
  1893. height: 36px;
  1894. border-radius: 8px;
  1895. font-weight: 600;
  1896. display: inline-flex;
  1897. align-items: center;
  1898. justify-content: center;
  1899. }
  1900. .supplier-settlement-dialog .el-dialog__footer .el-button .el-icon {
  1901. margin-right: 6px;
  1902. }
  1903. .supplier-settlement-dialog.sheet-edit-dialog .form-section {
  1904. margin-bottom: 20px;
  1905. padding-bottom: 16px;
  1906. border-bottom: 1px dashed var(--mint-border, #d9e8e2);
  1907. }
  1908. .supplier-settlement-dialog.sheet-edit-dialog .form-section:last-of-type {
  1909. margin-bottom: 0;
  1910. padding-bottom: 0;
  1911. border-bottom: none;
  1912. }
  1913. .supplier-settlement-dialog.sheet-edit-dialog .form-section h4 {
  1914. margin: 0 0 12px;
  1915. font-size: 13px;
  1916. font-weight: 700;
  1917. color: var(--mint-text-muted, #64748b);
  1918. letter-spacing: 0.04em;
  1919. }
  1920. .supplier-settlement-dialog.sheet-edit-dialog .form-grid-2 {
  1921. display: grid;
  1922. grid-template-columns: 1fr 1fr;
  1923. gap: 12px 16px;
  1924. }
  1925. .supplier-settlement-dialog.sheet-edit-dialog .form-col-span-2 {
  1926. grid-column: 1 / -1;
  1927. }
  1928. .supplier-settlement-dialog.sheet-edit-dialog .el-form-item {
  1929. margin-bottom: 0;
  1930. }
  1931. .supplier-settlement-dialog.sheet-edit-dialog .el-form-item__label {
  1932. color: var(--mint-text-muted, #64748b) !important;
  1933. font-weight: 600;
  1934. font-size: 12px;
  1935. line-height: 1.2;
  1936. padding-bottom: 6px;
  1937. }
  1938. .supplier-settlement-dialog.sheet-edit-dialog .el-input__wrapper,
  1939. .supplier-settlement-dialog.sheet-edit-dialog .el-textarea__inner {
  1940. box-shadow: 0 0 0 1px var(--mint-border, #d9e8e2) inset !important;
  1941. border-radius: 8px !important;
  1942. }
  1943. .supplier-settlement-dialog.sheet-edit-dialog .el-input__wrapper.is-focus,
  1944. .supplier-settlement-dialog.sheet-edit-dialog .el-textarea__inner:focus {
  1945. box-shadow:
  1946. 0 0 0 1px var(--mint-primary, #10b981) inset,
  1947. 0 0 0 2px rgba(16, 185, 129, 0.2) !important;
  1948. }
  1949. .supplier-settlement-dialog-mask {
  1950. backdrop-filter: none !important;
  1951. }
  1952. @media (max-width: 760px) {
  1953. .supplier-settlement-dialog.sheet-edit-dialog .form-grid-2 {
  1954. grid-template-columns: 1fr;
  1955. }
  1956. }
  1957. </style>