Browse Source

2025-1-14

cmy 10 months ago
parent
commit
b1f951cae5

+ 18 - 0
src/api/goods/index.ts

@@ -0,0 +1,18 @@
+import request from '@/axios'
+import { REQUEST_BASE } from '@/constants'
+import { designerSearch, designerData } from './types'
+export const getProductCategory = (params: {
+  page?: number
+  limit?: number
+}): Promise<IResponse> => {
+  return request.get({ url: `${REQUEST_BASE}/productCategory/index`, params })
+}
+export const addProductCategory = (data: designerData): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/decoration/designer`, data })
+}
+export const delProductCategory = (id: number): Promise<IResponse> => {
+  return request.delete({ url: `${REQUEST_BASE}/decoration/designer/${id}` })
+}
+export const putProductCategory = (data: designerData): Promise<IResponse> => {
+  return request.put({ url: `${REQUEST_BASE}/decoration/designer/${data.id}`, data })
+}

+ 35 - 0
src/api/goods/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
+}

+ 1 - 0
src/api/staff/types.ts

@@ -18,6 +18,7 @@ export interface designerData {
   longitude: string | number //经度
   latitude: string | number //纬度
   detail_address: string
+  start_job_year: string
 }
 export type categoryData = {
   id?: number

+ 31 - 0
src/router/model/Goods.ts

@@ -0,0 +1,31 @@
+import { Layout } from '@/utils/routerHelper'
+const pre = 'goods'
+
+export default {
+  path: `/${pre}`,
+  component: Layout,
+  name: `${pre}`,
+  meta: {
+    title: '商品管理',
+    icon: 'vi-eos-icons:role-binding',
+    alwaysShow: true
+  },
+  children: [
+    {
+      path: 'list',
+      component: () => import('@/views/Goods/list/index.vue'),
+      name: `${pre}-list`,
+      meta: {
+        title: '商品列表'
+      }
+    },
+    {
+      path: 'classify',
+      component: () => import('@/views/Goods/classify/index.vue'),
+      name: `${pre}-classify`,
+      meta: {
+        title: '商品分类'
+      }
+    }
+  ]
+}

+ 0 - 23
src/router/model/Maintenance.ts

@@ -1,23 +0,0 @@
-import { Layout } from '@/utils/routerHelper'
-const pre = 'maintenance'
-export default {
-  path: '/maintenance',
-  component: Layout,
-  name: `${pre}`,
-  redirect: '/maintenance/clean',
-  meta: {
-    title: '系统维护',
-    icon: 'vi-ep:setting',
-    alwaysShow: true
-  },
-  children: [
-    {
-      path: 'clean',
-      component: () => import('@/views/Authorization/User/User.vue'),
-      name: `${pre}-clean`,
-      meta: {
-        title: '数据清理'
-      }
-    }
-  ]
-}

+ 5 - 8
src/views/Authorization/Menu/components/Write.vue

@@ -141,7 +141,7 @@ const formSchema = reactive<FormSchema[]>([
   {
     field: 'meta.icon',
     label: t('menu.icon'),
-    hidden: false,
+    remove: false,
     component: 'IconPicker',
     value: ''
   },
@@ -213,7 +213,7 @@ const formSchema = reactive<FormSchema[]>([
   },
   {
     field: 'meta.affix',
-    label: t('menu.affix'),
+    label: '固定标签页',
     component: 'Switch'
   },
   {
@@ -226,10 +226,7 @@ const formSchema = reactive<FormSchema[]>([
     field: 'id',
     label: 'ID',
     component: 'Input',
-    hidden: true,
-    componentProps: {
-      type: 'number'
-    }
+    hidden: true
   }
 ])
 
@@ -281,7 +278,7 @@ const changeType = (bol: boolean) => {
   setSchema([
     {
       field: 'meta.icon',
-      path: 'hidden',
+      path: 'remove',
       value: bol
     },
     {
@@ -374,6 +371,6 @@ const openApiSeach = (res: any) => {
 </script>
 
 <template>
-  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+  <Form labelWidth="120px" :rules="rules" @register="formRegister" :schema="formSchema" />
   <addApiUrl v-model="showApiDrawer" @confirm="openApiSeach" />
 </template>

+ 2 - 5
src/views/Authorization/User/components/Write.vue

@@ -84,12 +84,9 @@ const formSchema = ref<FormSchema[]>([
   },
   {
     field: 'id',
-    label: '管理员id',
+    label: 'id',
     hidden: true,
-    component: 'Input',
-    componentProps: {
-      placeholder: '请输入管理员账号'
-    }
+    component: 'Input'
   }
 ])
 

+ 113 - 0
src/views/Goods/classify/components/Write.vue

@@ -0,0 +1,113 @@
+<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 { categoryData } from '@/api/staff/types'
+import { UpImgButtom } from '@/components/UpFile'
+const { required } = useValidator()
+
+const props = defineProps({
+  currentRow: {
+    type: Object as PropType<any>,
+    default: () => null
+  },
+  category: {
+    type: Array as PropType<categoryData[]>,
+    default: () => []
+  }
+})
+
+const formSchema = ref<FormSchema[]>([
+  {
+    field: 'pid',
+    label: '父级',
+    component: 'Select',
+    colProps: {
+      span: 24
+    },
+    value: 1,
+    componentProps: {
+      options: props.category
+    }
+  },
+  {
+    field: 'name',
+    label: '分类名称',
+    component: 'Input',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      placeholder: '请输入分类名称'
+    }
+  },
+  {
+    field: 'image',
+    label: '图标',
+    component: 'CheckboxGroup',
+    formItemProps: {
+      slots: {
+        default: (data) => {
+          return <UpImgButtom v-model={data.image} />
+        }
+      }
+    }
+  },
+  {
+    field: 'is_show',
+    colProps: {
+      span: 24
+    },
+    label: '是否显示',
+    component: 'Switch'
+  },
+  {
+    field: 'id',
+    label: 'id',
+    hidden: true,
+    component: 'Input'
+  }
+])
+
+const rules = ref({
+  name: [required('请填写分类名称')],
+  default_price: [required('请填写一口价')]
+})
+
+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>

+ 217 - 0
src/views/Goods/classify/index.vue

@@ -0,0 +1,217 @@
+<script setup lang="tsx">
+import { onMounted, reactive, ref, unref, useTemplateRef } from 'vue'
+import { getStaffJobs, delStaffJobs, addStaffJobs, putStaffJobs } from '@/api/staff'
+import { useTable } from '@/hooks/web/useTable'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Table, TableColumn } from '@/components/Table'
+import { ContentWrap } from '@/components/ContentWrap'
+import { Dialog } from '@/components/Dialog'
+import { BaseButton } from '@/components/Button'
+import { ElDivider, ElMessage, ElMessageBox } from 'element-plus'
+import { jobsData } from '@/api/staff/types'
+import Write from './components/Write.vue'
+import { getStaffCategory } from '@/api/staff'
+import { TableImage } from '@/components/tableImage'
+import { getProductCategory } from '@/api/goods'
+const CategoryList = ref<any[]>([])
+onMounted(() => {
+  getStaffCategory({
+    page: 1,
+    limit: 100
+  }).then((res) => {
+    CategoryList.value = res.data.list.map((res) => {
+      return {
+        label: `${res.type_chs}[${res.name}]`,
+        value: res.id
+      }
+    })
+  })
+})
+const { t } = useI18n()
+
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const res = await getProductCategory({
+      page: unref(currentPage) || 1,
+      limit: unref(pageSize) || 10
+    })
+    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: 'cate_name',
+    label: '分类名称',
+    minWidth: 100
+  },
+  {
+    field: 'pic',
+    label: '分类图标',
+    minWidth: 140,
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return <TableImage src={row.pic} />
+      }
+    }
+  },
+  {
+    field: 'sort',
+    align: 'center',
+    headerAlign: 'center',
+    label: '排序',
+    width: 100
+  },
+  {
+    field: 'is_show',
+    label: '状态',
+    width: 120,
+    slots: {
+      default: (data: any) => {
+        const row = data.row
+        return <>{row.is_show == 1 ? '启用' : '禁用'}</>
+      }
+    }
+  },
+  {
+    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 dialogVisible = ref(false)
+const currentRow = ref()
+const dialogTitle = ref('')
+const actionType = ref('')
+const saveLoading = ref(false)
+const writeRef = useTemplateRef('writeRef')
+
+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,
+      name: row.name,
+      is_show: row.is_show == 1 ? true : false,
+      default_price: row.default_price //一口价
+    }
+  }
+  dialogVisible.value = true
+}
+const delAction = (row: any) => {
+  ElMessageBox.confirm('删除后无法恢复,是否删除?', {
+    confirmButtonText: '删除',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const re = await delStaffJobs(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) {
+    saveLoading.value = true
+    const data: jobsData = {
+      id: formData.id || '',
+      name: formData.name, //分类名称
+      cate_id: formData.cate_id, //类型
+      is_show: formData.is_show ? 1 : 0, //是否启用
+      default_price: formData.default_price //一口价
+    }
+    if (actionType.value === 'edit') {
+      await putStaffJobs(data)
+    } else if (actionType.value === 'add') {
+      await addStaffJobs(data)
+    }
+    ElMessage({
+      showClose: true,
+      message: '添加成功',
+      type: 'success'
+    })
+    getList()
+    saveLoading.value = false
+    dialogVisible.value = false
+  }
+}
+</script>
+
+<template>
+  <ContentWrap>
+    <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="cate_name"
+      stripe
+      :data="dataList"
+      :loading="loading"
+      @register="tableRegister"
+      :pagination="{
+        total
+      }"
+    />
+  </ContentWrap>
+
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="500px" maxHeight="150px">
+    <Write ref="writeRef" :category="CategoryList" :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>

+ 83 - 0
src/views/Goods/list/components/BatchSet.vue

@@ -0,0 +1,83 @@
+<script setup lang="tsx">
+import { PropType, ref } from 'vue'
+import {
+  ElTabs,
+  ElTabPane,
+  ElSelect,
+  ElOption,
+  ElDivider,
+  ElRadioGroup,
+  ElRadio,
+  ElMessage
+} from 'element-plus'
+import { Dialog } from '@/components/Dialog'
+import { setGroup } from '@/api/user'
+
+const modelValue = defineModel({ type: Boolean, default: false })
+const props = defineProps({
+  selectList: {
+    type: Array as PropType<any[]>,
+    default: () => []
+  },
+  where: {
+    type: Object,
+    default: () => {
+      return {}
+    }
+  },
+  groupList: {
+    type: Array as PropType<any[]>,
+    default: () => undefined
+  }
+})
+const setType = ref('action')
+const saveLoading = ref(false)
+const group_id = ref()
+const save = async () => {
+  saveLoading.value = true
+  const re = await setGroup({
+    group_id: group_id.value,
+    uids: props.selectList
+      .map((e) => {
+        return e.uid
+      })
+      .join(','),
+    all: setType.value == 'all' ? 1 : 0,
+    where: props.where
+  })
+  saveLoading.value = false
+  if (re && re.status == 200) {
+    modelValue.value = false
+    ElMessage.success('设置成功')
+    emit('confirm')
+  }
+}
+const emit = defineEmits(['confirm'])
+</script>
+
+<template>
+  <Dialog v-model="modelValue" max-height="200px" title="批量设置" width="700px">
+    <el-tabs tab-position="left" class="h-[200px]">
+      <el-tab-pane label="设置用户分组">
+        <ElDivider content-position="left">用户分组</ElDivider>
+        <ElSelect v-model="group_id" placeholder="请选择分组" style="width: 100%">
+          <ElOption
+            v-for="item in groupList"
+            :key="item.id"
+            :label="item.label"
+            :value="item.value"
+          />
+        </ElSelect>
+        <ElDivider content-position="left">设置方式</ElDivider>
+        <ElRadioGroup v-model="setType" style="width: 100%">
+          <ElRadio value="action">当前页面</ElRadio>
+          <ElRadio value="all">全部页面</ElRadio>
+        </ElRadioGroup>
+      </el-tab-pane>
+    </el-tabs>
+    <template #footer>
+      <BaseButton type="primary" :loading="saveLoading" @click="save"> 保存 </BaseButton>
+      <BaseButton @click="modelValue = false">关闭</BaseButton>
+    </template>
+  </Dialog>
+</template>

+ 237 - 0
src/views/Goods/list/components/Detail.vue

@@ -0,0 +1,237 @@
+<script setup lang="tsx">
+import { ref, watch, PropType, useTemplateRef } from 'vue'
+import {
+  ElDrawer,
+  ElCol,
+  ElRow,
+  ElAvatar,
+  ElMessage,
+  ElButton,
+  ElOption,
+  ElSelect,
+  ElDialog,
+  ElRadioGroup,
+  ElRadio,
+  ElInput,
+  ElFormItem,
+  ElForm
+} from 'element-plus'
+import {
+  getUserDetail,
+  putUserData,
+  // getBalanceLog,
+  // getSpreadList,
+  // getBrokerageLog,
+  // getBillLog,
+  putGiveLevel,
+  updateAccount
+} from '@/api/user'
+import { userData } from '@/api/user/types'
+import DetailFrom from './DetailFrom.vue'
+
+const modelValue = defineModel({ type: Boolean, default: false })
+const props = defineProps({
+  uid: {
+    type: Number,
+    default: () => 0
+  },
+  levelList: {
+    type: Array as PropType<any[]>,
+    default: () => []
+  },
+  groupList: {
+    type: Array as PropType<any[]>,
+    default: () => []
+  }
+})
+const loading = ref(true)
+const userDetail = ref<userData>()
+watch(
+  () => modelValue,
+  async ({ value }) => {
+    if (props.uid == 0 || !value) return
+    const { data } = await getUserDetail(props.uid)
+    userDetail.value = data
+    loading.value = false
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+const detailFromRef = useTemplateRef('detailFromRef')
+const saveLoading = ref(false)
+const showDetail = ref(false)
+const saveDetail = async () => {
+  const formData = detailFromRef?.value?.getFormData()
+  if (formData) {
+    saveLoading.value = true
+    const res = await putUserData(formData)
+    if (res) {
+      ElMessage({
+        showClose: true,
+        message: '保存成功',
+        type: 'success'
+      })
+      emit('confirm')
+      const { data } = await getUserDetail(props.uid)
+      userDetail.value = data
+      showDetail.value = false
+    }
+    saveLoading.value = false
+  }
+}
+const actionLevel = ref()
+const dialogVisible = ref(false)
+const putUserLevel = async () => {
+  const res = await putGiveLevel({ id: props.uid, level_id: actionLevel.value })
+  if (res) {
+    ElMessage({
+      showClose: true,
+      message: '赠送成功',
+      type: 'success'
+    })
+    emit('confirm')
+    modelValue.value = false
+    dialogVisible.value = false
+  }
+}
+const emit = defineEmits(['confirm'])
+const upTypeTitle = ref('')
+const upType = ref('')
+const showDialogType = ref(false)
+const something = ref(1)
+const upTypeValue = ref<number>()
+const typeMark = ref<string>()
+const openTypeDialog = (type, title) => {
+  upType.value = type
+  showDialogType.value = true
+  upTypeTitle.value = title
+}
+const typeSaveLoding = ref(false)
+const updateAccountData = async () => {
+  typeSaveLoding.value = true
+  const res = await updateAccount(
+    {
+      money_type: upType.value,
+      pm: something.value,
+      num: upTypeValue.value || 0,
+      mark: typeMark.value || ''
+    },
+    props.uid
+  )
+  if (res) {
+    ElMessage({
+      showClose: true,
+      message: something.value == 1 ? '增加成功' : '减少成功',
+      type: 'success'
+    })
+    emit('confirm')
+    showDialogType.value = false
+    modelValue.value = false
+  }
+  typeSaveLoding.value = false
+}
+</script>
+
+<template>
+  <div>
+    <ElDrawer :show-close="false" v-model="modelValue" size="1000px">
+      <template #header>
+        <ElRow :gutter="20" v-loading="loading">
+          <ElCol :span="12">
+            <div class="flex juistify-start items-center">
+              <el-avatar :size="50" :src="userDetail?.avatar">
+                <img src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
+              /></el-avatar>
+              <div class="ml-[10px]">{{ userDetail?.nickname }}</div>
+            </div>
+          </ElCol>
+          <ElCol :span="12">
+            <div class="flex justify-end items-center h-full">
+              <BaseButton v-if="!showDetail" type="primary" @click="showDetail = true">
+                编辑
+              </BaseButton>
+              <BaseButton v-else @click="showDetail = false">取消</BaseButton>
+              <BaseButton
+                v-if="showDetail"
+                type="primary"
+                @click="saveDetail"
+                :loading="saveLoading"
+              >
+                完成
+              </BaseButton>
+              <BaseButton type="success" @click="openTypeDialog('now_money', '余额修改')"
+                >余额</BaseButton
+              >
+              <BaseButton @click="dialogVisible = true">赠送会员</BaseButton>
+            </div>
+          </ElCol>
+        </ElRow>
+      </template>
+      <DetailFrom
+        ref="detailFromRef"
+        v-loading="loading"
+        v-model="showDetail"
+        :userDetail="userDetail"
+        :levelList="levelList"
+        :groupList="groupList"
+      />
+    </ElDrawer>
+    <ElDialog title="赠送等级" v-model="dialogVisible" width="500">
+      <ElSelect v-model="actionLevel">
+        <ElOption
+          v-for="item in levelList"
+          :key="item.value"
+          :label="item.label"
+          :value="item.value"
+        />
+      </ElSelect>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="putUserLevel">确定</el-button>
+        </div>
+      </template>
+    </ElDialog>
+    <ElDialog :title="upTypeTitle" v-model="showDialogType" width="500">
+      <ElForm>
+        <ElFormItem label="类型">
+          <ElRadioGroup v-model="something">
+            <ElRadio :label="0">减少</ElRadio>
+            <ElRadio :label="1">增加</ElRadio>
+          </ElRadioGroup>
+        </ElFormItem>
+        <ElFormItem label="金额"
+          ><ElInput
+            v-model="upTypeValue"
+            type="number"
+            :placeholder="something == 0 ? '请输入要减少的余额' : '请输入要增加的余额'"
+        /></ElFormItem>
+        <ElFormItem label="备注">
+          <ElInput
+            class="mt-10px"
+            :rows="3"
+            v-model="typeMark"
+            type="textarea"
+            placeholder="备注信息"
+          />
+        </ElFormItem>
+      </ElForm>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="showDialogType = false">取消</el-button>
+          <el-button type="primary" :loading="typeSaveLoding" @click="updateAccountData"
+            >确定</el-button
+          >
+        </div>
+      </template>
+    </ElDialog>
+  </div>
+</template>
+<style lang="less" scoped>
+:deep(.el-tabs__content) {
+  padding: 0px;
+}
+</style>

+ 420 - 0
src/views/Goods/list/components/DetailFrom.vue

@@ -0,0 +1,420 @@
+<script setup lang="tsx">
+import { ref, watch, PropType, defineExpose } from 'vue'
+import {
+  FormRules,
+  ElTabs,
+  ElTabPane,
+  ElTable,
+  ElTableColumn,
+  ElPagination,
+  ElFormItem,
+  ElForm,
+  ElInput,
+  ElSelect,
+  ElOption,
+  ElDatePicker,
+  ElDivider,
+  ElSwitch,
+  ElTag,
+  ElDescriptions,
+  ElDescriptionsItem
+} from 'element-plus'
+import {
+  getBalanceLog,
+  getSpreadList,
+  getBrokerageLog
+  // getBillLog
+} from '@/api/user'
+import { userData } from '@/api/user/types'
+import { useValidator } from '@/hooks/web/useValidator'
+import InputPassword from '@/components/InputPassword/src/InputPassword.vue'
+import { formatToDate } from '@/utils/dateUtil'
+const { required, phone } = useValidator()
+const modelValue = defineModel<boolean>()
+const props = defineProps({
+  userDetail: {
+    type: Object as PropType<userData>,
+    default: () => ({})
+  },
+  levelList: {
+    type: Array as PropType<any[]>,
+    default: () => []
+  },
+  groupList: {
+    type: Array as PropType<any[]>,
+    default: () => []
+  }
+})
+const localUserDetail = ref<userData>({ ...props.userDetail })
+
+watch(
+  () => props.userDetail,
+  (newVal) => {
+    localUserDetail.value = { ...newVal }
+    if (typeof newVal.birthday == 'number') {
+      localUserDetail.value.birthday = newVal.birthday ? formatToDate(newVal.birthday * 1000) : ''
+      localUserDetail.value.is_promoter = newVal.is_promoter == 1 ? true : false
+      localUserDetail.value.status = newVal.status == 1 ? true : false
+    }
+  },
+  { deep: true, immediate: true }
+)
+
+const rules = ref<FormRules>({
+  phone: [required('请填写手机号'), phone()],
+  pwd: [{}],
+  true_pwd: [
+    {
+      validator: (_, val, callback) => {
+        if (!val) return callback()
+        if (localUserDetail.value.pwd !== val) {
+          callback(new Error('两次密码不一致'))
+        } else {
+          callback()
+        }
+      }
+    }
+  ],
+  true_trade_pwd: [
+    {
+      validator: (_, val, callback) => {
+        if (!val) return callback()
+        if (localUserDetail.value.trade_pwd && localUserDetail.value.trade_pwd !== val) {
+          callback(new Error('两次密码不一致'))
+        } else {
+          callback()
+        }
+      }
+    }
+  ]
+})
+const userRecordList = ref([
+  {
+    name: '佣金记录',
+    type: '3',
+    page: 1,
+    limit: 10,
+    total: 0,
+    request: getBrokerageLog,
+    data: [],
+    loading: true,
+    columns: [
+      {
+        label: '时间',
+        field: 'add_time'
+      },
+      {
+        label: '变动金额',
+        field: 'number'
+      },
+      {
+        label: '变动后余额',
+        field: 'balance'
+      },
+      {
+        label: '变动类型',
+        field: 'type'
+      },
+      {
+        label: '备注'
+      }
+    ]
+  },
+  {
+    name: '余额记录',
+    type: '2',
+    page: 1,
+    limit: 10,
+    total: 0,
+    loading: true,
+    request: getBalanceLog,
+    data: [],
+    columns: [
+      {
+        label: '时间',
+        field: 'add_time'
+      },
+      {
+        label: '变动金额',
+        field: 'number'
+      },
+      {
+        label: '变动后余额',
+        field: 'balance'
+      },
+      {
+        label: '变动类型',
+        field: 'type'
+      },
+      {
+        label: '备注'
+      }
+    ]
+  },
+  {
+    name: '推荐人列表',
+    type: '1',
+    page: 1,
+    limit: 10,
+    total: 100,
+    loading: true,
+    request: getSpreadList,
+    data: [],
+    columns: [
+      {
+        label: '用户ID',
+        field: 'uid'
+      },
+      {
+        label: '用户昵称',
+        field: 'nickname'
+      },
+      {
+        label: '用户手机号',
+        field: 'phone'
+      },
+      {
+        label: '用户余额',
+        field: 'now_money'
+      }
+    ]
+  }
+])
+const changeTable = (row: any) => {
+  for (let index = 0; index < userRecordList.value.length; index++) {
+    const element = userRecordList.value[index]
+    if (row == element.type) {
+      if (element.loading) {
+        element
+          .request(
+            {
+              page: element.page,
+              limit: element.limit
+            },
+            props.userDetail.uid
+          )
+          .then((res) => {
+            element.data = res.data.list
+            element.total = res.data.total
+            element.loading = false
+          })
+      }
+      break
+    }
+  }
+}
+const changePage = (row: any, item) => {
+  console.log(row, item, 'changePage')
+}
+const getFormData = () => {
+  const formData: userData = {
+    uid: localUserDetail.value.uid,
+    real_name: localUserDetail.value.real_name,
+    phone: localUserDetail.value.phone,
+    pwd: localUserDetail.value.pwd,
+    true_pwd: localUserDetail.value.true_pwd,
+    trade_pwd: localUserDetail.value.trade_pwd,
+    true_trade_pwd: localUserDetail.value.true_trade_pwd,
+    sex: localUserDetail.value.sex,
+    spread_uid: localUserDetail.value.spread_uid,
+    is_promoter: localUserDetail.value.is_promoter ? 1 : 0,
+    card_id: localUserDetail.value.card_id,
+    birthday: formatToDate(localUserDetail.value.birthday),
+    mark: localUserDetail.value.mark,
+    status: localUserDetail.value.status ? 1 : 0,
+    level: localUserDetail.value.level,
+    group_id: localUserDetail.value.group_id
+  }
+  return formData
+}
+defineExpose({
+  getFormData
+})
+
+const getLabel = (list, id) => {
+  // 检查列表是否存在且为数组类型,若不满足条件则返回null
+  if (!list || !Array.isArray(list)) return null
+
+  // 在列表中查找匹配给定ID的元素
+  const item = list.find((e) => e.value === id)
+
+  // 如果找到匹配的元素,则返回该元素的标签;否则,返回null
+  return item ? item.label : null
+}
+</script>
+
+<template>
+  <ElTabs type="border-card" @tab-change="changeTable">
+    <ElTabPane label="用户信息">
+      <div v-show="modelValue" class="p-20px">
+        <el-form label-width="110px" inline :rules="rules" :model="localUserDetail">
+          <ElDivider content-position="left"
+            ><span class="font-size-16px">基本信息</span></ElDivider
+          >
+          <el-form-item class="w-50%" label="用户编号" prop="uid">
+            <el-input disabled v-model="localUserDetail.uid" />
+          </el-form-item>
+
+          <el-form-item class="w-50%" label="真实姓名" prop="real_name">
+            <el-input v-model="localUserDetail.real_name" />
+          </el-form-item>
+
+          <el-form-item class="w-50%" label="手机号" prop="phone">
+            <el-input type="number" v-model="localUserDetail.phone" />
+          </el-form-item>
+          <el-form-item class="w-50%" label="生日" prop="birthday">
+            <el-date-picker type="date" v-model="localUserDetail.birthday" />
+          </el-form-item>
+          <el-form-item class="w-50%" label="身份证" prop="card_id">
+            <el-input v-model="localUserDetail.card_id" />
+          </el-form-item>
+          <el-form-item class="w-50%" label="性别" prop="sex">
+            <el-select v-model="localUserDetail.sex">
+              <el-option label="保密" :value="0" />
+              <el-option label="男" :value="1" />
+              <el-option label="女" :value="2" />
+            </el-select>
+          </el-form-item>
+          <ElDivider content-position="left"><span class="font-size-16px">密码</span></ElDivider>
+          <el-form-item class="w-50%" label="登录密码" prop="pwd">
+            <InputPassword class="w-100%" strength v-model="localUserDetail.pwd" />
+          </el-form-item>
+          <el-form-item class="w-50%" label="确认密码" prop="true_pwd">
+            <InputPassword class="w-100%" strength v-model="localUserDetail.true_pwd" />
+          </el-form-item>
+          <el-form-item class="w-50%" label="交易密码" prop="trade_pwd">
+            <el-input
+              maxlength="6"
+              minlength="6"
+              type="password"
+              v-model="localUserDetail.trade_pwd"
+            />
+          </el-form-item>
+          <el-form-item class="w-50%" label="确认交易密码" prop="true_trade_pwd">
+            <el-input
+              maxlength="6"
+              minlength="6"
+              type="password"
+              v-model="localUserDetail.true_trade_pwd"
+            />
+          </el-form-item>
+          <ElDivider class="w-50%" content-position="left"
+            ><span class="font-size-16px">用户概况</span></ElDivider
+          >
+          <el-form-item class="w-50%" label="用户分组" prop="group_id">
+            <el-select v-model="localUserDetail.group_id">
+              <el-option
+                v-for="(item, index) in groupList"
+                :key="index"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item class="w-50%" label="会员等级" prop="level">
+            <el-select v-model="localUserDetail.level">
+              <el-option
+                v-for="(item, index) in levelList"
+                :key="index"
+                :label="item.label"
+                :value="item.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item class="w-50%" label="推广员" prop="is_promoter">
+            <el-switch v-model="localUserDetail.is_promoter" />
+          </el-form-item>
+          <el-form-item class="w-50%" label="状态" prop="status">
+            <el-switch v-model="localUserDetail.status" />
+          </el-form-item>
+          <el-form-item class="w-50%" label="推荐人ID" prop="spread_uid">
+            <el-input v-model="localUserDetail.spread_uid" />
+          </el-form-item>
+          <el-form-item class="w-100%" label="备注" prop="mark">
+            <el-input type="textarea" :rows="2" v-model="localUserDetail.mark" />
+          </el-form-item>
+        </el-form>
+      </div>
+      <div v-show="!modelValue" class="p-20px">
+        <ElDivider content-position="left"><span class="font-size-16px">基本信息</span></ElDivider>
+        <el-descriptions>
+          <el-descriptions-item label="用户编号">{{ localUserDetail.uid }}</el-descriptions-item>
+          <el-descriptions-item label="真实姓名">{{
+            localUserDetail.real_name
+          }}</el-descriptions-item>
+          <el-descriptions-item label="手机号">{{ localUserDetail.phone }}</el-descriptions-item>
+          <el-descriptions-item label="生日">
+            {{ localUserDetail.birthday ? formatToDate(localUserDetail.birthday) : '' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="身份证">
+            {{ localUserDetail.card_id }}
+          </el-descriptions-item>
+          <el-descriptions-item label="性别">
+            <span v-if="localUserDetail.sex === 0">保密</span>
+            <span v-else-if="localUserDetail.sex === 1">男</span>
+            <span v-else-if="localUserDetail.sex === 2">女</span>
+          </el-descriptions-item>
+        </el-descriptions>
+        <ElDivider content-position="left"><span class="font-size-16px">密码</span></ElDivider>
+        <el-descriptions>
+          <el-descriptions-item label="登录密码"> ****** </el-descriptions-item>
+          <el-descriptions-item label="交易密码"> ****** </el-descriptions-item>
+        </el-descriptions>
+        <ElDivider content-position="left"><span class="font-size-16px">用户概况</span></ElDivider>
+        <el-descriptions>
+          <el-descriptions-item label="用户分组">
+            {{ getLabel(groupList, localUserDetail.group_id) || '' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="会员等级">
+            {{ getLabel(levelList, localUserDetail.level) || '' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="推广员">
+            <ElTag v-if="localUserDetail.is_promoter" size="small" type="success">开启</ElTag>
+            <ElTag v-else size="small" type="danger">关闭</ElTag>
+          </el-descriptions-item>
+          <el-descriptions-item label="状态">
+            <ElTag v-if="localUserDetail.status" size="small" type="success">启用</ElTag>
+            <ElTag v-else size="small" type="danger">禁用</ElTag>
+          </el-descriptions-item>
+          <el-descriptions-item label="推荐人ID">
+            {{ localUserDetail.spread_uid }}
+          </el-descriptions-item>
+          <el-descriptions-item label="备注">
+            {{ localUserDetail.mark }}
+          </el-descriptions-item>
+        </el-descriptions>
+      </div>
+    </ElTabPane>
+    <ElTabPane
+      :label="item.name"
+      :name="item.type"
+      v-for="(item, index) in userRecordList"
+      :key="index"
+    >
+      <ElTable v-loading="item.loading" :data="item.data" stripe>
+        <ElTableColumn
+          v-for="(ls, ind) in item.columns"
+          :key="ind"
+          :prop="ls.field"
+          :label="ls.label"
+        />
+      </ElTable>
+      <ElPagination
+        @change="changePage($event, item)"
+        v-model:current-page="item.page"
+        v-model:page-size="item.limit"
+        layout="sizes, prev, pager, next, jumper, ->, total"
+        :page-sizes="[10, 20, 30, 40, 50, 100]"
+        class="mt-10px"
+        :total="item.total"
+      />
+    </ElTabPane>
+  </ElTabs>
+</template>
+<style lang="less" scoped>
+:deep(.el-form-item) {
+  &.el-form-item {
+    margin-right: 0 !important;
+  }
+}
+</style>

+ 296 - 0
src/views/Goods/list/components/Write.vue

@@ -0,0 +1,296 @@
+<script setup lang="tsx">
+import { Form, FormSchema } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { PropType, watch, ref } from 'vue'
+import { FormRules } from 'element-plus'
+import { getUserDetail } from '@/api/user'
+import { formatToDate } from '@/utils/dateUtil'
+import { useValidator } from '@/hooks/web/useValidator'
+
+const { required, phone } = useValidator()
+
+const props = defineProps({
+  currentRow: {
+    type: Number,
+    default: () => null
+  },
+  levelList: {
+    type: Array as PropType<any>,
+    default: () => []
+  },
+  groupList: {
+    type: Array as PropType<any>,
+    default: () => []
+  },
+  type: {
+    type: String,
+    default: ''
+  }
+})
+
+const formSchema = ref<FormSchema[]>([
+  {
+    field: 'real_name',
+    label: '真实姓名',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入姓名'
+    }
+  },
+  {
+    field: 'phone',
+    label: '手机号',
+    component: 'Input',
+    componentProps: {
+      type: 'number',
+      placeholder: '请输入管理员账号'
+    }
+  },
+  {
+    field: 'birthday',
+    label: '生日',
+    component: 'DatePicker',
+    componentProps: {
+      placeholder: '请选择生日日期'
+    }
+  },
+  {
+    field: 'card_id',
+    label: '身份证',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入身份证'
+    }
+  },
+  {
+    field: 'sex',
+    label: '性别',
+    value: 0,
+    component: 'Select',
+    componentProps: {
+      placeholder: '请输入管理员账号',
+      options: [
+        {
+          label: '保密',
+          value: 0
+        },
+        {
+          label: '男',
+          value: 1
+        },
+        {
+          label: '女',
+          value: 2
+        }
+      ]
+    }
+  },
+  {
+    field: 'group_id',
+    label: '用户分组',
+    component: 'Select',
+    componentProps: {
+      placeholder: '会员分组',
+      options: props.groupList
+    }
+  },
+  {
+    field: 'pwd',
+    label: '密码',
+    component: 'InputPassword',
+    componentProps: {
+      strength: true,
+      placeholder: '请输入登录密码'
+    }
+  },
+  {
+    field: 'true_pwd',
+    label: '确认密码',
+    component: 'InputPassword',
+    componentProps: {
+      strength: true,
+      placeholder: '请输入重复输入登录密码'
+    }
+  },
+  {
+    field: 'trade_pwd',
+    label: '交易密码',
+    component: 'Input',
+    componentProps: {
+      maxlength: 6,
+      minlength: 6,
+      type: 'password',
+      placeholder: '请输入6位交易密码'
+    }
+  },
+  {
+    field: 'true_trade_pwd',
+    label: '确认交易密码',
+    component: 'Input',
+    componentProps: {
+      maxlength: 6,
+      minlength: 6,
+      type: 'password',
+      placeholder: '请输入重复输入交易密码'
+    }
+  },
+
+  {
+    field: 'level',
+    label: '会员等级',
+    component: 'Select',
+    componentProps: {
+      placeholder: '会员等级修改',
+      options: props.levelList
+    }
+  },
+
+  {
+    field: 'is_promoter',
+    label: '推广员',
+    component: 'Switch',
+    value: false,
+    componentProps: {
+      inlinePrompt: true,
+      activeText: '开启',
+      inactiveText: '关闭'
+    }
+  },
+  {
+    field: 'spread_uid',
+    label: '推荐人ID',
+    component: 'Input',
+    componentProps: {
+      type: 'number',
+      placeholder: '请输入推荐人ID'
+    }
+  },
+  {
+    field: 'status',
+    label: '状态',
+    component: 'Switch',
+    value: true,
+    componentProps: {
+      inlinePrompt: true,
+      activeText: '开启',
+      inactiveText: '关闭'
+    }
+  },
+  {
+    field: 'mark',
+    label: '备注',
+    colProps: {
+      span: 24
+    },
+    component: 'Input',
+    componentProps: {
+      type: 'textarea',
+      rows: 4,
+      placeholder: '请输入备注内容'
+    }
+  },
+  {
+    field: 'uid',
+    label: '用户id',
+    hidden: true,
+    component: 'Input'
+  }
+])
+
+const rules = ref<FormRules>({
+  phone: [required('请填写手机号'), phone()],
+  pwd: [{}],
+  true_pwd: [
+    {
+      validator: (_, val, callback) => {
+        // console.log(item)
+        getFormData().then((formData) => {
+          if (!val) return callback()
+          if (formData.pwd !== val) {
+            callback(new Error('两次密码不一致'))
+          } else {
+            callback()
+          }
+        })
+      }
+    }
+  ],
+  true_trade_pwd: [
+    {
+      validator: (_, val, callback) => {
+        // console.log(item)
+        getFormData().then((formData) => {
+          if (!val) return callback()
+          if (formData.trade_pwd && formData.trade_pwd !== val) {
+            callback(new Error('两次密码不一致'))
+          } else {
+            callback()
+          }
+        })
+      }
+    }
+  ]
+})
+watch(
+  () => props.type,
+  (val) => {
+    if (!val) {
+      rules.value.pwd = [required('请填写密码')]
+    }
+  },
+  { immediate: true }
+)
+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
+  }
+}
+const loading = ref(true)
+watch(
+  () => props.currentRow,
+  async (value) => {
+    if (!value) return (loading.value = false)
+    const { data } = await getUserDetail(value)
+    const currentRow = {
+      uid: data.uid,
+      real_name: data.real_name,
+      phone: data.phone,
+      pwd: '',
+      true_pwd: '',
+      trade_pwd: '',
+      true_trade_pwd: '',
+      sex: data.sex,
+      spread_uid: data.spread_uid == 0 ? '' : data.spread_uid,
+      is_promoter: data.is_promoter == 1 ? true : false,
+      card_id: data.card_id,
+      birthday: formatToDate(data.birthday * 1000),
+      mark: data.mark,
+      status: data.status == 1 ? true : false,
+      level: data.level == 0 ? '' : data.level,
+      group_id: data.group_id == 0 ? '' : data.group_id
+    }
+    loading.value = false
+    setValues(currentRow)
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+defineExpose({
+  submit
+})
+</script>
+
+<template>
+  <Form v-loading="loading" :rules="rules" @register="formRegister" :schema="formSchema" />
+</template>

+ 561 - 0
src/views/Goods/list/index.vue

@@ -0,0 +1,561 @@
+<script setup lang="tsx">
+import { onMounted, reactive, ref, unref, nextTick, watch } from 'vue'
+import {
+  getUserList,
+  getUserGroup,
+  getUserLevel,
+  addUserData,
+  // delUserData,
+  putUserData,
+  clearUserLevel
+} from '@/api/user'
+import type { userData } from '@/api/user/types'
+import { useI18n } from '@/hooks/web/useI18n'
+import { Search } from '@/components/Search'
+import { FormSchema } from '@/components/Form'
+import { ContentWrap } from '@/components/ContentWrap'
+import Detail from './components/Detail.vue'
+import Write from './components/Write.vue'
+import { Dialog } from '@/components/Dialog'
+import { BaseButton } from '@/components/Button'
+import { userSearch } from '@/api/user/types'
+import {
+  ElSelect,
+  ElOption,
+  ElMessage,
+  ElDivider,
+  ElText,
+  ElMessageBox,
+  ElDropdown,
+  ElDropdownMenu,
+  ElDropdownItem,
+  ElTable,
+  ElTableColumn,
+  ElPagination
+} from 'element-plus'
+import { searchTime } from '@/utils/searchTime'
+import { formatToDate } from '@/utils/dateUtil'
+import { TableImage } from '@/components/tableImage'
+import BatchSet from './components/BatchSet.vue'
+import { useSearch } from '@/hooks/web/useSearch'
+import Icon from '@/components/Icon/src/Icon.vue'
+const { searchRegister, searchMethods } = useSearch()
+const { setSchema } = searchMethods
+searchTime.field = 'user_time'
+const { t } = useI18n()
+const groupList = ref<any[]>([])
+const levelList = ref<any[]>([])
+const currentPage = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const dataList = ref<any[]>([])
+const loading = ref(false)
+const getList = async () => {
+  loading.value = true
+  const { data } = await getUserList({
+    page: unref(currentPage) || 1,
+    limit: unref(pageSize) || 10,
+    ...unref(searchParams)
+  })
+  loading.value = false
+  dataList.value = data.list
+  total.value = data.count
+}
+onMounted(async () => {
+  try {
+    getList()
+    const [levelResponse, groupResponse] = await Promise.all([
+      getUserLevel({ page: 1, limit: 999 }),
+      getUserGroup({ page: 1, limit: 999 })
+    ])
+
+    // 处理 userLevel 数据
+    const processLevelData = (data) =>
+      data.list.map((res) => ({
+        label: res.name,
+        value: res.id
+      }))
+
+    // 处理 userGroup 数据
+    const processGroupData = (data) =>
+      data.list.map((res) => ({
+        label: res.group_name,
+        value: res.id
+      }))
+
+    levelList.value = processLevelData(levelResponse.data)
+    groupList.value = processGroupData(groupResponse.data)
+
+    await nextTick()
+
+    setSchema([
+      {
+        field: 'level',
+        path: 'componentProps.options',
+        value: levelList.value
+      },
+      {
+        field: 'group_id',
+        path: 'componentProps.options',
+        value: groupList.value
+      }
+    ])
+  } catch (error) {
+    console.error('Failed to fetch data:', error)
+    // 可以在这里添加更详细的错误处理逻辑,例如显示错误信息给用户
+  }
+})
+
+const searchSchema = reactive<FormSchema[]>([
+  {
+    field: 'nickname',
+    label: '搜索',
+    component: 'Input',
+    componentProps: {
+      placeholder: '姓名、手机、UID查询',
+      slots: {
+        prepend: () => {
+          return (
+            <>
+              <ElSelect
+                v-model={searchParams.value.field_key}
+                placeholder="全部"
+                style={'width: 80px'}
+              >
+                <ElOption label="全部" value="all" />
+                <ElOption label="昵称" value="nickname" />
+                <ElOption label="手机" value="phone" />
+                <ElOption label="UID" value="uid" />
+              </ElSelect>
+            </>
+          )
+        }
+      }
+    }
+  },
+  {
+    field: 'status',
+    label: '用户状态',
+    component: 'Select',
+    componentProps: {
+      placeholder: '全部',
+      options: [
+        {
+          label: '全部',
+          value: ''
+        },
+        {
+          label: '封禁',
+          value: 0
+        },
+        {
+          label: '正常',
+          value: 1
+        }
+      ]
+    }
+  },
+  {
+    field: 'level',
+    label: '用户等级',
+    component: 'Select',
+    componentProps: {
+      placeholder: '全部'
+    }
+  },
+  {
+    field: 'group_id',
+    label: '用户分组',
+    component: 'Select',
+    componentProps: {
+      placeholder: '全部'
+    }
+  },
+  {
+    field: 'sex',
+    label: '性别',
+    value: '',
+    component: 'Select',
+    componentProps: {
+      placeholder: '全部',
+      options: [
+        {
+          label: '全部',
+          value: ''
+        },
+        {
+          label: '保密',
+          value: 0
+        },
+        {
+          label: '男',
+          value: 1
+        },
+        {
+          label: '女',
+          value: 2
+        }
+      ]
+    }
+  },
+  {
+    field: 'is_promoter',
+    label: '推广权限',
+    component: 'Select',
+    componentProps: {
+      placeholder: '全部',
+
+      options: [
+        {
+          label: '全部',
+          value: ''
+        },
+        {
+          label: '关闭',
+          value: 0
+        },
+        {
+          label: '开启',
+          value: 1
+        }
+      ]
+    }
+  },
+  {
+    field: 'now_money',
+    label: '余额排序',
+    component: 'Select',
+    componentProps: {
+      placeholder: '默认',
+      options: [
+        {
+          label: '默认',
+          value: ''
+        },
+        {
+          label: '降序',
+          value: 'desc'
+        },
+        {
+          label: '升序',
+          value: 'asc'
+        }
+      ]
+    }
+  },
+  // {
+  //   field: 'country',
+  //   label: '国家',
+  //   component: 'Select',
+  //   value: '',
+  //   componentProps: {
+  //     options: [
+  //       {
+  //         label: '全部',
+  //         value: ''
+  //       },
+  //       {
+  //         label: '国内',
+  //         value: 'domestic'
+  //       },
+  //       {
+  //         label: '国外',
+  //         value: 'abroad'
+  //       }
+  //     ]
+  //   }
+  // },
+  {
+    field: 'user_time_type',
+    label: '时间筛选类型',
+    value: 'all',
+    component: 'Select',
+    componentProps: {
+      options: [
+        {
+          label: '全部',
+          value: 'all'
+        },
+        {
+          label: '访问时间不在',
+          value: 'visitno'
+        },
+        {
+          label: '访问时间',
+          value: 'visit'
+        },
+        {
+          label: '添加时间',
+          value: 'add_time'
+        }
+      ]
+    }
+  },
+  searchTime
+])
+
+const searchParams = ref<userSearch>({
+  nickname: '',
+  status: '',
+  is_promoter: '',
+  user_type: '',
+  country: '',
+  province: '',
+  city: '',
+  sex: '',
+  user_time_type: 'all',
+  user_time: '',
+  level: '',
+  group_id: '',
+  now_money: '',
+  field_key: 'all'
+})
+const setSearchParams = (data: any) => {
+  searchParams.value = data
+  getList()
+}
+
+const dialogVisible = ref(false)
+const currentRow = ref()
+const dialogTitle = ref('')
+const actionType = ref('')
+const writeRef = ref<ComponentRef<typeof Write>>()
+const saveLoading = ref(false)
+const action = async (row: any, type: string) => {
+  dialogTitle.value = t('exampleDemo.edit')
+  actionType.value = type
+  currentRow.value = row.uid
+  dialogVisible.value = true
+}
+
+const AddAction = () => {
+  dialogTitle.value = t('exampleDemo.add')
+  currentRow.value = undefined
+  dialogVisible.value = true
+  actionType.value = ''
+}
+
+const delLevel = async (uid: any) => {
+  ElMessageBox.confirm('清除后无法恢复,是否清除用户等级?', {
+    confirmButtonText: '清除',
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      const res = await clearUserLevel(uid)
+      if (res.status === 200) {
+        ElMessage.success(res.msg)
+        getList()
+      }
+    })
+    .catch(() => {})
+}
+
+// const delAction = (row: any) => {
+//   ElMessageBox.confirm('删除后无法恢复,是否删除用户?', {
+//     confirmButtonText: '删除',
+//     cancelButtonText: '取消',
+//     type: 'warning'
+//   })
+//     .then(async () => {
+//       const re = await delUserData(row.uid)
+//       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) {
+    saveLoading.value = true
+    const data: userData = {
+      uid: formData.uid,
+      real_name: formData.real_name,
+      phone: formData.phone,
+      pwd: formData.pwd,
+      true_pwd: formData.true_pwd,
+      trade_pwd: formData.trade_pwd,
+      true_trade_pwd: formData.true_trade_pwd,
+      sex: formData.sex,
+      spread_uid: formData.spread_uid,
+      is_promoter: formData.is_promoter ? 1 : 0,
+      card_id: formData.card_id,
+      birthday: formatToDate(formData.birthday),
+      mark: formData.mark,
+      status: formData.status ? 1 : 0,
+      level: formData.level,
+      group_id: formData.group_id
+    }
+    let status = 'success'
+    // console.log(formData, 'formData')
+    if (actionType.value === 'edit') {
+      const res = await putUserData(data)
+      if (!res) {
+        status = 'error'
+      }
+    } else if (actionType.value === '') {
+      const res = await addUserData(data)
+      if (!res) {
+        status = 'error'
+      }
+    }
+    if (status == 'success') {
+      ElMessage({
+        showClose: true,
+        message: '保存成功',
+        type: 'success'
+      })
+      getList()
+      dialogVisible.value = false
+    }
+    saveLoading.value = false
+  }
+}
+const drawerUser = ref(false)
+const drawerUserId = ref()
+const detail = (item) => {
+  drawerUser.value = true
+  drawerUserId.value = item.uid
+}
+const selectChange = (res) => {
+  selectList.value = res
+}
+const selectList = ref<any[]>([])
+const dialogSet = ref(false)
+const allSet = () => {
+  dialogSet.value = true
+}
+watch(currentPage, (val) => {
+  if (val) {
+    // console.log(val)
+    currentPage.value = val
+    getList()
+  }
+})
+</script>
+
+<template>
+  <ContentWrap>
+    <div class="searchBox">
+      <Search
+        :schema="searchSchema"
+        @reset="setSearchParams"
+        @search="setSearchParams"
+        @register="searchRegister"
+        show-expand
+        buttonPosition="right"
+        expand-field="status"
+      />
+    </div>
+    <div class="mb-10px">
+      <BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
+      <BaseButton @click="allSet" :disabled="selectList.length == 0">批量设置</BaseButton>
+    </div>
+    <ElTable @selection-change="selectChange" :loading="loading" node-key="uid" :data="dataList">
+      <ElTableColumn type="selection" prop="selection" />
+      <ElTableColumn prop="uid" headerAlign="center" align="center" label="ID" width="70" />
+      <ElTableColumn prop="avatar" headerAlign="center" align="center" label="头像" width="80">
+        <template #default="{ row }">
+          <TableImage :src="row.avatar" alt="头像" />
+        </template>
+      </ElTableColumn>
+      <ElTableColumn prop="nickname" label="昵称" min-width="120">
+        <template #default="{ row }">
+          {{ row.nickname }}<br />
+          <ElText v-if="row.delete_time" type="danger">{{ row.delete }}</ElText>
+        </template>
+      </ElTableColumn>
+      <ElTableColumn prop="vip_name" label="等级" min-width="80" />
+      <ElTableColumn prop="group_id" label="分组" min-width="80" />
+      <ElTableColumn prop="phone" label="手机号" minWidth="120" />
+      <ElTableColumn prop="spread_uid_nickname" label="推荐人" min-width="80" />
+      <ElTableColumn prop="now_money" label="余额" min-width="80" />
+      <ElTableColumn
+        prop="action"
+        label="操作"
+        headerAlign="center"
+        align="center"
+        width="190"
+        :showOverflowTooltip="false"
+        fixed="right"
+      >
+        <template #default="{ row }">
+          <BaseButton link size="small" type="primary" @click="action(row, 'edit')">
+            编辑
+          </BaseButton>
+          <template v-if="!row.delete_time">
+            <ElDivider direction="vertical" />
+            <BaseButton link size="small" type="success" @click="detail(row)"> 详情 </BaseButton>
+          </template>
+          <ElDivider direction="vertical" />
+          <ElDropdown>
+            <BaseButton link size="small" type="info">
+              <Icon icon="vi-ep:more-filled" />
+            </BaseButton>
+            <template #dropdown
+              ><ElDropdownMenu>
+                <ElDropdownItem @click="delLevel(row.uid)">清除等级</ElDropdownItem>
+              </ElDropdownMenu></template
+            >
+          </ElDropdown>
+        </template>
+      </ElTableColumn>
+    </ElTable>
+    <ElPagination
+      v-model:current-page="currentPage"
+      v-model:page-size="pageSize"
+      layout="sizes, prev, pager, next, jumper, ->, total"
+      :page-sizes="[10, 20, 30, 40, 50, 100]"
+      class="mt-10px"
+      :total="total"
+    />
+  </ContentWrap>
+
+  <BatchSet
+    :group-list="groupList"
+    :where="searchParams"
+    v-model="dialogSet"
+    :select-list="selectList"
+    @confirm="getList()"
+  />
+  <Detail
+    :level-list="levelList"
+    :group-list="groupList"
+    v-model="drawerUser"
+    :uid="drawerUserId"
+    @confirm="getList()"
+  />
+  <Dialog v-model="dialogVisible" max-height="500px" :title="dialogTitle" width="700px">
+    <Write
+      ref="writeRef"
+      :current-row="currentRow"
+      :level-list="levelList"
+      :group-list="groupList"
+      :type="actionType"
+    />
+    <template #footer>
+      <BaseButton type="primary" :loading="saveLoading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </BaseButton>
+      <BaseButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</BaseButton>
+    </template>
+  </Dialog>
+</template>
+<style lang="less">
+.searchBox {
+  max-width: 1000px;
+}
+.example-showcase .el-dropdown-link {
+  cursor: pointer;
+  color: var(--el-color-primary);
+  display: flex;
+  align-items: center;
+}
+</style>

+ 19 - 16
src/views/Login/components/LoginForm.vue

@@ -242,22 +242,25 @@ const getRole = async (menus: any) => {
     text: '获取路由...',
     background: 'rgba(0, 0, 0, 0.7)'
   })
-  const list = loginRoluData(menus)
-  console.log(list, 'list')
-
-  const routers = list || []
-  userStore.setRoleRouters(routers)
-  appStore.getDynamicRouter && appStore.getServerDynamicRouter
-    ? await permissionStore.generateRoutes('server', routers).catch(() => {})
-    : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
-  permissionStore.getAddRouters.forEach((route) => {
-    addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
-  })
-  permissionStore.setIsAddRouters(true)
-  // console.log(permissionStore)
-
-  push({ path: redirect.value || permissionStore.addRouters[0].path })
-  loading.close()
+  try {
+    const list = loginRoluData(menus)
+    console.log(list, 'list')
+    const routers = list || []
+    userStore.setRoleRouters(routers)
+    appStore.getDynamicRouter && appStore.getServerDynamicRouter
+      ? await permissionStore.generateRoutes('server', routers).catch(() => {})
+      : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
+    permissionStore.getAddRouters.forEach((route) => {
+      addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
+    })
+    permissionStore.setIsAddRouters(true)
+    // console.log(permissionStore)
+    await push({ path: redirect.value || permissionStore.addRouters[0].path })
+    loading.close()
+  } catch (error) {
+    console.log(error)
+    loading.close()
+  }
 }
 
 const success = (params) => {

+ 2 - 5
src/views/Staff/category/components/Write.vue

@@ -57,12 +57,9 @@ const formSchema = ref<FormSchema[]>([
   },
   {
     field: 'id',
-    label: '管理员id',
+    label: 'id',
     hidden: true,
-    component: 'Input',
-    componentProps: {
-      placeholder: '请输入管理员账号'
-    }
+    component: 'Input'
   }
 ])
 

+ 11 - 0
src/views/Staff/designer/components/Write.vue

@@ -129,6 +129,16 @@ const formSchema = ref<FormSchema[]>([
     label: '出生日期',
     component: 'DatePicker'
   },
+
+  {
+    field: 'start_job_year',
+    label: '就业年份',
+    component: 'Input',
+    componentProps: {
+      placeholder: '例子:2014'
+    }
+  },
+
   {
     field: 'tag_list',
     label: '标签',
@@ -236,6 +246,7 @@ const rules = ref({
   avatar: [required('请上传头像')],
   phone: [required('请填写手机号'), phone()],
   birth_day_time: [required('请选择出生日期')],
+  start_job_year: [required('请填写就业年份')],
   detail_address: [
     {
       required: true,

+ 11 - 2
src/views/Staff/designer/index.vue

@@ -65,6 +65,13 @@ const tableColumns = reactive<TableColumn[]>([
     label: '手机号',
     minWidth: 120
   },
+  {
+    field: 'start_job_year',
+    label: '就业年份',
+    align: 'center',
+    headerAlign: 'center',
+    minWidth: 100
+  },
   {
     field: 'gender_chs',
     align: 'center',
@@ -219,7 +226,8 @@ const action = async (row: any, type: string) => {
     tag_list: row.tag_list.split(','),
     longitude: row.longitude,
     latitude: row.latitude,
-    detail_address: row.detail_address
+    detail_address: row.detail_address,
+    start_job_year: row.start_job_year
   }
   dialogVisible.value = true
 }
@@ -249,7 +257,8 @@ const save = async () => {
       tag_list: formData.tag_list,
       longitude: formData.longitude,
       latitude: formData.latitude,
-      detail_address: formData.detail_address
+      detail_address: formData.detail_address,
+      start_job_year: formData.start_job_year
     }
     let status = 'success'
     if (actionType.value === 'edit') {

+ 1 - 4
src/views/Staff/jobs/components/Write.vue

@@ -66,10 +66,7 @@ const formSchema = ref<FormSchema[]>([
     field: 'id',
     label: 'id',
     hidden: true,
-    component: 'Input',
-    componentProps: {
-      placeholder: '请输入管理员账号'
-    }
+    component: 'Input'
   }
 ])
 

+ 10 - 0
src/views/Staff/worker/components/Write.vue

@@ -129,6 +129,16 @@ const formSchema = ref<FormSchema[]>([
     label: '出生日期',
     component: 'DatePicker'
   },
+
+  {
+    field: 'start_job_year',
+    label: '就业年份',
+    component: 'Input',
+    componentProps: {
+      placeholder: '例子:2014'
+    }
+  },
+
   {
     field: 'tag_list',
     label: '标签',

+ 11 - 2
src/views/Staff/worker/index.vue

@@ -64,6 +64,13 @@ const tableColumns = reactive<TableColumn[]>([
     label: '手机号',
     minWidth: 120
   },
+  {
+    field: 'start_job_year',
+    label: '就业年份',
+    align: 'center',
+    headerAlign: 'center',
+    minWidth: 100
+  },
   {
     field: 'gender_chs',
     align: 'center',
@@ -217,7 +224,8 @@ const action = async (row: any, type: string) => {
     tag_list: row.tag_list,
     longitude: row.longitude,
     latitude: row.latitude,
-    detail_address: row.detail_address
+    detail_address: row.detail_address,
+    start_job_year: row.start_job_year
   }
   dialogVisible.value = true
 }
@@ -247,7 +255,8 @@ const save = async () => {
       tag_list: formData.tag_list,
       longitude: formData.longitude,
       latitude: formData.latitude,
-      detail_address: formData.detail_address
+      detail_address: formData.detail_address,
+      start_job_year: formData.start_job_year
     }
     let status = 'success'
     if (actionType.value === 'edit') {

+ 7 - 4
src/views/User/list/index.vue

@@ -424,7 +424,10 @@ const detail = (item) => {
   drawerUser.value = true
   drawerUserId.value = item.uid
 }
-const selectChange = ref<any[]>([])
+const selectChange = (res) => {
+  selectList.value = res
+}
+const selectList = ref<any[]>([])
 const dialogSet = ref(false)
 const allSet = () => {
   dialogSet.value = true
@@ -453,9 +456,9 @@ watch(currentPage, (val) => {
     </div>
     <div class="mb-10px">
       <BaseButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</BaseButton>
-      <BaseButton @click="allSet" :disabled="selectChange.length == 0">批量设置</BaseButton>
+      <BaseButton @click="allSet" :disabled="selectList.length == 0">批量设置</BaseButton>
     </div>
-    <ElTable :loading="loading" node-key="uid" :data="dataList">
+    <ElTable @selection-change="selectChange" :loading="loading" node-key="uid" :data="dataList">
       <ElTableColumn type="selection" prop="selection" />
       <ElTableColumn prop="uid" headerAlign="center" align="center" label="ID" width="70" />
       <ElTableColumn prop="avatar" headerAlign="center" align="center" label="头像" width="80">
@@ -519,7 +522,7 @@ watch(currentPage, (val) => {
     :group-list="groupList"
     :where="searchParams"
     v-model="dialogSet"
-    :select-list="selectChange"
+    :select-list="selectList"
     @confirm="getList()"
   />
   <Detail