Browse Source

feat: plan

lhl 10 months ago
parent
commit
0875e64734

+ 23 - 0
src/api/plan/index.ts

@@ -0,0 +1,23 @@
+import request from '@/axios'
+import { REQUEST_BASE } from '@/constants'
+
+export const getPlanList = (params: any): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/decoration/cases`, params })
+}
+export const getPlanApplyList = (params: any): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/decoration/cases_apply`, params })
+}
+export const addPlan = (data: any): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/store`, data })
+}
+export const applyPlan = (data: any): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/decoration/cases/audit/${data.id}`, data })
+}
+
+export const editPlan = (data: any): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/store/${data.id}`, data })
+}
+
+export const delPlan = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/store/${id}` })
+}

+ 35 - 0
src/api/plan/types.ts

@@ -0,0 +1,35 @@
+export type designerSearch = {
+  page?: number
+  limit?: number
+}
+
+export interface designerData {
+  id?: number
+  uid: number | null
+  name: string //真实姓名
+  phone: number | null //手机号
+  gender: 0 | 1 | 2 //性别 0保密 1男 2女
+  avatar: string //头像
+  birth_day_time?: string | number //生日
+  province: string //省
+  city: string //市
+  area: string //区
+  tag_list: Array<string> //标签
+  longitude: string | number //经度
+  latitude: string | number //纬度
+  detail_address: string
+  start_job_year: string
+}
+export type categoryData = {
+  id?: number
+  name: string
+  is_show: 1 | 0
+  type: 1 | 2
+}
+export type jobsData = {
+  id?: number
+  name: string
+  is_show: 1 | 0
+  cate_id: number
+  default_price: number
+}

+ 32 - 0
src/router/model/Plan.ts

@@ -0,0 +1,32 @@
+import { Layout } from '@/utils/routerHelper'
+const pre = 'plan'
+
+export default {
+  path: `/${pre}`,
+  component: Layout,
+  name: `${pre}`,
+  redirect: '/store/list',
+  meta: {
+    title: '方案管理',
+    icon: 'vi-ep:setting',
+    alwaysShow: true
+  },
+  children: [
+    {
+      path: 'list',
+      component: () => import('@/views/Plan/list/index.vue'),
+      name: `${pre}-list`,
+      meta: {
+        title: '案例列表'
+      }
+    },
+    {
+      path: 'apply',
+      component: () => import('@/views/Plan/apply/index.vue'),
+      name: `${pre}-apply`,
+      meta: {
+        title: '案列审核列表'
+      }
+    }
+  ]
+}

+ 129 - 0
src/views/Plan/apply/components/Write.vue

@@ -0,0 +1,129 @@
+<script setup lang="tsx">
+import { Form, FormSchema } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { PropType, watch, ref } from 'vue'
+import { useValidator } from '@/hooks/web/useValidator'
+import { cloneDeep } from 'lodash-es'
+import { ElSelect, ElOption } from 'element-plus'
+
+const { required } = useValidator()
+
+const props = defineProps({
+  currentRow: {
+    type: Object as PropType<any>,
+    default: () => null
+  }
+})
+
+const formSchema = ref<FormSchema[]>([
+  {
+    field: 'status',
+    label: '审核结果',
+    component: 'Select',
+    colProps: {
+      span: 24
+    },
+    value: 1,
+    formItemProps: {
+      slots: {
+        default: (data) => {
+          return (
+            <>
+              <ElSelect
+                v-model={data.status}
+                onChange={(val: any) => {
+                  selectStatus(val)
+                }}
+              >
+                <ElOption label={'通过'} value={1}></ElOption>
+                <ElOption label={'拒绝'} value={2}></ElOption>
+              </ElSelect>
+            </>
+          )
+        }
+      }
+    }
+    // componentProps: {
+    //   options: [
+    //     {
+    //       label: '通过',
+    //       value: 1
+    //     },
+    //     {
+    //       label: '拒绝',
+    //       value: 2
+    //     }
+    //   ]
+    // }
+  },
+  {
+    field: 'reason',
+    label: '原因',
+    component: 'Input',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      placeholder: '如拒绝请输入原因',
+      type: 'textarea'
+    }
+  },
+  {
+    field: 'id',
+    label: 'id',
+    hidden: true,
+    component: 'Input'
+  }
+])
+
+const rules = ref({
+  reason: [required('请输入拒绝原因')]
+})
+
+const selectStatus = (item: any) => {
+  console.log(item)
+  if (item == 2) {
+    rules.value.reason = [required('请输入拒绝原因')]
+  } else {
+    rules.value.reason = [
+      {
+        required: false
+      }
+    ]
+  }
+}
+const { formRegister, formMethods } = useForm()
+const { setValues, getFormData, getElFormExpose } = formMethods
+
+const submit = async () => {
+  const elForm = await getElFormExpose()
+  const valid = await elForm?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    const formData = await getFormData()
+    return formData
+  }
+}
+
+watch(
+  () => props.currentRow,
+  async (value) => {
+    if (!value) return
+    const currentRow = cloneDeep(value)
+    setValues(currentRow)
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+defineExpose({
+  submit
+})
+</script>
+
+<template>
+  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+</template>

+ 320 - 0
src/views/Plan/apply/index.vue

@@ -0,0 +1,320 @@
+<script setup lang="tsx">
+import { reactive, ref, unref, useTemplateRef } from 'vue'
+import { getPlanApplyList, applyPlan } from '@/api/plan'
+import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn } from '@/components/Table'
+import { Search } from '@/components/Search'
+import { FormSchema } from '@/components/Form'
+import { ContentWrap } from '@/components/ContentWrap'
+import { Dialog } from '@/components/Dialog'
+import { BaseButton } from '@/components/Button'
+import { ElMessage, ElTag } from 'element-plus'
+import { TableImage } from '@/components/tableImage'
+import Write from './components/Write.vue'
+
+const { t } = useI18n()
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const res = await getPlanApplyList({
+      page: unref(currentPage) || 1,
+      limit: unref(pageSize) || 10,
+      ...unref(searchParams)
+    })
+    return {
+      list: res.data.list,
+      total: res.data.count || 0
+    }
+  }
+})
+
+const { dataList, loading, total, currentPage, pageSize } = tableState
+const { getList } = tableMethods
+
+const tableColumns = reactive<TableColumn[]>([
+  {
+    field: 'id',
+    label: 'ID',
+    align: 'center',
+    width: 70
+  },
+  {
+    field: 'image',
+    label: '封面图',
+    width: 80,
+    slots: {
+      default: ({ row }: any) => {
+        return (
+          <>
+            <TableImage src={row.image} />
+          </>
+        )
+      }
+    }
+  },
+  {
+    field: 'slider_images',
+    label: '轮播图',
+    width: 240,
+    slots: {
+      default: ({ row }: any) => {
+        return (
+          <>
+            {row.slider_images_chs.map((item: any) => {
+              return (
+                <>
+                  <TableImage src={item} />
+                </>
+              )
+            })}
+          </>
+        )
+      }
+    }
+  },
+  {
+    field: 'content_images_chs',
+    label: '效果图',
+    width: 240,
+    slots: {
+      default: ({ row }: any) => {
+        return (
+          <>
+            {row.content_images_chs.map((item: any) => {
+              return (
+                <>
+                  <TableImage src={item} />
+                </>
+              )
+            })}
+          </>
+        )
+      }
+    }
+  },
+  {
+    field: 'title',
+    label: '标题',
+    minWidth: 100
+  },
+  {
+    field: 'address',
+    label: '地区',
+    minWidth: 150
+  },
+  {
+    field: 'room_type',
+    label: '户型',
+    minWidth: 80
+  },
+  {
+    field: 'room_area',
+    label: '面积',
+    minWidth: 80
+  },
+  {
+    field: 'style',
+    label: '风格',
+    width: 80
+  },
+  {
+    field: 'info',
+    label: '简介',
+    width: 120
+  },
+  {
+    field: 'status',
+    label: '审核状态',
+    width: 150,
+    slots: {
+      default: ({ row }: any) => {
+        return (
+          <>
+            <ElTag
+              class="mr-5px"
+              type={row.status == 0 ? 'primary' : row.status == 1 ? 'success' : 'danger'}
+            >
+              {row.status_chs}
+            </ElTag>
+          </>
+        )
+      }
+    }
+  },
+  {
+    field: 'reason',
+    label: '审核意见',
+    width: 120
+  },
+  {
+    field: 'action',
+    label: t('userDemo.action'),
+    width: 200,
+    fixed: 'right',
+    align: 'center',
+    headerAlign: 'center',
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        if (row.status == 0) {
+          return (
+            <>
+              <BaseButton link size="small" type="primary" onClick={() => action('edit', row)}>
+                审核
+              </BaseButton>
+            </>
+          )
+        } else {
+          return <></>
+        }
+      }
+    }
+  }
+])
+
+const searchSchema = reactive<FormSchema[]>([
+  {
+    field: 'type',
+    label: '类型',
+    component: 'Select',
+    value: '1',
+    componentProps: {
+      placeholder: '设计案例',
+      options: [
+        {
+          label: '设计案例',
+          value: '1'
+        },
+        {
+          label: '安装案例',
+          value: '2'
+        }
+      ]
+    }
+  }
+])
+
+const searchParams = ref<{
+  type?: string
+}>({
+  type: '1'
+})
+const setSearchParams = (data: any) => {
+  searchParams.value = data
+  getList()
+}
+
+const dialogVisible = ref(false)
+const currentRow = ref()
+const dialogTitle = ref('')
+const actionType = ref('')
+const saveLoading = ref(false)
+const writeRef = useTemplateRef('writeRef')
+
+// function arrayToObject(array) {
+//     var obj = {};
+//     for (var i = 0; i < array.length; i++) {
+//         obj[i] = array[i];
+//     }
+//     return obj;
+// }
+const action = async (type: string, row?: any) => {
+  actionType.value = type
+  if (type == 'add') {
+    dialogTitle.value = t('exampleDemo.add')
+    currentRow.value = undefined
+  }
+  if (type == 'edit') {
+    dialogTitle.value = t('exampleDemo.edit')
+    currentRow.value = {
+      id: row.id,
+      status: 1,
+      reason: ''
+    }
+    console.log(currentRow, 'currentRow')
+  }
+  dialogVisible.value = true
+}
+// const delAction = (row: any) => {
+//   ElMessageBox.confirm('删除后无法恢复,是否删除?', {
+//     confirmButtonText: '删除',
+//     cancelButtonText: '取消',
+//     type: 'warning'
+//   })
+//     .then(async () => {
+//       const re = await delStore(row.id)
+//       if (re) {
+//         ElMessage({
+//           showClose: true,
+//           message: '删除成功',
+//           type: 'success'
+//         })
+//       }
+//       await getList()
+//     })
+//     .catch(() => {})
+// }
+const save = async () => {
+  const write = unref(writeRef)
+  const formData = await write?.submit()
+  if (formData) {
+    console.log(formData, 'formData')
+    const data: any = {
+      id: formData.id || '',
+      status: formData.status,
+      reason: formData.reason
+    }
+    try {
+      if (actionType.value === 'edit') {
+        await applyPlan(data)
+      }
+      ElMessage({
+        showClose: true,
+        message: '审核成功',
+        type: 'success'
+      })
+      getList()
+      saveLoading.value = false
+      dialogVisible.value = false
+    } catch (error) {
+      console.log(error)
+    } finally {
+      saveLoading.value = false
+    }
+  }
+}
+</script>
+
+<template>
+  <ContentWrap>
+    <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
+    <div class="mb-10px">
+      <BaseButton type="primary" @click="action('add')">{{ t('exampleDemo.add') }}</BaseButton>
+    </div>
+    <Table
+      v-model:current-page="currentPage"
+      v-model:page-size="pageSize"
+      :columns="tableColumns"
+      default-expand-all
+      node-key="id"
+      stripe
+      :data="dataList"
+      :loading="loading"
+      @register="tableRegister"
+      :pagination="{
+        total
+      }"
+    />
+  </ContentWrap>
+
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="500px" maxHeight="120px">
+    <Write ref="writeRef" :current-row="currentRow" />
+    <template #footer>
+      <BaseButton type="primary" :loading="saveLoading" @click="save">
+        {{ t('common.ok') }}
+      </BaseButton>
+      <BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
+    </template>
+  </Dialog>
+</template>

+ 360 - 0
src/views/Plan/list/index.vue

@@ -0,0 +1,360 @@
+<script setup lang="tsx">
+import { reactive, ref, unref } from 'vue'
+import { getPlanList } from '@/api/plan'
+import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn } from '@/components/Table'
+import { Search } from '@/components/Search'
+import { FormSchema } from '@/components/Form'
+import { ContentWrap } from '@/components/ContentWrap'
+import { Dialog } from '@/components/Dialog'
+import { BaseButton } from '@/components/Button'
+import { ElTag } from 'element-plus'
+import { TableImage } from '@/components/tableImage'
+// import Write from './components/Write.vue'
+
+const { t } = useI18n()
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const res = await getPlanList({
+      page: unref(currentPage) || 1,
+      limit: unref(pageSize) || 10,
+      ...unref(searchParams)
+    })
+    return {
+      list: res.data.list,
+      total: res.data.count || 0
+    }
+  }
+})
+
+const { dataList, loading, total, currentPage, pageSize } = tableState
+const { getList } = tableMethods
+
+const tableColumns = reactive<TableColumn[]>([
+  {
+    field: 'id',
+    label: 'ID',
+    align: 'center',
+    width: 70
+  },
+  {
+    field: 'image',
+    label: '封面图',
+    width: 80,
+    slots: {
+      default: ({ row }: any) => {
+        return (
+          <>
+            <TableImage src={row.image} />
+          </>
+        )
+      }
+    }
+  },
+  {
+    field: 'slider_images',
+    label: '轮播图',
+    width: 240,
+    slots: {
+      default: ({ row }: any) => {
+        return (
+          <>
+            {row.slider_images_chs.map((item: any) => {
+              return (
+                <>
+                  <TableImage src={item} />
+                </>
+              )
+            })}
+          </>
+        )
+      }
+    }
+  },
+  {
+    field: 'content_images_chs',
+    label: '效果图',
+    width: 240,
+    slots: {
+      default: ({ row }: any) => {
+        return (
+          <>
+            {row.content_images_chs.map((item: any) => {
+              return (
+                <>
+                  <TableImage src={item} />
+                </>
+              )
+            })}
+          </>
+        )
+      }
+    }
+  },
+  {
+    field: 'title',
+    label: '标题',
+    minWidth: 100
+  },
+  {
+    field: 'address',
+    label: '地区',
+    minWidth: 150
+  },
+  {
+    field: 'room_type',
+    label: '户型',
+    minWidth: 80
+  },
+  {
+    field: 'room_area',
+    label: '面积',
+    minWidth: 80
+  },
+  {
+    field: 'style',
+    label: '风格',
+    width: 80
+  },
+  {
+    field: 'info',
+    label: '简介',
+    width: 120
+  },
+  {
+    field: 'status',
+    label: '是否展示',
+    width: 150,
+    slots: {
+      default: ({ row }: any) => {
+        return (
+          <>
+            <ElTag
+              class="mr-5px"
+              type={row.status == 0 ? 'primary' : row.status == 1 ? 'success' : 'danger'}
+            >
+              {row.status_chs}
+            </ElTag>
+          </>
+        )
+      }
+    }
+  },
+  {
+    field: 'reason',
+    label: '审核意见',
+    width: 120
+  }
+  // {
+  //   field: 'action',
+  //   label: t('userDemo.action'),
+  //   width: 200,
+  //   fixed: 'right',
+  //   align: 'center',
+  //   headerAlign: 'center',
+  //   slots: {
+  //     default: (data: any) => {
+  //       const row = data.row
+  //       return (
+  //         <>
+  //           <BaseButton link size="small" type="primary" onClick={() => action('edit', row)}>
+  //             编辑
+  //           </BaseButton>
+  //           <ElDivider direction="vertical" />
+  //           <BaseButton link size="small" type="danger" onClick={() => delAction(row)}>
+  //             删除
+  //           </BaseButton>
+  //         </>
+  //       )
+  //     }
+  //   }
+  // }
+])
+
+const searchSchema = reactive<FormSchema[]>([
+  {
+    field: 'type',
+    label: '类型',
+    component: 'Select',
+    value: '1',
+    componentProps: {
+      placeholder: '设计案例',
+      options: [
+        {
+          label: '设计案例',
+          value: '1'
+        },
+        {
+          label: '安装案例',
+          value: '2'
+        }
+      ]
+    }
+  }
+])
+
+const searchParams = ref<{
+  type?: string
+}>({
+  type: '1'
+})
+const setSearchParams = (data: any) => {
+  searchParams.value = data
+  getList()
+}
+
+const dialogVisible = ref(false)
+// const currentRow = ref()
+const dialogTitle = ref('')
+// const actionType = ref('')
+const saveLoading = ref(false)
+// const writeRef = useTemplateRef('writeRef')
+
+// function arrayToObject(array) {
+//     var obj = {};
+//     for (var i = 0; i < array.length; i++) {
+//         obj[i] = array[i];
+//     }
+//     return obj;
+// }
+// const action = async (type: string, row?: any) => {
+//   actionType.value = type
+//   if (type == 'add') {
+//     dialogTitle.value = t('exampleDemo.add')
+//     currentRow.value = undefined
+//   }
+//   if (type == 'edit') {
+//     dialogTitle.value = t('exampleDemo.edit')
+//     currentRow.value = {
+//       uid: row.uid,
+//       id: row.id,
+//       name: row.name,
+//       is_show: row.is_show == 1 ? true : false,
+//       introduction: row.introduction,
+//       address: row.address,
+//       area: row.area,
+//       city: row.city,
+//       province: row.province,
+//       detailed_address: row.detailed_address,
+//       latitude: row.latitude,
+//       logo: [row.logo],
+//       longitude: row.longitude,
+//       manager_name: row.manager_name,
+//       phone: row.phone,
+//       product_verify_status: row.product_verify_status == 1 ? true : false,
+//       product_status: row.product_status == 1 ? true : false,
+//       valid_range: row.valid_range,
+//       slide_images: row.slide_images_chs
+//     }
+//     console.log(currentRow, 'currentRow')
+//   }
+//   dialogVisible.value = true
+// }
+// const delAction = (row: any) => {
+//   ElMessageBox.confirm('删除后无法恢复,是否删除?', {
+//     confirmButtonText: '删除',
+//     cancelButtonText: '取消',
+//     type: 'warning'
+//   })
+//     .then(async () => {
+//       const re = await delStore(row.id)
+//       if (re) {
+//         ElMessage({
+//           showClose: true,
+//           message: '删除成功',
+//           type: 'success'
+//         })
+//       }
+//       await getList()
+//     })
+//     .catch(() => {})
+// }
+const save = async () => {
+  // // const write = unref(writeRef)
+  // // const formData = await write?.submit()
+  // if (formData) {
+  //   let arr: string[] = []
+  //   for (let key in formData.slide_images) {
+  //     arr.push(formData.slide_images[key] || '')
+  //   }
+  //   // const data: any = {
+  //   //   uid: formData.uid,
+  //   //   id: formData.id || '',
+  //   //   name: formData.name,
+  //   //   type: formData.type,
+  //   //   logo: formData.logo[0],
+  //   //   introduction: formData.introduction,
+  //   //   slide_images: arr,
+  //   //   manager_name: formData.manager_name,
+  //   //   phone: formData.phone,
+  //   //   valid_range: formData.valid_range,
+  //   //   product_status: formData.product_status ? 1 : 0,
+  //   //   product_verify_status: formData.product_verify_status ? 1 : 0,
+  //   //   detailed_address: formData.detailed_address,
+  //   //   address: formData.address,
+  //   //   area: formData.area,
+  //   //   city: formData.city,
+  //   //   province: formData.province,
+  //   //   latitude: formData.latitude,
+  //   //   longitude: formData.longitude,
+  //   //   is_show: formData.is_show ? 1 : 0,
+  //   //   store_account: formData.store_account,
+  //   //   store_password: formData.store_password
+  //   // }
+  //   try {
+  //     if (actionType.value === 'edit') {
+  //       // await editStore(data)
+  //     } else if (actionType.value === 'add') {
+  //       // await addStore(data)
+  //     }
+  //     ElMessage({
+  //       showClose: true,
+  //       message: '保存成功',
+  //       type: 'success'
+  //     })
+  //     getList()
+  //     saveLoading.value = false
+  //     dialogVisible.value = false
+  //   } catch (error) {
+  //     console.log(error)
+  //   } finally {
+  //     saveLoading.value = false
+  //   }
+  // }
+}
+</script>
+
+<template>
+  <ContentWrap>
+    <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
+    <!--    <div class="mb-10px">
+      <BaseButton type="primary" @click="action('add')">{{ t('exampleDemo.add') }}</BaseButton>
+    </div> -->
+    <Table
+      v-model:current-page="currentPage"
+      v-model:page-size="pageSize"
+      :columns="tableColumns"
+      default-expand-all
+      node-key="id"
+      stripe
+      :data="dataList"
+      :loading="loading"
+      @register="tableRegister"
+      :pagination="{
+        total
+      }"
+    />
+  </ContentWrap>
+
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="500px" maxHeight="700px">
+    <!-- <Write ref="writeRef" :current-row="currentRow" /> -->
+    <template #footer>
+      <BaseButton type="primary" :loading="saveLoading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </BaseButton>
+      <BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
+    </template>
+  </Dialog>
+</template>