Browse Source

用户、任务、奖励、推进器完成

st 2 months ago
parent
commit
136656a27c

+ 21 - 0
src/apis/production/thruster.ts

@@ -0,0 +1,21 @@
+import type * as T from '@/apis/production/type'
+import http from '@/utils/http'
+
+const BASE_URL = '/coin/speed'
+
+export function listThruster(query: T.ThrusterPageQuery) {
+  return http.get<PageRes<T.ThrusterListResp[]>>(`${BASE_URL}/upgrades/page`, query)
+}
+export function thrusterById(id: number) {
+  return http.get<T.ThrusterListResp[]>(`${BASE_URL}/upgrades/${id}`)
+}
+export function thrusterPost(data: T.ThrusterData) {
+  return http.post<any>(`${BASE_URL}/upgrades`, data)
+}
+
+export function thrusterUpdata(id: number, data: T.ThrusterData) {
+  return http.put<any>(`${BASE_URL}/upgrades/${id}`, data)
+}
+export function thrusterDel(id: number) {
+  return http.del<any>(`${BASE_URL}/upgrades/${id}`)
+}

+ 22 - 0
src/apis/production/type.ts

@@ -0,0 +1,22 @@
+// thruster
+
+export interface ThrusterListResp {
+  id: number
+  consumeGoldCoin: number
+  realMoney: number
+  numericalValue: string
+  level: number
+  createdBy: string
+  createdTime: string
+  updatedBy: string
+  updatedTime: string
+}
+
+export interface ThrusterData {
+  consumeGoldCoin: number
+  realMoney: number
+  numericalValue: string
+  level: number
+}
+export interface ThrusterPageQuery extends PageQuery {
+}

+ 21 - 0
src/apis/task/taskhome.ts

@@ -0,0 +1,21 @@
+import type * as T from '@/apis/task/type'
+import http from '@/utils/http'
+
+const BASE_URL = '/coin/task'
+
+export function listTask(query: T.TaskPageQuery) {
+  return http.get<PageRes<T.TaskListResp[]>>(`${BASE_URL}/info/page`, query)
+}
+export function taskById(id: number) {
+  return http.get<T.TaskListResp[]>(`${BASE_URL}/info/${id}`)
+}
+export function taskPost(data: T.TaskData) {
+  return http.post<any>(`${BASE_URL}/info`, data)
+}
+
+export function taskUpdata(id: number, data: T.TaskData) {
+  return http.put<any>(`${BASE_URL}/info/${id}`, data)
+}
+export function taskDel(id: number) {
+  return http.del<any>(`${BASE_URL}/info/${id}`)
+}

+ 56 - 0
src/apis/task/type.ts

@@ -0,0 +1,56 @@
+// task
+export interface TaskListResp {
+  id: number
+  name: string
+  nameJa: string
+  nameKo: string
+  nameTh: string
+  nameVi: string
+  nameRu: string
+  nameEn: string
+  namePt: string
+  nameFa: string
+  nameEs: string
+  instruction: string
+  frequency: number
+  taskType: string
+  random: number
+  loopDay: number
+  taskIcon: string
+  key: number
+  taskLink: string
+  categoryCode: string
+  status: number
+  goldCoin: number
+  sortNum: number
+  delFlag: number
+  createBy: number
+  createTime: string
+  updatedTime: string
+}
+export interface TaskQuery {
+  name?: string
+  taskType?: string
+  categoryCode?: number
+}
+export interface TaskData {
+  name: string
+  nameJa: string
+  nameKo: string
+  nameTh: string
+  nameVi: string
+  nameRu: string
+  nameEn: string
+  namePt: string
+  nameFa: string
+  nameEs: string
+  instruction: string
+  taskIcon: string
+  taskLink: string
+  categoryCode: string
+  status: number
+  goldCoin: number
+  sortNum: number
+}
+export interface TaskPageQuery extends TaskQuery, PageQuery {
+}

+ 30 - 1
src/apis/user/type.ts

@@ -1,3 +1,4 @@
+// user
 export interface UserListResp {
   id?: number
   tgId?: string
@@ -24,7 +25,7 @@ export interface UserListResp {
   mobile?: string
   sex?: string
   inviteCode?: string
-  referrerId?: string
+  referrerName?: string
   inviteReward?: number
   newUserFlag?: number
   historyKey?: number
@@ -46,3 +47,31 @@ export interface UserQuery {
 
 export interface UserPageQuery extends UserQuery, PageQuery {
 }
+// jiangli
+export interface JiangliListResp {
+  id: number
+  rewardsName: string
+  rewardsDescribe: string
+  inviteNum: number
+  goldCoinNum: number
+  rewardsLevel: number
+  createdBy: string
+  createdTime: string
+  updatedBy: string
+  updatedTime: string
+}
+export interface JiangliQuery {
+  rewardsName?: string
+  rewardsDescribe?: string
+  inviteNum?: number
+  goldCoinNum?: number
+}
+export interface JiangliData {
+  rewardsName?: string
+  rewardsDescribe?: string
+  rewardsLevel?: number
+  inviteNum?: number
+  goldCoinNum?: number
+}
+export interface JiangliPageQuery extends JiangliQuery, PageQuery {
+}

+ 18 - 0
src/apis/user/userlist.ts

@@ -2,6 +2,24 @@ import type * as T from '@/apis/user/type'
 import http from '@/utils/http'
 
 const BASE_URL = '/tgUser'
+const BASE_URL1 = '/invite/rewards'
 export function listUserTg(query: T.UserPageQuery) {
   return http.get<PageRes<T.UserListResp[]>>(`${BASE_URL}/getTgUserList`, query)
 }
+
+export function listJiangli(query: T.JiangliPageQuery) {
+  return http.get<PageRes<T.JiangliListResp[]>>(`${BASE_URL1}/rules/page`, query)
+}
+export function jiangliById(id: number) {
+  return http.get<T.JiangliListResp[]>(`${BASE_URL1}/rules/${id}`)
+}
+export function jiangliPost(data: T.JiangliData) {
+  return http.post<any>(`${BASE_URL1}/rules`, data)
+}
+
+export function jiangliUpdata(id: number, data: T.JiangliData) {
+  return http.put<any>(`${BASE_URL1}/rules/${id}`, data)
+}
+export function jiangliDel(id: number) {
+  return http.del<any>(`${BASE_URL1}/rules/${id}`)
+}

+ 11 - 1
src/constant/common.ts

@@ -1,4 +1,4 @@
-interface LabelValueItem { label: string, value: number, color?: string }
+interface LabelValueItem { label: string, value: number | string, color?: string }
 
 /** @desc 状态 */
 export const DisEnableStatusList: LabelValueItem[] = [
@@ -12,3 +12,13 @@ export const GenderList: LabelValueItem[] = [
   { label: '女', value: 2 },
   { label: '未知', value: 0 },
 ]
+
+export const TaskStatusList: LabelValueItem[] = [
+  { label: 'In-game', value: '1' },
+  { label: 'Partners', value: '2' },
+]
+
+export const TaskDisStatusList: LabelValueItem[] = [
+  { label: '开启', value: 1 },
+  { label: '关闭', value: 0 },
+]

+ 28 - 27
src/router/route.ts

@@ -21,15 +21,16 @@ export const systemRoutes: RouteRecordRaw[] = [
       {
         path: '/dashboard/workplace',
         name: 'Workplace',
-        component: () => import('@/views/dashboard/workplace/index.vue'),
-        meta: { title: '工作台', icon: 'desktop', hidden: false, affix: true },
-      },
-      {
-        path: '/dashboard/analysis',
-        name: 'Analysis',
+        // component: () => import('@/views/dashboard/workplace/index.vue'),
         component: () => import('@/views/dashboard/analysis/index.vue'),
-        meta: { title: '分析页', icon: 'insert-chart', hidden: false },
+        meta: { title: '工作台', icon: 'desktop', hidden: false, affix: true },
       },
+      // {
+      //   path: '/dashboard/analysis',
+      //   name: 'Analysis',
+      //   component: () => import('@/views/dashboard/analysis/index.vue'),
+      //   meta: { title: '分析页', icon: 'insert-chart', hidden: false },
+      // },
     ],
   },
   {
@@ -42,26 +43,26 @@ export const systemRoutes: RouteRecordRaw[] = [
     component: () => import('@/views/login/pwdExpired/index.vue'),
     meta: { hidden: true },
   },
-  {
-    path: '/setting',
-    name: 'Setting',
-    component: Layout,
-    meta: { hidden: true },
-    children: [
-      {
-        path: '/setting/profile',
-        name: 'SettingProfile',
-        component: () => import('@/views/setting/profile/index.vue'),
-        meta: { title: '个人中心', showInTabs: false },
-      },
-      {
-        path: '/setting/message',
-        name: 'SettingMessage',
-        component: () => import('@/views/setting/message/index.vue'),
-        meta: { title: '消息中心', showInTabs: false },
-      },
-    ],
-  },
+  // {
+  //   path: '/setting',
+  //   name: 'Setting',
+  //   component: Layout,
+  //   meta: { hidden: true },
+  //   children: [
+  //     {
+  //       path: '/setting/profile',
+  //       name: 'SettingProfile',
+  //       component: () => import('@/views/setting/profile/index.vue'),
+  //       meta: { title: '个人中心', showInTabs: false },
+  //     },
+  //     {
+  //       path: '/setting/message',
+  //       name: 'SettingMessage',
+  //       component: () => import('@/views/setting/message/index.vue'),
+  //       meta: { title: '消息中心', showInTabs: false },
+  //     },
+  //   ],
+  // },
   // {
   //   path: '/about',
   //   name: 'About',

+ 100 - 0
src/views/production/thruster/index.vue

@@ -0,0 +1,100 @@
+<template>
+  <div class="table-page">
+    <GiTable
+      row-key="id"
+      title="推进器列表"
+      :data="dataList"
+      :columns="columns"
+      :loading="loading"
+      :scroll="{ x: '100%', y: '100%', minWidth: 1200 }"
+      :pagination="pagination"
+      :disabled-tools="['size']"
+      :disabled-column-keys="['title']"
+      @refresh="search"
+    >
+      <template #toolbar-left>
+        <a-button @click="reset">
+          <template #icon><icon-refresh /></template>
+          <template #default>重置</template>
+        </a-button>
+      </template>
+      <template #toolbar-right>
+        <a-button type="primary" @click="onAdd">
+          <template #icon><icon-plus /></template>
+          <template #default>新增</template>
+        </a-button>
+      </template>
+      <template #action="{ record }">
+        <a-space>
+          <a-link title="修改" @click="onUpdate(record)">修改</a-link>
+          <a-link status="danger" title="删除" @click="onDelete(record)"> 删除 </a-link>
+        </a-space>
+      </template>
+    </GiTable>
+    <ThrusterAddModal ref="ThrusterAddModalRef" @save-success="search"></ThrusterAddModal>
+  </div>
+</template>
+
+<script setup lang="ts">
+import ThrusterAddModal from './thrusterAddModal.vue'
+import type { TableInstanceColumns } from '@/components/GiTable/type'
+import { useTable } from '@/hooks'
+import { isMobile } from '@/utils'
+import has from '@/utils/has'
+import { listThruster, thrusterDel } from '@/apis/production/thruster'
+import type { ThrusterListResp } from '@/apis/production/type'
+
+defineOptions({ name: 'Thruster' })
+
+const {
+  tableData: dataList,
+  loading,
+  pagination,
+  search,
+  handleDelete,
+} = useTable((page) => listThruster({ ...page }), { immediate: true })
+const columns: TableInstanceColumns[] = [
+  {
+    title: '序号',
+    width: 66,
+    align: 'center',
+    render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
+  },
+  { title: '消耗真实货币数量', dataIndex: 'realMoney', slotName: 'realMoney', align: 'center', width: 180 },
+  { title: '消耗金币数量', dataIndex: 'consumeGoldCoin', slotName: 'consumeGoldCoin', align: 'center', width: 180 },
+  { title: '每秒产量', dataIndex: 'numericalValue', slotName: 'numericalValue', align: 'center', width: 180 },
+  { title: 'level', dataIndex: 'level', slotName: 'level', align: 'center', width: 180 },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    slotName: 'action',
+    width: 350,
+    align: 'center',
+    fixed: !isMobile() ? 'right' : undefined,
+    show: has.hasPermOr(['system:notice:detail', 'system:notice:update', 'system:notice:delete']),
+  },
+]
+
+// 重置
+const reset = () => {
+  search()
+}
+
+// 新增
+const ThrusterAddModalRef = ref<InstanceType<typeof ThrusterAddModal>>()
+const onAdd = () => {
+  ThrusterAddModalRef.value?.onAdd()
+}
+const onUpdate = (record: ThrusterListResp) => {
+  ThrusterAddModalRef.value?.onUpdate(record.id)
+}
+
+const onDelete = (record: ThrusterListResp) => {
+  return handleDelete(() => thrusterDel(record.id), {
+    content: `是否确定删除推进器「${record.level}」?`,
+    showModal: true,
+  })
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 95 - 0
src/views/production/thruster/thrusterAddModal.vue

@@ -0,0 +1,95 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    :title="title"
+    :mask-closable="false"
+    :esc-to-close="false"
+    :width="width >= 500 ? 500 : '100%'"
+    draggable
+    @before-ok="save"
+    @close="reset"
+  >
+    <GiForm ref="formRef" v-model="form" :options="options" :columns="columns">
+    </GiForm>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import { Message } from '@arco-design/web-vue'
+import { useWindowSize } from '@vueuse/core'
+import type { Columns, GiForm, Options } from '@/components/GiForm'
+
+import { useResetReactive } from '@/hooks'
+import { thrusterById, thrusterPost, thrusterUpdata } from '@/apis/production/thruster'
+
+const emit = defineEmits<{
+  (e: 'save-success'): void
+}>()
+
+const { width } = useWindowSize()
+
+const dataId = ref()
+const visible = ref(false)
+const isUpdate = computed(() => !!dataId.value)
+const title = computed(() => (isUpdate.value ? '修改推进器' : '新增推进器'))
+const formRef = ref<InstanceType<typeof GiForm>>()
+
+const [form, resetForm] = useResetReactive({
+
+})
+const options: Options = {
+  form: { size: 'large' },
+  btns: { hide: true },
+}
+const columns: Columns = reactive([
+  { label: '消耗金币数量', field: 'consumeGoldCoin', type: 'input', rules: [{ required: true, message: '请输入消耗金币数量' }] },
+  { label: '消耗真实货币数量', field: 'realMoney', type: 'input', rules: [{ required: true, message: '请输入消耗真实货币数量' }] },
+  { label: '每秒产量', field: 'numericalValue', type: 'input', rules: [{ required: true, message: '请输入每秒产量' }] },
+  { label: '等级', field: 'level', type: 'input', rules: [{ required: true, message: '请输入等级' }] },
+])
+
+// 重置
+const reset = () => {
+  formRef.value?.formRef?.resetFields()
+  resetForm()
+}
+
+// 保存
+const save = async () => {
+  try {
+    const isInvalid = await formRef.value?.formRef?.validate()
+    if (isInvalid) return false
+    if (isUpdate.value) {
+      await thrusterUpdata(dataId.value, form)
+      Message.success('修改成功')
+    } else {
+      await thrusterPost({ ...form })
+      Message.success('新增成功')
+    }
+    emit('save-success')
+    return true
+  } catch (error) {
+    return false
+  }
+}
+
+// 新增
+const onAdd = () => {
+  reset()
+  dataId.value = null
+  visible.value = true
+}
+
+// 修改
+const onUpdate = async (id: number) => {
+  reset()
+  dataId.value = id
+  const { data } = await thrusterById(id)
+  Object.assign(form, data[0])
+  visible.value = true
+}
+
+defineExpose({ onAdd, onUpdate })
+</script>
+
+<style scoped lang="scss"></style>

+ 126 - 0
src/views/task/index.vue

@@ -0,0 +1,126 @@
+<template>
+  <div class="table-page">
+    <GiTable
+      row-key="id"
+      title="任务列表"
+      :data="dataList"
+      :columns="columns"
+      :loading="loading"
+      :scroll="{ x: '100%', y: '100%', minWidth: 1200 }"
+      :pagination="pagination"
+      :disabled-tools="['size']"
+      :disabled-column-keys="['title']"
+      @refresh="search"
+    >
+      <template #toolbar-left>
+        <a-input-search v-model="queryForm.name" placeholder="任务名称" allow-clear @search="search" />
+        <a-select
+          v-model="queryForm.categoryCode"
+          :options="TaskStatusList"
+          placeholder="请选择任务分类"
+          allow-clear
+          style="width: 180px"
+          @change="search"
+        />
+        <a-button @click="reset">
+          <template #icon><icon-refresh /></template>
+          <template #default>重置</template>
+        </a-button>
+      </template>
+      <template #toolbar-right>
+        <a-button type="primary" @click="onAdd">
+          <template #icon><icon-plus /></template>
+          <template #default>新增</template>
+        </a-button>
+      </template>
+      <template #taskIcon="{ record }">
+        <a-avatar v-if="record.taskIcon">
+          <img :src="record.taskIcon" />
+        </a-avatar>
+      </template>
+      <template #passengerFlowWay="{ record }">
+        <GiCellTag :value="record.categoryCode" :dict="task_type" />
+      </template>
+      <template #action="{ record }">
+        <a-space>
+          <a-link title="修改" @click="onUpdate(record)">修改</a-link>
+          <a-link status="danger" title="删除" @click="onDelete(record)"> 删除 </a-link>
+        </a-space>
+      </template>
+    </GiTable>
+    <TaskAddModal ref="TaskAddModalRef" @save-success="search"></TaskAddModal>
+  </div>
+</template>
+
+<script setup lang="ts">
+import TaskAddModal from './taskAddModal.vue'
+import type { TableInstanceColumns } from '@/components/GiTable/type'
+import { useTable } from '@/hooks'
+import { isMobile } from '@/utils'
+import has from '@/utils/has'
+import type { TaskListResp, TaskQuery } from '@/apis/task/type'
+import { listTask, taskDel } from '@/apis/task/taskhome'
+import { TaskStatusList } from '@/constant/common'
+import { useDict } from '@/hooks/app'
+
+defineOptions({ name: 'Jliangli' })
+
+const queryForm = reactive<TaskQuery>({} as TaskQuery)
+const { task_type } = useDict('task_type')
+const {
+  tableData: dataList,
+  loading,
+  pagination,
+  search,
+  handleDelete,
+} = useTable((page) => listTask({ ...queryForm, ...page }), { immediate: true })
+const columns: TableInstanceColumns[] = [
+  {
+    title: '序号',
+    width: 66,
+    align: 'center',
+    render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
+  },
+  { title: '任务图标', dataIndex: 'taskIcon', slotName: 'taskIcon', align: 'center', width: 180 },
+  { title: '任务分类', dataIndex: 'categoryCode', slotName: 'categoryCode', align: 'center', width: 180 },
+
+  { title: '名称', dataIndex: 'name', slotName: 'name', align: 'center', width: 180 },
+  { title: '金币数量', dataIndex: 'goldCoin', slotName: 'goldCoin', align: 'center', width: 180 },
+  { title: '创建时间', dataIndex: 'createTime', slotName: 'createTime', align: 'center', width: 180 },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    slotName: 'action',
+    width: 350,
+    align: 'center',
+    fixed: !isMobile() ? 'right' : undefined,
+    show: has.hasPermOr(['system:notice:detail', 'system:notice:update', 'system:notice:delete']),
+  },
+]
+
+// 重置
+const reset = () => {
+  queryForm.name = undefined
+  queryForm.taskType = undefined
+  queryForm.categoryCode = undefined
+  search()
+}
+
+// 新增
+const TaskAddModalRef = ref<InstanceType<typeof TaskAddModal>>()
+const onAdd = () => {
+  TaskAddModalRef.value?.onAdd()
+}
+const onUpdate = (record: TaskListResp) => {
+  TaskAddModalRef.value?.onUpdate(record.id)
+}
+
+const onDelete = (record: TaskListResp) => {
+  return handleDelete(() => taskDel(record.id), {
+    content: `是否确定任务「${record.name}」?`,
+    showModal: true,
+  })
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 177 - 0
src/views/task/taskAddModal.vue

@@ -0,0 +1,177 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    :title="title"
+    :mask-closable="false"
+    :esc-to-close="false"
+    :width="width >= 500 ? 500 : '100%'"
+    draggable
+    @before-ok="save"
+    @close="reset"
+  >
+    <GiForm ref="formRef" v-model="form" :options="options" :columns="columns">
+      <template #taskIcon>
+        <a-upload :show-file-list="false" :custom-request="handleUpload">
+          <template #upload-button>
+            <a-button type="primary" shape="round">
+              <template #icon>
+                <icon-upload />
+              </template>
+              <template #default>上传</template>
+            </a-button>
+          </template>
+        </a-upload>
+      </template>
+    </GiForm>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import { Message, type RequestOption } from '@arco-design/web-vue'
+import { useWindowSize } from '@vueuse/core'
+import type { Columns, GiForm, Options } from '@/components/GiForm'
+
+import { useResetReactive } from '@/hooks'
+import { taskById, taskPost, taskUpdata } from '@/apis/task/taskhome'
+import { TaskDisStatusList, TaskStatusList } from '@/constant/common'
+import { uploadFile } from '@/apis'
+
+const emit = defineEmits<{
+  (e: 'save-success'): void
+}>()
+
+const { width } = useWindowSize()
+
+const dataId = ref()
+const visible = ref(false)
+const isUpdate = computed(() => !!dataId.value)
+const title = computed(() => (isUpdate.value ? '修改任务' : '新增任务'))
+const formRef = ref<InstanceType<typeof GiForm>>()
+
+const [form, resetForm] = useResetReactive({
+
+})
+const options: Options = {
+  form: { size: 'large' },
+  btns: { hide: true },
+}
+const columns: Columns = reactive([
+  { label: '任务名称', field: 'name', type: 'input', rules: [{ required: true, message: '请输入任务名称' }] },
+  { label: '名称日文', field: 'nameJa', type: 'input', rules: [{ required: true, message: '请输入名称日文' }] },
+  { label: '名称韩文', field: 'nameKo', type: 'input', rules: [{ required: true, message: '请输入名称韩文' }] },
+  { label: '名称泰语', field: 'nameTh', type: 'input', rules: [{ required: true, message: '请输入名称泰语' }] },
+  { label: '名称越南语', field: 'nameVi', type: 'input', rules: [{ required: true, message: '请输入名称越南语' }] },
+  { label: '名称俄语', field: 'nameRu', type: 'input', rules: [{ required: true, message: '请输入名称俄语' }] },
+  { label: '名称英文', field: 'nameEn', type: 'input', rules: [{ required: true, message: '请输入名称英文' }] },
+  { label: '名称葡萄牙语', field: 'namePt', type: 'input', rules: [{ required: true, message: '请输入名称葡萄牙语' }] },
+  { label: '名称波斯语', field: 'nameFa', type: 'input', rules: [{ required: true, message: '请输入名称波斯语' }] },
+  { label: '名称西班牙语', field: 'nameEs', type: 'input', rules: [{ required: true, message: '请输入名称西班牙语' }] },
+  { label: '任务图标', field: 'taskIcon', type: 'upload', props: {
+    multiple: false,
+    limit: 1,
+  }, rules: [{ required: true, message: '请上传任务图标' }] },
+  { label: '任务链接', field: 'taskLink', type: 'input', rules: [{ required: true, message: '请输入任务链接' }] },
+  {
+    label: '任务分类',
+    field: 'categoryCode',
+    type: 'select',
+    options: TaskStatusList,
+    props: {
+      multiple: false,
+      allowClear: true,
+      allowSearch: { retainInputValue: true },
+      placeholder: '请选择任务分类',
+    },
+    rules: [{ required: true, message: '请选择任务分类' }],
+  },
+  {
+    label: '状态',
+    field: 'status',
+    type: 'select',
+    options: TaskDisStatusList,
+    props: {
+      multiple: false,
+      allowClear: true,
+      allowSearch: { retainInputValue: true },
+      placeholder: '请选择状态',
+    },
+    rules: [{ required: true, message: '请选择状态' }],
+  },
+  { label: '金币数量', field: 'goldCoin', type: 'input', rules: [{ required: true, message: '请输入金币数量' }] },
+  {
+    label: '排序',
+    field: 'sortNum',
+    type: 'input-number',
+    props: {
+      min: 1,
+      mode: 'button',
+    },
+  },
+])
+
+// 重置
+const reset = () => {
+  formRef.value?.formRef?.resetFields()
+  resetForm()
+}
+
+// 保存
+const save = async () => {
+  try {
+    const isInvalid = await formRef.value?.formRef?.validate()
+    if (isInvalid) return false
+    if (isUpdate.value) {
+      await taskUpdata(dataId.value, form)
+      Message.success('修改成功')
+    } else {
+      await taskPost({ ...form })
+      Message.success('新增成功')
+    }
+    emit('save-success')
+    return true
+  } catch (error) {
+    return false
+  }
+}
+
+// 新增
+const onAdd = () => {
+  reset()
+  dataId.value = null
+  visible.value = true
+}
+
+// 修改
+const onUpdate = async (id: number) => {
+  reset()
+  dataId.value = id
+  const { data } = await taskById(id)
+  Object.assign(form, data[0])
+  visible.value = true
+}
+const handleUpload = (options: RequestOption) => {
+  const controller = new AbortController()
+  ;(async function requestWrap() {
+    const { onProgress, onError, onSuccess, fileItem, name = 'file' } = options
+    onProgress(20)
+    const formData = new FormData()
+    formData.append(name as string, fileItem.file as Blob)
+    try {
+      const res = await uploadFile(formData)
+      form.taskIcon = res.data.url
+      Message.success('上传成功')
+      onSuccess(res)
+    } catch (error) {
+      onError(error)
+    }
+  })()
+  return {
+    abort() {
+      controller.abort()
+    },
+  }
+}
+defineExpose({ onAdd, onUpdate })
+</script>
+
+<style scoped lang="scss"></style>

+ 102 - 6
src/views/user/jiangli/index.vue

@@ -1,14 +1,110 @@
 <template>
-  <div>
-    1
-    123
+  <div class="table-page">
+    <GiTable
+      row-key="id"
+      title="奖励列表"
+      :data="dataList"
+      :columns="columns"
+      :loading="loading"
+      :scroll="{ x: '100%', y: '100%', minWidth: 1200 }"
+      :pagination="pagination"
+      :disabled-tools="['size']"
+      :disabled-column-keys="['title']"
+      @refresh="search"
+    >
+      <template #toolbar-left>
+        <a-input-search v-model="queryForm.rewardsName" placeholder="名称" allow-clear @search="search" />
+        <a-input-search v-model="queryForm.rewardsDescribe" placeholder="奖励说明" allow-clear @search="search" />
+        <a-input-search v-model="queryForm.inviteNum" placeholder="邀请好友数量" allow-clear @search="search" />
+        <a-input-search v-model="queryForm.goldCoinNum" placeholder="金币数量" allow-clear @search="search" />
+        <a-button @click="reset">
+          <template #icon><icon-refresh /></template>
+          <template #default>重置</template>
+        </a-button>
+      </template>
+      <template #toolbar-right>
+        <a-button type="primary" @click="onAdd">
+          <template #icon><icon-plus /></template>
+          <template #default>新增</template>
+        </a-button>
+      </template>
+      <template #action="{ record }">
+        <a-space>
+          <a-link title="修改" @click="onUpdate(record)">修改</a-link>
+          <a-link status="danger" title="删除" @click="onDelete(record)"> 删除 </a-link>
+        </a-space>
+      </template>
+    </GiTable>
+    <JiangliAddModal ref="JiangliAddModalRef" @save-success="search"></JiangliAddModal>
   </div>
 </template>
 
 <script setup lang="ts">
+import JiangliAddModal from './jiangliAddModal.vue'
+import type { TableInstanceColumns } from '@/components/GiTable/type'
+import { useTable } from '@/hooks'
+import { isMobile } from '@/utils'
+import has from '@/utils/has'
+import type { JiangliListResp, JiangliQuery } from '@/apis/user/type'
+import { jiangliDel, listJiangli } from '@/apis/user/userlist'
 
-</script>
+defineOptions({ name: 'Jliangli' })
+
+const queryForm = reactive<JiangliQuery>({} as JiangliQuery)
+
+const {
+  tableData: dataList,
+  loading,
+  pagination,
+  search,
+  handleDelete,
+} = useTable((page) => listJiangli({ ...queryForm, ...page }), { immediate: true })
+const columns: TableInstanceColumns[] = [
+  {
+    title: '序号',
+    width: 66,
+    align: 'center',
+    render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
+  },
+  { title: '名称', dataIndex: 'rewardsName', slotName: 'rewardsName', align: 'center', width: 180 },
+  { title: '奖励说明', dataIndex: 'rewardsDescribe', slotName: 'rewardsDescribe', align: 'center', width: 180 },
+  { title: '邀请好友数量', dataIndex: 'inviteNum', slotName: 'inviteNum', align: 'center', width: 180 },
+  { title: '金币数量', dataIndex: 'goldCoinNum', slotName: 'goldCoinNum', align: 'center', width: 180 },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    slotName: 'action',
+    width: 350,
+    align: 'center',
+    fixed: !isMobile() ? 'right' : undefined,
+    show: has.hasPermOr(['system:notice:detail', 'system:notice:update', 'system:notice:delete']),
+  },
+]
 
-<style scoped>
+// 重置
+const reset = () => {
+  queryForm.rewardsName = undefined
+  queryForm.rewardsDescribe = undefined
+  queryForm.inviteNum = undefined
+  queryForm.goldCoinNum = undefined
+  search()
+}
+
+// 新增
+const JiangliAddModalRef = ref<InstanceType<typeof JiangliAddModal>>()
+const onAdd = () => {
+  JiangliAddModalRef.value?.onAdd()
+}
+const onUpdate = (record: JiangliListResp) => {
+  JiangliAddModalRef.value?.onUpdate(record.id)
+}
+
+const onDelete = (record: JiangliListResp) => {
+  return handleDelete(() => jiangliDel(record.id), {
+    content: `是否确定删除奖励「${record.rewardsName}」?`,
+    showModal: true,
+  })
+}
+</script>
 
-</style>
+<style scoped lang="scss"></style>

+ 104 - 0
src/views/user/jiangli/jiangliAddModal.vue

@@ -0,0 +1,104 @@
+<template>
+  <a-modal
+    v-model:visible="visible"
+    :title="title"
+    :mask-closable="false"
+    :esc-to-close="false"
+    :width="width >= 500 ? 500 : '100%'"
+    draggable
+    @before-ok="save"
+    @close="reset"
+  >
+    <GiForm ref="formRef" v-model="form" :options="options" :columns="columns">
+    </GiForm>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import { Message } from '@arco-design/web-vue'
+import { useWindowSize } from '@vueuse/core'
+import type { Columns, GiForm, Options } from '@/components/GiForm'
+
+import { useResetReactive } from '@/hooks'
+import { jiangliById, jiangliPost, jiangliUpdata } from '@/apis/user/userlist'
+
+const emit = defineEmits<{
+  (e: 'save-success'): void
+}>()
+
+const { width } = useWindowSize()
+
+const dataId = ref()
+const visible = ref(false)
+const isUpdate = computed(() => !!dataId.value)
+const title = computed(() => (isUpdate.value ? '修改奖励' : '新增奖励'))
+const formRef = ref<InstanceType<typeof GiForm>>()
+
+const [form, resetForm] = useResetReactive({
+
+})
+const options: Options = {
+  form: { size: 'large' },
+  btns: { hide: true },
+}
+const columns: Columns = reactive([
+  { label: '奖励名称', field: 'rewardsName', type: 'input', rules: [{ required: true, message: '请输入奖励名称' }] },
+  { label: '奖励说明', field: 'rewardsDescribe', type: 'input', rules: [{ required: true, message: '请输入值奖励说明' }] },
+  { label: '邀请好友数量', field: 'inviteNum', type: 'input', rules: [{ required: true, message: '请输入值奖励说明' }] },
+  { label: '金币数量', field: 'goldCoinNum', type: 'input', rules: [{ required: true, message: '请输入金币数量' }] },
+  {
+    label: '排序',
+    field: 'rewardsLevel',
+    type: 'input-number',
+    props: {
+      min: 1,
+      mode: 'button',
+    },
+  },
+])
+
+// 重置
+const reset = () => {
+  formRef.value?.formRef?.resetFields()
+  resetForm()
+}
+
+// 保存
+const save = async () => {
+  try {
+    const isInvalid = await formRef.value?.formRef?.validate()
+    if (isInvalid) return false
+    if (isUpdate.value) {
+      await jiangliUpdata(dataId.value, form)
+      Message.success('修改成功')
+    } else {
+      await jiangliPost({ ...form })
+      Message.success('新增成功')
+    }
+    emit('save-success')
+    return true
+  } catch (error) {
+    return false
+  }
+}
+
+// 新增
+const onAdd = () => {
+  reset()
+  dataId.value = null
+  visible.value = true
+}
+
+// 修改
+const onUpdate = async (id: number) => {
+  reset()
+  dataId.value = id
+  const { data } = await jiangliById(id)
+  Object.assign(form, data[0])
+  visible.value = true
+}
+
+defineExpose({ onAdd, onUpdate })
+</script>
+
+<style scoped lang="scss"></style>

+ 7 - 7
src/views/user/userlist/index.vue

@@ -39,11 +39,11 @@
       </template>
       <template #action="{ record }">
         <a-space>
-          <a-link v-permission="['system:notice:detail']" title="详情" @click="onDetail(record)">详情</a-link>
-          <a-link v-permission="['system:notice:detail']" title="一级" @click="onDetail(record)">一级</a-link>
-          <a-link v-permission="['system:notice:detail']" title="二级" @click="onDetail(record)">二级</a-link>
-          <a-link v-permission="['system:notice:detail']" title="空投" @click="onDetail(record)">空投</a-link>
-          <a-link v-permission="['system:notice:detail']" title="升级成分销商" @click="onDetail(record)">升级成分销商</a-link>
+          <a-link title="详情" @click="onDetail(record)">详情</a-link>
+          <a-link title="一级" @click="onDetail(record)">一级</a-link>
+          <a-link title="二级" @click="onDetail(record)">二级</a-link>
+          <a-link title="空投" @click="onDetail(record)">空投</a-link>
+          <a-link title="升级成分销商" @click="onDetail(record)">升级成分销商</a-link>
           <!--          <a-link v-permission="['system:notice:update']" title="修改" @click="onUpdate(record)">修改</a-link> -->
           <!--          <a-link v-permission="['system:notice:delete']" status="danger" title="删除" @click="onDelete(record)"> 删除 </a-link> -->
         </a-space>
@@ -62,7 +62,7 @@ import DateRangePicker from '@/components/DateRangePicker/index.vue'
 import type { UserListResp, UserQuery } from '@/apis/user/type'
 import { listUserTg } from '@/apis/user/userlist'
 
-defineOptions({ name: 'SystemNotice' })
+defineOptions({ name: 'Userlist' })
 
 const { passenger_type } = useDict('passenger_type')
 
@@ -87,7 +87,7 @@ const columns: TableInstanceColumns[] = [
   { title: '名称', dataIndex: 'nickname', slotName: 'nickname', align: 'center', width: 180 },
   { title: 'TG账号', dataIndex: 'tgAccount', slotName: 'tgAccount', align: 'center', width: 180 },
   { title: '钱包地址', dataIndex: 'walletAddress', slotName: 'walletAddress', align: 'center', width: 180 },
-  { title: '推荐人', dataIndex: 'referrerId', slotName: 'referrerId', align: 'center', width: 180 },
+  { title: '推荐人', dataIndex: 'referrerName', slotName: 'referrerName', align: 'center', width: 180 },
   { title: '年限', dataIndex: 'ageLimit', slotName: 'ageLimit', align: 'center', width: 180 },
   { title: '客流途径', dataIndex: 'passengerFlowWay', slotName: 'passengerFlowWay', align: 'center', width: 180 },
   { title: '注册日期', dataIndex: 'createdTime', width: 180 },