zmq 1 år sedan
förälder
incheckning
ee11743646

+ 113 - 0
src/api/admin/data-contracts.ts

@@ -4531,3 +4531,116 @@ export interface ViewUpdateInput {
    */
   id: number
 }
+/** 分页信息输入 */
+export interface PageInputProjectGetPageDto {
+  /**
+   * 当前页标
+   * @format int32
+   */
+  currentPage?: number
+  /**
+   * 每页大小
+   * @format int32
+   */
+  pageSize?: number
+  dynamicFilter?: DynamicFilterInfo
+  filter?: ProjectGetPageDto
+}
+export interface ProjectGetPageDto {
+  /** 关键字 项目名称 */
+  keywrods?: string | null
+  // 项目状态
+  status?: number | null
+}
+/** 结果输出 */
+export interface ResultOutputPageOutputProjectListOutput {
+  /** 是否成功标记 */
+  success?: boolean
+  /** 编码 */
+  code?: string | null
+  /** 消息 */
+  msg?: string | null
+  /** 分页信息输出 */
+  data?: PageOutputProjectListOutput
+}
+/** 分页信息输出 */
+export interface PageOutputProjectListOutput {
+  /**
+   * 数据总数
+   * @format int64
+   */
+  total?: number
+  /** 数据 */
+  list?: ProjectListOutput[] | null
+}
+export interface ProjectListOutput {
+  /**
+   * 主键
+   * @format int64
+   */
+  id?: number
+  /** 项目图标 */
+  logo?: string | null  
+  /** 项目标题 */
+  name?: string | null
+  /** 结算方式 */
+  settleDay?: string | null
+  /** 状态 */
+  status?: string | null
+}
+/** 添加项目 */
+export interface ProjectAddInput {
+  /**
+   * 项目Id
+   * @format int64
+   */
+  id?: number
+  /**
+   * 项目名称
+   * @minLength 1
+   */
+  name: string
+  /**
+   * 项目图标
+   * @minLength 1
+   */
+  logo: string
+  /** 项目简介 */
+  tips: string
+  /**
+   * 最高佣金
+   * @minLength 1
+   */
+  maxPrice: number
+  /**
+ * 结算周期
+ * @minLength 1
+ */
+  settleDay: number
+  /**
+   * 视频教程
+   * @minLength 1
+   */
+  videoUrl: string
+  /** 详情 */
+  detail: string 
+  // 项目价格
+  prices: ProjectPriceAddInput[]
+}
+export interface ProjectPriceAddInput {
+  /**
+   * 项目价格Id
+   * @format int64
+   */
+  id?: number
+  /**
+   * 结算标准
+   * @minLength 1
+   */
+  name: string
+  /**
+   * 价格
+   * @minLength 1
+   */
+  price: number  
+}

+ 187 - 0
src/api/admin/project.ts

@@ -0,0 +1,187 @@
+/* eslint-disable */
+/* tslint:disable */
+/*
+ * ---------------------------------------------------------------
+ * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
+ * ##                                                           ##
+ * ## AUTHOR: acacode                                           ##
+ * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
+ * ---------------------------------------------------------------
+ */
+
+import { AxiosResponse } from 'axios'
+import {
+    PageInputProjectGetPageDto,
+    ResultOutputInt64,
+    ResultOutputPageOutputProjectListOutput,
+    ResultOutputTenantGetOutput,
+    TenantAddInput,
+    TenantSetEnableInput,
+    TenantUpdateInput,
+} from './data-contracts'
+import { ContentType, HttpClient, RequestParams } from './http-client'
+
+export class ProjectApi<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
+    /**
+     * No description
+     *
+     * @tags project
+     * @name Get
+     * @summary 查询
+     * @request GET:/api/admin/project/get
+     * @secure
+     */
+    get = (
+        query?: {
+            /** @format int64 */
+            id?: number
+        },
+        params: RequestParams = {}
+    ) =>
+        this.request<ResultOutputTenantGetOutput, any>({
+            path: `/api/admin/project/get`,
+            method: 'GET',
+            query: query,
+            secure: true,
+            format: 'json',
+            ...params,
+        })
+    /**
+     * No description
+     *
+     * @tags tenant
+     * @name GetPage
+     * @summary 查询分页
+     * @request POST:/api/admin/tenant/get-page
+     * @secure
+     */
+    getPage = (data: PageInputProjectGetPageDto, params: RequestParams = {}) =>
+        this.request<ResultOutputPageOutputProjectListOutput, any>({
+            path: `/api/admin/project/get-page`,
+            method: 'POST',
+            body: data,
+            secure: true,
+            type: ContentType.Json,
+            format: 'json',
+            ...params,
+        })
+    /**
+     * No description
+     *
+     * @tags tenant
+     * @name Add
+     * @summary 新增
+     * @request POST:/api/admin/tenant/add
+     * @secure
+     */
+    add = (data: TenantAddInput, params: RequestParams = {}) =>
+        this.request<ResultOutputInt64, any>({
+            path: `/api/admin/tenant/add`,
+            method: 'POST',
+            body: data,
+            secure: true,
+            type: ContentType.Json,
+            format: 'json',
+            ...params,
+        })
+    /**
+     * No description
+     *
+     * @tags tenant
+     * @name Update
+     * @summary 修改
+     * @request PUT:/api/admin/tenant/update
+     * @secure
+     */
+    update = (data: TenantUpdateInput, params: RequestParams = {}) =>
+        this.request<AxiosResponse, any>({
+            path: `/api/admin/tenant/update`,
+            method: 'PUT',
+            body: data,
+            secure: true,
+            type: ContentType.Json,
+            ...params,
+        })
+    /**
+     * No description
+     *
+     * @tags tenant
+     * @name Delete
+     * @summary 彻底删除
+     * @request DELETE:/api/admin/tenant/delete
+     * @secure
+     */
+    delete = (
+        query?: {
+            /** @format int64 */
+            id?: number
+        },
+        params: RequestParams = {}
+    ) =>
+        this.request<AxiosResponse, any>({
+            path: `/api/admin/tenant/delete`,
+            method: 'DELETE',
+            query: query,
+            secure: true,
+            ...params,
+        })
+    /**
+     * No description
+     *
+     * @tags tenant
+     * @name SoftDelete
+     * @summary 删除
+     * @request DELETE:/api/admin/tenant/soft-delete
+     * @secure
+     */
+    softDelete = (
+        query?: {
+            /** @format int64 */
+            id?: number
+        },
+        params: RequestParams = {}
+    ) =>
+        this.request<AxiosResponse, any>({
+            path: `/api/admin/tenant/soft-delete`,
+            method: 'DELETE',
+            query: query,
+            secure: true,
+            ...params,
+        })
+    /**
+     * No description
+     *
+     * @tags tenant
+     * @name BatchSoftDelete
+     * @summary 批量删除
+     * @request PUT:/api/admin/tenant/batch-soft-delete
+     * @secure
+     */
+    batchSoftDelete = (data: number[], params: RequestParams = {}) =>
+        this.request<AxiosResponse, any>({
+            path: `/api/admin/tenant/batch-soft-delete`,
+            method: 'PUT',
+            body: data,
+            secure: true,
+            type: ContentType.Json,
+            ...params,
+        })
+    /**
+     * No description
+     *
+     * @tags tenant
+     * @name SetEnable
+     * @summary 设置启用
+     * @request POST:/api/admin/tenant/set-enable
+     * @secure
+     */
+    setEnable = (data: TenantSetEnableInput, params: RequestParams = {}) =>
+        this.request<AxiosResponse, any>({
+            path: `/api/admin/tenant/set-enable`,
+            method: 'POST',
+            body: data,
+            secure: true,
+            type: ContentType.Json,
+            ...params,
+        })
+}

+ 2 - 0
src/types/mitt.d.ts

@@ -21,6 +21,7 @@
  * @method refreshTenant 刷新租户
  * @method refreshUser 刷新用户
  * @method refreshView 刷新视图
+ * @method refreshProject 刷新項目
  */
 declare type MittType<T = any> = {
   openSetingsDrawer?: string
@@ -45,6 +46,7 @@ declare type MittType<T = any> = {
   refreshUser?: T
   refreshView?: T
   refreshFile?: T
+  refreshProject?: T
 }
 
 // mitt 参数类型定义

+ 3 - 0
src/views/admin/link/index.vue

@@ -0,0 +1,3 @@
+<template>
+    <h1>推广连接</h1>
+</template>

+ 284 - 0
src/views/admin/project/components/project-form.vue

@@ -0,0 +1,284 @@
+<template>  
+  <div>
+    <el-dialog
+      v-model="state.showDialog"
+      destroy-on-close
+      :title="title"
+      draggable
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      width="769px"
+    >
+      <el-form ref="formRef" :model="form" size="default" label-width="80px">
+        <el-row :gutter="35">
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+              <el-form-item label="" prop="logo" :rules="[{ required: true, message: '请上传项目图标', trigger: ['blur', 'change'] }]">
+                <el-upload class="avatar-uploader"
+                ref="uploadRef" 
+                :data="{ fileDirectory: state.fileDirectory }"
+                :action="uploadAction"  
+                :headers="uploadHeaders"
+                :show-file-list="false" 
+                :on-success="onSuccess"
+            :on-error="onError">
+                  <img v-if="form.logo" :src="form.logo" class="avatar">
+                  <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+                </el-upload>
+              </el-form-item>
+            </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+            <el-form-item label="项目名称" prop="name" :rules="[{ required: true, message: '请输入项目名称', trigger: ['blur', 'change'] }]">
+              <el-input v-model="form.name" autocomplete="off" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+            <el-form-item label="结算周期" prop="code" :rules="[{ required: true, message: '请输入结算周期', trigger: ['blur', 'change'] }]">
+              <el-input v-model="form.code" autocomplete="off" />
+            </el-form-item>
+          </el-col>          
+          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
+            <el-form-item label="最高佣金" prop="realName" :rules="[{ required: true, message: '请输入最高佣金', trigger: ['blur', 'change'] }]">
+              <el-input v-model="form.realName" autocomplete="off" />
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+              <el-form-item label="项目简介" prop="realName" :rules="[{ required: true, message: '请输入最高佣金', trigger: ['blur', 'change'] }]">
+                <el-input v-model="form.description" clearable type="textarea" />
+              </el-form-item>
+          </el-col>          
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+            <el-form-item label="项目价格" prop="realName" :rules="[{ required: true, message: '请输入最高佣金', trigger: ['blur', 'change'] }]">
+                <el-form :inline="true" ref="formPrice" :model="state.formPrice" class="demo-form-inline">
+                    <el-form-item label="结算标准">
+                      <el-input v-model="state.formPrice.name"></el-input>
+                    </el-form-item>
+                    <el-form-item label="价格">
+                        <el-input v-model="state.formPrice.price"></el-input>
+                    </el-form-item>
+                    <el-form-item>
+                      <el-button type="primary">添加</el-button>
+                    </el-form-item>
+                </el-form>
+            </el-form-item>
+            <el-form-item label="" prop="realName" :rules="[{ required: true, message: '请输入最高佣金', trigger: ['blur', 'change'] }]">
+                  <el-table :data="state.form.prices" row-key="id" height="'100%'" style="width: 100%; height: 100%">
+                <el-table-column type="index" width="80" label="序号"></el-table-column>                                
+                <el-table-column prop="name" label="结算标准" min-width="140" show-overflow-tooltip/>                  
+                <el-table-column prop="price" label="价格" width="100" show-overflow-tooltip/>                
+                <!-- <el-table-column label="操作" width="140" header-align="center" align="center" fixed="right">
+                    <template #default="{ row }">
+                        <el-button v-auth="'api:admin:tenant:update'" icon="ele-EditPen" size="small" text type="primary"                            
+                            >删除</el-button>                        
+                    </template>
+                </el-table-column> -->
+            </el-table>
+              </el-form-item>
+            
+          </el-col>
+
+
+                        
+
+          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
+            <el-form-item label="项目详情">
+              <el-input v-model="form.description" clearable type="textarea" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="onCancel" size="default">取 消</el-button>
+          <el-button type="primary" @click="onSure" size="default" :loading="state.sureLoading">确 定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup name="admin/project/form">
+import { reactive, toRefs, getCurrentInstance, ref, computed } from 'vue'
+import { ProjectAddInput, TenantUpdateInput } from '/@/api/admin/data-contracts'
+import { ProjectApi } from '/@/api/admin/project'
+ import type { UploadInstance, UploadProps, UploadFile } from 'element-plus'
+import eventBus from '/@/utils/mitt'
+import { ElMessage } from 'element-plus'
+import { useUserInfo } from '/@/stores/userInfo'
+
+const storesUserInfo = useUserInfo()
+
+const uploadRef = ref<UploadInstance>()
+
+defineProps({
+  title: {
+    type: String,
+    default: '',
+  },
+})
+
+const { proxy } = getCurrentInstance() as any
+
+const formRef = ref()
+const state = reactive({  
+  showDialog: false,
+  sureLoading: false,  
+  form: {} as ProjectAddInput & TenantUpdateInput,
+  fileList: [] as UploadFile[],
+  fileDirectory:'project',
+  token: storesUserInfo.getToken(),
+  formPrice: {
+    name: "",
+    price:""
+  }
+})
+const { form } = toRefs(state)
+
+// const getPkgs = async () => {
+//   const res = await new PkgApi().getList().catch(() => {
+//     state.pkgData = []
+//   })
+
+//   state.pkgData = res?.data ?? []
+// }
+
+// 打开对话框
+const open = async (row: any = {}) => {
+  // await getPkgs()
+
+  if (row.id > 0) {
+    const res = await new ProjectApi().get({ id: row.id }, { loading: true }).catch(() => {
+      proxy.$modal.closeLoading()
+    })
+
+    if (res?.success) {
+      state.form = res.data as ProjectAddInput & TenantUpdateInput
+    }
+  } else {
+    state.form = { pkgIds: [] as number[], enabled: true } as ProjectAddInput & TenantUpdateInput
+  }
+  state.showDialog = true
+}
+
+// //手机号失去焦点
+// const onBlurMobile = () => {
+//   if (!state.form.userName && state.form.phone && isMobile(state.form.phone)) {
+//     state.form.userName = state.form.phone
+//   }
+// }
+
+// 取消
+const onCancel = () => {
+  state.showDialog = false
+}
+
+// 确定
+const onSure = () => {
+  formRef.value.validate(async (valid: boolean) => {
+    if (!valid) return
+
+    state.sureLoading = true
+    let res = {} as any
+    if (state.form.id != undefined && state.form.id > 0) {
+      res = await new ProjectApi().update(state.form, { showSuccessMessage: true }).catch(() => {
+        state.sureLoading = false
+      })
+    } else {
+      res = await new ProjectApi().add(state.form, { showSuccessMessage: true }).catch(() => {
+        state.sureLoading = false
+      })
+    }
+    state.sureLoading = false
+
+    if (res?.success) {
+      eventBus.emit('refreshTenant')
+      state.showDialog = false
+    }
+  })
+}
+
+const uploadAction = computed(() => {
+  return import.meta.env.VITE_API_URL + '/api/admin/file/upload-file'
+})
+const uploadHeaders = computed(() => {
+  return { Authorization: 'Bearer ' + state.token }
+})
+// 上传成功
+const onSuccess: UploadProps['onSuccess'] = (response) => {  
+  if (response?.success) {
+    state.form.logo = response.data.linkUrl;
+  }
+}
+//上传失败
+const onError: UploadProps['onError'] = (error) => {
+  let message = ''
+  if (error.message) {
+    try {
+      message = JSON.parse(error.message)?.msg
+    } catch (err) {
+      message = error.message || ''
+    }
+  }
+  if (message)
+    ElMessage({
+      message: message,
+      type: 'error',
+    })
+}
+//
+// const cellEdit = (row, colum, cell, event) => {
+//   state.showDialog = false
+// }
+
+// const changeData=(value) =>{
+//   const reg = /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g
+//   if (!reg.test(value.kpi.value)) {
+//     return this.$message.error('只能输入数字')
+//   } else if (value.kpi.value > 1000000000) {
+//     return this.$message.error('输入数字过大')
+//   }
+//   let params = {
+//     feeKpi: value.kpi.value,
+//     meetingId: value.id.value,
+//     paperKpi: 0,
+//     registerKpi: 0,
+//     viewKpi: 0,
+//     type: 1
+//   };
+//   let { data } = await setUpMeeting(params);
+//   if (data.code == 0) {
+//     // this.$message.success('设置成功');
+//     value.kpi.edit = false;
+//   }
+// }    
+
+defineExpose({
+  open,
+})
+</script>
+<style>
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+
+.avatar-uploader .el-upload:hover {
+  border-color: #409EFF;
+}
+
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 100px;
+  height: 100px;
+  line-height: 100px;
+  text-align: center;
+}
+
+.avatar {
+  width: 100px;
+  height: 100px;
+  display: block;
+}</style>

+ 176 - 0
src/views/admin/project/components/project-select.vue

@@ -0,0 +1,176 @@
+<template>
+  <el-dialog
+    v-model="state.showDialog"
+    destroy-on-close
+    :title="title"
+    append-to-body
+    draggable
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    width="780px"
+  >
+    <div style="padding: 0px 0px 8px 8px; background-color: var(--ba-bg-color)">
+      <el-card shadow="never" :body-style="{ paddingBottom: '0' }" style="margin-top: 8px">
+        <el-form :model="state.filter" :inline="true" @submit.stop.prevent>
+          <el-form-item label="企业名" prop="name">
+            <el-input v-model="state.filter.name" placeholder="企业名" @keyup.enter="onQuery" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="ele-Search" @click="onQuery"> 查询 </el-button>
+          </el-form-item>
+        </el-form>
+      </el-card>
+
+      <el-card shadow="never" style="margin-top: 8px">
+        <el-table
+          ref="tenantTableRef"
+          :data="state.tenantListData"
+          style="width: 100%"
+          v-loading="state.loading"
+          row-key="id"
+          default-expand-all
+          :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+          :highlight-current-row="!multiple"
+          @row-click="onRowClick"
+          @row-dblclick="onRowDbClick"
+        >
+          <el-table-column v-if="multiple" type="selection" width="55" />
+          <el-table-column prop="name" label="企业名" min-width="80" show-overflow-tooltip />
+          <el-table-column prop="code" label="企业编码" min-width="120" show-overflow-tooltip />
+          <!-- <el-table-column prop="email" label="邮箱" min-width="120" show-overflow-tooltip /> -->
+        </el-table>
+        <div class="my-flex my-flex-end" style="margin-top: 20px">
+          <el-pagination
+            v-model:currentPage="state.pageInput.currentPage"
+            v-model:page-size="state.pageInput.pageSize"
+            :total="state.total"
+            :page-sizes="[10, 20, 50, 100]"
+            small
+            background
+            @size-change="onSizeChange"
+            @current-change="onCurrentChange"
+            layout="total, sizes, prev, pager, next"
+          />
+        </div>
+      </el-card>
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="onCancel" size="default">取 消</el-button>
+        <el-button type="primary" @click="onSure" size="default" :loading="sureLoading">确 定</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup name="admin/tenant/components/tenant-select">
+import { ref, reactive } from 'vue'
+import { ElTable } from 'element-plus'
+import { TenantListOutput, PageInputTenantGetPageDto } from '/@/api/admin/data-contracts'
+import { TenantApi } from '/@/api/admin/Tenant'
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: '',
+  },
+  multiple: {
+    type: Boolean,
+    default: false,
+  },
+  sureLoading: {
+    type: Boolean,
+    default: false,
+  },
+})
+
+const emits = defineEmits(['sure'])
+
+const tenantTableRef = ref<InstanceType<typeof ElTable>>()
+
+const state = reactive({
+  showDialog: false,
+  loading: false,
+  filter: {
+    name: '',
+  },
+  total: 0,
+  pageInput: {
+    currentPage: 1,
+    pageSize: 20,
+    filter: {
+      name: '',
+    },
+  } as PageInputTenantGetPageDto,
+  tenantListData: [] as Array<TenantListOutput>,
+})
+
+// 打开对话框
+const open = () => {
+  state.showDialog = true
+
+  onQuery()
+}
+
+// 关闭对话框
+const close = () => {
+  state.showDialog = false
+}
+
+const onQuery = async () => {
+  state.loading = true
+  state.pageInput.filter = state.filter
+  const res = await new TenantApi().getPage(state.pageInput).catch(() => {
+    state.loading = false
+  })
+
+  state.tenantListData = res?.data?.list ?? []
+  state.total = res?.data?.total ?? 0
+  state.loading = false
+}
+
+const onSizeChange = (val: number) => {
+  state.pageInput.pageSize = val
+  onQuery()
+}
+
+const onCurrentChange = (val: number) => {
+  state.pageInput.currentPage = val
+  onQuery()
+}
+
+const onRowClick = (row: TenantListOutput) => {
+  // TODO: improvement typing when refactor table
+  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+  // @ts-expect-error
+  tenantTableRef.value!.toggleRowSelection(row, props.multiple ? undefined : true)
+}
+
+const onRowDbClick = () => {
+  if (!props.multiple) {
+    onSure()
+  }
+}
+
+// 取消
+const onCancel = () => {
+  state.showDialog = false
+}
+
+// 确定
+const onSure = () => {
+  const selectionRows = tenantTableRef.value!.getSelectionRows() as TenantListOutput[]
+  emits('sure', props.multiple ? selectionRows : selectionRows.length > 0 ? selectionRows[0] : null)
+}
+
+defineExpose({
+  open,
+  close,
+})
+</script>
+
+<style scoped lang="scss">
+:deep(.el-dialog__body) {
+  padding: 5px 10px;
+}
+</style>

+ 133 - 0
src/views/admin/project/index.vue

@@ -0,0 +1,133 @@
+<template>    
+    <div class="my-layout">
+        <el-card class="mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
+            <el-form :inline="true" @submit.stop.prevent>
+                <el-form-item label="项目标题">
+                    <el-input v-model="state.filter.keywrods" placeholder="项目标题" />
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="ele-Search" @click="onQuery"> 查询 </el-button>
+                    <el-button v-auth="'api:admin:tenant:add'" type="primary" icon="ele-Plus" @click="onAdd"> 新增
+                    </el-button>
+                </el-form-item>
+            </el-form>
+        </el-card>
+
+        <el-card class="my-fill mt8" shadow="never">
+            <el-table v-loading="state.loading" :data="state.projectListData" row-key="id" height="'100%'"
+                style="width: 100%; height: 100%">
+                <el-table-column type="index" width="80" label="序号"></el-table-column>
+                <el-table-column prop="logo" label="项目图标" width="100">
+                    <template #default="{ row }">
+                        <el-image style="width: 50px; height: 50px" :src="row.logo"></el-image>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="name" label="项目标题" min-width="120" show-overflow-tooltip />
+                <el-table-column prop="settleDay" label="结算方式" width="140" show-overflow-tooltip>
+                    <template #default="{ row }">
+                        T+{{ row.settleDay }}
+                    </template>
+                </el-table-column>
+                <el-table-column prop="status" label="状态" width="120" show-overflow-tooltip />             
+                <el-table-column label="操作" width="180" header-align="center" align="center" fixed="right"> 
+                    <template #default="{ row }">
+                        <el-button v-auth="'api:admin:project:update'" icon="ele-EditPen" size="small" text type="primary" @click="onEdit(row)">编辑</el-button>
+                    </template>                   
+                </el-table-column>
+            </el-table>
+
+            <div class="my-flex my-flex-end" style="margin-top: 20px">
+                <el-pagination v-model:currentPage="state.pageInput.currentPage"
+                    v-model:page-size="state.pageInput.pageSize" :total="state.total" :page-sizes="[10, 20, 50, 100]" small
+                    background @size-change="onSizeChange" @current-change="onCurrentChange"
+                    layout="total, sizes, prev, pager, next, jumper" />
+            </div>
+        </el-card>
+
+        <project-form ref="projectFormRef" :title="state.ProjectFormTitle"></project-form>
+    </div>
+</template>
+
+<script lang="ts" setup name="admin/project">
+import { ref, reactive, onMounted,  getCurrentInstance, onBeforeMount, defineAsyncComponent } from 'vue'
+import { ProjectListOutput, PageInputProjectGetPageDto } from '/@/api/admin/data-contracts'
+import { ProjectApi } from '/@/api/admin/project'
+import eventBus from '/@/utils/mitt'
+import { auth } from '/@/utils/authFunction'
+
+// 引入组件
+const ProjectForm = defineAsyncComponent(() => import('./components/project-form.vue'))
+
+ const { proxy } = getCurrentInstance() as any
+
+const projectFormRef = ref()
+
+const state = reactive({
+    loading: false,
+    ProjectFormTitle: '',
+    total: 0,
+    filter: {
+        keywrods: '',
+        status: 0
+    },
+    pageInput: {
+        currentPage: 1,
+        pageSize: 20,
+    } as PageInputProjectGetPageDto,
+    projectListData: [] as Array<ProjectListOutput>,
+})
+
+onMounted(() => {
+    onQuery()
+    eventBus.off('refreshProject')
+    eventBus.on('refreshProject', async () => {
+        onQuery()
+    })
+})
+
+onBeforeMount(() => {
+    eventBus.off('refreshProject')
+})
+
+const onQuery = async () => {
+    state.loading = true
+    state.pageInput.filter = state.filter
+    const res = await new ProjectApi().getPage(state.pageInput).catch(() => {
+        state.loading = false
+    })
+
+    state.projectListData = res?.data?.list ?? []
+    state.total = res?.data?.total ?? 0
+    state.loading = false
+}
+
+const onAdd = () => {
+    state.ProjectFormTitle = '新增项目'
+    projectFormRef.value.open()
+}
+
+const onEdit = (row: ProjectListOutput) => {
+    state.ProjectFormTitle = '编辑项目'
+    projectFormRef.value.open(row)
+}
+
+// const onDelete = (row: ProjectListOutput) => {
+//     proxy.$modal
+//         .confirmDelete(`确定要删除【${row.name}】?`)
+//         .then(async () => {
+//             await new ProjectApi().delete({ id: row.id }, { loading: true, showSuccessMessage: true })
+//             onQuery()
+//         })
+//         .catch(() => { })
+// }
+
+const onSizeChange = (val: number) => {
+    state.pageInput.pageSize = val
+    onQuery()
+}
+
+const onCurrentChange = (val: number) => {
+    state.pageInput.currentPage = val
+    onQuery()
+}
+</script>