feat: 构架绿植租赁方案管理页面
Some checks are pending
Gitea Actions Demo / Explore-Gitea-Actions (push) Waiting to run

This commit is contained in:
fyy 2025-07-01 17:48:47 +08:00
parent 0a56fa2194
commit 33f2197ea8
8 changed files with 417 additions and 62 deletions

View File

@ -204,6 +204,8 @@ async function handleConfirm() {
return;
}
const data = cloneDeep(await formApi.getValues());
console.log(data);
//
const selectedService = cleanListData.find(item => item.id === data.name);
if (selectedService) {

View File

@ -285,12 +285,12 @@ function handleEditDetailReload(data: any) {
function handleDeleteDetail(record: any, index: number) {
detailTable.value.splice(index, 1);
}
//
function handleViewDetail(record: any) {
detailModalApi.setData({ ...record, readonly: true });
detailModalApi.open();
}
//
function handleEditDetail(record: any, index: number) {
detailModalApi.setData({ ...record, index, readonly: false });
detailModalApi.open();

View File

@ -8,22 +8,22 @@ import type { FormSchema } from '../../../../../../../packages/@core/ui-kit/form
const cleanStore = useCleanStore();
export const querySchema: (areaList: any[]) => FormSchema[] = (areaList) => [
{
label: '服务地址',
component: 'TreeSelect',
fieldName: 'location',
componentProps: {
treeData: areaList,
fieldNames: {
label: 'label',
value: 'id',
children: 'children',
},
placeholder: '请选择服务地址',
showSearch: true,
treeDefaultExpandAll: true,
},
},
// {
// label: '服务地址',
// component: 'TreeSelect',
// fieldName: 'location',
// componentProps: {
// treeData: areaList || [],
// fieldNames: {
// label: 'label',
// value: 'id',
// children: 'children',
// },
// placeholder: '请选择服务地址',
// showSearch: true,
// treeDefaultExpandAll: true,
// },
// },
// {
// label: '服务地址',
// component: 'TreeSelect',
@ -83,7 +83,7 @@ export const columns: VxeGridProps['columns'] = [
field: 'payState',
width: '120',
slots: {
default: ({ row }) => (row.stater === 1 ? '已支付' : '待支付'),
default: ({ row }) => (row.state === 1 ? '已支付' : '待支付'),
},
},
{
@ -110,6 +110,12 @@ export const columns: VxeGridProps['columns'] = [
title: '联系电话',
field: 'phone',
width: 'auto',
},
{
title: '订单状态',
field: 'state',
width: 'auto',
slots: { default: 'state' },
},
// {
// title: '提交时间',
@ -121,6 +127,7 @@ export const columns: VxeGridProps['columns'] = [
slots: { default: 'action' },
title: '操作',
},
];
export const modalSchema: FormSchemaGetter = () => [

View File

@ -7,7 +7,7 @@ import type { CleanForm } from '#/api/property/clean/model';
import { Page, useVbenModal } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import { Modal, Popconfirm, Space, Tag } from 'ant-design-vue';
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
import {
@ -162,6 +162,11 @@ async function handleView(row: Required<CleanForm>) {
</a-button>
</Space>
</template>
<template #state="{ row }">
<Tag v-if="row.state === 1" color="success">审核通过</Tag>
<Tag v-else-if="row.state === 2" color="error">审核不通过</Tag>
<Tag v-else color="default">未审核</Tag>
</template>
<template #action="{ row }">
<Space>
<ghost-button @click.stop="handleView(row)"> 查看 </ghost-button>

View File

@ -41,6 +41,11 @@ export const columns: VxeGridProps['columns'] = [
{
title: '序号',
field: 'id',
slots: {
default: ({ rowIndex }) => {
return (rowIndex + 1).toString();
},
},
},
{
title: '方案名称',
@ -57,10 +62,6 @@ export const columns: VxeGridProps['columns'] = [
{
title: '植物组合包',
field: 'scene',
},
{
title: '基础服务项',
field: 'scene',
},
{
title: '价格',
@ -70,10 +71,7 @@ export const columns: VxeGridProps['columns'] = [
title: '状态',
field: 'state',
slots: {
default: ({ row }) => {
// 可选从DictEnum中获取 DictEnum.WY_KG 便于维护
return renderDict(row.state, 'wy_kg');
},
default: 'state'
},
},
{
@ -119,16 +117,6 @@ export const modalSchema: FormSchemaGetter = () => [
label: '价格体系',
fieldName: 'price',
component: 'Input',
},
{
label: '基础服务项',
fieldName: 'price',
component: 'Input',
},
{
label: '优惠活动',
fieldName: 'price',
component: 'Input',
},
{
label: '状态',
@ -136,7 +124,16 @@ export const modalSchema: FormSchemaGetter = () => [
component: 'Select',
componentProps: {
// 可选从DictEnum中获取 DictEnum.WY_KG 便于维护
options: getDictOptions('wy_kg'),
options: [
{
label: '待支付',
value: 0,
},
{
label: '已支付',
value: 1,
},
],
},
rules: 'selectRequired',
},

View File

@ -6,7 +6,7 @@ import { ref } from 'vue';
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import { Modal, Popconfirm, Space,Tag } from 'ant-design-vue';
import dayjs from 'dayjs';
import {
@ -102,7 +102,10 @@ async function handleDelete(row: Required<RentalPlanForm>) {
await rentalPlanRemove(row.id);
await tableApi.query();
}
async function handleView(row: Required<RentalPlanForm>) {
modalApi.setData({ id: row.id, readonly: true,ab:"wegqw" });
modalApi.open();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Required<RentalPlanForm>) => row.id);
@ -122,6 +125,7 @@ function handleDownloadExcel() {
fieldMappingTime: formOptions.fieldMappingTime,
});
}
</script>
<template>
@ -152,8 +156,14 @@ function handleDownloadExcel() {
</a-button>
</Space>
</template>
<template #state="{ row }">
<Tag v-if="row.state === 0" color="error">禁用</Tag>
<Tag v-else-if="row.state === 1" color="success">启用</Tag>
<Tag v-else color="default">未审核</Tag>
</template>
<template #action="{ row }">
<Space>
<ghost-button @click.stop="handleView(row)"> 查看 </ghost-button>
<ghost-button
v-access:code="['property:rentalPlan:edit']"
@click.stop="handleEdit(row)"

View File

@ -0,0 +1,238 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { plantsProductList } from '#/api/property/productManagement';
const emit = defineEmits<{ reload: [data: any], editReload: [data: any] }>();
const isUpdate = ref(false);
const isAdd = ref(false);
const isView = ref(false);
const title = computed(() => {
if(isAdd.value){
return $t('pages.common.add');
}else if(isView.value){
return '查看';
}else{
return $t('pages.common.edit');
}
});
//
let plantListData: any[] = [];
const detailIndex = ref<number>();//index,
const detailSchema = [
{
label: '产品名称',
fieldName: 'plantName',
component: 'ApiSelect',
componentProps: {
api: async () => {
const res = await plantsProductList({inventory:0});
plantListData = res.rows || [];
return res;
},
resultField: 'rows',
labelField: 'plantName',
valueField: 'id',
onChange: async (value: string) => {
console.log(value);
//
const selectedService = plantListData.find(item => item.id === value);
console.log(selectedService,'1203');
if (selectedService) {
//
await formApi.setValues({
plantCode: selectedService.plantCode,
plantType: selectedService.plantType,
imgPath: selectedService.imgPath,
specification: selectedService.specification,
rent: selectedService.rent,
state: selectedService.state,
remark: selectedService.remark,
});
}
},
},
rules: 'required',
},
{
label: '产品编号',
fieldName: 'plantCode',
component: 'Input',
componentProps: {
disabled: true,
},
rules: 'required',
},
{
label: '产品分类',
fieldName: 'plantType',
component: 'Input',
componentProps: {
disabled: true,
},
rules: 'required',
},
{
label: '图片',
fieldName: 'imgPath',
component: 'InputNumber',
componentProps: {
disabled: true,
},
},
{
label: '规格',
fieldName: 'specification',
component: 'Input',
componentProps: {
disabled: true,
},
rules: 'required',
},
{
label: '租金',
fieldName: 'rent',
component: 'Input',
componentProps: {
disabled: true,
},
rules: 'required',
},
{
label: '状态',
fieldName: 'state',
component: 'Select',
componentProps: {
disabled: true,
options: [
{ label: '上架', value: 1 },
{ label: '下架', value: 0 },
],
},
rules: 'required',
},
{
label: '备注',
fieldName: 'remark',
component: 'Input',
componentProps: {
disabled: true,
},
},
];
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
labelWidth: 120,
componentProps: {
class: 'w-full',
},
},
schema: detailSchema,
showDefaultActions: false,
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicModal, modalApi] = useVbenModal({
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
onOpenChange: async (isOpen) => {
if (!isOpen) {
return null;
}
modalApi.modalLoading(true);
const data = modalApi.getData();
detailIndex.value = modalApi.getData().index;
if(!data || Object.keys(data).length === 0){
//modalApi.getData()
isAdd.value = true;
}else if(detailIndex.value == undefined || detailIndex.value == null){
//detailIndex.value
isView.value = true;
}else{
//
isUpdate.value = true;
}
// TODO:
await formApi.setValues(modalApi.getData());
await markInitialized();
modalApi.modalLoading(false);
},
});
async function handleConfirm() {
try {
modalApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const data = cloneDeep(await formApi.getValues());
console.log(data);
//
const selectedService = plantListData.find(item => item.id === data.plantName);
console.log(selectedService,'selectedService');
if (selectedService) {
data.plantName = selectedService.plantName;
data.id = selectedService.id
}
//index>=0
if (detailIndex.value! >= 0) {
emit('editReload', data);
}
handleClosed()
await markInitialized();
console.log(data);
emit('reload', data);
modalApi.close();
} catch (error) {
console.error(error);
} finally {
modalApi.lock(false);
}
}
async function handleClosed() {
isAdd.value = false;
isView.value = false;
isUpdate.value = false;
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicModal :title="title">
<BasicForm >
</BasicForm>
</BasicModal>
</template>
<style scoped>
/* 使用 :deep() 穿透 scoped 样式,影响子组件 */
:deep(.ant-input[disabled]),
:deep(.ant-input-number-disabled .ant-input-number-input),
:deep(.ant-select-disabled .ant-select-selection-item) {
/* 设置一个更深的颜色,可以自己调整 */
color: rgba(0, 0, 0, 0.65) !important;
/* 有些浏览器需要这个来覆盖默认颜色 */
-webkit-text-fill-color: rgba(0, 0, 0, 0.65) !important;
}
</style>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Button, Table } from 'ant-design-vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
@ -10,12 +10,13 @@ import { rentalPlanAdd, rentalPlanInfo, rentalPlanUpdate } from '#/api/property/
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { modalSchema } from './data';
import productDetailModal from './rentalPlan-detial-modal.vue';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const isReadonly = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
return isUpdate.value ? $t('pages.common.edit') : isReadonly.value ? '详情' : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
@ -25,9 +26,10 @@ const [BasicForm, formApi] = useVbenForm({
// label px
labelWidth: 120,
//
componentProps: {
componentProps: computed(() => ({
class: 'w-full',
}
disabled: isReadonly.value,
})),
},
schema: modalSchema(),
showDefaultActions: false,
@ -52,12 +54,20 @@ const [BasicModal, modalApi] = useVbenModal({
if (!isOpen) {
return null;
}
console.log(isOpen);
modalApi.modalLoading(true);
console.log(modalApi.getData(),'====================');
const { id } = modalApi.getData() as { id?: number | string };
isUpdate.value = !!id;
if (isUpdate.value && id) {
const { id, readonly } = modalApi.getData() as { id?: number | string, readonly?: boolean; };
isReadonly.value = !!readonly;
if(isReadonly.value){
isUpdate.value = false;
}else{
isUpdate.value = !!id;
}
//
if ((isUpdate.value || isReadonly.value) && id) {
const record = await rentalPlanInfo(id);
await formApi.setValues(record);
}
@ -66,6 +76,7 @@ const [BasicModal, modalApi] = useVbenModal({
modalApi.modalLoading(false);
},
});
const detailTable = ref<any>([]);
async function handleConfirm() {
try {
@ -76,6 +87,10 @@ async function handleConfirm() {
}
// getValuesreadonly
const data = cloneDeep(await formApi.getValues());
console.log(data);
console.log(detailTable.value);
data.productIds = detailTable.value.map((item:any) => item.id);
await (isUpdate.value ? rentalPlanUpdate(data) : rentalPlanAdd(data));
resetInitialized();
emit('reload');
@ -88,9 +103,14 @@ async function handleConfirm() {
}
async function handleClosed() {
detailTable.value = [];
await formApi.resetForm();
resetInitialized();
}
const [ProductDetailModal, detailModalApi] = useVbenModal({
connectedComponent: productDetailModal,
});
const detailColumns = [
{ title: '序号', key: 'index' },
{ title: '产品编号', dataIndex: 'plantCode', key: 'plantCode' },
@ -121,16 +141,43 @@ const detailColumns = [
fixed: 'right' as const,
},
];
function handleAddDetail() {
detailModalApi.setData({});
detailModalApi.open();
}
//
function handleDetailReload (data: any) {
detailTable.value.push(data);
}
//
function handleEditDetailReload (data: any) {
detailTable.value[data.index] = data;
}
//
function handleDeleteDetail(record: any, index: number) {
detailTable.value.splice(index, 1);
}
//
function handleViewDetail(record: any) {
detailModalApi.setData({ ...record, readonly: true });
detailModalApi.open();
}
//
function handleEditDetail(record: any, index: number) {
detailModalApi.setData({ ...record, index, readonly: false });
detailModalApi.open();
}
</script>
<template>
<BasicModal :title="title">
<BasicForm />
<!-- 添加订单详情部分 -->
<!-- 添加产品部分 -->
<div class="mt-4">
<div class="mb-2 flex items-center justify-between">
<h3 class="text-lg font-medium">添加保洁订单详情</h3>
<a-button type="primary" @click="handleAddDetail">
<h3 class="text-lg font-medium">{{ isUpdate ? '编辑植物组合包产品' : isReadonly ? '查看植物组合包产品' : '添加植物组合包产品' }} </h3>
<a-button v-if="!isReadonly" type="primary" @click="handleAddDetail">
{{ $t('pages.common.add') }}
</a-button>
</div>
@ -144,14 +191,63 @@ const detailColumns = [
{{ index + 1 }}
</template>
<template v-else-if="column.key === 'action'">
<Button danger @click="handleDeleteDetail(record, index)">
删除
</Button>
<template v-if="isReadonly">
<Button @click="handleViewDetail(record)">查看</Button>
</template>
<template v-else >
<Button type="primary" size="small" style="margin-right: 5px;" @click="handleViewDetail(record)">查看</Button>
<Button type="primary" size="small" style="margin-right: 5px;" @click="handleEditDetail(record, index)">编辑</Button>
<Button danger size="small" @click="handleDeleteDetail(record, index)">
删除
</Button>
</template>
</template>
</template>
</Table>
<div>费用合计{{ totalSumPeices }}</div>
<!-- <div>费用合计{{ totalSumPeices }}</div> -->
</div>
<ProductDetailModal @reload="handleDetailReload" @editReload="handleEditDetailReload"/>
</BasicModal>
</template>
<style scoped>
.mt-4 {
margin-top: 1rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.font-medium {
font-weight: 500;
}
/* 使用 :deep() 穿透 scoped 样式,影响子组件 */
:deep(.ant-input[disabled]),
:deep(.ant-input-number-disabled .ant-input-number-input),
:deep(.ant-select-disabled .ant-select-selection-item),
:deep(.ant-picker-disabled .ant-picker-input > input) {
/* 设置一个更深的颜色 */
color: rgb(0 0 0 / 65%) !important;
/* 有些浏览器需要这个来覆盖默认颜色 */
-webkit-text-fill-color: rgb(0 0 0 / 65%) !important;
}
</style>