Commit ae18fb6b7c0da38edd55e423d539ac8b25ec9e5c

Authored by gesilong
1 parent 71164877

commit: 合同列表开发

  1 +import request from '@/utils/request'
  2 +import { ContentTypeEnum } from '@/utils/httpEnum';
  3 +
  4 +export const statusStyle = [
  5 + { color: '#2BA471', bgColor:'#E3F9E9',text: '审核通过' },
  6 + { color: '#D54941', bgColor:'#FFF0ED',text: '已驳回' },
  7 + { color: '#3D48A3', bgColor:'#F2F3FF',text: '审核中' },
  8 +];
  9 +
  10 +export const statusMap = {
  11 + '生产中': '#2BA471',
  12 + '审批中': '#3D48A3',
  13 + '已签收': '#E7E7E7',
  14 + '已发货': '#D54941',
  15 +}
  16 +
  17 +const baseUrl = '/contract';
  18 +// 查询合同框架列表
  19 +export function queryApi(params) {
  20 + return request({
  21 + url: `${baseUrl}/contractFramework/query`,
  22 + method: 'get',
  23 + params
  24 + })
  25 +}
  26 +
  27 +// 查询合同列表
  28 +export function queryContractApi(params) {
  29 + return request({
  30 + url: `${baseUrl}/contractDistributorStandard/query`,
  31 + method: 'get',
  32 + params
  33 + })
  34 +}
\ No newline at end of file
... ...
... ... @@ -19,8 +19,8 @@
19 19 }, {
20 20 "path": "pages/mine/index",
21 21 "style": {
22   - "navigationBarTitleText": "我的",
23   - "navigationBarTextStyle": "white"
  22 + "navigationBarTitleText": "我的",
  23 + "navigationBarTextStyle": "white"
24 24 }
25 25 }, {
26 26 "path": "pages/mine/index2",
... ... @@ -35,17 +35,17 @@
35 35 }, {
36 36 "path": "pages/mine/pwd/index",
37 37 "style": {
38   - "navigationBarTitleText": "修改密码"
  38 + "navigationBarTitleText": "修改密码"
39 39 }
40 40 }, {
41 41 "path": "pages/mine/setting/index",
42 42 "style": {
43   - "navigationBarTitleText": "应用设置"
  43 + "navigationBarTitleText": "应用设置"
44 44 }
45 45 }, {
46 46 "path": "pages/mine/about/index",
47 47 "style": {
48   - "navigationBarTitleText": "关于我们"
  48 + "navigationBarTitleText": "关于我们"
49 49 }
50 50 }, {
51 51 "path": "pages/dev_manage/index",
... ... @@ -54,6 +54,62 @@
54 54 "navigationBarBackgroundColor": "#ffffff",
55 55 "navigationBarTextStyle": "black"
56 56 }
  57 + }, {
  58 + "path": "pages/contract_framework/index",
  59 + "style": {
  60 + "navigationBarTitleText": "框架合同",
  61 + "navigationBarBackgroundColor": "#ffffff",
  62 + "navigationBarTextStyle": "black"
  63 + }
  64 + }, {
  65 + "path": "pages/contract_retail/index",
  66 + "style": {
  67 + "navigationBarTitleText": "经销标准合同",
  68 + "navigationBarBackgroundColor": "#ffffff",
  69 + "navigationBarTextStyle": "black"
  70 + }
  71 + }, {
  72 + "path": "pages/contract_stock/index",
  73 + "style": {
  74 + "navigationBarTitleText": "经销库存合同",
  75 + "navigationBarBackgroundColor": "#ffffff",
  76 + "navigationBarTextStyle": "black"
  77 + }
  78 + }, {
  79 + "path": "pages/contract_unplan/index",
  80 + "style": {
  81 + "navigationBarTitleText": "经销未锁规合同",
  82 + "navigationBarBackgroundColor": "#ffffff",
  83 + "navigationBarTextStyle": "black"
  84 + }
  85 + }, {
  86 + "path": "pages/contract_process/index",
  87 + "style": {
  88 + "navigationBarTitleText": "加工标准合同",
  89 + "navigationBarBackgroundColor": "#ffffff",
  90 + "navigationBarTextStyle": "black"
  91 + }
  92 + }, {
  93 + "path": "pages/contract_foreign_std/index",
  94 + "style": {
  95 + "navigationBarTitleText": "外贸标准合同",
  96 + "navigationBarBackgroundColor": "#ffffff",
  97 + "navigationBarTextStyle": "black"
  98 + }
  99 + }, {
  100 + "path": "pages/contract_foreign_stock/index",
  101 + "style": {
  102 + "navigationBarTitleText": "外贸库存合同",
  103 + "navigationBarBackgroundColor": "#ffffff",
  104 + "navigationBarTextStyle": "black"
  105 + }
  106 + }, {
  107 + "path": "pages/contract_foreign_unplan/index",
  108 + "style": {
  109 + "navigationBarTitleText": "外贸未锁规合同",
  110 + "navigationBarBackgroundColor": "#ffffff",
  111 + "navigationBarTextStyle": "black"
  112 + }
57 113 }
58 114 ],
59 115 "subPackages": [
... ... @@ -197,4 +253,4 @@
197 253 "navigationBarBackgroundColor": "$theme-primary",
198 254 "navigationBarTextStyle": "white"
199 255 }
200   -}
\ No newline at end of file
  256 +}
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="contract-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar
  6 + v-model="searchKeyword"
  7 + radius="6"
  8 + placeholder="请输入需方名称或编号或办事处"
  9 + clearButton="auto"
  10 + cancelButton="none"
  11 + bgColor="#F3F3F3"
  12 + textColor="rgba(0,0,0,0.4)"
  13 + @confirm="search"
  14 + @input="onSearchInput"
  15 + />
  16 + <view class="tool-icons">
  17 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  18 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  19 + </view>
  20 + </view>
  21 +
  22 + <view class="tabs">
  23 + <view
  24 + v-for="(t, i) in tabs"
  25 + :key="i"
  26 + :class="['tab', { active: status === t.value }]"
  27 + @click="switchTab(t)"
  28 + >
  29 + {{ t.label }}
  30 + </view>
  31 + </view>
  32 + </view>
  33 +
  34 + <view :class="['list-box', { 'pad-batch': batchMode }]">
  35 + <card-list
  36 + ref="cardRef"
  37 + :fetch-fn="fetchList"
  38 + :query="query"
  39 + :extra="extraParams"
  40 + :selectable="batchMode"
  41 + row-key="id"
  42 + :selectedKeys.sync="selectedKeys"
  43 + :enable-refresh="true"
  44 + :enable-load-more="true"
  45 + @loaded="onCardLoaded"
  46 + @error="onCardError"
  47 + >
  48 + <template v-slot="{ item }">
  49 + <view class="card">
  50 + <view class="card-header">
  51 + <text class="title omit2">{{ item.buyerName }}</text>
  52 + <text v-if="item.status === 'STANDARD'" :class="['status']" :style="{ background: statusMap[item.shippingStatusName] }">{{ item.shippingStatusName }}</text>
  53 + </view>
  54 + <view class="info-row">
  55 + <text>编号</text><text>{{ item.code }}</text>
  56 + </view>
  57 + <view class="info-row">
  58 + <text>办事处</text><text>{{ item.deptName }}</text>
  59 + </view>
  60 + <view class="info-row" v-if="item.status !== 'STANDARD'">
  61 + <text>订单总额</text><text class="amount" :style="{ color: '#b67a76' }">{{ item.totalAmountIncludingTax ? '¥' : '' }}{{ formatAmount(item.totalAmountIncludingTax) || '-' }}</text>
  62 + </view>
  63 + <view class="info-row" v-if="item.status === 'STANDARD'">
  64 + <text>正式合同规范性审核状态</text><span class="info-status" :style="item.standardApprovedName ? getStatusCss(item.standardApprovedName) : ''">{{ item.standardApprovedName || '-' }}</span>
  65 + </view>
  66 + <view class="info-row">
  67 + <text>订货日期</text><text>{{ item.orderDate }}</text>
  68 + </view>
  69 + </view>
  70 + </template>
  71 + </card-list>
  72 + </view>
  73 +
  74 + <filter-modal
  75 + :visible.sync="filterVisible"
  76 + :value.sync="filterForm"
  77 + title="筛选"
  78 + @reset="onFilterReset"
  79 + @confirm="onFilterConfirm"
  80 + >
  81 + <template v-slot="{ model }">
  82 + <view class="filter-form">
  83 + <view class="form-item">
  84 + <view class="label">订货日期</view>
  85 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  86 + </view>
  87 + </view>
  88 + </template>
  89 + </filter-modal>
  90 +
  91 + </view>
  92 +</template>
  93 +
  94 +<script>
  95 +import CardList from '@/components/card/index.vue'
  96 +import FilterModal from '@/components/filter/index.vue'
  97 +import { queryContractApi, statusStyle,statusMap } from '@/api/contract.js'
  98 +import { officeQueryApi } from '@/api/devManage.js'
  99 +
  100 +export default {
  101 + components: { CardList, FilterModal },
  102 + data() {
  103 + return {
  104 + searchKeyword: '',
  105 + searchKeywordDebounced: '',
  106 +
  107 + tabs: [
  108 + { label: '草稿合同', value: 'DRAFT' },
  109 + { label: '标准合同', value: 'STANDARD' },
  110 + { label: '已删除合同', value: 'DELETED' }
  111 + ],
  112 + status: 'DRAFT',
  113 +
  114 + query: { deptId: '', deptName: '', dateRange: [] },
  115 + extraParams: {},
  116 +
  117 + batchMode: false,
  118 + selectedKeys: [],
  119 + currentItems: [],
  120 +
  121 + filterVisible: false,
  122 + filterForm: { deptId: '', deptName: '', dateRange: [] },
  123 + deptSelectVisible: false,
  124 + statusStyle,
  125 + statusMap,
  126 + }
  127 + },
  128 + computed: {
  129 + extraCombined() {
  130 + return {
  131 + keyword: this.searchKeywordDebounced || undefined,
  132 + status: this.status || undefined,
  133 + type: 'INTL_STD_CONTRACT'
  134 + }
  135 + }
  136 + },
  137 + watch: {
  138 + extraCombined: {
  139 + deep: true,
  140 + handler(v) {
  141 + this.extraParams = v
  142 + },
  143 + immediate: true
  144 + }
  145 + },
  146 + created() {
  147 + },
  148 + onReachBottom() {
  149 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  150 + this.$refs.cardRef.onLoadMore()
  151 + }
  152 + },
  153 + beforeDestroy() {
  154 + if (this.searchDebounceTimer) {
  155 + clearTimeout(this.searchDebounceTimer)
  156 + this.searchDebounceTimer = null
  157 + }
  158 + },
  159 + methods: {
  160 + onCardLoaded({ items }) {
  161 + this.currentItems = items
  162 + },
  163 + onCardError() {
  164 + uni.showToast({ title: '列表加载失败', icon: 'none' })
  165 + },
  166 + onSearchInput() {
  167 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  168 + this.searchDebounceTimer = setTimeout(() => {
  169 + this.searchKeywordDebounced = this.searchKeyword
  170 + this.searchDebounceTimer = null
  171 + }, 1200)
  172 + },
  173 + search(e) {
  174 + const val = e && e.value != null ? e.value : this.searchKeyword
  175 + this.searchKeyword = val
  176 + this.searchKeywordDebounced = val
  177 + },
  178 + switchTab(item) {
  179 + this.status = item.value
  180 + },
  181 + openFilter() {
  182 + this.filterVisible = true
  183 + },
  184 + onFilterReset(payload) {
  185 + this.filterForm = payload
  186 + },
  187 + onFilterConfirm(payload) {
  188 + this.query = { ...payload }
  189 + },
  190 + openDeptSelect() {
  191 + this.deptSelectVisible = true
  192 + },
  193 + onDeptConfirm(payload) {
  194 + const val = payload && payload.value != null ? payload.value : ''
  195 + const label = payload && payload.label != null ? payload.label : ''
  196 + this.filterForm.deptId = val
  197 + this.filterForm.deptName = label
  198 + },
  199 + toggleBatch() {
  200 + this.batchMode = !this.batchMode
  201 + if (!this.batchMode) this.selectedKeys = []
  202 + },
  203 + onAdd() {
  204 + uni.showToast({ title: '点击新增', icon: 'none' })
  205 + },
  206 + fetchList({ pageIndex, pageSize, query, extra }) {
  207 + console.log('fetchList', pageIndex, pageSize, query, extra)
  208 + const params = { pageIndex, pageSize, ...extra, ...query }
  209 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  210 + params.orderDateStart = params.dateRange[0]
  211 + params.orderDateEnd = params.dateRange[1]
  212 + delete params.dateRange
  213 + }
  214 + return queryContractApi(params)
  215 + .then(res => {
  216 + const _data = res.data || {}
  217 + const records = _data.datas || []
  218 + const totalCount = _data.totalCount || 0
  219 + const hasNext = _data.hasNext || false
  220 + return { records, totalCount, hasNext }
  221 + })
  222 + .catch(err => {
  223 + console.error('queryContractApi error', err)
  224 + this.onCardError()
  225 + return { records: [], totalCount: 0, hasNext: false }
  226 + })
  227 + },
  228 +
  229 + formatAmount(val) {
  230 + if (val == null) return '0.00'
  231 + try {
  232 + const num = Number(val)
  233 + return num.toFixed(2)
  234 + } catch (e) {
  235 + return String(val)
  236 + }
  237 + },
  238 + getStatusCss(name) {
  239 + const list = Array.isArray(this.statusStyle) ? this.statusStyle : []
  240 + const found = list.find(it => it && it.text === name) || {}
  241 + return {
  242 + color: found.color || '#000',
  243 + backgroundColor: found.bgColor || '#000'
  244 + }
  245 + }
  246 + }
  247 +}
  248 +</script>
  249 +
  250 +<style lang="scss" scoped>
  251 +.page {
  252 + display: flex;
  253 + flex-direction: column;
  254 + height: 100vh;
  255 +}
  256 +.contract-list-fixed {
  257 + position: fixed;
  258 + top: 96rpx;
  259 + left: 0;
  260 + right: 0;
  261 + z-index: 2;
  262 + background: #fff;
  263 +
  264 + .search-row {
  265 + display: flex;
  266 + align-items: center;
  267 + padding: 16rpx 32rpx;
  268 + .uni-searchbar { padding: 0; flex: 1; }
  269 + .tool-icons { display: flex; .tool-icon { width: 48rpx; height: 48rpx; display: block; margin-left: 32rpx; } }
  270 + }
  271 +
  272 + .tabs {
  273 + display: flex;
  274 + align-items: center;
  275 + justify-content: space-between;
  276 + padding: 26rpx 0;
  277 + border-bottom: solid 1px #E7E7E7;
  278 + .tab { width: 50%; text-align: center; color: rgba(0,0,0,0.9); line-height: 44rpx; position: relative; font-weight: 600; }
  279 + .tab.active { color: $theme-primary; }
  280 + .tab.active::after {
  281 + content: '';
  282 + position: absolute;
  283 + left: 50%;
  284 + transform: translateX(-50%);
  285 + bottom: -13px;
  286 + width: 32rpx;
  287 + height: 6rpx;
  288 + border-radius: 4rpx;
  289 + background: $theme-primary;
  290 + }
  291 + }
  292 +}
  293 +::v-deep .uni-searchbar__box { height: 80rpx !important; justify-content: start; .uni-searchbar__box-search-input { font-size: 32rpx !important; } }
  294 +
  295 +.list-box {
  296 + flex: 1;
  297 + padding-top: 236rpx;
  298 + &.pad-batch { padding-bottom: 144rpx; }
  299 + .card { position: relative; }
  300 + .card-header {
  301 + margin-bottom: 28rpx;
  302 + position: relative;
  303 +
  304 + .title {
  305 + font-size: 36rpx;
  306 + font-weight: 600;
  307 + line-height: 50rpx;
  308 + color: #323241;
  309 + width: 578rpx;
  310 + }
  311 +
  312 + .status {
  313 + font-size: 30rpx;
  314 + font-weight: 600;
  315 + position: absolute;
  316 + top: -36rpx;
  317 + right: -32rpx;
  318 + height: 48rpx;
  319 + line-height: 48rpx;
  320 + color: #fff;
  321 + font-size: 24rpx;
  322 + padding: 0 14rpx;
  323 + border-radius: 6rpx;
  324 + }
  325 + }
  326 + .info-row {
  327 + display: flex;
  328 + align-items: center;
  329 + color: rgba(0,0,0,0.6);
  330 + font-size: 28rpx;
  331 + margin-bottom: 24rpx;
  332 + height: 32rpx;
  333 + .info-status {
  334 + padding: 4rpx 12rpx;
  335 + border-radius: 6rpx;
  336 + font-size: 24rpx;
  337 + color: #fff;
  338 + line-height: 40rpx;
  339 + }
  340 + &:last-child {
  341 + margin-bottom: 0;
  342 + }
  343 + text {
  344 + width: 60%;
  345 + &:last-child {
  346 + color: rgba(0,0,0,0.9);
  347 + width: 40%;
  348 + }
  349 + }
  350 + }
  351 +}
  352 +
  353 +.filter-form {
  354 + .form-item { margin-bottom: 24rpx; }
  355 + .label {
  356 + margin-bottom: 20rpx;
  357 + color: rgba(0,0,0,0.9);
  358 + height: 44rpx;
  359 + line-height: 44rpx;
  360 + font-size: 30rpx;
  361 + }
  362 + .fake-select {
  363 + height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx;
  364 + .placeholder { color: #999; }
  365 + .value { color: #333; }
  366 + }
  367 +}
  368 +</style>
\ No newline at end of file
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="contract-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar
  6 + v-model="searchKeyword"
  7 + radius="6"
  8 + placeholder="请输入需方名称或编号或办事处"
  9 + clearButton="auto"
  10 + cancelButton="none"
  11 + bgColor="#F3F3F3"
  12 + textColor="rgba(0,0,0,0.4)"
  13 + @confirm="search"
  14 + @input="onSearchInput"
  15 + />
  16 + <view class="tool-icons">
  17 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  18 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  19 + </view>
  20 + </view>
  21 +
  22 + <view class="tabs">
  23 + <view
  24 + v-for="(t, i) in tabs"
  25 + :key="i"
  26 + :class="['tab', { active: status === t.value }]"
  27 + @click="switchTab(t)"
  28 + >
  29 + {{ t.label }}
  30 + </view>
  31 + </view>
  32 + </view>
  33 +
  34 + <view :class="['list-box', { 'pad-batch': batchMode }]">
  35 + <card-list
  36 + ref="cardRef"
  37 + :fetch-fn="fetchList"
  38 + :query="query"
  39 + :extra="extraParams"
  40 + :selectable="batchMode"
  41 + row-key="id"
  42 + :selectedKeys.sync="selectedKeys"
  43 + :enable-refresh="true"
  44 + :enable-load-more="true"
  45 + @loaded="onCardLoaded"
  46 + @error="onCardError"
  47 + >
  48 + <template v-slot="{ item }">
  49 + <view class="card">
  50 + <view class="card-header">
  51 + <text class="title omit2">{{ item.buyerName }}</text>
  52 + <text v-if="item.status === 'STANDARD'" :class="['status']" :style="{ background: statusMap[item.shippingStatusName] }">{{ item.shippingStatusName }}</text>
  53 + </view>
  54 + <view class="info-row">
  55 + <text>编号</text><text>{{ item.code }}</text>
  56 + </view>
  57 + <view class="info-row">
  58 + <text>办事处</text><text>{{ item.deptName }}</text>
  59 + </view>
  60 + <view class="info-row" v-if="item.status === 'DRAFT' || item.status === 'DELETED'">
  61 + <text>订单总额</text><text class="amount" :style="{ color: '#b67a76' }">{{ item.totalAmountIncludingTax ? '¥' : '' }}{{ formatAmount(item.totalAmountIncludingTax) || '-' }}</text>
  62 + </view>
  63 + <view class="info-row" v-if="item.status === 'STANDARD' || item.status === 'FORMAL'">
  64 + <text>{{ item.status === 'STANDARD' ? '标准合同' : '正式合同' }}规范性审核状态</text><span class="info-status" :style="item.standardApprovedName ? getStatusCss(item.standardApprovedName) : ''">{{ item.standardApprovedName || '-' }}</span>
  65 + </view>
  66 + <view class="info-row">
  67 + <text>订货日期</text><text>{{ item.orderDate }}</text>
  68 + </view>
  69 + </view>
  70 + </template>
  71 + </card-list>
  72 + </view>
  73 +
  74 + <filter-modal
  75 + :visible.sync="filterVisible"
  76 + :value.sync="filterForm"
  77 + title="筛选"
  78 + @reset="onFilterReset"
  79 + @confirm="onFilterConfirm"
  80 + >
  81 + <template v-slot="{ model }">
  82 + <view class="filter-form">
  83 + <view class="form-item">
  84 + <view class="label">订货日期</view>
  85 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  86 + </view>
  87 + </view>
  88 + </template>
  89 + </filter-modal>
  90 +
  91 + </view>
  92 +</template>
  93 +
  94 +<script>
  95 +import CardList from '@/components/card/index.vue'
  96 +import FilterModal from '@/components/filter/index.vue'
  97 +import { queryContractApi, statusStyle,statusMap } from '@/api/contract.js'
  98 +import { officeQueryApi } from '@/api/devManage.js'
  99 +
  100 +export default {
  101 + components: { CardList, FilterModal },
  102 + data() {
  103 + return {
  104 + searchKeyword: '',
  105 + searchKeywordDebounced: '',
  106 +
  107 + tabs: [
  108 + { label: '草稿合同', value: 'DRAFT' },
  109 + { label: '正式合同', value: 'FORMAL' },
  110 + { label: '标准合同', value: 'STANDARD' },
  111 + { label: '已删除合同', value: 'DELETED' }
  112 + ],
  113 + status: 'DRAFT',
  114 +
  115 + query: { deptId: '', deptName: '', dateRange: [] },
  116 + extraParams: {},
  117 +
  118 + batchMode: false,
  119 + selectedKeys: [],
  120 + currentItems: [],
  121 +
  122 + filterVisible: false,
  123 + filterForm: { deptId: '', deptName: '', dateRange: [] },
  124 + deptSelectVisible: false,
  125 + statusStyle,
  126 + statusMap,
  127 + }
  128 + },
  129 + computed: {
  130 + extraCombined() {
  131 + return {
  132 + keyword: this.searchKeywordDebounced || undefined,
  133 + status: this.status || undefined,
  134 + type: 'INTL_INVENTORY_AGMT'
  135 + }
  136 + }
  137 + },
  138 + watch: {
  139 + extraCombined: {
  140 + deep: true,
  141 + handler(v) {
  142 + this.extraParams = v
  143 + },
  144 + immediate: true
  145 + }
  146 + },
  147 + created() {
  148 + },
  149 + onReachBottom() {
  150 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  151 + this.$refs.cardRef.onLoadMore()
  152 + }
  153 + },
  154 + beforeDestroy() {
  155 + if (this.searchDebounceTimer) {
  156 + clearTimeout(this.searchDebounceTimer)
  157 + this.searchDebounceTimer = null
  158 + }
  159 + },
  160 + methods: {
  161 + onCardLoaded({ items }) {
  162 + this.currentItems = items
  163 + },
  164 + onCardError() {
  165 + uni.showToast({ title: '列表加载失败', icon: 'none' })
  166 + },
  167 + onSearchInput() {
  168 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  169 + this.searchDebounceTimer = setTimeout(() => {
  170 + this.searchKeywordDebounced = this.searchKeyword
  171 + this.searchDebounceTimer = null
  172 + }, 1200)
  173 + },
  174 + search(e) {
  175 + const val = e && e.value != null ? e.value : this.searchKeyword
  176 + this.searchKeyword = val
  177 + this.searchKeywordDebounced = val
  178 + },
  179 + switchTab(item) {
  180 + this.status = item.value
  181 + },
  182 + openFilter() {
  183 + this.filterVisible = true
  184 + },
  185 + onFilterReset(payload) {
  186 + this.filterForm = payload
  187 + },
  188 + onFilterConfirm(payload) {
  189 + this.query = { ...payload }
  190 + },
  191 + openDeptSelect() {
  192 + this.deptSelectVisible = true
  193 + },
  194 + onDeptConfirm(payload) {
  195 + const val = payload && payload.value != null ? payload.value : ''
  196 + const label = payload && payload.label != null ? payload.label : ''
  197 + this.filterForm.deptId = val
  198 + this.filterForm.deptName = label
  199 + },
  200 + toggleBatch() {
  201 + this.batchMode = !this.batchMode
  202 + if (!this.batchMode) this.selectedKeys = []
  203 + },
  204 + onAdd() {
  205 + uni.showToast({ title: '点击新增', icon: 'none' })
  206 + },
  207 + fetchList({ pageIndex, pageSize, query, extra }) {
  208 + const params = { pageIndex, pageSize, ...extra, ...query }
  209 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  210 + params.orderDateStart = params.dateRange[0]
  211 + params.orderDateEnd = params.dateRange[1]
  212 + delete params.dateRange
  213 + }
  214 + return queryContractApi(params)
  215 + .then(res => {
  216 + const _data = res.data || {}
  217 + const records = _data.datas || []
  218 + const totalCount = _data.totalCount || 0
  219 + const hasNext = _data.hasNext || false
  220 + return { records, totalCount, hasNext }
  221 + })
  222 + .catch(err => {
  223 + console.error('queryContractApi error', err)
  224 + this.onCardError()
  225 + return { records: [], totalCount: 0, hasNext: false }
  226 + })
  227 + },
  228 +
  229 + formatAmount(val) {
  230 + if (val == null) return '0.00'
  231 + try {
  232 + const num = Number(val)
  233 + return num.toFixed(2)
  234 + } catch (e) {
  235 + return String(val)
  236 + }
  237 + },
  238 + getStatusCss(name) {
  239 + const list = Array.isArray(this.statusStyle) ? this.statusStyle : []
  240 + const found = list.find(it => it && it.text === name) || {}
  241 + return {
  242 + color: found.color || '#000',
  243 + backgroundColor: found.bgColor || '#000'
  244 + }
  245 + }
  246 + }
  247 +}
  248 +</script>
  249 +
  250 +<style lang="scss" scoped>
  251 +.page {
  252 + display: flex;
  253 + flex-direction: column;
  254 + height: 100vh;
  255 +}
  256 +.contract-list-fixed {
  257 + position: fixed;
  258 + top: 96rpx;
  259 + left: 0;
  260 + right: 0;
  261 + z-index: 2;
  262 + background: #fff;
  263 +
  264 + .search-row {
  265 + display: flex;
  266 + align-items: center;
  267 + padding: 16rpx 32rpx;
  268 + .uni-searchbar { padding: 0; flex: 1; }
  269 + .tool-icons { display: flex; .tool-icon { width: 48rpx; height: 48rpx; display: block; margin-left: 32rpx; } }
  270 + }
  271 +
  272 + .tabs {
  273 + display: flex;
  274 + align-items: center;
  275 + justify-content: space-between;
  276 + padding: 26rpx 0;
  277 + border-bottom: solid 1px #E7E7E7;
  278 + .tab { width: 50%; text-align: center; color: rgba(0,0,0,0.9); line-height: 44rpx; position: relative; font-weight: 600; }
  279 + .tab.active { color: $theme-primary; }
  280 + .tab.active::after {
  281 + content: '';
  282 + position: absolute;
  283 + left: 50%;
  284 + transform: translateX(-50%);
  285 + bottom: -13px;
  286 + width: 32rpx;
  287 + height: 6rpx;
  288 + border-radius: 4rpx;
  289 + background: $theme-primary;
  290 + }
  291 + }
  292 +}
  293 +::v-deep .uni-searchbar__box { height: 80rpx !important; justify-content: start; .uni-searchbar__box-search-input { font-size: 32rpx !important; } }
  294 +
  295 +.list-box {
  296 + flex: 1;
  297 + padding-top: 236rpx;
  298 + &.pad-batch { padding-bottom: 144rpx; }
  299 + .card { position: relative; }
  300 + .card-header {
  301 + margin-bottom: 28rpx;
  302 + position: relative;
  303 +
  304 + .title {
  305 + font-size: 36rpx;
  306 + font-weight: 600;
  307 + line-height: 50rpx;
  308 + color: #323241;
  309 + width: 578rpx;
  310 + }
  311 +
  312 + .status {
  313 + font-size: 30rpx;
  314 + font-weight: 600;
  315 + position: absolute;
  316 + top: -36rpx;
  317 + right: -32rpx;
  318 + height: 48rpx;
  319 + line-height: 48rpx;
  320 + color: #fff;
  321 + font-size: 24rpx;
  322 + padding: 0 14rpx;
  323 + border-radius: 6rpx;
  324 + &.status_1 {
  325 + background: #3D48A3;
  326 + }
  327 + &.status_2 {
  328 + background: #2BA471;
  329 + }
  330 + &.status_3 {
  331 + background: #D54941;
  332 + }
  333 + &.status_4 {
  334 + background: #E7E7E7;
  335 + color: #323241;
  336 + }
  337 + }
  338 + }
  339 + .info-row {
  340 + display: flex;
  341 + align-items: center;
  342 + color: rgba(0,0,0,0.6);
  343 + font-size: 28rpx;
  344 + margin-bottom: 24rpx;
  345 + height: 32rpx;
  346 + .info-status {
  347 + padding: 4rpx 12rpx;
  348 + border-radius: 6rpx;
  349 + font-size: 24rpx;
  350 + color: #fff;
  351 + line-height: 40rpx;
  352 + }
  353 + &:last-child {
  354 + margin-bottom: 0;
  355 + }
  356 + text {
  357 + width: 60%;
  358 + &:last-child {
  359 + color: rgba(0,0,0,0.9);
  360 + width: 40%;
  361 + }
  362 + }
  363 + }
  364 +}
  365 +
  366 +.filter-form {
  367 + .form-item { margin-bottom: 24rpx; }
  368 + .label {
  369 + margin-bottom: 20rpx;
  370 + color: rgba(0,0,0,0.9);
  371 + height: 44rpx;
  372 + line-height: 44rpx;
  373 + font-size: 30rpx;
  374 + }
  375 + .fake-select {
  376 + height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx;
  377 + .placeholder { color: #999; }
  378 + .value { color: #333; }
  379 + }
  380 +}
  381 +</style>
\ No newline at end of file
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="contract-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar
  6 + v-model="searchKeyword"
  7 + radius="6"
  8 + placeholder="请输入需方名称或编号或办事处"
  9 + clearButton="auto"
  10 + cancelButton="none"
  11 + bgColor="#F3F3F3"
  12 + textColor="rgba(0,0,0,0.4)"
  13 + @confirm="search"
  14 + @input="onSearchInput"
  15 + />
  16 + <view class="tool-icons">
  17 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  18 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  19 + </view>
  20 + </view>
  21 +
  22 + <view class="tabs">
  23 + <view
  24 + v-for="(t, i) in tabs"
  25 + :key="i"
  26 + :class="['tab', { active: status === t.value }]"
  27 + @click="switchTab(t)"
  28 + >
  29 + {{ t.label }}
  30 + </view>
  31 + </view>
  32 + </view>
  33 +
  34 + <view :class="['list-box', { 'pad-batch': batchMode }]">
  35 + <card-list
  36 + ref="cardRef"
  37 + :fetch-fn="fetchList"
  38 + :query="query"
  39 + :extra="extraParams"
  40 + :selectable="batchMode"
  41 + row-key="id"
  42 + :selectedKeys.sync="selectedKeys"
  43 + :enable-refresh="true"
  44 + :enable-load-more="true"
  45 + @loaded="onCardLoaded"
  46 + @error="onCardError"
  47 + >
  48 + <template v-slot="{ item }">
  49 + <view class="card">
  50 + <view class="card-header">
  51 + <text class="title omit2">{{ item.buyerName }}</text>
  52 + <text v-if="item.status === 'STANDARD'" :class="['status']" :style="{ background: statusMap[item.shippingStatusName] }">{{ item.shippingStatusName }}</text>
  53 + </view>
  54 + <view class="info-row">
  55 + <text>编号</text><text>{{ item.code }}</text>
  56 + </view>
  57 + <view class="info-row">
  58 + <text>办事处</text><text>{{ item.deptName }}</text>
  59 + </view>
  60 + <view class="info-row" v-if="item.status === 'DRAFT' || item.status === 'DELETED'">
  61 + <text>订单总额</text><text class="amount" :style="{ color: '#b67a76' }">{{ item.totalAmountIncludingTax ? '¥' : '' }}{{ formatAmount(item.totalAmountIncludingTax) || '-' }}</text>
  62 + </view>
  63 + <view class="info-row" v-if="item.status === 'STANDARD' || item.status === 'FORMAL'">
  64 + <text>{{ item.status === 'STANDARD' ? '标准合同' : '正式合同' }}规范性审核状态</text><span class="info-status" :style="item.standardApprovedName ? getStatusCss(item.standardApprovedName) : ''">{{ item.standardApprovedName || '-' }}</span>
  65 + </view>
  66 + <view class="info-row">
  67 + <text>订货日期</text><text>{{ item.orderDate }}</text>
  68 + </view>
  69 + </view>
  70 + </template>
  71 + </card-list>
  72 + </view>
  73 +
  74 + <filter-modal
  75 + :visible.sync="filterVisible"
  76 + :value.sync="filterForm"
  77 + title="筛选"
  78 + @reset="onFilterReset"
  79 + @confirm="onFilterConfirm"
  80 + >
  81 + <template v-slot="{ model }">
  82 + <view class="filter-form">
  83 + <view class="form-item">
  84 + <view class="label">订货日期</view>
  85 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  86 + </view>
  87 + </view>
  88 + </template>
  89 + </filter-modal>
  90 +
  91 + </view>
  92 +</template>
  93 +
  94 +<script>
  95 +import CardList from '@/components/card/index.vue'
  96 +import FilterModal from '@/components/filter/index.vue'
  97 +import { queryContractApi, statusStyle,statusMap } from '@/api/contract.js'
  98 +import { officeQueryApi } from '@/api/devManage.js'
  99 +
  100 +export default {
  101 + components: { CardList, FilterModal },
  102 + data() {
  103 + return {
  104 + searchKeyword: '',
  105 + searchKeywordDebounced: '',
  106 +
  107 + tabs: [
  108 + { label: '草稿合同', value: 'DRAFT' },
  109 + { label: '正式合同', value: 'FORMAL' },
  110 + { label: '标准合同', value: 'STANDARD' },
  111 + { label: '已删除合同', value: 'DELETED' }
  112 + ],
  113 + status: 'DRAFT',
  114 +
  115 + query: { deptId: '', deptName: '', dateRange: [] },
  116 + extraParams: {},
  117 +
  118 + batchMode: false,
  119 + selectedKeys: [],
  120 + currentItems: [],
  121 +
  122 + filterVisible: false,
  123 + filterForm: { deptId: '', deptName: '', dateRange: [] },
  124 + deptSelectVisible: false,
  125 + statusStyle,
  126 + statusMap,
  127 + }
  128 + },
  129 + computed: {
  130 + extraCombined() {
  131 + return {
  132 + keyword: this.searchKeywordDebounced || undefined,
  133 + status: this.status || undefined,
  134 + type: 'INTL_OPEN_SPEC_AGMT'
  135 + }
  136 + }
  137 + },
  138 + watch: {
  139 + extraCombined: {
  140 + deep: true,
  141 + handler(v) {
  142 + this.extraParams = v
  143 + },
  144 + immediate: true
  145 + }
  146 + },
  147 + created() {
  148 + },
  149 + onReachBottom() {
  150 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  151 + this.$refs.cardRef.onLoadMore()
  152 + }
  153 + },
  154 + beforeDestroy() {
  155 + if (this.searchDebounceTimer) {
  156 + clearTimeout(this.searchDebounceTimer)
  157 + this.searchDebounceTimer = null
  158 + }
  159 + },
  160 + methods: {
  161 + onCardLoaded({ items }) {
  162 + this.currentItems = items
  163 + },
  164 + onCardError() {
  165 + uni.showToast({ title: '列表加载失败', icon: 'none' })
  166 + },
  167 + onSearchInput() {
  168 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  169 + this.searchDebounceTimer = setTimeout(() => {
  170 + this.searchKeywordDebounced = this.searchKeyword
  171 + this.searchDebounceTimer = null
  172 + }, 1200)
  173 + },
  174 + search(e) {
  175 + const val = e && e.value != null ? e.value : this.searchKeyword
  176 + this.searchKeyword = val
  177 + this.searchKeywordDebounced = val
  178 + },
  179 + switchTab(item) {
  180 + this.status = item.value
  181 + },
  182 + openFilter() {
  183 + this.filterVisible = true
  184 + },
  185 + onFilterReset(payload) {
  186 + this.filterForm = payload
  187 + },
  188 + onFilterConfirm(payload) {
  189 + this.query = { ...payload }
  190 + },
  191 + openDeptSelect() {
  192 + this.deptSelectVisible = true
  193 + },
  194 + onDeptConfirm(payload) {
  195 + const val = payload && payload.value != null ? payload.value : ''
  196 + const label = payload && payload.label != null ? payload.label : ''
  197 + this.filterForm.deptId = val
  198 + this.filterForm.deptName = label
  199 + },
  200 + toggleBatch() {
  201 + this.batchMode = !this.batchMode
  202 + if (!this.batchMode) this.selectedKeys = []
  203 + },
  204 + onAdd() {
  205 + uni.showToast({ title: '点击新增', icon: 'none' })
  206 + },
  207 + fetchList({ pageIndex, pageSize, query, extra }) {
  208 + const params = { pageIndex, pageSize, ...extra, ...query }
  209 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  210 + params.orderDateStart = params.dateRange[0]
  211 + params.orderDateEnd = params.dateRange[1]
  212 + delete params.dateRange
  213 + }
  214 + return queryContractApi(params)
  215 + .then(res => {
  216 + const _data = res.data || {}
  217 + const records = _data.datas || []
  218 + const totalCount = _data.totalCount || 0
  219 + const hasNext = _data.hasNext || false
  220 + return { records, totalCount, hasNext }
  221 + })
  222 + .catch(err => {
  223 + console.error('queryContractApi error', err)
  224 + this.onCardError()
  225 + return { records: [], totalCount: 0, hasNext: false }
  226 + })
  227 + },
  228 +
  229 + formatAmount(val) {
  230 + if (val == null) return '0.00'
  231 + try {
  232 + const num = Number(val)
  233 + return num.toFixed(2)
  234 + } catch (e) {
  235 + return String(val)
  236 + }
  237 + },
  238 + getStatusCss(name) {
  239 + const list = Array.isArray(this.statusStyle) ? this.statusStyle : []
  240 + const found = list.find(it => it && it.text === name) || {}
  241 + return {
  242 + color: found.color || '#000',
  243 + backgroundColor: found.bgColor || '#000'
  244 + }
  245 + }
  246 + }
  247 +}
  248 +</script>
  249 +
  250 +<style lang="scss" scoped>
  251 +.page {
  252 + display: flex;
  253 + flex-direction: column;
  254 + height: 100vh;
  255 +}
  256 +.contract-list-fixed {
  257 + position: fixed;
  258 + top: 96rpx;
  259 + left: 0;
  260 + right: 0;
  261 + z-index: 2;
  262 + background: #fff;
  263 +
  264 + .search-row {
  265 + display: flex;
  266 + align-items: center;
  267 + padding: 16rpx 32rpx;
  268 + .uni-searchbar { padding: 0; flex: 1; }
  269 + .tool-icons { display: flex; .tool-icon { width: 48rpx; height: 48rpx; display: block; margin-left: 32rpx; } }
  270 + }
  271 +
  272 + .tabs {
  273 + display: flex;
  274 + align-items: center;
  275 + justify-content: space-between;
  276 + padding: 26rpx 0;
  277 + border-bottom: solid 1px #E7E7E7;
  278 + .tab { width: 50%; text-align: center; color: rgba(0,0,0,0.9); line-height: 44rpx; position: relative; font-weight: 600; }
  279 + .tab.active { color: $theme-primary; }
  280 + .tab.active::after {
  281 + content: '';
  282 + position: absolute;
  283 + left: 50%;
  284 + transform: translateX(-50%);
  285 + bottom: -13px;
  286 + width: 32rpx;
  287 + height: 6rpx;
  288 + border-radius: 4rpx;
  289 + background: $theme-primary;
  290 + }
  291 + }
  292 +}
  293 +::v-deep .uni-searchbar__box { height: 80rpx !important; justify-content: start; .uni-searchbar__box-search-input { font-size: 32rpx !important; } }
  294 +
  295 +.list-box {
  296 + flex: 1;
  297 + padding-top: 236rpx;
  298 + &.pad-batch { padding-bottom: 144rpx; }
  299 + .card { position: relative; }
  300 + .card-header {
  301 + margin-bottom: 28rpx;
  302 + position: relative;
  303 +
  304 + .title {
  305 + font-size: 36rpx;
  306 + font-weight: 600;
  307 + line-height: 50rpx;
  308 + color: #323241;
  309 + width: 578rpx;
  310 + }
  311 +
  312 + .status {
  313 + font-size: 30rpx;
  314 + font-weight: 600;
  315 + position: absolute;
  316 + top: -36rpx;
  317 + right: -32rpx;
  318 + height: 48rpx;
  319 + line-height: 48rpx;
  320 + color: #fff;
  321 + font-size: 24rpx;
  322 + padding: 0 14rpx;
  323 + border-radius: 6rpx;
  324 + &.status_1 {
  325 + background: #3D48A3;
  326 + }
  327 + &.status_2 {
  328 + background: #2BA471;
  329 + }
  330 + &.status_3 {
  331 + background: #D54941;
  332 + }
  333 + &.status_4 {
  334 + background: #E7E7E7;
  335 + color: #323241;
  336 + }
  337 + }
  338 + }
  339 + .info-row {
  340 + display: flex;
  341 + align-items: center;
  342 + color: rgba(0,0,0,0.6);
  343 + font-size: 28rpx;
  344 + margin-bottom: 24rpx;
  345 + height: 32rpx;
  346 + .info-status {
  347 + padding: 4rpx 12rpx;
  348 + border-radius: 6rpx;
  349 + font-size: 24rpx;
  350 + color: #fff;
  351 + line-height: 40rpx;
  352 + }
  353 + &:last-child {
  354 + margin-bottom: 0;
  355 + }
  356 + text {
  357 + width: 60%;
  358 + &:last-child {
  359 + color: rgba(0,0,0,0.9);
  360 + width: 40%;
  361 + }
  362 + }
  363 + }
  364 +}
  365 +
  366 +.filter-form {
  367 + .form-item { margin-bottom: 24rpx; }
  368 + .label {
  369 + margin-bottom: 20rpx;
  370 + color: rgba(0,0,0,0.9);
  371 + height: 44rpx;
  372 + line-height: 44rpx;
  373 + font-size: 30rpx;
  374 + }
  375 + .fake-select {
  376 + height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx;
  377 + .placeholder { color: #999; }
  378 + .value { color: #333; }
  379 + }
  380 +}
  381 +</style>
\ No newline at end of file
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="framework-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar
  6 + v-model="searchKeyword"
  7 + radius="6"
  8 + placeholder="请输入客户名称或编号"
  9 + clearButton="auto"
  10 + cancelButton="none"
  11 + bgColor="#F3F3F3"
  12 + textColor="rgba(0,0,0,0.4)"
  13 + @confirm="search"
  14 + @input="onSearchInput"
  15 + />
  16 + <view class="tool-icons">
  17 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  18 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  19 + </view>
  20 + </view>
  21 +
  22 + </view>
  23 + <!-- 列表卡片组件 -->
  24 + <view :class="['list-box', { 'pad-batch': batchMode }]">
  25 + <card-list
  26 + ref="cardRef"
  27 + :fetch-fn="fetchList"
  28 + :query="query"
  29 + :extra="extraParams"
  30 + :selectable="batchMode"
  31 + row-key="id"
  32 + :enable-refresh="true"
  33 + :enable-load-more="true"
  34 + @error="onCardError"
  35 + >
  36 + <template v-slot="{ item, selected }">
  37 + <view class="card">
  38 + <view class="card-header">
  39 + <text class="title omit2">{{ item.customerName }}</text>
  40 + </view>
  41 + <view class="info-row">
  42 + <text>框架合同编号</text><text>{{ item.code }}</text>
  43 + </view>
  44 + <view class="info-row">
  45 + <text>是否签订</text><text>{{ item.hasFrameworkAgreement }}</text>
  46 + </view>
  47 + <view class="info-row">
  48 + <text>品种</text><text>{{ item.materialTypeName }}</text>
  49 + </view>
  50 + <view class="info-row">
  51 + <text>授信截至时间</text><text>{{ item.validityTime }}</text>
  52 + </view>
  53 + </view>
  54 + </template>
  55 + </card-list>
  56 + </view>
  57 + <!-- 筛选弹框 -->
  58 + <filter-modal
  59 + :visible.sync="filterVisible"
  60 + :value.sync="filterForm"
  61 + title="筛选"
  62 + @reset="onFilterReset"
  63 + @confirm="onFilterConfirm"
  64 + >
  65 + <template v-slot="{ model }">
  66 + <view class="filter-form">
  67 + <view class="form-item">
  68 + <view class="label">品种</view>
  69 + <uni-data-checkbox
  70 + mode="tag"
  71 + :multiple="false"
  72 + :value-field="'value'"
  73 + :text-field="'text'"
  74 + v-model="model.materialTypeId"
  75 + @change="onMaterialTypeChange"
  76 + :localdata="materialTypeOptions"
  77 + />
  78 + </view>
  79 + <view class="form-item">
  80 + <view class="label">授权截止时间</view>
  81 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  82 + </view>
  83 + </view>
  84 + </template>
  85 + </filter-modal>
  86 + </view>
  87 +</template>
  88 +
  89 +<script>
  90 +import CardList from '@/components/card/index.vue'
  91 +import FilterModal from '@/components/filter/index.vue'
  92 +import {getDicByCodes, getDicName} from '@/utils/dic';
  93 +import { queryApi } from '@/api/contract.js'
  94 +import { productVarietyQueryApi } from '@/api/devManage.js'
  95 +export default {
  96 + components: { CardList, FilterModal },
  97 + data() {
  98 + return {
  99 + searchKeyword: '',
  100 + searchKeywordDebounced: '',
  101 + // 批量选择
  102 + batchMode: false,
  103 + // 给到 card 的筛选值
  104 + query: { materialTypeId: '', dateRange: [] },
  105 + extraParams: {},
  106 +
  107 + // 筛选弹框
  108 + filterVisible: false,
  109 + filterForm: { materialTypeId: '', dateRange: [] },
  110 + materialTypeOptions: [],
  111 + }
  112 +
  113 + },
  114 + computed: {
  115 +
  116 + },
  117 + watch: {
  118 +
  119 + },
  120 + created() {
  121 + this.getProductVariety();
  122 + },
  123 + // 页面触底兜底:当页面自身滚动到底部时,转调卡片组件加载更多
  124 + onReachBottom() {
  125 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  126 + this.$refs.cardRef.onLoadMore()
  127 + }
  128 + },
  129 + beforeDestroy() {
  130 + if (this.searchDebounceTimer) {
  131 + clearTimeout(this.searchDebounceTimer)
  132 + this.searchDebounceTimer = null
  133 + }
  134 + },
  135 + methods: {
  136 + // uni-search-bar 确认搜索:更新关键字并触发 CardList 刷新
  137 + search(e) {
  138 + const val = e && e.value != null ? e.value : this.searchKeyword
  139 + this.searchKeyword = val
  140 + this.searchKeywordDebounced = val
  141 + },
  142 + // 输入实时搜索:1200ms 防抖,仅在停止输入超过阈值后刷新
  143 + onSearchInput(val) {
  144 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  145 + this.searchDebounceTimer = setTimeout(() => {
  146 + this.searchKeywordDebounced = this.searchKeyword
  147 + this.searchDebounceTimer = null
  148 + }, 1200)
  149 + },
  150 + // 列表接口(真实请求)
  151 + fetchList({ pageIndex, pageSize, query, extra }) {
  152 + const params = { pageIndex, pageSize, ...extra, ...query }
  153 + // 处理日期范围
  154 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  155 + params.validityTimeStart = params.dateRange[0] + ' 00:00:00'
  156 + params.validityTimeEnd = params.dateRange[1] + ' 23:59:59'
  157 + delete params.dateRange
  158 + }
  159 + // 关键字(使用去抖后的值避免频繁触发)
  160 + if (this.searchKeywordDebounced) {
  161 + params.customerName = this.searchKeywordDebounced
  162 + }
  163 + return queryApi(params)
  164 + .then(res => {
  165 + console.log('fetchList', res)
  166 + const _data = res.data || {};
  167 + const records = _data.datas || [];
  168 + const totalCount = _data.totalCount || 0;
  169 + const hasNext = _data.hasNext || false
  170 + return { records, totalCount, hasNext }
  171 + })
  172 + .catch(err => {
  173 + console.error('fetchList error', err)
  174 + this.onCardError()
  175 + return { records: [], totalCount: 0, hasNext: false }
  176 + })
  177 + },
  178 + onCardError() {
  179 + uni.showToast({ title: '列表加载失败', icon: 'none' })
  180 + },
  181 +
  182 + openFilter() {
  183 + this.filterVisible = true
  184 + },
  185 + onFilterReset(payload) {
  186 + // 保持弹框不关闭,仅同步表单
  187 + this.filterForm = payload
  188 + },
  189 + onFilterConfirm(payload) {
  190 + // 合并筛选值
  191 + this.query = { ...this.query, ...payload }
  192 + },
  193 + onMaterialTypeChange(e) {
  194 + const raw = e && e.detail && e.detail.value !== undefined
  195 + ? e.detail.value
  196 + : (e && e.value !== undefined ? e.value : '')
  197 + // 直接同步到外层 filterForm,驱动 FilterModal 的 innerModel 更新
  198 + this.filterForm.materialTypeId = raw
  199 + },
  200 +
  201 + onAdd() {
  202 + // uni.navigateTo({ url: '/pages/contract_framework/add' })
  203 + },
  204 +
  205 +
  206 +
  207 + getProductVariety() {
  208 + productVarietyQueryApi({
  209 + pageIndex: 1,
  210 + pageSize: 9999,
  211 + available: true}).then(res => {
  212 + const _data = res.data || {};
  213 + const records = _data.datas || [];
  214 + this.materialTypeOptions = records.map(item => ({
  215 + value: item.id,
  216 + text: item.name
  217 + }))
  218 + })
  219 + },
  220 + }
  221 +}
  222 +</script>
  223 +
  224 +<style lang="scss" scoped>
  225 +.page {
  226 + display: flex;
  227 + flex-direction: column;
  228 + height: 100vh;
  229 +}
  230 +.framework-list-fixed {
  231 + position: fixed;
  232 + top: 96rpx;
  233 + left: 0;
  234 + right: 0;
  235 + z-index: 2;
  236 + background: #fff;
  237 +
  238 + .search-row {
  239 + display: flex;
  240 + align-items: center;
  241 + padding: 16rpx 32rpx;
  242 + .uni-searchbar {
  243 + padding: 0;
  244 + flex: 1;
  245 + }
  246 + .tool-icons {
  247 + display: flex;
  248 + .tool-icon {
  249 + width: 48rpx; height: 48rpx; display: block;
  250 + margin-left: 32rpx;
  251 + }
  252 + }
  253 + }
  254 +
  255 +
  256 +}
  257 +/* 仅当前页覆盖 uni-search-bar 盒子高度 */
  258 +::v-deep .uni-searchbar__box {
  259 + height: 80rpx !important;
  260 + justify-content: start;
  261 + .uni-searchbar__box-search-input {
  262 + font-size: 32rpx !important;
  263 + }
  264 +}
  265 +
  266 +.list-box {
  267 + flex: 1;
  268 + padding-top: 112rpx;
  269 + &.pad-batch { padding-bottom: 144rpx; }
  270 + .card { position: relative; }
  271 + .card-header {
  272 + margin-bottom: 28rpx;
  273 + position: relative;
  274 +
  275 + .title {
  276 + font-size: 36rpx;
  277 + font-weight: 600;
  278 + line-height: 50rpx;
  279 + color: #323241;
  280 + width: 578rpx;
  281 + }
  282 +
  283 + .status {
  284 + font-size: 30rpx;
  285 + font-weight: 600;
  286 + position: absolute;
  287 + top: -36rpx;
  288 + right: -32rpx;
  289 + height: 48rpx;
  290 + line-height: 48rpx;
  291 + color: #fff;
  292 + font-size: 24rpx;
  293 + padding: 0 14rpx;
  294 + border-radius: 6rpx;
  295 + &.status_1 {
  296 + background: #3D48A3;
  297 + }
  298 + &.status_2 {
  299 + background: #2BA471;
  300 + }
  301 + &.status_3 {
  302 + background: #D54941;
  303 + }
  304 + &.status_4 {
  305 + background: #E7E7E7;
  306 + color: #323241;
  307 + }
  308 + }
  309 + }
  310 + .info-row {
  311 + display: flex;
  312 + align-items: center;
  313 + color: rgba(0,0,0,0.6);
  314 + font-size: 28rpx;
  315 + margin-bottom: 24rpx;
  316 + height: 32rpx;
  317 + &:last-child {
  318 + margin-bottom: 0;
  319 + }
  320 + text {
  321 + width: 60%;
  322 + &:last-child {
  323 + color: rgba(0,0,0,0.9);
  324 + width: 40%;
  325 + }
  326 + }
  327 + }
  328 +}
  329 +
  330 +.filter-form {
  331 + .form-item { margin-bottom: 24rpx; }
  332 + .label {
  333 + margin-bottom: 20rpx;
  334 + color: rgba(0,0,0,0.9);
  335 + height: 44rpx;
  336 + line-height: 44rpx;
  337 + font-size: 30rpx;
  338 + }
  339 + .fake-select {
  340 + height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx;
  341 + .placeholder { color: #999; }
  342 + .value { color: #333; }
  343 + }
  344 +}
  345 +
  346 +/* 深度覆盖 uni-data-checkbox(mode=tag)内部的 tag 展示与间距 */
  347 +::v-deep .filter-form .uni-data-checklist .checklist-group {
  348 + .checklist-box {
  349 + &.is--tag {
  350 + width: 212rpx;
  351 + margin-top: 0;
  352 + margin-bottom: 24rpx;
  353 + margin-right: 24rpx;
  354 + height: 80rpx;
  355 + padding: 0;
  356 + border-radius: 12rpx;
  357 + background-color: #f3f3f3;
  358 + border-color: #f3f3f3;
  359 + &:nth-child(3n) {
  360 + margin-right: 0;
  361 + }
  362 + .checklist-content {
  363 + display: flex;
  364 + justify-content: center;
  365 + }
  366 + .checklist-text {
  367 + color: rgba(0,0,0,0.9);
  368 + font-size: 28rpx;
  369 + }
  370 + }
  371 + &.is-checked {
  372 + background-color: $theme-primary-plain-bg !important;
  373 + border-color: $theme-primary-plain-bg !important;
  374 + .checklist-text {
  375 + color: $theme-primary !important;
  376 + }
  377 + }
  378 + }
  379 +
  380 +}
  381 +
  382 +
  383 +</style>
\ No newline at end of file
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="contract-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar
  6 + v-model="searchKeyword"
  7 + radius="6"
  8 + placeholder="请输入需方名称或编号或办事处"
  9 + clearButton="auto"
  10 + cancelButton="none"
  11 + bgColor="#F3F3F3"
  12 + textColor="rgba(0,0,0,0.4)"
  13 + @confirm="search"
  14 + @input="onSearchInput"
  15 + />
  16 + <view class="tool-icons">
  17 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  18 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  19 + </view>
  20 + </view>
  21 +
  22 + <view class="tabs">
  23 + <view
  24 + v-for="(t, i) in tabs"
  25 + :key="i"
  26 + :class="['tab', { active: status === t.value }]"
  27 + @click="switchTab(t)"
  28 + >
  29 + {{ t.label }}
  30 + </view>
  31 + </view>
  32 + </view>
  33 +
  34 + <view :class="['list-box', { 'pad-batch': batchMode }]">
  35 + <card-list
  36 + ref="cardRef"
  37 + :fetch-fn="fetchList"
  38 + :query="query"
  39 + :extra="extraParams"
  40 + :selectable="batchMode"
  41 + row-key="id"
  42 + :selectedKeys.sync="selectedKeys"
  43 + :enable-refresh="true"
  44 + :enable-load-more="true"
  45 + @loaded="onCardLoaded"
  46 + @error="onCardError"
  47 + >
  48 + <template v-slot="{ item }">
  49 + <view class="card">
  50 + <view class="card-header">
  51 + <text class="title omit2">{{ item.buyerName }}</text>
  52 + <text v-if="item.status === 'STANDARD'" :class="['status']" :style="{ background: statusMap[item.shippingStatusName] }">{{ item.shippingStatusName }}</text>
  53 + </view>
  54 + <view class="info-row">
  55 + <text>编号</text><text>{{ item.code }}</text>
  56 + </view>
  57 + <view class="info-row">
  58 + <text>办事处</text><text>{{ item.deptName }}</text>
  59 + </view>
  60 + <view class="info-row" v-if="item.status !== 'STANDARD'">
  61 + <text>订单总额</text><text class="amount" :style="{ color: '#b67a76' }">{{ item.totalAmountIncludingTax ? '¥' : '' }}{{ formatAmount(item.totalAmountIncludingTax) || '-' }}</text>
  62 + </view>
  63 + <view class="info-row" v-if="item.status === 'STANDARD'">
  64 + <text>正式合同规范性审核状态</text><span class="info-status" :style="item.standardApprovedName ? getStatusCss(item.standardApprovedName) : ''">{{ item.standardApprovedName || '-' }}</span>
  65 + </view>
  66 + <view class="info-row">
  67 + <text>订货日期</text><text>{{ item.orderDate }}</text>
  68 + </view>
  69 + </view>
  70 + </template>
  71 + </card-list>
  72 + </view>
  73 +
  74 + <filter-modal
  75 + :visible.sync="filterVisible"
  76 + :value.sync="filterForm"
  77 + title="筛选"
  78 + @reset="onFilterReset"
  79 + @confirm="onFilterConfirm"
  80 + >
  81 + <template v-slot="{ model }">
  82 + <view class="filter-form">
  83 + <view class="form-item">
  84 + <view class="label">订货日期</view>
  85 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  86 + </view>
  87 + </view>
  88 + </template>
  89 + </filter-modal>
  90 +
  91 + </view>
  92 +</template>
  93 +
  94 +<script>
  95 +import CardList from '@/components/card/index.vue'
  96 +import FilterModal from '@/components/filter/index.vue'
  97 +import { queryContractApi, statusStyle,statusMap } from '@/api/contract.js'
  98 +import { officeQueryApi } from '@/api/devManage.js'
  99 +
  100 +export default {
  101 + components: { CardList, FilterModal },
  102 + data() {
  103 + return {
  104 + searchKeyword: '',
  105 + searchKeywordDebounced: '',
  106 +
  107 + tabs: [
  108 + { label: '草稿合同', value: 'DRAFT' },
  109 + { label: '标准合同', value: 'STANDARD' },
  110 + { label: '已删除合同', value: 'DELETED' }
  111 + ],
  112 + status: 'DRAFT',
  113 +
  114 + query: { deptId: '', deptName: '', dateRange: [] },
  115 + extraParams: {},
  116 +
  117 + batchMode: false,
  118 + selectedKeys: [],
  119 + currentItems: [],
  120 +
  121 + filterVisible: false,
  122 + filterForm: { deptId: '', deptName: '', dateRange: [] },
  123 + deptSelectVisible: false,
  124 + statusStyle,
  125 + statusMap,
  126 + }
  127 + },
  128 + computed: {
  129 + extraCombined() {
  130 + return {
  131 + keyword: this.searchKeywordDebounced || undefined,
  132 + status: this.status || undefined,
  133 + type: 'PROCESS_STD_AGMT'
  134 + }
  135 + }
  136 + },
  137 + watch: {
  138 + extraCombined: {
  139 + deep: true,
  140 + handler(v) {
  141 + this.extraParams = v
  142 + },
  143 + immediate: true
  144 + }
  145 + },
  146 + created() {
  147 + },
  148 + onReachBottom() {
  149 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  150 + this.$refs.cardRef.onLoadMore()
  151 + }
  152 + },
  153 + beforeDestroy() {
  154 + if (this.searchDebounceTimer) {
  155 + clearTimeout(this.searchDebounceTimer)
  156 + this.searchDebounceTimer = null
  157 + }
  158 + },
  159 + methods: {
  160 + onCardLoaded({ items }) {
  161 + this.currentItems = items
  162 + },
  163 + onCardError() {
  164 + uni.showToast({ title: '列表加载失败', icon: 'none' })
  165 + },
  166 + onSearchInput() {
  167 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  168 + this.searchDebounceTimer = setTimeout(() => {
  169 + this.searchKeywordDebounced = this.searchKeyword
  170 + this.searchDebounceTimer = null
  171 + }, 1200)
  172 + },
  173 + search(e) {
  174 + const val = e && e.value != null ? e.value : this.searchKeyword
  175 + this.searchKeyword = val
  176 + this.searchKeywordDebounced = val
  177 + },
  178 + switchTab(item) {
  179 + this.status = item.value
  180 + },
  181 + openFilter() {
  182 + this.filterVisible = true
  183 + },
  184 + onFilterReset(payload) {
  185 + this.filterForm = payload
  186 + },
  187 + onFilterConfirm(payload) {
  188 + this.query = { ...payload }
  189 + },
  190 + openDeptSelect() {
  191 + this.deptSelectVisible = true
  192 + },
  193 + onDeptConfirm(payload) {
  194 + const val = payload && payload.value != null ? payload.value : ''
  195 + const label = payload && payload.label != null ? payload.label : ''
  196 + this.filterForm.deptId = val
  197 + this.filterForm.deptName = label
  198 + },
  199 + toggleBatch() {
  200 + this.batchMode = !this.batchMode
  201 + if (!this.batchMode) this.selectedKeys = []
  202 + },
  203 + onAdd() {
  204 + uni.showToast({ title: '点击新增', icon: 'none' })
  205 + },
  206 + fetchList({ pageIndex, pageSize, query, extra }) {
  207 + console.log('fetchList', pageIndex, pageSize, query, extra)
  208 + const params = { pageIndex, pageSize, ...extra, ...query }
  209 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  210 + params.orderDateStart = params.dateRange[0]
  211 + params.orderDateEnd = params.dateRange[1]
  212 + delete params.dateRange
  213 + }
  214 + return queryContractApi(params)
  215 + .then(res => {
  216 + const _data = res.data || {}
  217 + const records = _data.datas || []
  218 + const totalCount = _data.totalCount || 0
  219 + const hasNext = _data.hasNext || false
  220 + return { records, totalCount, hasNext }
  221 + })
  222 + .catch(err => {
  223 + console.error('queryContractApi error', err)
  224 + this.onCardError()
  225 + return { records: [], totalCount: 0, hasNext: false }
  226 + })
  227 + },
  228 +
  229 + formatAmount(val) {
  230 + if (val == null) return '0.00'
  231 + try {
  232 + const num = Number(val)
  233 + return num.toFixed(2)
  234 + } catch (e) {
  235 + return String(val)
  236 + }
  237 + },
  238 + getStatusCss(name) {
  239 + const list = Array.isArray(this.statusStyle) ? this.statusStyle : []
  240 + const found = list.find(it => it && it.text === name) || {}
  241 + return {
  242 + color: found.color || '#000',
  243 + backgroundColor: found.bgColor || '#000'
  244 + }
  245 + }
  246 + }
  247 +}
  248 +</script>
  249 +
  250 +<style lang="scss" scoped>
  251 +.page {
  252 + display: flex;
  253 + flex-direction: column;
  254 + height: 100vh;
  255 +}
  256 +.contract-list-fixed {
  257 + position: fixed;
  258 + top: 96rpx;
  259 + left: 0;
  260 + right: 0;
  261 + z-index: 2;
  262 + background: #fff;
  263 +
  264 + .search-row {
  265 + display: flex;
  266 + align-items: center;
  267 + padding: 16rpx 32rpx;
  268 + .uni-searchbar { padding: 0; flex: 1; }
  269 + .tool-icons { display: flex; .tool-icon { width: 48rpx; height: 48rpx; display: block; margin-left: 32rpx; } }
  270 + }
  271 +
  272 + .tabs {
  273 + display: flex;
  274 + align-items: center;
  275 + justify-content: space-between;
  276 + padding: 26rpx 0;
  277 + border-bottom: solid 1px #E7E7E7;
  278 + .tab { width: 50%; text-align: center; color: rgba(0,0,0,0.9); line-height: 44rpx; position: relative; font-weight: 600; }
  279 + .tab.active { color: $theme-primary; }
  280 + .tab.active::after {
  281 + content: '';
  282 + position: absolute;
  283 + left: 50%;
  284 + transform: translateX(-50%);
  285 + bottom: -13px;
  286 + width: 32rpx;
  287 + height: 6rpx;
  288 + border-radius: 4rpx;
  289 + background: $theme-primary;
  290 + }
  291 + }
  292 +}
  293 +::v-deep .uni-searchbar__box { height: 80rpx !important; justify-content: start; .uni-searchbar__box-search-input { font-size: 32rpx !important; } }
  294 +
  295 +.list-box {
  296 + flex: 1;
  297 + padding-top: 236rpx;
  298 + &.pad-batch { padding-bottom: 144rpx; }
  299 + .card { position: relative; }
  300 + .card-header {
  301 + margin-bottom: 28rpx;
  302 + position: relative;
  303 +
  304 + .title {
  305 + font-size: 36rpx;
  306 + font-weight: 600;
  307 + line-height: 50rpx;
  308 + color: #323241;
  309 + width: 578rpx;
  310 + }
  311 +
  312 + .status {
  313 + font-size: 30rpx;
  314 + font-weight: 600;
  315 + position: absolute;
  316 + top: -36rpx;
  317 + right: -32rpx;
  318 + height: 48rpx;
  319 + line-height: 48rpx;
  320 + color: #fff;
  321 + font-size: 24rpx;
  322 + padding: 0 14rpx;
  323 + border-radius: 6rpx;
  324 + &.status_1 {
  325 + background: #3D48A3;
  326 + }
  327 + &.status_2 {
  328 + background: #2BA471;
  329 + }
  330 + &.status_3 {
  331 + background: #D54941;
  332 + }
  333 + &.status_4 {
  334 + background: #E7E7E7;
  335 + color: #323241;
  336 + }
  337 + }
  338 + }
  339 + .info-row {
  340 + display: flex;
  341 + align-items: center;
  342 + color: rgba(0,0,0,0.6);
  343 + font-size: 28rpx;
  344 + margin-bottom: 24rpx;
  345 + height: 32rpx;
  346 + .info-status {
  347 + padding: 4rpx 12rpx;
  348 + border-radius: 6rpx;
  349 + font-size: 24rpx;
  350 + color: #fff;
  351 + line-height: 40rpx;
  352 + }
  353 + &:last-child {
  354 + margin-bottom: 0;
  355 + }
  356 + text {
  357 + width: 60%;
  358 + &:last-child {
  359 + color: rgba(0,0,0,0.9);
  360 + width: 40%;
  361 + }
  362 + }
  363 + }
  364 +}
  365 +
  366 +.filter-form {
  367 + .form-item { margin-bottom: 24rpx; }
  368 + .label {
  369 + margin-bottom: 20rpx;
  370 + color: rgba(0,0,0,0.9);
  371 + height: 44rpx;
  372 + line-height: 44rpx;
  373 + font-size: 30rpx;
  374 + }
  375 + .fake-select {
  376 + height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx;
  377 + .placeholder { color: #999; }
  378 + .value { color: #333; }
  379 + }
  380 +}
  381 +</style>
\ No newline at end of file
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="contract-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar
  6 + v-model="searchKeyword"
  7 + radius="6"
  8 + placeholder="请输入需方名称或编号或办事处"
  9 + clearButton="auto"
  10 + cancelButton="none"
  11 + bgColor="#F3F3F3"
  12 + textColor="rgba(0,0,0,0.4)"
  13 + @confirm="search"
  14 + @input="onSearchInput"
  15 + />
  16 + <view class="tool-icons">
  17 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  18 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  19 + </view>
  20 + </view>
  21 +
  22 + <view class="tabs">
  23 + <view
  24 + v-for="(t, i) in tabs"
  25 + :key="i"
  26 + :class="['tab', { active: status === t.value }]"
  27 + @click="switchTab(t)"
  28 + >
  29 + {{ t.label }}
  30 + </view>
  31 + </view>
  32 + </view>
  33 +
  34 + <view :class="['list-box', { 'pad-batch': batchMode }]">
  35 + <card-list
  36 + ref="cardRef"
  37 + :fetch-fn="fetchList"
  38 + :query="query"
  39 + :extra="extraParams"
  40 + :selectable="batchMode"
  41 + row-key="id"
  42 + :selectedKeys.sync="selectedKeys"
  43 + :enable-refresh="true"
  44 + :enable-load-more="true"
  45 + @loaded="onCardLoaded"
  46 + @error="onCardError"
  47 + >
  48 + <template v-slot="{ item }">
  49 + <view class="card">
  50 + <view class="card-header">
  51 + <text class="title omit2">{{ item.buyerName }}</text>
  52 + <text v-if="item.status === 'STANDARD'" :class="['status']" :style="{ background: statusMap[item.shippingStatusName] }">{{ item.shippingStatusName }}</text>
  53 + </view>
  54 + <view class="info-row">
  55 + <text>编号</text><text>{{ item.code }}</text>
  56 + </view>
  57 + <view class="info-row">
  58 + <text>办事处</text><text>{{ item.deptName }}</text>
  59 + </view>
  60 + <view class="info-row" v-if="item.status !== 'STANDARD'">
  61 + <text>订单总额</text><text class="amount" :style="{ color: '#b67a76' }">{{ item.totalAmountIncludingTax ? '¥' : '' }}{{ formatAmount(item.totalAmountIncludingTax) || '-' }}</text>
  62 + </view>
  63 + <view class="info-row" v-if="item.status === 'STANDARD'">
  64 + <text>正式合同规范性审核状态</text><span class="info-status" :style="item.standardApprovedName ? getStatusCss(item.standardApprovedName) : ''">{{ item.standardApprovedName || '-' }}</span>
  65 + </view>
  66 + <view class="info-row">
  67 + <text>订货日期</text><text>{{ item.orderDate }}</text>
  68 + </view>
  69 + </view>
  70 + </template>
  71 + </card-list>
  72 + </view>
  73 +
  74 + <filter-modal
  75 + :visible.sync="filterVisible"
  76 + :value.sync="filterForm"
  77 + title="筛选"
  78 + @reset="onFilterReset"
  79 + @confirm="onFilterConfirm"
  80 + >
  81 + <template v-slot="{ model }">
  82 + <view class="filter-form">
  83 + <view class="form-item">
  84 + <view class="label">订货日期</view>
  85 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  86 + </view>
  87 + </view>
  88 + </template>
  89 + </filter-modal>
  90 +
  91 + </view>
  92 +</template>
  93 +
  94 +<script>
  95 +import CardList from '@/components/card/index.vue'
  96 +import FilterModal from '@/components/filter/index.vue'
  97 +import { queryContractApi, statusStyle, statusMap } from '@/api/contract.js'
  98 +import { officeQueryApi } from '@/api/devManage.js'
  99 +
  100 +export default {
  101 + components: { CardList, FilterModal },
  102 + data() {
  103 + return {
  104 + searchKeyword: '',
  105 + searchKeywordDebounced: '',
  106 +
  107 + tabs: [
  108 + { label: '草稿合同', value: 'DRAFT' },
  109 + { label: '标准合同', value: 'STANDARD' },
  110 + { label: '已删除合同', value: 'DELETED' }
  111 + ],
  112 + status: 'DRAFT',
  113 +
  114 + query: { deptId: '', deptName: '', dateRange: [] },
  115 + extraParams: {},
  116 +
  117 + batchMode: false,
  118 + selectedKeys: [],
  119 + currentItems: [],
  120 +
  121 + filterVisible: false,
  122 + filterForm: { deptId: '', deptName: '', dateRange: [] },
  123 + deptSelectVisible: false,
  124 + statusStyle,
  125 + statusMap
  126 + }
  127 + },
  128 + computed: {
  129 + extraCombined() {
  130 + return {
  131 + keyword: this.searchKeywordDebounced || undefined,
  132 + status: this.status || undefined,
  133 + type: 'DISTRIB_STD'
  134 + }
  135 + }
  136 + },
  137 + watch: {
  138 + extraCombined: {
  139 + deep: true,
  140 + handler(v) {
  141 + this.extraParams = v
  142 + },
  143 + immediate: true
  144 + }
  145 + },
  146 + created() {
  147 + },
  148 + onReachBottom() {
  149 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  150 + this.$refs.cardRef.onLoadMore()
  151 + }
  152 + },
  153 + beforeDestroy() {
  154 + if (this.searchDebounceTimer) {
  155 + clearTimeout(this.searchDebounceTimer)
  156 + this.searchDebounceTimer = null
  157 + }
  158 + },
  159 + methods: {
  160 + onCardLoaded({ items }) {
  161 + this.currentItems = items
  162 + },
  163 + onCardError() {
  164 + uni.showToast({ title: '列表加载失败', icon: 'none' })
  165 + },
  166 + onSearchInput() {
  167 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  168 + this.searchDebounceTimer = setTimeout(() => {
  169 + this.searchKeywordDebounced = this.searchKeyword
  170 + this.searchDebounceTimer = null
  171 + }, 1200)
  172 + },
  173 + search(e) {
  174 + const val = e && e.value != null ? e.value : this.searchKeyword
  175 + this.searchKeyword = val
  176 + this.searchKeywordDebounced = val
  177 + },
  178 + switchTab(item) {
  179 + this.status = item.value
  180 + },
  181 + openFilter() {
  182 + this.filterVisible = true
  183 + },
  184 + onFilterReset(payload) {
  185 + this.filterForm = payload
  186 + },
  187 + onFilterConfirm(payload) {
  188 + this.query = { ...payload }
  189 + },
  190 + openDeptSelect() {
  191 + this.deptSelectVisible = true
  192 + },
  193 + onDeptConfirm(payload) {
  194 + const val = payload && payload.value != null ? payload.value : ''
  195 + const label = payload && payload.label != null ? payload.label : ''
  196 + this.filterForm.deptId = val
  197 + this.filterForm.deptName = label
  198 + },
  199 + toggleBatch() {
  200 + this.batchMode = !this.batchMode
  201 + if (!this.batchMode) this.selectedKeys = []
  202 + },
  203 + onAdd() {
  204 + uni.showToast({ title: '点击新增', icon: 'none' })
  205 + },
  206 + fetchList({ pageIndex, pageSize, query, extra }) {
  207 + console.log('fetchList', pageIndex, pageSize, query, extra)
  208 + const params = { pageIndex, pageSize, ...extra, ...query }
  209 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  210 + params.orderDateStart = params.dateRange[0]
  211 + params.orderDateEnd = params.dateRange[1]
  212 + delete params.dateRange
  213 + }
  214 + return queryContractApi(params)
  215 + .then(res => {
  216 + const _data = res.data || {}
  217 + const records = _data.datas || []
  218 + const totalCount = _data.totalCount || 0
  219 + const hasNext = _data.hasNext || false
  220 + return { records, totalCount, hasNext }
  221 + })
  222 + .catch(err => {
  223 + console.error('queryContractApi error', err)
  224 + this.onCardError()
  225 + return { records: [], totalCount: 0, hasNext: false }
  226 + })
  227 + },
  228 +
  229 + formatAmount(val) {
  230 + if (val == null) return '0.00'
  231 + try {
  232 + const num = Number(val)
  233 + return num.toFixed(2)
  234 + } catch (e) {
  235 + return String(val)
  236 + }
  237 + },
  238 + getStatusCss(name) {
  239 + const list = Array.isArray(this.statusStyle) ? this.statusStyle : []
  240 + const found = list.find(it => it && it.text === name) || {}
  241 + return {
  242 + color: found.color || '#000',
  243 + backgroundColor: found.bgColor || '#000'
  244 + }
  245 + }
  246 + }
  247 +}
  248 +</script>
  249 +
  250 +<style lang="scss" scoped>
  251 +.page {
  252 + display: flex;
  253 + flex-direction: column;
  254 + height: 100vh;
  255 +}
  256 +.contract-list-fixed {
  257 + position: fixed;
  258 + top: 96rpx;
  259 + left: 0;
  260 + right: 0;
  261 + z-index: 2;
  262 + background: #fff;
  263 +
  264 + .search-row {
  265 + display: flex;
  266 + align-items: center;
  267 + padding: 16rpx 32rpx;
  268 + .uni-searchbar { padding: 0; flex: 1; }
  269 + .tool-icons { display: flex; .tool-icon { width: 48rpx; height: 48rpx; display: block; margin-left: 32rpx; } }
  270 + }
  271 +
  272 + .tabs {
  273 + display: flex;
  274 + align-items: center;
  275 + justify-content: space-between;
  276 + padding: 26rpx 0;
  277 + border-bottom: solid 1px #E7E7E7;
  278 + .tab { width: 50%; text-align: center; color: rgba(0,0,0,0.9); line-height: 44rpx; position: relative; font-weight: 600; }
  279 + .tab.active { color: $theme-primary; }
  280 + .tab.active::after {
  281 + content: '';
  282 + position: absolute;
  283 + left: 50%;
  284 + transform: translateX(-50%);
  285 + bottom: -13px;
  286 + width: 32rpx;
  287 + height: 6rpx;
  288 + border-radius: 4rpx;
  289 + background: $theme-primary;
  290 + }
  291 + }
  292 +}
  293 +::v-deep .uni-searchbar__box { height: 80rpx !important; justify-content: start; .uni-searchbar__box-search-input { font-size: 32rpx !important; } }
  294 +
  295 +.list-box {
  296 + flex: 1;
  297 + padding-top: 236rpx;
  298 + &.pad-batch { padding-bottom: 144rpx; }
  299 + .card { position: relative; }
  300 + .card-header {
  301 + margin-bottom: 28rpx;
  302 + position: relative;
  303 +
  304 + .title {
  305 + font-size: 36rpx;
  306 + font-weight: 600;
  307 + line-height: 50rpx;
  308 + color: #323241;
  309 + width: 578rpx;
  310 + }
  311 +
  312 + .status {
  313 + font-size: 30rpx;
  314 + font-weight: 600;
  315 + position: absolute;
  316 + top: -36rpx;
  317 + right: -32rpx;
  318 + height: 48rpx;
  319 + line-height: 48rpx;
  320 + color: #fff;
  321 + font-size: 24rpx;
  322 + padding: 0 14rpx;
  323 + border-radius: 6rpx;
  324 + &.status_1 {
  325 + background: #3D48A3;
  326 + }
  327 + &.status_2 {
  328 + background: #2BA471;
  329 + }
  330 + &.status_3 {
  331 + background: #D54941;
  332 + }
  333 + &.status_4 {
  334 + background: #E7E7E7;
  335 + color: #323241;
  336 + }
  337 + }
  338 + }
  339 + .info-row {
  340 + display: flex;
  341 + align-items: center;
  342 + color: rgba(0,0,0,0.6);
  343 + font-size: 28rpx;
  344 + margin-bottom: 24rpx;
  345 + height: 32rpx;
  346 + .info-status {
  347 + padding: 4rpx 12rpx;
  348 + border-radius: 6rpx;
  349 + font-size: 24rpx;
  350 + color: #fff;
  351 + line-height: 40rpx;
  352 + }
  353 + &:last-child {
  354 + margin-bottom: 0;
  355 + }
  356 + text {
  357 + width: 60%;
  358 + &:last-child {
  359 + color: rgba(0,0,0,0.9);
  360 + width: 40%;
  361 + }
  362 + }
  363 + }
  364 +}
  365 +
  366 +.filter-form {
  367 + .form-item { margin-bottom: 24rpx; }
  368 + .label {
  369 + margin-bottom: 20rpx;
  370 + color: rgba(0,0,0,0.9);
  371 + height: 44rpx;
  372 + line-height: 44rpx;
  373 + font-size: 30rpx;
  374 + }
  375 + .fake-select {
  376 + height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx;
  377 + .placeholder { color: #999; }
  378 + .value { color: #333; }
  379 + }
  380 +}
  381 +</style>
\ No newline at end of file
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="contract-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar
  6 + v-model="searchKeyword"
  7 + radius="6"
  8 + placeholder="请输入需方名称或编号或办事处"
  9 + clearButton="auto"
  10 + cancelButton="none"
  11 + bgColor="#F3F3F3"
  12 + textColor="rgba(0,0,0,0.4)"
  13 + @confirm="search"
  14 + @input="onSearchInput"
  15 + />
  16 + <view class="tool-icons">
  17 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  18 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  19 + </view>
  20 + </view>
  21 +
  22 + <view class="tabs">
  23 + <view
  24 + v-for="(t, i) in tabs"
  25 + :key="i"
  26 + :class="['tab', { active: status === t.value }]"
  27 + @click="switchTab(t)"
  28 + >
  29 + {{ t.label }}
  30 + </view>
  31 + </view>
  32 + </view>
  33 +
  34 + <view :class="['list-box', { 'pad-batch': batchMode }]">
  35 + <card-list
  36 + ref="cardRef"
  37 + :fetch-fn="fetchList"
  38 + :query="query"
  39 + :extra="extraParams"
  40 + :selectable="batchMode"
  41 + row-key="id"
  42 + :selectedKeys.sync="selectedKeys"
  43 + :enable-refresh="true"
  44 + :enable-load-more="true"
  45 + @loaded="onCardLoaded"
  46 + @error="onCardError"
  47 + >
  48 + <template v-slot="{ item }">
  49 + <view class="card">
  50 + <view class="card-header">
  51 + <text class="title omit2">{{ item.buyerName }}</text>
  52 + <text v-if="item.status === 'STANDARD'" :class="['status']" :style="{ background: statusMap[item.shippingStatusName] }">{{ item.shippingStatusName }}</text>
  53 + </view>
  54 + <view class="info-row">
  55 + <text>编号</text><text>{{ item.code }}</text>
  56 + </view>
  57 + <view class="info-row">
  58 + <text>办事处</text><text>{{ item.deptName }}</text>
  59 + </view>
  60 + <view class="info-row" v-if="item.status === 'DRAFT' || item.status === 'DELETED'">
  61 + <text>订单总额</text><text class="amount" :style="{ color: '#b67a76' }">¥{{ formatAmount(item.totalAmountIncludingTax) }}</text>
  62 + </view>
  63 + <view class="info-row" v-if="item.status === 'STANDARD' || item.status === 'FORMAL'">
  64 + <text>{{ item.status === 'STANDARD' ? '标准合同' : '正式合同' }}规范性审核状态</text><span class="info-status" :style="getStatusCss(item.standardApprovedName)">{{ item.standardApprovedName }}</span>
  65 + </view>
  66 + <view class="info-row">
  67 + <text>订货日期</text><text>{{ item.orderDate }}</text>
  68 + </view>
  69 + </view>
  70 + </template>
  71 + </card-list>
  72 + </view>
  73 +
  74 + <filter-modal
  75 + :visible.sync="filterVisible"
  76 + :value.sync="filterForm"
  77 + title="筛选"
  78 + @reset="onFilterReset"
  79 + @confirm="onFilterConfirm"
  80 + >
  81 + <template v-slot="{ model }">
  82 + <view class="filter-form">
  83 + <view class="form-item">
  84 + <view class="label">订货日期</view>
  85 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  86 + </view>
  87 + </view>
  88 + </template>
  89 + </filter-modal>
  90 +
  91 + </view>
  92 +</template>
  93 +
  94 +<script>
  95 +import CardList from '@/components/card/index.vue'
  96 +import FilterModal from '@/components/filter/index.vue'
  97 +import { queryContractApi, statusStyle,statusMap } from '@/api/contract.js'
  98 +import { officeQueryApi } from '@/api/devManage.js'
  99 +
  100 +export default {
  101 + components: { CardList, FilterModal },
  102 + data() {
  103 + return {
  104 + searchKeyword: '',
  105 + searchKeywordDebounced: '',
  106 +
  107 + tabs: [
  108 + { label: '草稿合同', value: 'DRAFT' },
  109 + { label: '正式合同', value: 'FORMAL' },
  110 + { label: '标准合同', value: 'STANDARD' },
  111 + { label: '已删除合同', value: 'DELETED' }
  112 + ],
  113 + status: 'DRAFT',
  114 +
  115 + query: { deptId: '', deptName: '', dateRange: [] },
  116 + extraParams: {},
  117 +
  118 + batchMode: false,
  119 + selectedKeys: [],
  120 + currentItems: [],
  121 +
  122 + filterVisible: false,
  123 + filterForm: { deptId: '', deptName: '', dateRange: [] },
  124 + deptSelectVisible: false,
  125 + statusStyle,
  126 + statusMap,
  127 + }
  128 + },
  129 + computed: {
  130 + extraCombined() {
  131 + return {
  132 + keyword: this.searchKeywordDebounced || undefined,
  133 + status: this.status || undefined,
  134 + type: 'DIST_STOCK_CONTRACT'
  135 + }
  136 + }
  137 + },
  138 + watch: {
  139 + extraCombined: {
  140 + deep: true,
  141 + handler(v) {
  142 + this.extraParams = v
  143 + },
  144 + immediate: true
  145 + }
  146 + },
  147 + created() {
  148 + },
  149 + onReachBottom() {
  150 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  151 + this.$refs.cardRef.onLoadMore()
  152 + }
  153 + },
  154 + beforeDestroy() {
  155 + if (this.searchDebounceTimer) {
  156 + clearTimeout(this.searchDebounceTimer)
  157 + this.searchDebounceTimer = null
  158 + }
  159 + },
  160 + methods: {
  161 + onCardLoaded({ items }) {
  162 + this.currentItems = items
  163 + },
  164 + onCardError() {
  165 + uni.showToast({ title: '列表加载失败', icon: 'none' })
  166 + },
  167 + onSearchInput() {
  168 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  169 + this.searchDebounceTimer = setTimeout(() => {
  170 + this.searchKeywordDebounced = this.searchKeyword
  171 + this.searchDebounceTimer = null
  172 + }, 1200)
  173 + },
  174 + search(e) {
  175 + const val = e && e.value != null ? e.value : this.searchKeyword
  176 + this.searchKeyword = val
  177 + this.searchKeywordDebounced = val
  178 + },
  179 + switchTab(item) {
  180 + this.status = item.value
  181 + },
  182 + openFilter() {
  183 + this.filterVisible = true
  184 + },
  185 + onFilterReset(payload) {
  186 + this.filterForm = payload
  187 + },
  188 + onFilterConfirm(payload) {
  189 + this.query = { ...payload }
  190 + },
  191 + openDeptSelect() {
  192 + this.deptSelectVisible = true
  193 + },
  194 + onDeptConfirm(payload) {
  195 + const val = payload && payload.value != null ? payload.value : ''
  196 + const label = payload && payload.label != null ? payload.label : ''
  197 + this.filterForm.deptId = val
  198 + this.filterForm.deptName = label
  199 + },
  200 + toggleBatch() {
  201 + this.batchMode = !this.batchMode
  202 + if (!this.batchMode) this.selectedKeys = []
  203 + },
  204 + onAdd() {
  205 + uni.showToast({ title: '点击新增', icon: 'none' })
  206 + },
  207 + fetchList({ pageIndex, pageSize, query, extra }) {
  208 + const params = { pageIndex, pageSize, ...extra, ...query }
  209 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  210 + params.orderDateStart = params.dateRange[0]
  211 + params.orderDateEnd = params.dateRange[1]
  212 + delete params.dateRange
  213 + }
  214 + return queryContractApi(params)
  215 + .then(res => {
  216 + const _data = res.data || {}
  217 + const records = _data.datas || []
  218 + const totalCount = _data.totalCount || 0
  219 + const hasNext = _data.hasNext || false
  220 + return { records, totalCount, hasNext }
  221 + })
  222 + .catch(err => {
  223 + console.error('queryContractApi error', err)
  224 + this.onCardError()
  225 + return { records: [], totalCount: 0, hasNext: false }
  226 + })
  227 + },
  228 +
  229 + formatAmount(val) {
  230 + if (val == null) return '0.00'
  231 + try {
  232 + const num = Number(val)
  233 + return num.toFixed(2)
  234 + } catch (e) {
  235 + return String(val)
  236 + }
  237 + },
  238 + getStatusCss(name) {
  239 + const list = Array.isArray(this.statusStyle) ? this.statusStyle : []
  240 + const found = list.find(it => it && it.text === name) || {}
  241 + return {
  242 + color: found.color || '#000',
  243 + backgroundColor: found.bgColor || '#000'
  244 + }
  245 + }
  246 + }
  247 +}
  248 +</script>
  249 +
  250 +<style lang="scss" scoped>
  251 +.page {
  252 + display: flex;
  253 + flex-direction: column;
  254 + height: 100vh;
  255 +}
  256 +.contract-list-fixed {
  257 + position: fixed;
  258 + top: 96rpx;
  259 + left: 0;
  260 + right: 0;
  261 + z-index: 2;
  262 + background: #fff;
  263 +
  264 + .search-row {
  265 + display: flex;
  266 + align-items: center;
  267 + padding: 16rpx 32rpx;
  268 + .uni-searchbar { padding: 0; flex: 1; }
  269 + .tool-icons { display: flex; .tool-icon { width: 48rpx; height: 48rpx; display: block; margin-left: 32rpx; } }
  270 + }
  271 +
  272 + .tabs {
  273 + display: flex;
  274 + align-items: center;
  275 + justify-content: space-between;
  276 + padding: 26rpx 0;
  277 + border-bottom: solid 1px #E7E7E7;
  278 + .tab { width: 50%; text-align: center; color: rgba(0,0,0,0.9); line-height: 44rpx; position: relative; font-weight: 600; }
  279 + .tab.active { color: $theme-primary; }
  280 + .tab.active::after {
  281 + content: '';
  282 + position: absolute;
  283 + left: 50%;
  284 + transform: translateX(-50%);
  285 + bottom: -13px;
  286 + width: 32rpx;
  287 + height: 6rpx;
  288 + border-radius: 4rpx;
  289 + background: $theme-primary;
  290 + }
  291 + }
  292 +}
  293 +::v-deep .uni-searchbar__box { height: 80rpx !important; justify-content: start; .uni-searchbar__box-search-input { font-size: 32rpx !important; } }
  294 +
  295 +.list-box {
  296 + flex: 1;
  297 + padding-top: 236rpx;
  298 + &.pad-batch { padding-bottom: 144rpx; }
  299 + .card { position: relative; }
  300 + .card-header {
  301 + margin-bottom: 28rpx;
  302 + position: relative;
  303 +
  304 + .title {
  305 + font-size: 36rpx;
  306 + font-weight: 600;
  307 + line-height: 50rpx;
  308 + color: #323241;
  309 + width: 578rpx;
  310 + }
  311 +
  312 + .status {
  313 + font-size: 30rpx;
  314 + font-weight: 600;
  315 + position: absolute;
  316 + top: -36rpx;
  317 + right: -32rpx;
  318 + height: 48rpx;
  319 + line-height: 48rpx;
  320 + color: #fff;
  321 + font-size: 24rpx;
  322 + padding: 0 14rpx;
  323 + border-radius: 6rpx;
  324 + &.status_1 {
  325 + background: #3D48A3;
  326 + }
  327 + &.status_2 {
  328 + background: #2BA471;
  329 + }
  330 + &.status_3 {
  331 + background: #D54941;
  332 + }
  333 + &.status_4 {
  334 + background: #E7E7E7;
  335 + color: #323241;
  336 + }
  337 + }
  338 + }
  339 + .info-row {
  340 + display: flex;
  341 + align-items: center;
  342 + color: rgba(0,0,0,0.6);
  343 + font-size: 28rpx;
  344 + margin-bottom: 24rpx;
  345 + height: 32rpx;
  346 + .info-status {
  347 + padding: 4rpx 12rpx;
  348 + border-radius: 6rpx;
  349 + font-size: 24rpx;
  350 + color: #fff;
  351 + line-height: 40rpx;
  352 + }
  353 + &:last-child {
  354 + margin-bottom: 0;
  355 + }
  356 + text {
  357 + width: 60%;
  358 + &:last-child {
  359 + color: rgba(0,0,0,0.9);
  360 + width: 40%;
  361 + }
  362 + }
  363 + }
  364 +}
  365 +
  366 +.filter-form {
  367 + .form-item { margin-bottom: 24rpx; }
  368 + .label {
  369 + margin-bottom: 20rpx;
  370 + color: rgba(0,0,0,0.9);
  371 + height: 44rpx;
  372 + line-height: 44rpx;
  373 + font-size: 30rpx;
  374 + }
  375 + .fake-select {
  376 + height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx;
  377 + .placeholder { color: #999; }
  378 + .value { color: #333; }
  379 + }
  380 +}
  381 +</style>
\ No newline at end of file
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="contract-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar
  6 + v-model="searchKeyword"
  7 + radius="6"
  8 + placeholder="请输入需方名称或编号或办事处"
  9 + clearButton="auto"
  10 + cancelButton="none"
  11 + bgColor="#F3F3F3"
  12 + textColor="rgba(0,0,0,0.4)"
  13 + @confirm="search"
  14 + @input="onSearchInput"
  15 + />
  16 + <view class="tool-icons">
  17 + <image class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  18 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  19 + </view>
  20 + </view>
  21 +
  22 + <view class="tabs">
  23 + <view
  24 + v-for="(t, i) in tabs"
  25 + :key="i"
  26 + :class="['tab', { active: status === t.value }]"
  27 + @click="switchTab(t)"
  28 + >
  29 + {{ t.label }}
  30 + </view>
  31 + </view>
  32 + </view>
  33 +
  34 + <view :class="['list-box', { 'pad-batch': batchMode }]">
  35 + <card-list
  36 + ref="cardRef"
  37 + :fetch-fn="fetchList"
  38 + :query="query"
  39 + :extra="extraParams"
  40 + :selectable="batchMode"
  41 + row-key="id"
  42 + :selectedKeys.sync="selectedKeys"
  43 + :enable-refresh="true"
  44 + :enable-load-more="true"
  45 + @loaded="onCardLoaded"
  46 + @error="onCardError"
  47 + >
  48 + <template v-slot="{ item }">
  49 + <view class="card">
  50 + <view class="card-header">
  51 + <text class="title omit2">{{ item.buyerName }}</text>
  52 + <text v-if="item.status === 'STANDARD'" :class="['status']" :style="{ background: statusMap[item.shippingStatusName] }">{{ item.shippingStatusName }}</text>
  53 + </view>
  54 + <view class="info-row">
  55 + <text>编号</text><text>{{ item.code }}</text>
  56 + </view>
  57 + <view class="info-row">
  58 + <text>办事处</text><text>{{ item.deptName }}</text>
  59 + </view>
  60 + <view class="info-row" v-if="item.status === 'DRAFT' || item.status === 'DELETED'">
  61 + <text>订单总额</text><text class="amount" :style="{ color: '#b67a76' }">{{ item.totalAmountIncludingTax ? '¥' : '' }}{{ formatAmount(item.totalAmountIncludingTax) || '-' }}</text>
  62 + </view>
  63 + <view class="info-row" v-if="item.status === 'STANDARD' || item.status === 'FORMAL'">
  64 + <text>{{ item.status === 'STANDARD' ? '标准合同' : '正式合同' }}规范性审核状态</text><span class="info-status" :style="item.standardApprovedName ? getStatusCss(item.standardApprovedName) : ''">{{ item.standardApprovedName || '-' }}</span>
  65 + </view>
  66 + <view class="info-row">
  67 + <text>订货日期</text><text>{{ item.orderDate }}</text>
  68 + </view>
  69 + </view>
  70 + </template>
  71 + </card-list>
  72 + </view>
  73 +
  74 + <filter-modal
  75 + :visible.sync="filterVisible"
  76 + :value.sync="filterForm"
  77 + title="筛选"
  78 + @reset="onFilterReset"
  79 + @confirm="onFilterConfirm"
  80 + >
  81 + <template v-slot="{ model }">
  82 + <view class="filter-form">
  83 + <view class="form-item">
  84 + <view class="label">订货日期</view>
  85 + <uni-datetime-picker type="daterange" v-model="model.dateRange" start="2023-01-01" />
  86 + </view>
  87 + </view>
  88 + </template>
  89 + </filter-modal>
  90 +
  91 + </view>
  92 +</template>
  93 +
  94 +<script>
  95 +import CardList from '@/components/card/index.vue'
  96 +import FilterModal from '@/components/filter/index.vue'
  97 +import { queryContractApi, statusStyle,statusMap } from '@/api/contract.js'
  98 +import { officeQueryApi } from '@/api/devManage.js'
  99 +
  100 +export default {
  101 + components: { CardList, FilterModal },
  102 + data() {
  103 + return {
  104 + searchKeyword: '',
  105 + searchKeywordDebounced: '',
  106 +
  107 + tabs: [
  108 + { label: '草稿合同', value: 'DRAFT' },
  109 + { label: '正式合同', value: 'FORMAL' },
  110 + { label: '标准合同', value: 'STANDARD' },
  111 + { label: '已删除合同', value: 'DELETED' }
  112 + ],
  113 + status: 'DRAFT',
  114 +
  115 + query: { deptId: '', deptName: '', dateRange: [] },
  116 + extraParams: {},
  117 +
  118 + batchMode: false,
  119 + selectedKeys: [],
  120 + currentItems: [],
  121 +
  122 + filterVisible: false,
  123 + filterForm: { deptId: '', deptName: '', dateRange: [] },
  124 + deptSelectVisible: false,
  125 + statusStyle,
  126 + statusMap,
  127 + }
  128 + },
  129 + computed: {
  130 + extraCombined() {
  131 + return {
  132 + keyword: this.searchKeywordDebounced || undefined,
  133 + status: this.status || undefined,
  134 + type: 'DRAFT_DIST_AGMT'
  135 + }
  136 + }
  137 + },
  138 + watch: {
  139 + extraCombined: {
  140 + deep: true,
  141 + handler(v) {
  142 + this.extraParams = v
  143 + },
  144 + immediate: true
  145 + }
  146 + },
  147 + created() {
  148 + },
  149 + onReachBottom() {
  150 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  151 + this.$refs.cardRef.onLoadMore()
  152 + }
  153 + },
  154 + beforeDestroy() {
  155 + if (this.searchDebounceTimer) {
  156 + clearTimeout(this.searchDebounceTimer)
  157 + this.searchDebounceTimer = null
  158 + }
  159 + },
  160 + methods: {
  161 + onCardLoaded({ items }) {
  162 + this.currentItems = items
  163 + },
  164 + onCardError() {
  165 + uni.showToast({ title: '列表加载失败', icon: 'none' })
  166 + },
  167 + onSearchInput() {
  168 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  169 + this.searchDebounceTimer = setTimeout(() => {
  170 + this.searchKeywordDebounced = this.searchKeyword
  171 + this.searchDebounceTimer = null
  172 + }, 1200)
  173 + },
  174 + search(e) {
  175 + const val = e && e.value != null ? e.value : this.searchKeyword
  176 + this.searchKeyword = val
  177 + this.searchKeywordDebounced = val
  178 + },
  179 + switchTab(item) {
  180 + this.status = item.value
  181 + },
  182 + openFilter() {
  183 + this.filterVisible = true
  184 + },
  185 + onFilterReset(payload) {
  186 + this.filterForm = payload
  187 + },
  188 + onFilterConfirm(payload) {
  189 + this.query = { ...payload }
  190 + },
  191 + openDeptSelect() {
  192 + this.deptSelectVisible = true
  193 + },
  194 + onDeptConfirm(payload) {
  195 + const val = payload && payload.value != null ? payload.value : ''
  196 + const label = payload && payload.label != null ? payload.label : ''
  197 + this.filterForm.deptId = val
  198 + this.filterForm.deptName = label
  199 + },
  200 + toggleBatch() {
  201 + this.batchMode = !this.batchMode
  202 + if (!this.batchMode) this.selectedKeys = []
  203 + },
  204 + onAdd() {
  205 + uni.showToast({ title: '点击新增', icon: 'none' })
  206 + },
  207 + fetchList({ pageIndex, pageSize, query, extra }) {
  208 + const params = { pageIndex, pageSize, ...extra, ...query }
  209 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  210 + params.orderDateStart = params.dateRange[0]
  211 + params.orderDateEnd = params.dateRange[1]
  212 + delete params.dateRange
  213 + }
  214 + return queryContractApi(params)
  215 + .then(res => {
  216 + const _data = res.data || {}
  217 + const records = _data.datas || []
  218 + const totalCount = _data.totalCount || 0
  219 + const hasNext = _data.hasNext || false
  220 + return { records, totalCount, hasNext }
  221 + })
  222 + .catch(err => {
  223 + console.error('queryContractApi error', err)
  224 + this.onCardError()
  225 + return { records: [], totalCount: 0, hasNext: false }
  226 + })
  227 + },
  228 +
  229 + formatAmount(val) {
  230 + if (val == null) return '0.00'
  231 + try {
  232 + const num = Number(val)
  233 + return num.toFixed(2)
  234 + } catch (e) {
  235 + return String(val)
  236 + }
  237 + },
  238 + getStatusCss(name) {
  239 + const list = Array.isArray(this.statusStyle) ? this.statusStyle : []
  240 + const found = list.find(it => it && it.text === name) || {}
  241 + return {
  242 + color: found.color || '#000',
  243 + backgroundColor: found.bgColor || '#000'
  244 + }
  245 + }
  246 + }
  247 +}
  248 +</script>
  249 +
  250 +<style lang="scss" scoped>
  251 +.page {
  252 + display: flex;
  253 + flex-direction: column;
  254 + height: 100vh;
  255 +}
  256 +.contract-list-fixed {
  257 + position: fixed;
  258 + top: 96rpx;
  259 + left: 0;
  260 + right: 0;
  261 + z-index: 2;
  262 + background: #fff;
  263 +
  264 + .search-row {
  265 + display: flex;
  266 + align-items: center;
  267 + padding: 16rpx 32rpx;
  268 + .uni-searchbar { padding: 0; flex: 1; }
  269 + .tool-icons { display: flex; .tool-icon { width: 48rpx; height: 48rpx; display: block; margin-left: 32rpx; } }
  270 + }
  271 +
  272 + .tabs {
  273 + display: flex;
  274 + align-items: center;
  275 + justify-content: space-between;
  276 + padding: 26rpx 0;
  277 + border-bottom: solid 1px #E7E7E7;
  278 + .tab { width: 50%; text-align: center; color: rgba(0,0,0,0.9); line-height: 44rpx; position: relative; font-weight: 600; }
  279 + .tab.active { color: $theme-primary; }
  280 + .tab.active::after {
  281 + content: '';
  282 + position: absolute;
  283 + left: 50%;
  284 + transform: translateX(-50%);
  285 + bottom: -13px;
  286 + width: 32rpx;
  287 + height: 6rpx;
  288 + border-radius: 4rpx;
  289 + background: $theme-primary;
  290 + }
  291 + }
  292 +}
  293 +::v-deep .uni-searchbar__box { height: 80rpx !important; justify-content: start; .uni-searchbar__box-search-input { font-size: 32rpx !important; } }
  294 +
  295 +.list-box {
  296 + flex: 1;
  297 + padding-top: 236rpx;
  298 + &.pad-batch { padding-bottom: 144rpx; }
  299 + .card { position: relative; }
  300 + .card-header {
  301 + margin-bottom: 28rpx;
  302 + position: relative;
  303 +
  304 + .title {
  305 + font-size: 36rpx;
  306 + font-weight: 600;
  307 + line-height: 50rpx;
  308 + color: #323241;
  309 + width: 578rpx;
  310 + }
  311 +
  312 + .status {
  313 + font-size: 30rpx;
  314 + font-weight: 600;
  315 + position: absolute;
  316 + top: -36rpx;
  317 + right: -32rpx;
  318 + height: 48rpx;
  319 + line-height: 48rpx;
  320 + color: #fff;
  321 + font-size: 24rpx;
  322 + padding: 0 14rpx;
  323 + border-radius: 6rpx;
  324 + &.status_1 {
  325 + background: #3D48A3;
  326 + }
  327 + &.status_2 {
  328 + background: #2BA471;
  329 + }
  330 + &.status_3 {
  331 + background: #D54941;
  332 + }
  333 + &.status_4 {
  334 + background: #E7E7E7;
  335 + color: #323241;
  336 + }
  337 + }
  338 + }
  339 + .info-row {
  340 + display: flex;
  341 + align-items: center;
  342 + color: rgba(0,0,0,0.6);
  343 + font-size: 28rpx;
  344 + margin-bottom: 24rpx;
  345 + height: 32rpx;
  346 + .info-status {
  347 + padding: 4rpx 12rpx;
  348 + border-radius: 6rpx;
  349 + font-size: 24rpx;
  350 + color: #fff;
  351 + line-height: 40rpx;
  352 + }
  353 + &:last-child {
  354 + margin-bottom: 0;
  355 + }
  356 + text {
  357 + width: 60%;
  358 + &:last-child {
  359 + color: rgba(0,0,0,0.9);
  360 + width: 40%;
  361 + }
  362 + }
  363 + }
  364 +}
  365 +
  366 +.filter-form {
  367 + .form-item { margin-bottom: 24rpx; }
  368 + .label {
  369 + margin-bottom: 20rpx;
  370 + color: rgba(0,0,0,0.9);
  371 + height: 44rpx;
  372 + line-height: 44rpx;
  373 + font-size: 30rpx;
  374 + }
  375 + .fake-select {
  376 + height: 80rpx; line-height: 80rpx; padding: 0 20rpx; background: #f3f3f3; border-radius: 12rpx;
  377 + .placeholder { color: #999; }
  378 + .value { color: #333; }
  379 + }
  380 +}
  381 +</style>
\ No newline at end of file
... ...
... ... @@ -86,7 +86,7 @@
86 86 link: '/pages/contract_stock/index'
87 87 },
88 88 {
89   - text: '经销未规划合同',
  89 + text: '经销未锁规合同',
90 90 icon: '/static/images/index/contract_unplan.png',
91 91 link: '/pages/contract_unplan/index'
92 92 },
... ... @@ -106,7 +106,7 @@
106 106 link: '/pages/contract_foreign_stock/index'
107 107 },
108 108 {
109   - text: '外贸未规划合同',
  109 + text: '外贸未锁规合同',
110 110 icon: '/static/images/index/contract_foreign_unplan.png',
111 111 link: '/pages/contract_foreign_unplan/index'
112 112 },
... ...