Commit f6d6c842e8a6c0be170922e9dc2b298c67bb9077

Authored by 史婷婷
1 parent ad5fb2dc

feat: 客户池-列表&筛选

1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <project version="4">
3 3 <component name="ChangeListManager">
4   - <list default="true" id="22e3db16-f3f9-44cc-b7ca-0b5f75df2820" name="Changes" comment="">
5   - <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
6   - <change beforePath="$PROJECT_DIR$/pages/change_list/add.vue" beforeDir="false" afterPath="$PROJECT_DIR$/pages/change_list/add.vue" afterDir="false" />
7   - <change beforePath="$PROJECT_DIR$/pages/change_list/modify.vue" beforeDir="false" afterPath="$PROJECT_DIR$/pages/change_list/modify.vue" afterDir="false" />
8   - <change beforePath="$PROJECT_DIR$/pages/change_list/product.vue" beforeDir="false" afterPath="$PROJECT_DIR$/pages/change_list/product.vue" afterDir="false" />
9   - <change beforePath="$PROJECT_DIR$/pages/order_list/product.vue" beforeDir="false" afterPath="$PROJECT_DIR$/pages/order_list/product.vue" afterDir="false" />
10   - <change beforePath="$PROJECT_DIR$/pages/revoke_list/product.vue" beforeDir="false" afterPath="$PROJECT_DIR$/pages/revoke_list/product.vue" afterDir="false" />
11   - </list>
  4 + <list default="true" id="22e3db16-f3f9-44cc-b7ca-0b5f75df2820" name="Changes" comment="" />
12 5 <option name="SHOW_DIALOG" value="false" />
13 6 <option name="HIGHLIGHT_CONFLICTS" value="true" />
14 7 <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
... ...
  1 +import request from '@/utils/request'
  2 +import { ContentTypeEnum } from '@/utils/httpEnum';
  3 +
  4 +const baseUrl = '/basedata/customer';
  5 +// 查询列表
  6 +export function queryApi(params) {
  7 + return request({
  8 + url: baseUrl + `/query`,
  9 + method: 'get',
  10 + params
  11 + })
  12 +}
  13 +
  14 +// 根据ID查询详情数据
  15 +export function getDetailApi(id) {
  16 + return request({
  17 + url: baseUrl,
  18 + method: 'get',
  19 + params: { id }
  20 + })
  21 +}
  22 +
  23 +// 新增保存
  24 +export function createApi(params) {
  25 + return request({
  26 + url: baseUrl,
  27 + method: 'post',
  28 + data: params,
  29 + contentType: ContentTypeEnum.FORM_URLENCODED
  30 + })
  31 +}
  32 +
  33 +
  34 +// 修改保存
  35 +export function updateApi(params) {
  36 + return request({
  37 + url: baseUrl,
  38 + method: 'put',
  39 + data: params,
  40 + contentType: ContentTypeEnum.FORM_URLENCODED
  41 + })
  42 +}
... ...
... ... @@ -106,6 +106,14 @@
106 106 }
107 107 },
108 108 {
  109 + "path": "pages/customer/index",
  110 + "style": {
  111 + "navigationBarTitleText": "客户信息",
  112 + "navigationBarBackgroundColor": "#ffffff",
  113 + "navigationBarTextStyle": "black"
  114 + }
  115 + },
  116 + {
109 117 "path": "pages/credit_manage/index",
110 118 "style": {
111 119 "navigationBarTitleText": "客户资信管理",
... ...
  1 +<template>
  2 + <view class="page">
  3 + <view class="dev-list-fixed">
  4 + <view class="search-row">
  5 + <uni-search-bar v-model="searchKeyword" radius="6" placeholder="请输入名称或编号" clearButton="auto"
  6 + cancelButton="none" bgColor="#F3F3F3" textColor="rgba(0,0,0,0.4)" @confirm="search"
  7 + @input="onSearchInput" />
  8 + <view class="tool-icons">
  9 + <image v-if="$auth.hasPermi('customer-credit-manage:customer-credit-plan:add')" class="tool-icon" src="/static/images/dev_manage/add_icon.png" @click="onAdd" />
  10 + <image class="tool-icon" src="/static/images/dev_manage/filter_icon.png" @click="openFilter" />
  11 + </view>
  12 + </view>
  13 + </view>
  14 +
  15 +
  16 + <!-- 列表卡片组件 -->
  17 + <view class="list-box">
  18 + <card-list ref="cardRef" :fetch-fn="fetchList" :query="query" :extra="extraParams" row-key="id"
  19 + :enable-refresh="true" :enable-load-more="true" @loaded="onCardLoaded" @error="onCardError">
  20 + <template v-slot="{ item, selected }">
  21 + <view class="card" @click.stop="onCardClick(item)">
  22 + <view class="card-header">
  23 + <text class="title omit2">{{ item.name }}</text>
  24 + </view>
  25 + <view class="info-row">
  26 + <text>状态</text><text>{{ item.available ? '启用' : '停用' }}</text>
  27 + </view>
  28 + <view class="info-row">
  29 + <text>编号</text><text>{{ item.code || '-' }}</text>
  30 + </view>
  31 + <view class="info-row">
  32 + <text>备注</text><text>{{ item.description || '-' }}</text>
  33 + </view>
  34 + </view>
  35 + </template>
  36 + </card-list>
  37 + </view>
  38 +
  39 + <!-- 筛选弹框 -->
  40 + <filter-modal :visible.sync="filterVisible" :value.sync="filterForm" title="筛选" @reset="onFilterReset"
  41 + @confirm="onFilterConfirm">
  42 + <template v-slot="{ model }">
  43 + <view class="filter-form">
  44 + <view class="form-item">
  45 + <view class="label">状态</view>
  46 + <uni-data-checkbox mode="tag" :multiple="false" :value-field="'value'" :text-field="'text'"
  47 + v-model="model.available" @change="onStatusChange" :localdata="statusLocal" />
  48 + </view>
  49 + </view>
  50 + </template>
  51 + </filter-modal>
  52 + </view>
  53 +</template>
  54 +
  55 +<script>
  56 + import CardList from '@/components/card/index.vue'
  57 + import FilterModal from '@/components/filter/index.vue'
  58 + import SingleSelectSheet from '@/components/single-select/index.vue'
  59 + import {
  60 + queryApi
  61 + } from '@/api/customer.js'
  62 + export default {
  63 + components: {
  64 + CardList,
  65 + FilterModal,
  66 + SingleSelectSheet
  67 + },
  68 + data() {
  69 + return {
  70 + searchKeyword: '',
  71 + searchKeywordDebounced: '',
  72 + tabs: [],
  73 + // 给到 card 的筛选值
  74 + query: {
  75 + available: true
  76 + },
  77 + extraParams: {},
  78 +
  79 + // 批量选择
  80 + rowKey: 'id',
  81 + currentItems: [],
  82 +
  83 + // 筛选弹框
  84 + filterVisible: false,
  85 + filterForm: {
  86 + available: true
  87 + },
  88 + statusLocal: [{
  89 + value: true,
  90 + text: '启用'
  91 + },
  92 + {
  93 + value: false,
  94 + text: '停用'
  95 + }
  96 + ],
  97 + categoryLocal: []
  98 + }
  99 + },
  100 + computed: {
  101 + extraCombined() {
  102 + return {
  103 + searchKey: this.searchKeywordDebounced || undefined
  104 + }
  105 + }
  106 + },
  107 + watch: {
  108 + extraCombined: {
  109 + deep: true,
  110 + handler(v) {
  111 + this.extraParams = v
  112 + },
  113 + immediate: true
  114 + },
  115 +
  116 + },
  117 + created() {
  118 + },
  119 + onLoad() {},
  120 + // 页面触底兜底:当页面自身滚动到底部时,转调卡片组件加载更多
  121 + onReachBottom() {
  122 + if (this.$refs && this.$refs.cardRef && this.$refs.cardRef.onLoadMore) {
  123 + this.$refs.cardRef.onLoadMore()
  124 + }
  125 + },
  126 + beforeDestroy() {
  127 + if (this.searchDebounceTimer) {
  128 + clearTimeout(this.searchDebounceTimer)
  129 + this.searchDebounceTimer = null
  130 + }
  131 + },
  132 + methods: {
  133 + onCardLoaded({
  134 + items
  135 + }) {
  136 + this.currentItems = items
  137 + },
  138 + onCardError() {
  139 + uni.showToast({
  140 + title: '列表加载失败',
  141 + icon: 'none'
  142 + })
  143 + },
  144 + // 输入实时搜索:1200ms 防抖,仅在停止输入超过阈值后刷新
  145 + onSearchInput(val) {
  146 + if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer)
  147 + this.searchDebounceTimer = setTimeout(() => {
  148 + this.searchKeywordDebounced = this.searchKeyword
  149 + this.searchDebounceTimer = null
  150 + }, 1200)
  151 + },
  152 + // uni-search-bar 确认搜索:更新关键字并触发 CardList 刷新
  153 + search(e) {
  154 + const val = e && e.value != null ? e.value : this.searchKeyword
  155 + this.searchKeyword = val
  156 + this.searchKeywordDebounced = val
  157 + },
  158 + onAdd() {
  159 + uni.navigateTo({
  160 + url: '/pages/credit_manage/add'
  161 + })
  162 + },
  163 + openFilter() {
  164 + this.filterVisible = true
  165 + },
  166 + onFilterReset(payload) {
  167 + this.filterForm = {
  168 + available: true
  169 + }
  170 + },
  171 + onFilterConfirm(payload) {
  172 + let q = { ...payload
  173 + }
  174 + // 转换字符串 boolean 为真实 boolean,视后端需求而定,这里假设传 boolean 或 undefined
  175 + if ((payload.available === '' || payload.available == null) && this.filterForm.available !== '') {
  176 + q.available = this.filterForm.available
  177 + }
  178 +
  179 + this.query = q
  180 + },
  181 + onStatusChange(e) {
  182 + const raw = e && e.detail && e.detail.value !== undefined ?
  183 + e.detail.value :
  184 + (e && e.value !== undefined ? e.value : '')
  185 + this.filterForm.available = raw
  186 + },
  187 +
  188 + // 列表接口(真实请求)
  189 + fetchList({
  190 + pageIndex,
  191 + pageSize,
  192 + query,
  193 + extra
  194 + }) {
  195 + const params = {
  196 + pageIndex,
  197 + pageSize,
  198 + ...extra,
  199 + ...query
  200 + }
  201 + if (Array.isArray(params.dateRange) && params.dateRange.length === 2) {
  202 + params.registerDateStart = params.dateRange[0] + ' 00:00:00'
  203 + params.registerDateEnd = params.dateRange[1] + ' 23:59:59'
  204 + delete params.dateRange
  205 + }
  206 + if (this.searchKeywordDebounced) {
  207 + params.searchKey = this.searchKeywordDebounced
  208 + }
  209 + return queryApi(params)
  210 + .then(res => {
  211 + const _data = res.data || {};
  212 + const records = _data.datas || [];
  213 + const totalCount = _data.totalCount || 0;
  214 + const hasNext = _data.hasNext || false
  215 + return {
  216 + records,
  217 + totalCount,
  218 + hasNext
  219 + }
  220 + })
  221 + .catch(err => {
  222 + console.error('fetchList error', err)
  223 + this.onCardError()
  224 + return {
  225 + records: [],
  226 + totalCount: 0,
  227 + hasNext: false
  228 + }
  229 + })
  230 + },
  231 + onCardClick(item) {
  232 + const id = (item && (item.id || item.code)) || ''
  233 + if (!id) return
  234 + const query = '?id=' + encodeURIComponent(id)
  235 + uni.navigateTo({
  236 + url: '/pages/credit_manage/detail' + query
  237 + })
  238 + },
  239 +
  240 + }
  241 + }
  242 +</script>
  243 +
  244 +<style lang="scss" scoped>
  245 + .page {
  246 + display: flex;
  247 + flex-direction: column;
  248 + height: 100vh;
  249 + }
  250 +
  251 + .dev-list-fixed {
  252 + position: fixed;
  253 + top: 96rpx;
  254 + left: 0;
  255 + right: 0;
  256 + z-index: 2;
  257 + background: #fff;
  258 +
  259 + .search-row {
  260 + display: flex;
  261 + align-items: center;
  262 + padding: 16rpx 32rpx;
  263 +
  264 + .uni-searchbar {
  265 + padding: 0;
  266 + flex: 1;
  267 + }
  268 +
  269 + .tool-icons {
  270 + display: flex;
  271 +
  272 + .tool-icon {
  273 + width: 48rpx;
  274 + height: 48rpx;
  275 + display: block;
  276 + margin-left: 32rpx;
  277 + }
  278 + }
  279 + }
  280 +
  281 + }
  282 +
  283 + /* 仅当前页覆盖 uni-search-bar 盒子高度 */
  284 + ::v-deep .uni-searchbar__box {
  285 + height: 80rpx !important;
  286 + justify-content: start;
  287 +
  288 + .uni-searchbar__box-search-input {
  289 + font-size: 32rpx !important;
  290 + }
  291 + }
  292 +
  293 + .list-box {
  294 + flex: 1;
  295 + padding-top: 132rpx;
  296 +
  297 + &.pad-batch {
  298 + padding-bottom: 144rpx;
  299 + }
  300 +
  301 + .card {
  302 + position: relative;
  303 + }
  304 +
  305 + .card-header {
  306 + margin-bottom: 28rpx;
  307 + position: relative;
  308 +
  309 + .title {
  310 + font-size: 36rpx;
  311 + font-weight: 600;
  312 + line-height: 50rpx;
  313 + color: rgba(0,0,0,0.9);
  314 + width: 578rpx;
  315 + }
  316 +
  317 + .status-box {
  318 + position: absolute;
  319 + top: -32rpx;
  320 + right: -32rpx;
  321 +
  322 + .status {
  323 + display: block;
  324 + height: 48rpx;
  325 + line-height: 48rpx;
  326 + font-weight: 600;
  327 + color: #fff;
  328 + font-size: 24rpx;
  329 + padding: 0 14rpx;
  330 + border-radius: 6rpx;
  331 + margin-bottom: 8rpx;
  332 +
  333 + &.status_AUDIT {
  334 + background: $theme-primary;
  335 + }
  336 +
  337 + &.status_PASS {
  338 + background: #2BA471;
  339 + }
  340 +
  341 + &.status_REFUSE {
  342 + background: #D54941;
  343 + }
  344 +
  345 + &.status_CANCEL {
  346 + background: #E7E7E7;
  347 + color: rgba(0,0,0,0.9);
  348 + }
  349 + }
  350 +
  351 + .status2 {
  352 + display: block;
  353 + font-weight: 600;
  354 + line-height: 48rpx;
  355 + height: 48rpx;
  356 + color: #fff;
  357 + font-size: 24rpx;
  358 + padding: 0 14rpx;
  359 + border-radius: 6rpx;
  360 + background: #E7E7E7;
  361 + color: rgba(0,0,0,0.9);
  362 +
  363 + }
  364 +
  365 + }
  366 +
  367 + }
  368 +
  369 + .info-row {
  370 + display: flex;
  371 + align-items: center;
  372 + color: rgba(0, 0, 0, 0.6);
  373 + font-size: 28rpx;
  374 + margin-bottom: 24rpx;
  375 +
  376 + &:last-child {
  377 + margin-bottom: 0;
  378 + }
  379 +
  380 + text {
  381 + width: 60%;
  382 + line-height: 32rpx;
  383 +
  384 + &:last-child {
  385 + color: rgba(0, 0, 0, 0.9);
  386 + width: 40%;
  387 + }
  388 +
  389 + &.category {
  390 + display: inline-block;
  391 + padding: 4rpx 12rpx;
  392 + border-radius: 6rpx;
  393 + font-size: 24rpx;
  394 + width: auto;
  395 + &.category_A {
  396 + background: #FFF0ED;
  397 + color: #D54941;
  398 + }
  399 + &.category_B {
  400 + background: #FFF1E9;
  401 + color: #E37318;
  402 + }
  403 + &.category_C {
  404 + background: #F2F3FF;
  405 + color: $theme-primary;
  406 + }
  407 + &.category_D {
  408 + background: #E3F9E9;
  409 + color: #2BA471;
  410 + }
  411 + }
  412 + }
  413 +
  414 + }
  415 + }
  416 +
  417 + .filter-form {
  418 + .form-item {
  419 + margin-bottom: 24rpx;
  420 + }
  421 +
  422 + .label {
  423 + margin-bottom: 20rpx;
  424 + color: rgba(0, 0, 0, 0.9);
  425 + height: 44rpx;
  426 + line-height: 44rpx;
  427 + font-size: 30rpx;
  428 + }
  429 +
  430 + .fake-select {
  431 + height: 80rpx;
  432 + line-height: 80rpx;
  433 + padding: 0 20rpx;
  434 + background: #f3f3f3;
  435 + border-radius: 12rpx;
  436 +
  437 + .placeholder {
  438 + color: #999;
  439 + }
  440 +
  441 + .value {
  442 + color: #333;
  443 + }
  444 + }
  445 + }
  446 +
  447 + /* 深度覆盖 uni-data-checkbox(mode=tag)内部的 tag 展示与间距 */
  448 + ::v-deep .filter-form .uni-data-checklist .checklist-group {
  449 + .checklist-box {
  450 + &.is--tag {
  451 + width: 212rpx;
  452 + margin-top: 0;
  453 + margin-bottom: 24rpx;
  454 + margin-right: 24rpx;
  455 + height: 80rpx;
  456 + padding: 0;
  457 + border-radius: 12rpx;
  458 + background-color: #f3f3f3;
  459 + border-color: #f3f3f3;
  460 +
  461 + &:nth-child(3n) {
  462 + margin-right: 0;
  463 + }
  464 +
  465 + .checklist-content {
  466 + display: flex;
  467 + justify-content: center;
  468 + }
  469 +
  470 + .checklist-text {
  471 + color: rgba(0, 0, 0, 0.9);
  472 + font-size: 28rpx;
  473 + }
  474 + }
  475 +
  476 + &.is-checked {
  477 + background-color: $theme-primary-plain-bg !important;
  478 + border-color: $theme-primary-plain-bg !important;
  479 +
  480 + .checklist-text {
  481 + color: $theme-primary !important;
  482 + }
  483 + }
  484 + }
  485 +
  486 + }
  487 +
  488 +</style>
\ No newline at end of file
... ...