diff --git a/.env.base b/.env.base
index 0552a5a..5a22736 100644
--- a/.env.base
+++ b/.env.base
@@ -6,9 +6,9 @@ VITE_DEV=true
 # 请求路径
 # VITE_BASE_URL='http://localhost:48080'
 
-# VITE_BASE_URL='http://47.98.161.246:48080'
+VITE_BASE_URL='http://47.98.161.246:48080'
 # VITE_BASE_URL='http://114.55.169.15:48080'
-VITE_BASE_URL='http://114.215.207.150:48080'
+# VITE_BASE_URL='http://114.215.207.150:48080'
 
 # 上传路径
 VITE_UPLOAD_URL='http://47.98.161.246:48080/admin-api/system/file/upload'
diff --git a/src/api/customer/customer.js b/src/api/customer/customer.js
index 8f040a0..16ed286 100644
--- a/src/api/customer/customer.js
+++ b/src/api/customer/customer.js
@@ -22,7 +22,8 @@ export const deleteCustomer = (userId) => {
 
 // 获取考试车型
 export const getCustomerExamCarType = () => {
-  return request.get({ url: '/admin-api/applet/xunjia/car/simple-list' })
+  return request.post({ url: 'https://cloud.ahduima.com//driver-api/tdTenantCar/list' })
+  // return request.post({ url: import.meta.env.VITE_BASE_URL + '/driver-api/tdTenantCar/list' })
 }
 
 // 导入学员
diff --git a/src/api/xjapplet/discount.js b/src/api/xjapplet/discount.js
new file mode 100644
index 0000000..0956658
--- /dev/null
+++ b/src/api/xjapplet/discount.js
@@ -0,0 +1,48 @@
+import request from '@/config/axios'
+
+export const getVipDiscountList = async (params) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/member/discount/list',
+    params: params
+  })
+}
+
+export const addVipDiscount = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/member/discount/add',
+    data
+  })
+}
+
+export const updateVipDiscount = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/member/discount/update',
+    data
+  })
+}
+
+export const deleteVipDiscount = async (id) => {
+  return await request.delete({
+    url: '/admin-api/applet/xunjia/member/discount/delete?discountId=' + id
+  })
+}
+
+export const getVipDiscountOptions = async () => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/member/discount/simple-list'
+  })
+}
+
+export const getUserDiscountList = async (params) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/user/discount/list',
+    params: params
+  })
+}
+
+export const giveUserDiscount = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/user/discount/send',
+    data
+  })
+}
diff --git a/src/api/xjapplet/resell.js b/src/api/xjapplet/resell.js
new file mode 100644
index 0000000..4042167
--- /dev/null
+++ b/src/api/xjapplet/resell.js
@@ -0,0 +1,32 @@
+import request from '@/config/axios'
+
+export const getResellList = async (params) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/distribution/list',
+    params: params
+  })
+}
+
+export const addResell = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/distribution/add',
+    data
+  })
+}
+
+export const updateResell = async (data) => {
+  return await request.put({
+    url: '/admin-api/applet/xunjia/distribution/update',
+    data
+  })
+}
+export const deleteResell = async (id) => {
+  return await request.delete({
+    url: '/admin-api/applet/xunjia/distribution/delete?distributionId=' + id
+  })
+}
+export const getResellDetail = async (id) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/distribution/' + id
+  })
+}
diff --git a/src/api/xjapplet/vip.js b/src/api/xjapplet/vip.js
new file mode 100644
index 0000000..5581edd
--- /dev/null
+++ b/src/api/xjapplet/vip.js
@@ -0,0 +1,49 @@
+import request from '@/config/axios'
+
+export const getUserMemberList = async (params) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/user/member/list',
+    params: params
+  })
+}
+
+export const giveUserMember = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/user/member/add',
+    data
+  })
+}
+
+export const getVipTypeList = async (params) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/member/list',
+    params: params
+  })
+}
+
+export const addVipType = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/member/add',
+    data
+  })
+}
+
+export const updateVipType = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/member/update',
+    data
+  })
+}
+
+export const deleteVipType = async (id) => {
+  return await request.delete({
+    url: '/admin-api/applet/xunjia/member/memberId?id=' + id
+  })
+}
+
+export const getVipTypeOptions = async (params) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/member/simple-list',
+    params: params
+  })
+}
diff --git a/src/api/xjapplet/vipdatabase.js b/src/api/xjapplet/vipdatabase.js
new file mode 100644
index 0000000..2eca4d0
--- /dev/null
+++ b/src/api/xjapplet/vipdatabase.js
@@ -0,0 +1,67 @@
+import request from '@/config/axios'
+
+export const addJx = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/question/jx/add',
+    data: data
+  })
+}
+
+export const getJxQuestionList = async (params) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/question/jx/list',
+    params: params
+  })
+}
+
+export const delJxData = async (id) => {
+  return await request.delete({
+    url: `/admin-api/applet/xunjia/question/jx/delete?id=${id}`
+  })
+}
+
+export const addMj = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/secret/add',
+    data: data
+  })
+}
+
+export const getMjList = async (params) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/secret/list',
+    params: params
+  })
+}
+
+export const delMj = async (secretId) => {
+  return await request.delete({
+    url: `/admin-api/applet/xunjia/secret/delete?secretId=${secretId}`
+  })
+}
+
+export const clearMj = async (secretId) => {
+  return await request.delete({
+    url: `/admin-api/applet/xunjia/secret/clear?secretId=${secretId}`
+  })
+}
+
+export const getMjQuestionList = async (params) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/secret/question/list',
+    params: params
+  })
+}
+
+export const addMjQuestion = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/secret/question/add',
+    data: data
+  })
+}
+
+export const delMjQuestion = async (id) => {
+  return await request.delete({
+    url: `/admin-api/applet/xunjia/secret/question/delete?id=${id}`
+  })
+}
diff --git a/src/api/xjapplet/xjdatabase.js b/src/api/xjapplet/xjdatabase.js
new file mode 100644
index 0000000..3cebccf
--- /dev/null
+++ b/src/api/xjapplet/xjdatabase.js
@@ -0,0 +1,48 @@
+import request from '@/config/axios'
+export const searchQuestion = async (param) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/question/list',
+    params: param
+  })
+}
+
+export const updateQuestion = async (data) => {
+  return await request.put({
+    url: '/admin-api/applet/xunjia/question/update',
+    data: data
+  })
+}
+
+export const addQuestion = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/question/add',
+    data: data
+  })
+}
+
+export const deleteQuestion = async (id) => {
+  return await request.delete({
+    url: '/admin-api/applet/xunjia/question/delete?id=' + id
+  })
+}
+
+export const uploadFile = async (data) => {
+  return await request.post({
+    url: '/admin-api/applet/xunjia/question/upload',
+    data: data
+  })
+}
+
+export const getQuestionSort = async (param) => {
+  return await request.get({
+    url: '/admin-api/applet/xunjia/question/sort/list',
+    params: param
+  })
+}
+
+export const getMjList = async (params) => {
+  return await request.get({
+    url: 'http://localhost/tiku-api/tiku/xunjia/secret/list',
+    params: params
+  })
+}
diff --git a/src/config/axios/service.ts b/src/config/axios/service.ts
index 620be60..0a0f094 100644
--- a/src/config/axios/service.ts
+++ b/src/config/axios/service.ts
@@ -202,7 +202,7 @@ service.interceptors.response.use(
           '<div>5 分钟搭建本地环境</div>'
       })
       return Promise.reject(new Error(msg))
-    } else if (code !== 200) {
+    } else if (code !== 200 && code !== '0000') {
       if (msg === '无效的刷新令牌') {
         // hard coding:忽略这个提示,直接登出
         console.log(msg)
diff --git a/src/permission.js b/src/permission.js
index 991ff06..378929b 100644
--- a/src/permission.js
+++ b/src/permission.js
@@ -7,7 +7,8 @@ import { usePageLoading } from '@/hooks/web/usePageLoading'
 import { useDictStoreWithOut } from '@/store/modules/dict'
 import { useUserStoreWithOut } from '@/store/modules/user'
 import { usePermissionStoreWithOut } from '@/store/modules/permission'
-import { getTenantId, getAppId } from '@/utils/auth'
+import { useAppStoreWithOut } from '@/store/modules/app'
+import { getTenantId, getAppId, setTenantId, setAppId } from '@/utils/auth'
 import cache from '@/plugins/cache'
 
 const { start, done } = useNProgress()
@@ -30,6 +31,13 @@ router.beforeEach(async (to, from, next) => {
   } else {
     if (getAccessToken()) {
       if (to.path === '/login') {
+        // next({ path: '/' })
+        if (to.query?.tenantId && to.query?.appId) {
+          setApp(to.query.tenantId, to.query.appId)
+          // setTimeout(() => {
+          //   next({ path: '/' })
+          // }, 1500)
+        }
         next({ path: '/' })
       } else {
         // 获取所有字典
@@ -72,6 +80,14 @@ router.beforeEach(async (to, from, next) => {
   }
 })
 
+function setApp(tenantId, appId) {
+  setTenantId(tenantId)
+
+  setAppId(appId)
+  const appStore = useAppStoreWithOut()
+  appStore.setAppInfo(appId)
+}
+
 router.afterEach((to) => {
   useTitle(to?.meta?.title)
   done() // 结束Progress
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index 1d5f990..7c246dd 100644
--- a/src/router/modules/remaining.ts
+++ b/src/router/modules/remaining.ts
@@ -60,7 +60,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
     },
     children: [
       {
-        path: '/index',
+        path: 'index',
         component: () => import('@/views/Home/index.vue'),
         name: 'Home',
         meta: {
diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts
index 50a7ea3..a4d4d1c 100644
--- a/src/store/modules/dict.ts
+++ b/src/store/modules/dict.ts
@@ -75,7 +75,7 @@ export const useDictStore = defineStore('dict', {
     },
     async resetDict() {
       cache.session.delete(CACHE_KEY.DICT_CACHE)
-      const res = await listSimpleDictData()
+      const res = (await listSimpleDictData()) || []
       // 设置数据
       const dictDataMap = new Map<string, any>()
       res.forEach((dictData: DictDataVO) => {
diff --git a/src/utils/auth.ts b/src/utils/auth.ts
index 8ceb741..bf91499 100644
--- a/src/utils/auth.ts
+++ b/src/utils/auth.ts
@@ -1,6 +1,5 @@
 import { TokenType } from '@/api/login/types'
 import { decrypt, encrypt } from '@/utils/jsencrypt'
-
 import cache from '@/plugins/cache'
 
 const AccessTokenKey = 'ACCESS_TOKEN'
@@ -9,26 +8,34 @@ const RefreshTokenKey = 'REFRESH_TOKEN'
 // 获取token
 export const getAccessToken = () => {
   // 此处与TokenKey相同,此写法解决初始化时Cookies中不存在TokenKey报错
-  return cache.local.get(AccessTokenKey)
-    ? cache.local.get(AccessTokenKey)
-    : cache.local.get('ACCESS_TOKEN')
+  return localStorage.getItem(AccessTokenKey)
+    ? localStorage.getItem(AccessTokenKey)
+    : localStorage.getItem('ACCESS_TOKEN')
+  // return cache.local.get(AccessTokenKey)
+  //   ? cache.local.get(AccessTokenKey)
+  //   : cache.local.get('ACCESS_TOKEN')
 }
 
 // 刷新token
 export const getRefreshToken = () => {
-  return cache.local.get(RefreshTokenKey)
+  return localStorage.getItem(RefreshTokenKey)
+  // return cache.local.get(RefreshTokenKey)
 }
 
 // 设置token
 export const setToken = (token: TokenType) => {
-  cache.local.set(RefreshTokenKey, token.refreshToken)
-  cache.local.set(AccessTokenKey, token.accessToken)
+  localStorage.setItem(AccessTokenKey, token.accessToken)
+  localStorage.setItem(RefreshTokenKey, token.refreshToken)
+  // cache.local.set(RefreshTokenKey, token.refreshToken)
+  // cache.local.set(AccessTokenKey, token.accessToken)
 }
 
 // 删除token
 export const removeToken = () => {
-  cache.local.delete(AccessTokenKey)
-  cache.local.delete(RefreshTokenKey)
+  localStorage.removeItem(AccessTokenKey)
+  localStorage.removeItem(RefreshTokenKey)
+  // cache.local.delete(AccessTokenKey)
+  // cache.local.delete(RefreshTokenKey)
 }
 
 /** 格式化token(jwt格式) */
diff --git a/src/views/Customer/AppletUser/index.vue b/src/views/Customer/AppletUser/index.vue
new file mode 100644
index 0000000..7a43666
--- /dev/null
+++ b/src/views/Customer/AppletUser/index.vue
@@ -0,0 +1,118 @@
+<template>
+  <div>
+    <el-form :model="searchForm" inline @submit.prevent>
+      <el-form-item>
+        <el-input
+          v-model="searchForm.phone"
+          placeholder="请输入手机号"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-select
+          v-model="searchForm.resellMan"
+          placeholder="选择分销人"
+          clearable
+          filterable
+          @change="handleQuery"
+        >
+          <el-option
+            v-for="item in resellOptions"
+            :key="item.distributionId"
+            :label="item.name"
+            :value="item.distributionId"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-date-picker
+          v-model="searchForm.createDate"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="注册日期"
+          end-placeholder="注册日期"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery">搜索</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-table v-loading="loading" :data="tableList" border stripe>
+      <el-table-column type="index" width="50" />
+      <el-table-column label="手机号码" prop="phone" width="120" />
+      <el-table-column label="分销人" prop="distributionName" min-width="120" />
+      <el-table-column label="注册日期" prop="registerDate" min-width="120" />
+      <el-table-column label="最近登陆日期" prop="lastLoginTime" min-width="120" />
+    </el-table>
+    <Pagination
+      :total="total"
+      v-model:page="searchForm.pageNo"
+      v-model:limit="searchForm.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script name="AppletUser" setup>
+// import { removeNullField } from '@/utils'
+// import * as CustomerApi from '@/api/customer/customer.js'
+
+const searchForm = ref({
+  resellMan: undefined,
+  phone: '',
+  createDate: [],
+  pageNo: 1,
+  pageSize: 20
+})
+
+const resellOptions = ref([])
+
+onMounted(() => {
+  // CustomerApi.getCustomerExamCarType().then((res) => {
+  //   carTypeOptions.value = res
+  // })
+  handleQuery()
+})
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  searchForm.value.pageNo = 1
+  getList()
+}
+
+const loading = ref(false)
+const tableList = ref([])
+const total = ref(0)
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    // const data = await CustomerApi.getCustomerPage(removeNullField(searchForm.value))
+    const data = {
+      list: [
+        {
+          phone: '12345678901',
+          distributionName: '分销人A',
+          registerDate: '2023-10-01',
+          lastLoginTime: '2023-10-02'
+        },
+        {
+          phone: '12345678902',
+          distributionName: '分销人B',
+          registerDate: '2023-10-03',
+          lastLoginTime: '2023-10-04'
+        }
+      ],
+      total: 2
+    }
+    tableList.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/Customer/ExamRecord/index.vue b/src/views/Customer/ExamRecord/index.vue
index a241973..38b20e4 100644
--- a/src/views/Customer/ExamRecord/index.vue
+++ b/src/views/Customer/ExamRecord/index.vue
@@ -103,7 +103,7 @@ const carTypeOptions = ref([])
 
 const searchForm = ref({
   userName: undefined,
-  carTypeId: 1011,
+  carTypeId: undefined,
   pageNo: 1,
   pageSize: 20
 })
diff --git a/src/views/Customer/Resell/index.vue b/src/views/Customer/Resell/index.vue
new file mode 100644
index 0000000..ba306b2
--- /dev/null
+++ b/src/views/Customer/Resell/index.vue
@@ -0,0 +1,194 @@
+<template>
+  <div>
+    <el-form :model="searchForm" label-width="0" inline @submit.prevent>
+      <el-form-item>
+        <el-input v-model="searchForm.phone" placeholder="请输入手机号" />
+      </el-form-item>
+      <el-form-item>
+        <el-input v-model="searchForm.name" placeholder="请输入姓名" />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="searchList" v-hasPermi="['customer:resell:search']">查询</el-button>
+        <el-button type="primary" @click="handleAdd" v-hasPermi="['customer:resell:add']">
+          新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+    <el-table v-loading="loading" :data="tableData" max-height="calc(100vh - 200px)">
+      <el-table-column type="index" width="50" />
+      <el-table-column prop="phone" label="手机号" />
+      <el-table-column prop="name" label="姓名" />
+      <el-table-column prop="mark" label="标记" />
+      <el-table-column prop="memberName" label="赠送会员" />
+      <el-table-column prop="discountDiscription" label="赠送折扣" />
+      <el-table-column label="小程序码" align="center" width="120px">
+        <template #default="{ row }">
+          <img :src="row.appletUrl" style="width: 80px; height: 80px" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="120px">
+        <template #default="{ row }">
+          <el-button
+            type="primary"
+            link
+            @click="handleEdit(row)"
+            v-hasPermi="['customer:resell:update']"
+          >
+            修改
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      v-model:page="searchForm.pageNo"
+      v-model:limit="searchForm.pageSize"
+      @pagination="getList"
+    />
+
+    <el-dialog title="分销配置" v-model="showDialog" width="500px">
+      <el-form :model="form" ref="formRef" :rules="rules" label-width="80px">
+        <el-form-item label="手机号" prop="phone">
+          <el-input v-model="form.phone" maxlength="11" />
+        </el-form-item>
+        <el-form-item label="姓名" prop="name">
+          <el-input v-model="form.name" />
+        </el-form-item>
+        <el-form-item label="标记" prop="mark">
+          <el-input v-model="form.mark" />
+        </el-form-item>
+        <el-form-item label="赠送会员" prop="memberId">
+          <el-select v-model="form.memberId" clearable filterable style="width: 100%">
+            <el-option
+              v-for="item in vipOptions"
+              :key="item.memberId"
+              :label="item.memberName"
+              :value="item.memberId"
+            >
+              <span style="float: left">{{ item.memberName }}</span>
+              <span style="float: right; color: #aaa">{{ item.carName }}</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="赠送折扣" prop="discountId">
+          <el-select v-model="form.discountId" clearable filterable style="width: 100%">
+            <el-option
+              v-for="item in discountOptions"
+              :key="item.id"
+              :label="item.description"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <span>
+          <el-button @click="showDialog = false">取消</el-button>
+          <el-button type="primary" @click="handleSave">确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Resell">
+import { getResellList, addResell, updateResell } from '@/api/xjapplet/resell'
+import { getVipDiscountOptions } from '@/api/xjapplet/discount'
+import { getVipTypeOptions } from '@/api/xjapplet/vip'
+
+const message = useMessage()
+
+const searchForm = ref({
+  pageNo: 1,
+  pageSize: 50,
+  phone: '',
+  name: ''
+})
+
+const tableData = ref([])
+const total = ref(0)
+
+const loading = ref(false)
+const showDialog = ref(false)
+
+const form = ref({
+  phone: '',
+  name: '',
+  mark: '',
+  discountId: undefined,
+  memberId: undefined
+})
+
+const rules = {
+  phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
+  name: [{ required: true, message: '请输入姓名', trigger: 'blur' }]
+}
+
+const vipOptions = ref([])
+const discountOptions = ref([])
+
+onMounted(() => {
+  searchList()
+  getVipDiscountOptions().then((response) => {
+    discountOptions.value = response
+  })
+  getVipTypeOptions({ carTypeId: searchForm.value.carTypeId }).then((response) => {
+    vipOptions.value = response
+  })
+})
+
+function searchList() {
+  searchForm.value.pageNo = 1
+  getList()
+}
+
+function getList() {
+  loading.value = true
+  getResellList(searchForm.value).then((response) => {
+    tableData.value = response.list
+    total.value = response.total
+    loading.value = false
+  })
+}
+
+function handleAdd() {
+  showDialog.value = true
+  form.value = {
+    phone: '',
+    name: '',
+    mark: '',
+    discountId: undefined,
+    memberId: undefined
+  }
+}
+function handleEdit(row) {
+  form.value = { ...row }
+  showDialog.value = true
+}
+
+const formRef = ref(null)
+async function handleSave() {
+  if (!formRef.value) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+
+  // 调用接口
+  if (form.value.distributionId) {
+    updateResell(form.value).then(() => {
+      message.success('修改成功')
+      showDialog.value = false
+      searchList()
+    })
+  } else {
+    addResell(form.value).then(() => {
+      message.success('新增成功')
+      showDialog.value = false
+      searchList()
+    })
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/Customer/Vip/components/Recharge.vue b/src/views/Customer/Vip/components/Recharge.vue
new file mode 100644
index 0000000..ad4f2ab
--- /dev/null
+++ b/src/views/Customer/Vip/components/Recharge.vue
@@ -0,0 +1,157 @@
+<template>
+  <div>
+    <el-form :model="searchForm" inline @submit.prevent>
+      <el-form-item>
+        <el-input v-model="searchForm.phone" placeholder="学员手机号" />
+      </el-form-item>
+      <el-form-item>
+        <el-select v-model="searchForm.memberId" placeholder="选择分销人" clearable filterable>
+          <el-option
+            v-for="item in vipOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-date-picker
+          v-model="searchForm.createDate"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="充值日期"
+          end-placeholder="充值日期"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="searchList">查询</el-button>
+      </el-form-item>
+    </el-form>
+    <el-table v-loading="loading" :data="tableList" height="calc(100vh - 260px)">
+      <el-table-column type="index" width="55" align="center" />
+      <el-table-column label="手机号" align="left" prop="phone" width="140" />
+      <el-table-column label="会员名" align="left" prop="memberName" min-width="140" />
+      <el-table-column label="支付金额" align="left" prop="operUser" min-width="100" />
+      <el-table-column label="车型" align="left" width="100">
+        <template #default="{ row }">
+          {{ row.carTypeId == 1001 ? '小车' : '摩托车' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="科目" align="left" prop="subjects" width="100" />
+      <el-table-column label="充值日期" align="left" prop="startDate" min-width="120" />
+      <el-table-column label="截止日期" align="left" prop="endDate" min-width="120" />
+      <el-table-column label="分销人" align="left" prop="useTypeName" min-width="100" />
+    </el-table>
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      v-model:page="searchForm.pageNo"
+      v-model:limit="searchForm.pageSize"
+      @pagination="getList"
+    />
+
+    <el-dialog title="赠送会员" v-model="showDialog" width="500px" :close-on-click-modal="false">
+      <el-form :model="form" ref="formRef" :rules="rules" label-width="80px">
+        <el-form-item label="手机号" prop="phone">
+          <el-input v-model="form.phone" maxlength="11" />
+        </el-form-item>
+        <el-form-item label="会员类型" prop="memberId">
+          <el-select v-model="form.memberId" clearable filterable style="width: 100%">
+            <el-option
+              v-for="item in vipOptions"
+              :key="item.memberId"
+              :label="item.memberName"
+              :value="item.memberId"
+            >
+              <span style="float: left">{{ item.memberName }}</span>
+              <span style="float: right; color: #aaa">{{ item.carName }}</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <span>
+          <el-button @click="showDialog = false">取消</el-button>
+          <el-button type="primary" @click="sureAdd">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Recharge">
+import { getUserMemberList, giveUserMember, getVipTypeOptions } from '@/api/xjapplet/vip'
+const message = useMessage()
+const searchForm = ref({
+  carTypeId: '1001',
+  memberId: undefined,
+  phone: undefined,
+  pageNo: 1,
+  pageSize: 50
+})
+
+const loading = ref(false)
+const tableList = ref([])
+const total = ref(0)
+
+const vipOptions = ref([])
+
+onMounted(() => {
+  changeCarType()
+})
+
+function changeCarType() {
+  getVipTypeOptions({ carTypeId: searchForm.value.carTypeId }).then((response) => {
+    vipOptions.value = response
+  })
+  searchList()
+}
+
+function searchList() {
+  searchForm.value.pageNo = 1
+  getList()
+}
+
+function getList() {
+  loading.value = true
+  getUserMemberList(searchForm.value).then((response) => {
+    tableList.value = response.list
+    total.value = response.total
+    loading.value = false
+  })
+}
+
+const showDialog = ref(false)
+const form = ref({
+  phone: '',
+  memberId: ''
+})
+const rules = ref({
+  phone: [{ required: true, message: '请输入用户手机号', trigger: 'blur' }],
+  memberId: [{ required: true, message: '请选择会员类型', trigger: 'change' }]
+})
+
+function addVipUser() {
+  showDialog.value = true
+}
+
+const formRef = ref(null)
+async function sureAdd() {
+  if (!formRef.value) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+
+  giveUserMember(form.value).then((response) => {
+    if (response) {
+      message.success('赠送成功')
+      showDialog.value = false
+      searchList()
+    } else {
+      message.error('赠送失败')
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/Customer/Vip/components/UserDiscount.vue b/src/views/Customer/Vip/components/UserDiscount.vue
new file mode 100644
index 0000000..bee8940
--- /dev/null
+++ b/src/views/Customer/Vip/components/UserDiscount.vue
@@ -0,0 +1,144 @@
+<template>
+  <div>
+    <el-form :model="searchForm" inline @submit.prevent>
+      <el-form-item>
+        <el-input v-model="searchForm.phone" placeholder="学员手机号" />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="searchList" v-hasPermi="['xj-applet:vip:user-discount:search']"
+          >查询</el-button
+        >
+        <el-button
+          type="primary"
+          @click="addVipUser"
+          v-hasPermi="['xj-applet:vip:user-discount:send']"
+          >赠送折扣</el-button
+        >
+      </el-form-item>
+    </el-form>
+    <el-table v-loading="loading" :data="tableList">
+      <el-table-column type="index" width="55" align="center" />
+      <el-table-column label="手机号" align="left" prop="phone" width="140" />
+      <el-table-column label="折扣描述" align="left" prop="description" min-width="140" />
+      <el-table-column label="折后价格" align="center" prop="discount" width="100" />
+      <el-table-column
+        label="截止时间"
+        align="left"
+        prop="endTime"
+        :formatter="dateFormatter"
+        width="150"
+      />
+      <el-table-column label="操作人" align="left" prop="operUser" width="100" />
+      <el-table-column label="操作时间" align="left" prop="operTime" width="150" />
+    </el-table>
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      v-model:page="searchForm.pageNo"
+      v-model:limit="searchForm.pageSize"
+      @pagination="getList"
+    />
+
+    <el-dialog title="赠送折扣" v-model="showDialog" width="500px" :close-on-click-modal="false">
+      <el-form :model="form" ref="formRef" :rules="rules" label-width="80px">
+        <el-form-item label="手机号" prop="phone">
+          <el-input v-model="form.phone" maxlength="11" />
+        </el-form-item>
+        <el-form-item label="折扣描述" prop="discountId">
+          <el-select v-model="form.discountId" clearable filterable style="width: 100%">
+            <el-option
+              v-for="item in discountOptions"
+              :key="item.id"
+              :label="item.description"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <span>
+          <el-button @click="showDialog = false">取消</el-button>
+          <el-button type="primary" @click="sureAdd">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="UserDiscount">
+import {
+  getUserDiscountList,
+  giveUserDiscount,
+  getVipDiscountOptions
+} from '@/api/xjapplet/discount'
+import { dateFormatter } from '@/utils/formatTime'
+
+const message = useMessage()
+
+const searchForm = ref({
+  phone: '',
+  pageNo: 1,
+  pageSize: 50
+})
+
+const loading = ref(false)
+const tableList = ref([])
+const total = ref(0)
+
+const discountOptions = ref([])
+
+onMounted(() => {
+  searchList()
+  getVipDiscountOptions().then((response) => {
+    discountOptions.value = response
+  })
+})
+function searchList() {
+  searchForm.value.pageNo = 1
+  getList()
+}
+
+function getList() {
+  loading.value = true
+  getUserDiscountList(searchForm.value).then((response) => {
+    tableList.value = response.list
+    total.value = response.total
+    loading.value = false
+  })
+}
+
+const showDialog = ref(false)
+const form = ref({
+  phone: '',
+  discountId: ''
+})
+const rules = ref({
+  phone: [{ required: true, message: '请输入用户手机号', trigger: 'blur' }],
+  discountId: [{ required: true, message: '请选择会员类型', trigger: 'change' }]
+})
+
+function addVipUser() {
+  showDialog.value = true
+}
+
+const formRef = ref(null)
+async function sureAdd() {
+  if (!formRef.value) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+
+  // 调用接口
+  giveUserDiscount(form.value).then((response) => {
+    if (response) {
+      message.success('赠送成功')
+      showDialog.value = false
+      searchList()
+    } else {
+      message.error('赠送失败')
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/Customer/Vip/components/VipDiscount.vue b/src/views/Customer/Vip/components/VipDiscount.vue
new file mode 100644
index 0000000..875e731
--- /dev/null
+++ b/src/views/Customer/Vip/components/VipDiscount.vue
@@ -0,0 +1,286 @@
+<template>
+  <div>
+    <el-row>
+      <el-button @click="searchList" v-hasPermi="['xj-applet:vip:vip-discount:search']"
+        >查询</el-button
+      >
+      <el-button type="primary" @click="addDiscount" v-hasPermi="['xj-applet:vip:vip-discount:add']"
+        >新增折扣</el-button
+      >
+    </el-row>
+
+    <el-table v-loading="loading" :data="tableList">
+      <el-table-column type="index" width="55" align="center" />
+      <el-table-column label="折扣描述" align="center" prop="description" min-width="140" />
+      <el-table-column label="折扣价" align="center" prop="discount" />
+      <el-table-column label="有效期" align="center" prop="duration" />
+      <el-table-column label="单位" align="center">
+        <template #default="{ row }">
+          <el-tag v-if="row.unit == 1">天</el-tag>
+          <el-tag v-else type="danger">年</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template #default="{ row }">
+          <el-button
+            type="primary"
+            link
+            @click="editDiscount(row)"
+            v-hasPermi="['xj-applet:vip:vip-discount:update']"
+            >修改</el-button
+          >
+          <el-button
+            type="primary"
+            link
+            @click="deleteDiscount(row)"
+            v-hasPermi="['xj-applet:vip:vip-discount:delete']"
+            >删除</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      v-model:page="searchForm.pageNo"
+      v-model:limit="searchForm.pageSize"
+      @pagination="getList"
+    />
+
+    <el-dialog title="折扣详情" v-model="showDialog" width="500px" :close-on-click-modal="false">
+      <el-form :model="form" ref="formRef" :rules="rules" label-width="80px">
+        <el-form-item label="折扣类型" prop="discountType">
+          <el-select v-model="form.discountType" style="width: 100%" :disabled="!!form.id">
+            <el-option
+              v-for="item in discountTypeOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="折扣描述">
+          {{ discountDesc }}
+        </el-form-item>
+        <el-form-item v-if="form.discountType == 1" label="科目" prop="subject">
+          <el-select v-model="form.subject" style="width: 100%" :disabled="!!form.id">
+            <el-option
+              v-for="item in subjectOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="变量1" prop="param1">
+          <el-select v-model="form.param1" style="width: 100%" clearable :disabled="!!form.id">
+            <el-option
+              v-for="item in vipTypeOptions"
+              :key="item.memberId"
+              :label="item.memberName"
+              :value="item.memberId"
+            >
+              <span style="float: left">{{ item.memberName }}</span>
+              <span style="float: right; color: #aaa">{{ item.carName }}</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="变量2" prop="param2">
+          <el-select v-model="form.param2" style="width: 100%" :disabled="!!form.id">
+            <el-option
+              v-for="item in vipTypeOptions"
+              :key="item.memberId"
+              :label="item.memberName"
+              :value="item.memberId"
+            >
+              <span style="float: left">{{ item.memberName }}</span>
+              <span style="float: right; color: #aaa">{{ item.carName }}</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="折扣价" prop="discount">
+          <el-input v-model="form.discount" type="number" />
+        </el-form-item>
+        <el-form-item label="有效期" prop="duration">
+          <el-input v-model="form.duration" type="number" />
+        </el-form-item>
+        <el-form-item label="单位" prop="unit">
+          <el-radio-group v-model="form.unit">
+            <el-radio v-for="(item, index) in unitOptions" :key="index" :label="item.value">{{
+              item.label
+            }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <span>
+          <el-button @click="showDialog = false">取消</el-button>
+          <el-button type="primary" @click="sureAdd">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="VipDiscount">
+import {
+  getVipDiscountList,
+  addVipDiscount,
+  updateVipDiscount,
+  deleteVipDiscount
+} from '@/api/xjapplet/discount'
+
+import { getVipTypeOptions } from '@/api/xjapplet/vip'
+
+const message = useMessage()
+
+const searchForm = ref({
+  pageNo: 1,
+  pageSize: 50
+})
+
+onMounted(() => {
+  searchList()
+
+  getVipTypeOptions().then((response) => {
+    vipTypeOptions.value = response
+  })
+})
+
+const loading = ref(false)
+const tableList = ref([])
+const total = ref(0)
+
+const discountTypeOptions = [
+  {
+    label: '【$科目】模考首次通过后,在拥有【$会员名1】的情况下,购买【$会员名2】,享折扣价',
+    value: '1'
+  },
+  { label: '之前拥有过【$会员名1】,购买【$会员名2】,享折扣价', value: '2' }
+]
+
+const subjectOptions = [
+  { label: '科一', value: '1' },
+  { label: '科四', value: '4' }
+]
+
+const unitOptions = [
+  { label: '天', value: '1' },
+  { label: '年', value: '3' }
+]
+
+const discountDesc = computed(() => {
+  const vipTypeObj = {}
+  vipTypeOptions.value.forEach((item) => {
+    vipTypeObj[item.memberId] = `${item.memberName}(${item.carName})`
+  })
+  const { discountType, subject, param1, param2, discount, duration, unit } = form.value
+  let baseStr = discountTypeOptions.find((item) => item.value == discountType).label
+  const vip1 = param1 ? vipTypeObj[param1] : '无需会员'
+  const vip2 = param2 ? vipTypeObj[param2] : '会员名2'
+  return baseStr
+    .replace('$科目', subjectOptions.find((item) => item.value == subject).label)
+    .replace('$会员名1', vip1)
+    .replace('$会员名2', vip2)
+    .concat(
+      `${discount || ''},有效期${duration || 0}${
+        unitOptions.find((item) => item.value == unit).label
+      }`
+    )
+})
+
+onMounted(() => {
+  searchList()
+})
+
+const vipTypeOptions = ref([])
+function searchList() {
+  searchForm.value.pageNo = 1
+  getList()
+}
+
+function getList() {
+  loading.value = true
+  getVipDiscountList(searchForm.value).then((response) => {
+    tableList.value = response.list
+    total.value = response.total
+    loading.value = false
+  })
+}
+
+const showDialog = ref(false)
+const form = ref({
+  discountType: '1',
+  subject: '1',
+  param1: undefined,
+  param2: undefined,
+  discount: undefined,
+  duration: undefined,
+  unit: '1',
+  description: undefined
+})
+const rules = ref({
+  param2: [{ required: true, message: '请选择变量2', trigger: 'change' }],
+  discount: [{ required: true, message: '请输入折扣价', trigger: 'blur' }],
+  duration: [{ required: true, message: '请输入有效期', trigger: 'blur' }]
+})
+
+function addDiscount() {
+  showDialog.value = true
+  form.value = {
+    discountType: '1',
+    subject: '1',
+    param1: undefined,
+    param2: undefined,
+    discount: undefined,
+    duration: undefined,
+    unit: '1',
+    description: undefined
+  }
+}
+
+function editDiscount(row) {
+  form.value = {
+    ...row,
+    param1: row.param1 ? Number(row.param1) : undefined,
+    param2: row.param2 ? Number(row.param2) : undefined
+  }
+  showDialog.value = true
+}
+
+function deleteDiscount(row) {
+  message.confirm('确定删除该折扣?').then(() => {
+    deleteVipDiscount(row.id).then(() => {
+      message.success('删除成功')
+      searchList()
+    })
+  })
+}
+
+const formRef = ref(null)
+async function sureAdd() {
+  if (!formRef.value) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+
+  form.value.description = discountDesc.value
+
+  // 调用接口
+  if (form.value.id) {
+    updateVipDiscount(form.value).then(() => {
+      showDialog.value = false
+      message.success('修改成功')
+      searchList()
+    })
+  } else {
+    addVipDiscount(form.value).then(() => {
+      showDialog.value = false
+      message.success('新增成功')
+      searchList()
+    })
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/Customer/Vip/components/VipType.vue b/src/views/Customer/Vip/components/VipType.vue
new file mode 100644
index 0000000..beb17b2
--- /dev/null
+++ b/src/views/Customer/Vip/components/VipType.vue
@@ -0,0 +1,213 @@
+<template>
+  <div>
+    <el-form :model="searchForm" inline @submit.prevent>
+      <el-form-item>
+        <el-input v-model="searchForm.memberName" placeholder="会员名称" />
+      </el-form-item>
+      <el-form-item>
+        <el-radio-group v-model="searchForm.carTypeId" @change="searchList">
+          <el-radio label="1001">小车</el-radio>
+          <el-radio label="1002">摩托车</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="searchList">查询</el-button>
+        <el-button type="primary" @click="addVip">新增会员类型</el-button>
+      </el-form-item>
+    </el-form>
+    <el-table v-loading="loading" :data="tableList" height="calc(100vh - 260px)">
+      <el-table-column type="index" width="55" align="center" />
+      <el-table-column label="会员名" align="center" prop="memberName" min-width="140" />
+      <el-table-column label="车型" align="center" prop="carName" />
+      <el-table-column label="科目" align="center" prop="subjects" />
+      <el-table-column label="原价" align="center" prop="price" />
+      <el-table-column label="折扣价" align="center" prop="discount" />
+      <el-table-column label="有效期" align="center" prop="duration" />
+      <el-table-column label="单位" align="center">
+        <template #default="{ row }">
+          <el-tag v-if="row.unit == 1">天</el-tag>
+          <el-tag v-else type="danger">年</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="使用方式" align="center" prop="useTypeName" />
+      <el-table-column label="操作" width="160">
+        <template #default="{ row }">
+          <el-button
+            type="primary"
+            link
+            @click="editVip(row)"
+            v-hasPermi="['xj-applet:vip:vip-type:update']"
+            >修改</el-button
+          >
+          <el-button
+            type="primary"
+            link
+            @click="handleDelete(row.memberId)"
+            v-hasPermi="['xj-applet:vip:vip-type:delete']"
+            >删除</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      v-model:page="searchForm.pageNo"
+      v-model:limit="searchForm.pageSize"
+      @pagination="getList"
+    />
+
+    <el-dialog title="会员详情" v-model="showDialog" width="500px" :close-on-click-modal="false">
+      <el-form :model="form" ref="formRef" :rules="rules" label-width="80px">
+        <el-form-item label="会员名称" prop="memberName">
+          <el-input v-model="form.memberName" />
+        </el-form-item>
+        <el-form-item label="车型" prop="carTypeId">
+          <el-select v-model="form.carTypeId" style="width: 100%">
+            <el-option label="小车" value="1001" />
+            <el-option label="摩托车" value="1002" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="科目" prop="subjects">
+          <el-select v-model="form.subjects" placeholder="多选" multiple style="width: 100%">
+            <el-option label="科一" value="4" />
+            <el-option label="科四" value="1" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="原价" prop="price">
+          <el-input v-model="form.price" type="number" />
+        </el-form-item>
+        <el-form-item label="折扣价" prop="discount">
+          <el-input v-model="form.discount" type="number" />
+        </el-form-item>
+        <el-form-item label="有效期" prop="duration">
+          <el-input v-model="form.duration" type="number" />
+        </el-form-item>
+        <el-form-item label="单位" prop="unit">
+          <el-radio-group v-model="form.unit">
+            <el-radio :label="1">天</el-radio>
+            <el-radio :label="3">年</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="使用方式" prop="useTypes">
+          <el-checkbox-group v-model="form.useTypes">
+            <el-checkbox :label="1"> 用户购买 </el-checkbox>
+            <el-checkbox :label="2"> 客服赠送 </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <span>
+          <el-button @click="showDialog = false">取消</el-button>
+          <el-button type="primary" @click="sureAdd">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="VipType">
+import { getVipTypeList, addVipType, updateVipType, deleteVipType } from '@/api/xjapplet/vip'
+const message = useMessage()
+const searchForm = ref({
+  memberName: undefined,
+  carTypeId: '1001',
+  pageNo: 1,
+  pageSize: 50
+})
+
+const loading = ref(false)
+const tableList = ref([])
+const total = ref(0)
+
+onMounted(() => {
+  searchList()
+})
+function searchList() {
+  searchForm.value.pageNo = 1
+  getList()
+}
+
+function getList() {
+  loading.value = true
+  getVipTypeList(searchForm.value).then((response) => {
+    tableList.value = response.list
+    total.value = response.total
+    loading.value = false
+  })
+}
+
+const showDialog = ref(false)
+const form = ref({
+  memberName: '',
+  carTypeId: undefined,
+  subjects: [],
+  price: '',
+  discount: '',
+  duration: '',
+  unit: 1,
+  useTypes: [1]
+})
+const rules = ref({
+  memberName: [{ required: true, message: '请输入会员名称', trigger: 'blur' }],
+  carTypeId: [{ required: true, message: '请输入车型', trigger: 'change' }],
+  subjects: [{ required: true, message: '请输入科目', trigger: 'blur' }],
+  price: [{ required: true, message: '请输入价格', trigger: 'blur' }],
+  discount: [{ required: true, message: '请输入折扣价', trigger: 'blur' }],
+  duration: [{ required: true, message: '请输入有效期', trigger: 'blur' }]
+})
+
+function addVip() {
+  showDialog.value = true
+
+  form.value.carTypeId = {
+    memberName: '',
+    carTypeId: searchForm.value.carTypeId,
+    subjects: [],
+    price: '',
+    discount: '',
+    duration: '',
+    unit: 1,
+    useTypes: [1]
+  }
+}
+
+function editVip(row) {
+  form.value = { ...row, subjects: row.subjects.split(','), unit: Number(row.unit) }
+  showDialog.value = true
+}
+
+const formRef = ref(null)
+async function sureAdd() {
+  if (!formRef.value) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+
+  // 调用接口
+  if (form.value.memberId) {
+    updateVipType(form.value).then(() => {
+      message.success('修改成功')
+      showDialog.value = false
+      searchList()
+    })
+  } else {
+    addVipType(form.value).then(() => {
+      message.success('添加成功')
+      showDialog.value = false
+      searchList()
+    })
+  }
+}
+
+function handleDelete(id) {
+  message.confirm('是否确认删除该会员?').then(() => {
+    deleteVipType(id).then(() => {
+      message.success('删除成功')
+      searchList()
+    })
+  })
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/Customer/Vip/components/VipUser.vue b/src/views/Customer/Vip/components/VipUser.vue
new file mode 100644
index 0000000..fe5401f
--- /dev/null
+++ b/src/views/Customer/Vip/components/VipUser.vue
@@ -0,0 +1,155 @@
+<template>
+  <div>
+    <el-form :model="searchForm" inline @submit.prevent>
+      <el-form-item>
+        <el-input v-model="searchForm.phone" placeholder="学员手机号" />
+      </el-form-item>
+      <el-form-item>
+        <el-radio-group v-model="searchForm.carTypeId" @change="changeCarType">
+          <el-radio label="1001">小车</el-radio>
+          <el-radio label="1002">摩托车</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <!-- <el-form-item>
+        <el-select v-model="searchForm.memberId" placeholder="选择会员类型" clearable filterable>
+          <el-option
+            v-for="item in vipOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item> -->
+      <el-form-item>
+        <el-button @click="searchList">查询</el-button>
+        <el-button type="primary" @click="addVipUser">赠送会员</el-button>
+      </el-form-item>
+    </el-form>
+    <el-table v-loading="loading" :data="tableList" height="calc(100vh - 260px)">
+      <el-table-column type="index" width="55" align="center" />
+      <el-table-column label="手机号" align="left" prop="phone" min-width="140" />
+      <el-table-column label="会员名" align="left" prop="memberName" min-width="140" />
+      <el-table-column label="车型" align="left" min-width="100">
+        <template #default="{ row }">
+          {{ row.carTypeId == 1001 ? '小车' : '摩托车' }}
+        </template>
+      </el-table-column>
+      <el-table-column label="科目" align="left" prop="subjects" min-width="100" />
+      <el-table-column label="来源" align="left" prop="useTypeName" min-width="100" />
+      <el-table-column label="截止日期" align="left" prop="endDate" min-width="120" />
+      <el-table-column label="操作人" align="left" prop="operUser" min-width="100" />
+      <el-table-column label="操作时间" align="left" prop="createTime" min-width="120" />
+    </el-table>
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      v-model:page="searchForm.pageNo"
+      v-model:limit="searchForm.pageSize"
+      @pagination="getList"
+    />
+
+    <el-dialog title="赠送会员" v-model="showDialog" width="500px" :close-on-click-modal="false">
+      <el-form :model="form" ref="formRef" :rules="rules" label-width="80px">
+        <el-form-item label="手机号" prop="phone">
+          <el-input v-model="form.phone" maxlength="11" />
+        </el-form-item>
+        <el-form-item label="会员类型" prop="memberId">
+          <el-select v-model="form.memberId" clearable filterable style="width: 100%">
+            <el-option
+              v-for="item in vipOptions"
+              :key="item.memberId"
+              :label="item.memberName"
+              :value="item.memberId"
+            >
+              <span style="float: left">{{ item.memberName }}</span>
+              <span style="float: right; color: #aaa">{{ item.carName }}</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+
+      <template #footer>
+        <span>
+          <el-button @click="showDialog = false">取消</el-button>
+          <el-button type="primary" @click="sureAdd">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="VipUser">
+import { getUserMemberList, giveUserMember, getVipTypeOptions } from '@/api/xjapplet/vip'
+const message = useMessage()
+const searchForm = ref({
+  carTypeId: '1001',
+  memberId: undefined,
+  phone: undefined,
+  pageNo: 1,
+  pageSize: 50
+})
+
+const loading = ref(false)
+const tableList = ref([])
+const total = ref(0)
+
+const vipOptions = ref([])
+
+onMounted(() => {
+  changeCarType()
+})
+
+function changeCarType() {
+  getVipTypeOptions({ carTypeId: searchForm.value.carTypeId }).then((response) => {
+    vipOptions.value = response
+  })
+  searchList()
+}
+
+function searchList() {
+  searchForm.value.pageNo = 1
+  getList()
+}
+
+function getList() {
+  loading.value = true
+  getUserMemberList(searchForm.value).then((response) => {
+    tableList.value = response.list
+    total.value = response.total
+    loading.value = false
+  })
+}
+
+const showDialog = ref(false)
+const form = ref({
+  phone: '',
+  memberId: ''
+})
+const rules = ref({
+  phone: [{ required: true, message: '请输入用户手机号', trigger: 'blur' }],
+  memberId: [{ required: true, message: '请选择会员类型', trigger: 'change' }]
+})
+
+function addVipUser() {
+  showDialog.value = true
+}
+
+const formRef = ref(null)
+async function sureAdd() {
+  if (!formRef.value) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+
+  giveUserMember(form.value).then((response) => {
+    if (response) {
+      message.success('赠送成功')
+      showDialog.value = false
+      searchList()
+    } else {
+      message.error('赠送失败')
+    }
+  })
+}
+</script>
+
+<style lang="scss" scoped></style>
diff --git a/src/views/Customer/Vip/index.vue b/src/views/Customer/Vip/index.vue
new file mode 100644
index 0000000..3eef263
--- /dev/null
+++ b/src/views/Customer/Vip/index.vue
@@ -0,0 +1,42 @@
+<template>
+  <div>
+    <el-tabs v-model="tabIndex">
+      <el-tab-pane label="会员用户" :name="1" v-if="checkPermi(['customer:applet:vip:vip-user'])">
+        <VipUser v-if="tabIndex == 1" />
+      </el-tab-pane>
+      <el-tab-pane label="会员类型" :name="2" v-if="checkPermi(['customer:applet:vip:vip-type'])">
+        <VipType v-if="tabIndex == 2" />
+      </el-tab-pane>
+      <!-- <el-tab-pane
+        label="会员折扣"
+        :name="3"
+        v-if="checkPermi(['customer-applet:vip:vip-discount'])"
+      >
+        <VipDiscount v-if="tabIndex == 3" />
+      </el-tab-pane>
+      <el-tab-pane
+        label="用户折扣"
+        :name="4"
+        v-if="checkPermi(['customer-applet:vip:user-discount'])"
+      >
+        <UserDiscount v-if="tabIndex == 4" />
+      </el-tab-pane> -->
+      <el-tab-pane label="充值记录" :name="5" v-if="checkPermi(['customer:applet:vip:recharge'])">
+        <Recharge v-if="tabIndex == 5" />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script setup name="Vip">
+import { checkPermi } from '@/utils/permission'
+// import UserDiscount from './components/UserDiscount.vue'
+// import VipDiscount from './components/VipDiscount.vue'
+import VipType from './components/VipType.vue'
+import VipUser from './components/VipUser.vue'
+import Recharge from './components/Recharge.vue'
+
+const tabIndex = ref(1)
+</script>
+
+<style lang="scss" scoped></style>