zjk
2026-02-25 eb20130c3fafe53b70cbe5474ceaf036c1b29268
feat: 定价管理列表接口、计费规则、编辑和删除
6个文件已修改
353 ■■■■■ 已修改文件
service.config.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes.static.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/transaction.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/pricingManagement/components/TableModal.vue 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/pricingManagement/index.vue 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transaction/pricingManagement/rules.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service.config.ts
@@ -3,6 +3,7 @@
  dev: {
    // url: 'http://localhost:3000',
    url: 'http://221.182.45.100:8000',
    // url: 'http://221.182.45.100:8000',
  },
  production: {
    url: 'https://mock.apifox.cn/m1/4071143-0-default',
src/router/routes.static.ts
@@ -522,6 +522,19 @@
    pid: 6,
  },
  {
    name: 'setPrice',
    path: '/transaction/pricingManagement/setPrice',
    title: '设置价格',
    requiresAuth: true,
    icon: 'icon-park-outline:vip-1',
    menuType: 'page',
    componentPath: '/transaction/pricingManagement/detail.vue',
    id: 603,
    pid: 6,
    hide: true, // 不在菜单中显示
    activeMenu: '/transaction/pricingManagement',
  },
  {
    name: 'pricingDetail',
    path: '/transaction/pricingManagement/detail',
    title: '定价详情',
@@ -529,7 +542,7 @@
    icon: 'icon-park-outline:vip-1',
    menuType: 'page',
    componentPath: '/transaction/pricingManagement/detail.vue',
    id: 603,
    id: 604,
    pid: 6,
    hide: true, // 不在菜单中显示
    activeMenu: '/transaction/pricingManagement',
@@ -542,7 +555,7 @@
    icon: 'icon-park-outline:vip-1',
    menuType: 'page',
    componentPath: '/transaction/pricingManagement/rules.vue',
    id: 604,
    id: 605,
    pid: 6,
    hide: true, // 不在菜单中显示
    activeMenu: '/transaction/pricingManagement',
@@ -555,7 +568,7 @@
    icon: 'icon-park-outline:alarm',
    menuType: 'page',
    componentPath: '/transaction/transactionStrategy/index.vue',
    id: 605,
    id: 606,
    pid: 6,
  },
  {
@@ -566,7 +579,7 @@
    icon: 'icon-park-outline:alarm',
    menuType: 'page',
    componentPath: '/transaction/navigationAndRoutePlanning/index.vue',
    id: 606,
    id: 607,
    pid: 6,
  },
  {
src/service/api/transaction.ts
@@ -14,7 +14,7 @@
// 导出订单Excel
export function exportOrderExcel(params: any) {
  return request.Post<Service.ResponseResult<any>>('/order/api/v1/order/exportExcel', params, { responseType: 'blob' })
  return request.Post<Service.ResponseResult<any>>('/order/api/v1/order/exportExcel', params)
}
// 改派订单
@@ -33,7 +33,6 @@
}
/** 订单评价 */
// 获取订单评价列表
export function getOrderEvaluateList(params: any) {
@@ -80,3 +79,37 @@
export function editTradingStrategy(params: any) {
  return request.Post<Service.ResponseResult<any>>('/operation/api/v1/tradingStrategy/edit', params)
}
// 定价管理
/** 获取定价列表 */
export function getPricingList(params: any) {
  return request.Post<Service.ResponseResult<any>>('/operation/api/v1/pricing/pageList', params)
}
/** 添加定价 */
export function addPricing(params: any) {
  return request.Post<Service.ResponseResult<any>>('/operation/api/v1/pricing/add', params)
}
/** 设置价格 */
export function setPricing(params: any) {
  return request.Post<Service.ResponseResult<any>>('/operation/api/v1/pricing/setPrice', params)
}
/** 删除定价 */
export function deletePricing(id: any) {
  return request.Delete<Service.ResponseResult<any>>('/operation/api/v1/pricing/deleteById?id=' + id)
}
/** 获取定价详情 */
export function getPricingDetail(id: any) {
  return request.Get<Service.ResponseResult<any>>('/operation/api/v1/pricing/queryById?id=' + id)
}
/** 计费规则 */
export function setRule(params: any) {
  return request.Post<Service.ResponseResult<any>>('/operation/api/v1/pricing/setRule', params)
}
src/views/transaction/pricingManagement/components/TableModal.vue
@@ -1,10 +1,13 @@
<script setup lang="ts">
import { useBoolean } from '@/hooks'
import { fetchRoleList } from '@/service'
import { getCityList, addPricing, getPricingDetail } from '@/service/api/transaction'
import type { FormItemRule } from 'naive-ui'
interface Props {
  modalName?: string
}
const dialog = useDialog()
const {
  modalName = '',
@@ -13,6 +16,7 @@
const emit = defineEmits<{
  open: []
  close: []
  refresh: []
}>()
const { bool: modalVisible, setTrue: showModal, setFalse: hiddenModal } = useBoolean(false)
@@ -20,13 +24,14 @@
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
const formDefault = {
  name: '',
  city: null,
  vehicleModel: 'Robotaxi',
  cityId: '',
  remark: '',
  rules: '1',
  channel: '',
  pricingType: 1,
  channel: '小鹏小程序',
}
const formModel = ref({ ...formDefault })
let data: any = {}
type ModalType = 'add' | 'view' | 'edit'
const modalType = shallowRef<ModalType>('add')
@@ -39,11 +44,14 @@
  return `${titleMap[modalType.value]}${modalName}`
})
async function openModal(type: ModalType = 'add', data: any) {
async function openModal(type: ModalType = 'add', id: string) {
  emit('open')
  modalType.value = type
  showModal()
  getRoleList()
  // 重置数据
  data = {}
  // 获取城市列表数据
  await getCityListData(type, id)
  // 等待数据获取完成后再显示模态框
  const handlers = {
    async add() {
      formModel.value = { ...formDefault }
@@ -60,9 +68,12 @@
    },
  }
  await handlers[type]()
  // 在所有数据加载完成后显示模态框
  showModal()
}
function closeModal() {
  formModel.value = { ...formDefault }
  hiddenModal()
  endLoading()
  emit('close')
@@ -77,19 +88,18 @@
  const handlers = {
    async add() {
      return new Promise((resolve) => {
        setTimeout(() => {
          window.$message.success('模拟新增成功')
          resolve(true)
        }, 2000)
        addPricing(formModel.value).then((res) => {
          if (res.code === 200) {
            window.$message.success('新增成功')
            resolve(true)
          } else {
            resolve(false)
          }
        })
      })
    },
    async edit() {
      return new Promise((resolve) => {
        setTimeout(() => {
          window.$message.success('模拟编辑成功')
          resolve(true)
        }, 2000)
      })
      return true
    },
    async view() {
      return true
@@ -97,11 +107,33 @@
  }
  await formRef.value?.validate()
  startLoading()
  await handlers[modalType.value]() && closeModal()
  const result = await handlers[modalType.value]()
  endLoading()
  if (result) {
    if (modalType.value === 'add') {
      dialog.create({
        title: '创建成功,是否去设置价格?',
        content: '',
        positiveText: '设置价格',
        negativeText: '稍后',
        draggable: true,
        onPositiveClick: () => {
        },
        onNegativeClick: () => {
          emit('refresh')
          closeModal()
        }
      })
    } else {
      emit('refresh')
      closeModal()
    }
  }
}
const rules = {
  name: {
  vehicleModel: {
    required: true,
    message: '请输入名称',
    trigger: 'blur',
@@ -111,34 +143,56 @@
    message: '请输入渠道',
    trigger: 'blur',
  },
  city: {
  cityId: {
    type: 'number',
    required: true,
    message: '请选择适用城市',
    trigger: 'change',
  },
  rules: {
  } as FormItemRule,
  pricingType: {
    type: 'number',
    required: true,
    message: '请选择计费规则',
    trigger: 'change',
  },
}
const options = ref()
async function getRoleList() {
  const { data } = await fetchRoleList()
  options.value = data
  } as FormItemRule,
}
const routePlanOptions = computed(() => [
  {
    label: '动态计费',
    value: '1',
    value: 1,
  },
  {
    label: '一口价',
    value: '2',
    value: 2,
  },
])
const cityList = ref<any[]>([])
const getCityListData = async (type: ModalType, id: string) => {
  try {
    const res = await getCityList()
    if (res.code === 200) {
      cityList.value = res.data || []
      cityList.value.forEach((item) => {
        item.label = item.name
        item.value = Number(item.id)
      })
    }
  } finally {
    if (type !== 'add') {
      await getPricingDetailData(id)
    }
  }
}
const getPricingDetailData = async (id: string) => {
  const res = await getPricingDetail(id)
  if (res.code === 200) {
    data = res.data || {}
  }
}
</script>
<template>
@@ -147,28 +201,29 @@
      content: true,
      action: true,
    }">
    <n-form ref="formRef" :rules="rules" :model="formModel" :disabled="modalType === 'view'" require-mark-placement="left" >
      <n-form-item label="车型" path="name">
          <n-input v-model:value="formModel.name" />
        </n-form-item>
        <n-form-item label="渠道" path="channel">
          <n-input v-model:value="formModel.channel" />
        </n-form-item>
        <n-form-item label="城市" path="city">
         <PcaCascader v-model:value="formModel.city" />
        </n-form-item>
        <n-form-item label="说明" path="remark">
          <n-input v-model:value="formModel.remark" type="textarea" />
        </n-form-item>
        <n-form-item label="计费规则" path="rules">
          <n-radio-group v-model:value="formModel.rules" name="rules">
            <n-space>
              <n-radio v-for="option in routePlanOptions" :key="option.value" :value="option.value">
                {{ option.label }}
              </n-radio>
            </n-space>
          </n-radio-group>
        </n-form-item>
    <n-form ref="formRef" :rules="rules" :model="formModel" :disabled="modalType === 'view'"
      require-mark-placement="left">
      <n-form-item label="车型" path="vehicleModel">
        <n-input v-model:value="formModel.vehicleModel" disabled />
      </n-form-item>
      <n-form-item label="渠道" path="channel">
        <n-input v-model:value="formModel.channel" disabled />
      </n-form-item>
      <n-form-item label="城市" path="cityId">
        <n-select v-model:value="formModel.cityId" :options="cityList" />
      </n-form-item>
      <n-form-item label="说明" path="remark">
        <n-input v-model:value="formModel.remark" type="textarea" />
      </n-form-item>
      <n-form-item label="计费规则" path="pricingType">
        <n-radio-group v-model:value="formModel.pricingType" name="pricingType">
          <n-space>
            <n-radio v-for="option in routePlanOptions" :key="option.value" :value="option.value">
              {{ option.label }}
            </n-radio>
          </n-space>
        </n-radio-group>
      </n-form-item>
    </n-form>
    <template #action>
      <n-space justify="center">
src/views/transaction/pricingManagement/index.vue
@@ -1,8 +1,8 @@
<script setup lang="tsx">
import type { DataTableColumns, FormInst } from 'naive-ui'
import { useBoolean } from '@/hooks'
import { fetchUserPage } from '@/service'
import { NButton, NPopconfirm, NSpace } from 'naive-ui'
import { getPricingList, deletePricing } from '@/service/api/transaction'
import SearchPanel from '@/components/custom/SearchPanel.vue'
import TableModal from './components/TableModal.vue'
import {useRouter} from 'vue-router'
@@ -13,9 +13,8 @@
const { t } = useI18n()
const initialModel = {
  condition_1: '',
  condition_2: '',
  condition_3: null,
  vehicleModel: '',
  cityName: '',
}
const model = ref({ ...initialModel })
function handleResetSearch() {
@@ -25,8 +24,19 @@
const formRef = ref<FormInst | null>()
const modalRef = ref()
const pageParams = ref({
  pageNum: 1,
  pageSize: 10,
})
function delteteUser(id: number) {
  window.$message.success(`删除用户id:${id}`)
  deletePricing(id).then((res) => {
    if (res.code === 200) {
      window.$message.success('删除成功')
    }
  }).finally(() => {
    getList()
  })
}
const columns = computed<DataTableColumns<any>>(() =>[
@@ -45,28 +55,28 @@
  {
    title: '车型',
    align: 'center',
    key: 'carType',
    key: 'vehicleModel',
    ellipsis: true
  },
  {
    title: '开通城市',
    title: '适用城市',
    align: 'center',
    key: 'city',
    key: 'useCity',
    ellipsis: true
  },
  {
    title: '渠道',
    align: 'center',
    key: 'channel',
    ellipsis: true
    ellipsis: true,
  },
  {
    title: '计费规则',
    align: 'center',
    key: 'pricingRules',
    key: 'pricingType',
    ellipsis: true,
    render: (row) => {
      return row.pricingRules === 1 ? '动态计费' : '一口价'
      return row.pricingType === 1 ? '动态计费' : '一口价'
    },
  },
  {
@@ -102,7 +112,7 @@
          <NButton
            text
            type='primary'
            onClick={() => modalRef.value.openModal('edit', row)}
            onClick={() => modalRef.value.openModal('edit', row.id)}
          >
            编辑
          </NButton>
@@ -121,13 +131,13 @@
const filterItems = computed(() => [
  {
    label: '车型',
    path: 'condition_1',
    render: () => <n-input v-model:value={model.value.condition_1} />,
    path: 'vehicleModel',
    render: () => <n-input v-model:value={model.value.vehicleModel} />,
  },
  {
    label: '城市',
    path: 'condition_1',
    render: () => <n-input v-model:value={model.value.condition_1} />,
    path: 'cityName',
    render: () => <n-input v-model:value={model.value.cityName} />,
  },
])
@@ -136,47 +146,37 @@
async function getList() {
  startLoading()
  setTimeout(() => {
    listData.value = [
      {
        id: 1,
        city: '四川省成都市',
        carType: 'RoboTaxi',
        channel: '小鹏小程序',
        pricingRules: 1,
        createTime: '2023-01-01 10:00:00',
        updateTime: '2023-01-01 10:00:00',
      },
      {
        id: 2,
        city: '四川省成都市',
        carType: 'RoboTaxi',
        channel: '小鹏小程序',
        pricingRules: 2,
        createTime: '2023-01-01 10:00:00',
        updateTime: '2023-01-01 10:00:00',
      },
    ]
  const params = {
    ...pageParams.value,
    cityName: model.value.cityName,
    vehicleModel: model.value.vehicleModel,
  }
  getPricingList(params).then((res) => {
    listData.value = res.data.records || []
    listData.value.forEach((item: any, index: number)  => {
      item.serialNumber = index + 1
    })
    count.value = 1
    count.value = res.data.total || res.data.records?.length
  }).finally(() => {
    endLoading()
  }, 500)
  })
}
onMounted(() => {
  getList()
})
function changePage(page: number, size: number) {
  window.$message.success(`分页器:${page},${size}`)
  pageParams.value.pageNum = page
  pageParams.value.pageSize = size
  getList()
}
</script>
<template>
  <n-flex>
  <n-flex class="roboTaxi">
    <NSpace vertical class="flex-1">
      <n-card>
      <div class="roboTaxi-form-card">
        <n-form ref="formRef" :model="model" label-placement="left" inline :show-feedback="false">
          <SearchPanel>
            <n-gi v-for="item in filterItems" :key="item.path">
@@ -185,38 +185,32 @@
              </n-form-item>
            </n-gi>
            <template #action>
              <NButton strong secondary @click="handleResetSearch">
                <template #icon>
                  <icon-park-outline-redo />
                </template>
              <NButton strong @click="handleResetSearch">
                {{ $t('common.reset') }}
              </NButton>
              <NButton type="primary" @click="getList">
                <template #icon>
                  <icon-park-outline-search />
                </template>
                {{ $t('common.search') }}
              </NButton>
            </template>
          </SearchPanel>
        </n-form>
      </n-card>
      </div>
      <n-card class="flex-1">
        <template #header>
          <NButton type="primary" @click="modalRef.openModal('add')">
            <template #icon>
              <icon-park-outline-add-one />
            </template>
            新增定价
          </NButton>
          <n-flex justify="flex-end">
            <NButton type="primary" secondary @click="modalRef.openModal('add')">
              新增定价
            </NButton>
          </n-flex>
        </template>
        <NSpace vertical>
          <n-data-table :columns="columns" :data="listData" :loading="loading" />
        <NSpace vertical :size="16">
          <n-data-table :columns="columns" :data="listData" :loading="loading" :bordered="false" :header-height="18"
            :row-height="18" />
          <Pagination :count="count" @change="changePage" />
        </NSpace>
        <TableModal ref="modalRef" modal-name="定价" />
        <TableModal ref="modalRef" modal-name="定价" @refresh="getList" />
      </n-card>
    </NSpace>
  </n-flex>
src/views/transaction/pricingManagement/rules.vue
@@ -2,6 +2,7 @@
import { NButton, NSpace } from 'naive-ui'
import { useRoute, useRouter } from 'vue-router'
import { useTabStore } from '@/store'
import { setRule, getPricingDetail } from '@/service/api/transaction'
const route = useRoute()
const router = useRouter()
@@ -10,7 +11,14 @@
const text = ref('')
const handleSubmit = async () => {
  handleCancel()
  const res = await setRule({
    id: route.query.id as string,
    priceRule: text.value,
  })
  if (res.code === 200) {
    window.$message.success('保存成功')
    handleCancel()
  }
}
const handleCancel = () => {
@@ -18,8 +26,15 @@
  router.back()
}
onMounted(() => {
const getPricingDetailData = async (id: string) => {
  const res = await getPricingDetail(id)
  if (res.code === 200) {
    text.value = res.data?.priceRule || ''
  }
}
onMounted(() => {
  getPricingDetailData(route.query.id as string)
})
</script>