Kaynağa Gözat

feat(agent): 添加设计师销售信息功能

- 新增销售信息页面和相关组件
- 实现销售数据展示和订单列表功能
-优化设计师档案页面布局
- 添加销售信息相关API接口
EvilDragon 3 ay önce
ebeveyn
işleme
ebb5be0c0e

+ 76 - 0
packages/app/src/components/list-helper-evo.vue

@@ -0,0 +1,76 @@
+<script setup lang="ts" generic="T extends AnyObject">
+import { UnwrapRef } from 'vue'
+
+const props = withDefaults(
+  defineProps<{
+    request: (query: Partial<T>) => Promise<IResData<T[]>>
+    query?: Partial<T>
+    automatic?: boolean
+    mockList?: Partial<T>[]
+  }>(),
+  {
+    automatic: true,
+    query: () => ({}),
+  },
+)
+const slot = defineSlots<{
+  default(props: { item: UnwrapRef<T>; index: number; isLast: boolean }): any
+}>()
+const { data, run: setData } = useRequest(() => props.request({ ...props.query }), {
+  immediate: false,
+})
+onMounted(async () => {
+  if (props.mockList) {
+    data.value = props.mockList as T[]
+    return
+  }
+  if (props.automatic) {
+    await setData()
+  }
+})
+watch(
+  () => props.query,
+  async () => {
+    if (props.mockList) {
+      data.value = props.mockList as T[]
+      return
+    }
+    await setData()
+  },
+)
+defineExpose({
+  reload: async () => {
+    await setData()
+  },
+})
+</script>
+
+<template>
+  <div class="flex-grow flex flex-col relative">
+    <template v-for="(it, index) in data" :key="index">
+      <slot :item="it as UnwrapRef<T>" :index="index" :isLast="index == data.length - 1"></slot>
+    </template>
+    <div
+      class="construction-dashed absolute top-0 right-0 left-0 bottom-0 bg-red/20 flex items-center justify-center pointer-events-none"
+    >
+      <div class="text-16 text-black/30">Debug</div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+@layer utilities {
+  .construction-dashed {
+    @apply relative border-4 border-black/50 border-dashed; /* 基础虚线样式 */
+    animation: dashed-move 2s linear infinite; /* 虚线移动动画 */
+  }
+}
+@keyframes dashed-move {
+  from {
+    border-spacing: 0;
+  }
+  to {
+    border-spacing: 10px;
+  }
+}
+</style>

+ 13 - 0
packages/app/src/core/libs/models.ts

@@ -807,6 +807,19 @@ export interface DesignerFamilyInfo {
   familyOccupation: string
   createTime: string
 }
+
+/**
+ * 设计师奖项
+ */
+export interface DesignerAward {
+  id: number
+  userId: number
+  awardsName: string
+  awardsTime: string
+  awardsRank: string
+  awardsFileUrl: string
+  createTime: string
+}
 export interface DesignerEvent {
   name: string
   applyTime: string

+ 86 - 0
packages/merchant/src/components/list-helper-evo.vue

@@ -0,0 +1,86 @@
+<script setup lang="ts" generic="T extends AnyObject">
+import { UnwrapRef } from 'vue'
+
+const props = withDefaults(
+  defineProps<{
+    request?: (query: Partial<T>) => Promise<IResData<T[]>>
+    items?: T[]
+    query?: Partial<T>
+    automatic?: boolean
+    mockList?: Partial<T>[]
+  }>(),
+  {
+    automatic: true,
+    query: () => ({}),
+  },
+)
+const slot = defineSlots<{
+  default(props: { item: UnwrapRef<T>; index: number; isLast: boolean }): any
+}>()
+const request = computed(() => {
+  if (props.request) {
+    return props.request
+  } else {
+    return async () => {
+      return { code: 0, msg: '', data: props.items }
+    }
+  }
+})
+const { data, run: setData } = useRequest(() => request.value({ ...props.query }), {
+  immediate: false,
+})
+onMounted(async () => {
+  if (props.mockList) {
+    data.value = props.mockList as T[]
+    return
+  }
+  if (props.automatic) {
+    await setData()
+  }
+})
+watch(
+  () => props.query,
+  async () => {
+    if (props.mockList) {
+      data.value = props.mockList as T[]
+      return
+    }
+    await setData()
+  },
+)
+defineExpose({
+  reload: async () => {
+    await setData()
+  },
+})
+</script>
+
+<template>
+  <div class="flex-grow flex flex-col relative">
+    <template v-for="(it, index) in data" :key="index">
+      <slot :item="it as UnwrapRef<T>" :index="index" :isLast="index == data.length - 1"></slot>
+    </template>
+    <div
+      class="construction-dashed absolute top-0 right-0 left-0 bottom-0 bg-red/20 flex items-center justify-center pointer-events-none"
+    >
+      <div class="text-16 text-black/30">Debug</div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+@layer utilities {
+  .construction-dashed {
+    @apply relative border-4 border-black/50 border-dashed; /* 基础虚线样式 */
+    animation: dashed-move 2s linear infinite; /* 虚线移动动画 */
+  }
+}
+@keyframes dashed-move {
+  from {
+    border-spacing: 0;
+  }
+  to {
+    border-spacing: 10px;
+  }
+}
+</style>

+ 31 - 0
packages/merchant/src/core/libs/agent-requests.ts

@@ -9,6 +9,7 @@ import {
   DesignerEvent,
   DesignerFamilyInfo,
   AgentPoint,
+  DesignerAward,
 } from '@designer-hub/app/src/core/libs/models'
 /**
  * 通过ID获取用户信息
@@ -164,3 +165,33 @@ export const getPointsDetails = (query: { year?: string } = {}) =>
       createTime: string
     }>
   >(`/app-api/member/points-details/pageAllByYear/${query.year}`, query)
+/**
+ * 设计师奖品列表
+ */
+export const getAwards = (query = {}) =>
+  httpGet<DesignerAward[]>('/app-api/member/stylist-awards/list', query)
+/**
+ * 创建设计师奖品
+ */
+export const createAward = (data: Partial<DesignerAward>) =>
+  httpPost('/app-api/member/stylist-awards/create', data)
+/**
+ * 删除设计师奖品
+ */
+export const deleteAward = (id: number) =>
+  httpDelete('/app-api/member/stylist-awards/delete', { id })
+/**
+ * 获取设计师销售订单
+ */
+export const getSalesOrders = (query = {}) =>
+  httpGet<
+    ResPageData<{
+      id: number
+      materials: string
+      materialsBrand: string
+      projectName: string
+      customerName: string
+      customerPhone: string
+      orderMoney: number
+    }>
+  >('/app-api/member/stylist-other-sales/pageByDate', query)

+ 12 - 0
packages/merchant/src/core/libs/messages.ts

@@ -57,5 +57,17 @@ export const messages = {
       familyOccupation: '职业',
       createTime: '创建时间',
     },
+    designerAward: {
+      id: 'ID',
+      userId: '用户ID',
+      awardsName: '奖项名称',
+      awardsNamePlaceHolder: '请输入奖项名称',
+      awardsTime: '获奖时间',
+      awardsTimePlaceHolder: '请选择获奖时间',
+      awardsRank: '奖项等级',
+      awardsRankPlaceHolder: '请输入奖项等级',
+      awardsFileUrl: '附件',
+      createTime: '创建时间',
+    },
   },
 }

+ 8 - 0
packages/merchant/src/pages.json

@@ -214,6 +214,14 @@
       }
     },
     {
+      "path": "pages/agent/designer/archives/sale-info/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "销售明细",
+        "navigationBarBackgroundColor": "#fff"
+      }
+    },
+    {
       "path": "pages/mine/merchant/orders/detail/index",
       "type": "page",
       "style": {

+ 184 - 7
packages/merchant/src/pages/agent/designer/archives/index.vue

@@ -8,6 +8,9 @@
 </route>
 <script setup lang="ts">
 import {
+  createAward,
+  deleteAward,
+  getAwards,
   getDesignerBasicInfo,
   getDesignerExtraEvents,
   getDesignerFamilyInfo,
@@ -20,12 +23,15 @@ import { renders } from '../../../../core/libs/renders'
 import DataRender from '@/components/data-render.vue'
 import PageHelperEvo from '@/components/page-helper-evo.vue'
 import { DataFormSchema } from '../../../../components/data-form'
-import { DesignerFamilyInfo } from '@designer-hub/app/src/core/libs/models'
+import { DesignerAward, DesignerFamilyInfo } from '@designer-hub/app/src/core/libs/models'
 import DataForm from '@/components/data-form.vue'
 import { requestToast } from '@designer-hub/app/src/core/utils/common'
 import { ComponentExposed } from 'vue-component-type-helpers'
+import ListHelperEvo from '@/components/list-helper-evo.vue'
+import SectionHeading from '@designer-hub/app/src/components/section-heading.vue'
+import dayjs from 'dayjs'
 
-const tab = ref('basic')
+const tab = ref('sale')
 // 基础信息 家庭信息 奖项信息 销售信息 游学/活动信息
 const tabs = [
   { label: '基础信息', value: 'basic' },
@@ -42,8 +48,9 @@ const eventsQuery = computed(() => ({ type: '3' }))
 const actionSheetStatus = ref(false)
 const schema = ref<DataFormSchema>()
 const formData = ref({})
-const submitType = ref<'family'>()
+const submitType = ref<'family' | 'award'>()
 const familyPageRef = ref<ComponentExposed<typeof PageHelperEvo>>()
+const awardsListRef = ref<ComponentExposed<typeof ListHelperEvo>>()
 // const {} = useRequest()
 const handleEditBasicInfo = async () => {
   await uni.navigateTo({ url: `/pages/designer/archives/basic-info/index?id=${id.value}` })
@@ -85,6 +92,37 @@ const handleAddFamilyInfo = async () => {
   schema.value = familySchema
   actionSheetStatus.value = true
 }
+const handleAddAward = async () => {
+  submitType.value = 'award'
+  schema.value = {
+    awardsName: {
+      type: 'TextField',
+      label: messages.objects.designerAward.awardsName,
+      props: {
+        placeholder: messages.objects.designerAward.awardsNamePlaceHolder,
+      },
+    },
+    awardsRank: {
+      type: 'TextField',
+      label: messages.objects.designerAward.awardsRank,
+      props: {
+        placeholder: messages.objects.designerAward.awardsRankPlaceHolder,
+      },
+    },
+    awardsTime: {
+      type: 'TimePick',
+      label: messages.objects.designerAward.awardsTime,
+      props: {
+        placeholder: messages.objects.designerAward.awardsTimePlaceHolder,
+      },
+    },
+    awardsFileUrl: {
+      type: 'ImageUploader',
+      label: messages.objects.designerAward.awardsFileUrl,
+    },
+  }
+  actionSheetStatus.value = true
+}
 const handleSubmit = async () => {
   switch (submitType.value) {
     case 'family': {
@@ -101,23 +139,45 @@ const handleSubmit = async () => {
       }
       break
     }
+    case 'award':
+      {
+        const { code } = await requestToast(
+          () => createAward({ ...formData.value, userId: Number(id.value) }),
+          {
+            success: true,
+            successTitle: '保存成功',
+          },
+        )
+        if (code === 0) {
+          actionSheetStatus.value = false
+          awardsListRef.value?.reload()
+        }
+      }
+      break
     default:
       break
   }
 }
-onLoad(async (query: { id: string }) => {
-  id.value = query.id
+const handleDeleteAward = async (item: DesignerAward) => {
+  await requestToast(() => deleteAward(item.id), {
+    success: true,
+    successTitle: '删除成功',
+  })
+  awardsListRef.value?.reload()
+}
+onLoad(async (query?: Record<string | 'id', any>) => {
+  id.value = query?.id
   await setBasicData()
 })
 </script>
 <template>
-  <div class="flex-grow bg-white">
+  <div class="flex-grow flex flex-col">
     <wd-tabs v-model="tab" :swipeable="true" :slidable-num="4">
       <template v-for="(it, index) in tabs" :key="index">
         <wd-tab :title="it.label" :name="it.value"></wd-tab>
       </template>
     </wd-tabs>
-    <div class="flex flex-col px-5 gap-5">
+    <div class="flex flex-col flex-grow gap-5">
       <template v-if="tab === 'basic'">
         <template
           v-for="([key, value], index) in Object.entries(omit(basicData, ['id', 'userId']))"
@@ -146,6 +206,123 @@ onLoad(async (query: { id: string }) => {
           </template>
         </PageHelperEvo>
       </template>
+      <template v-if="tab === 'award'">
+        <div class="bg-white p-4 mt-4 flex-grow flex flex-col">
+          <div class="flex items-center justify-between">
+            <div>奖项信息</div>
+            <div>
+              <wd-button type="text" icon="add-circle1" @click="handleAddAward">
+                添加获奖信息
+              </wd-button>
+            </div>
+          </div>
+          <ListHelperEvo
+            ref="awardsListRef"
+            :request="getAwards"
+            :query="{ userId: id }"
+            :mock-list="[
+              { awardsName: '123', awardsRank: '123' },
+              {
+                awardsName: '筑巢奖',
+              },
+            ]"
+          >
+            <template #default="{ item, isLast }">
+              <div class="flex flex-col gap-4 py-4">
+                <SectionHeading
+                  title="奖项名称"
+                  size="base"
+                  :end-text="item.awardsName"
+                ></SectionHeading>
+                <SectionHeading
+                  title="奖项日期"
+                  :end-text="dayjs(item.awardsTime).format('YYYY-MM-DD')"
+                ></SectionHeading>
+                <SectionHeading title="奖项名次" :end-text="item.awardsRank"></SectionHeading>
+                <SectionHeading title="奖项照片" end-arrow></SectionHeading>
+                <wd-button type="text" @click="handleDeleteAward(item)">删除</wd-button>
+                <div v-if="!isLast" class="w-full h-1 bg-[#dadada]"></div>
+              </div>
+            </template>
+          </ListHelperEvo>
+          <!--          <template v-for="(it, index) in source?.list" :key="index">-->
+          <!--            <div>-->
+          <!--            </div>-->
+          <!--          </template>-->
+          <!--          <PageHelperEvo-->
+          <!--            class="flex-grow flex flex-col"-->
+          <!--            :request="async () => ({data: { list: [{}], total: 0 }, msg: 0, code: 0})"-->
+          <!--            :query="eventsQuery"-->
+          <!--            custom-class="flex-grow flex flex-col"-->
+          <!--          >-->
+          <!--            <template #default="{ source }">-->
+          <!--              -->
+          <!--            </template>-->
+          <!--          </PageHelperEvo>-->
+        </div>
+      </template>
+
+      <template v-if="tab === 'sale'">
+        <div class="bg-white p-4 mt-4 flex-grow flex flex-col">
+          <div class="flex items-center justify-between">
+            <div>销售信息</div>
+            <!--            <div>-->
+            <!--              <wd-button type="text" icon="add-circle1" @click="handleAddAward">-->
+            <!--                添加获奖信息-->
+            <!--              </wd-button>-->
+            <!--            </div>-->
+          </div>
+          <ListHelperEvo
+            ref="saleListRef"
+            :items="[
+              { label: '', value: 0, userId: '' },
+              { label: '', value: '' },
+            ]"
+            :query="{ userId: id }"
+            :mock-list="[
+              { label: '今年成交订单数', value: 6 },
+              { label: '今年成交金额', value: 100000 },
+              { label: '累计成交订单数', value: 6 },
+              { label: '累计成交金额', value: 100000 },
+              { label: '其他销售信息', value: '2条' },
+            ]"
+          >
+            <template #default="{ item, isLast }">
+              <div class="flex flex-col gap-4 py-4">
+                <div v-if="isLast" class="w-full h-1 bg-[#dadada]"></div>
+                <SectionHeading
+                  :title="item.label"
+                  size="base"
+                  :end-text="String(item.value)"
+                  end-arrow
+                  path="/pages/agent/designer/archives/sale-info/index"
+                ></SectionHeading>
+                <!--                <SectionHeading-->
+                <!--                  title="奖项日期"-->
+                <!--                  :end-text="dayjs(item.awardsTime).format('YYYY-MM-DD')"-->
+                <!--                ></SectionHeading>-->
+                <!--                <SectionHeading title="奖项名次" :end-text="item.awardsRank"></SectionHeading>-->
+                <!--                <SectionHeading title="奖项照片" end-arrow></SectionHeading>-->
+                <!--                <wd-button type="text" @click="handleDeleteAward(item)">删除</wd-button>-->
+              </div>
+            </template>
+          </ListHelperEvo>
+          <!--          <template v-for="(it, index) in source?.list" :key="index">-->
+          <!--            <div>-->
+          <!--            </div>-->
+          <!--          </template>-->
+          <!--          <PageHelperEvo-->
+          <!--            class="flex-grow flex flex-col"-->
+          <!--            :request="async () => ({data: { list: [{}], total: 0 }, msg: 0, code: 0})"-->
+          <!--            :query="eventsQuery"-->
+          <!--            custom-class="flex-grow flex flex-col"-->
+          <!--          >-->
+          <!--            <template #default="{ source }">-->
+          <!--              -->
+          <!--            </template>-->
+          <!--          </PageHelperEvo>-->
+        </div>
+      </template>
       <template v-if="tab === 'events'">
         <PageHelperEvo :request="getDesignerExtraEvents" :query="eventsQuery">
           <template #default="{ source }">

+ 119 - 0
packages/merchant/src/pages/agent/designer/archives/sale-info/index.vue

@@ -0,0 +1,119 @@
+<route lang="json">
+{ "style": { "navigationBarTitleText": "销售明细", "navigationBarBackgroundColor": "#fff" } }
+</route>
+<script setup lang="ts">
+import SectionHeading from '@/components/section-heading.vue'
+// import {
+//   getDesignerInfo,
+//   getBrowseHistories,
+//   getReserveHistory,
+//   countThisYear,
+// } from '../../../../core/libs/requests'
+import { storeToRefs } from 'pinia'
+import Card from '@/components/card.vue'
+import PageHelper from '@/components/page-helper.vue'
+import dayjs from 'dayjs'
+import PageHelperEvo from '@/components/page-helper-evo.vue'
+import { useUserStore } from '@/store'
+import { getSalesOrders } from '@/core/libs/agent-requests'
+
+const userStore = useUserStore()
+const { userInfo } = storeToRefs(userStore)
+const current = ref('累计')
+const request =
+  ref<
+    () => Promise<IResData<{ shareCount: number; viewCount: number; winCustomerCount: number }>>
+  >()
+const data = ref()
+const info = computed(() => [
+  { label: '订单数', value: data.value?.shareCount || 0, unit: '' },
+  { label: '订单金额', value: data.value?.viewCount || 0, unit: '¥' },
+])
+const tab = ref('分享')
+const tabs = ref([
+  { label: '分享明细', value: '分享' },
+  { label: '浏览明细', value: '浏览' },
+  { label: '获客明细', value: '获客' },
+])
+const query = computed(() => ({}))
+const setData = async ({ value }) => {
+  console.log(1111)
+  console.log(value)
+
+  // request.value = {
+  //   累计: () =>
+  //     getDesignerInfo(userInfo.value.userId).then((res) => ({
+  //       code: res.code,
+  //       msg: res.msg,
+  //       data: {
+  //         shareCount: res.data.shareCount,
+  //         viewCount: res.data.viewCount,
+  //         winCustomerCount: res.data.winCustomerCount,
+  //       },
+  //     })),
+  //   本年: () =>
+  //     countThisYear({ userId: userInfo.value.userId, year: new Date().getFullYear() }).then(
+  //       (res) => ({
+  //         code: res.code,
+  //         msg: res.msg,
+  //         data: {
+  //           shareCount: res.data.find((it) => it.bizType === '1')?.quantity || 0,
+  //           viewCount: res.data.find((it) => it.bizType === '2')?.quantity || 0,
+  //           winCustomerCount: res.data.find((it) => it.bizType === '3')?.quantity || 0,
+  //         },
+  //       }),
+  //     ),
+  // }[value]
+  // const { data: resData } = await request.value()
+  // data.value = resData
+  // console.log(data.value)
+}
+onMounted(async () => {
+  console.log(1111)
+
+  await setData({ value: current.value })
+  console.log(data.value)
+  // await countThisYear({ userId: userInfo.value.userId, year: new Date().getFullYear() })
+})
+</script>
+<template>
+  <div class="flex-grow flex flex-col gap-5 px-3.5 py-6">
+    <Card>
+      <SectionHeading title="销售数据">
+        <template #append>
+          <div>
+            <wd-segmented
+              v-model:value="current"
+              :options="['累计', '本年']"
+              @change="setData"
+            ></wd-segmented>
+          </div>
+        </template>
+      </SectionHeading>
+      <div class="flex mt-7">
+        <template v-for="(it, i) in info" :key="i">
+          <div class="flex-1 flex flex-col items-center gap-2">
+            <div class="flex items-end flex-row-reverse gap-0.5">
+              <div class="text-black text-2xl font-medium font-['DIN'] leading-6">
+                {{ it.value }}
+              </div>
+              <div class="text-[#333333] text-sm font-normal font-['PingFang_SC']">
+                {{ it.unit }}
+              </div>
+            </div>
+            <div class="text-black/40 text-xs font-normal font-['PingFang_SC'] leading-normal">
+              {{ it.label }}
+            </div>
+          </div>
+        </template>
+      </div>
+    </Card>
+    <PageHelperEvo :request="getSalesOrders">
+      <template #default="{ source }">
+        <template v-for="(it, index) in source?.list" :key="index">
+          <Card>{{ it }}</Card>
+        </template>
+      </template>
+    </PageHelperEvo>
+  </div>
+</template>

+ 1 - 0
packages/merchant/src/types/uni-pages.d.ts

@@ -24,6 +24,7 @@ interface NavigateToOptions {
        "/pages/mine/agent/invite/index" |
        "/pages/mine/agent/settings/index" |
        "/pages/agent/designer/archives/basic-info/index" |
+       "/pages/agent/designer/archives/sale-info/index" |
        "/pages/mine/merchant/orders/detail/index";
 }
 interface RedirectToOptions extends NavigateToOptions {}