Explorar o código

```
feat(workflowTrade): 添加工作流交易功能和详情页面

- 新增 getQuestDetail API 接口用于查询工作流详情
- 添加国际化文案包括天、小时和工作流交易相关提示信息
- 实现工作流详情页面,显示需求背景、具体要求和联系方式
- 集成 i18n 国际化支持,使用 t() 函数替换硬编码文本
- 优化工作流列表页面,使用真实数据替换模拟数据
- 添加报名功能和截止时间格式化工具函数
- 实现工作流详情获取和显示功能
```

zhangningning hai 1 mes
pai
achega
6e507d9468

+ 6 - 0
src/api/apply.js

@@ -0,0 +1,6 @@
+import request from './request.js'
+
+// 新增报名记录
+export function signUp(data = {}) {
+  return request.post('/apply',data)
+}

+ 4 - 0
src/api/workflowTrade.js

@@ -8,4 +8,8 @@ export function questAdd(data = {}) {
 // 查询寻找工作流列表
 export function getQuestList(data = {}) {
   return request.get('/quest/list',data)
+}
+// 查询寻找工作流详情
+export function getQuestDetail(data = {}) {
+  return request.get('/quest/'+data.questId)
 }

+ 7 - 0
src/locales/en.js

@@ -12,6 +12,8 @@ export default {
     gongzuoliu_trade: 'Workflow Trade',
     zaixianbaoming: 'Online Registration',
     mibi: 'Mibit',
+    day: 'Day',
+    hour: 'Hour',
   },
   login: {
     smsLogin: 'SMS Login',
@@ -38,5 +40,10 @@ export default {
     courseDetail: 'Course Detail',
     myLearning: 'My Learning',
     myLearningCourseDetail: 'My Learning Course Detail'
+  },
+  workflowTrade: {
+    signUpSuccess: 'Sign Up Success',
+    signUpFailed: 'Sign Up Failed, please try again later',
+    publishSuccess: 'Publish Success',
   }
 }

+ 8 - 0
src/locales/zh-CN.js

@@ -13,6 +13,8 @@ export default {
     gongzuoliu_trade: '工作流交易',
     zaixianbaoming: '在线报名',
     mibi: '米币',
+    day: '天',
+    hour: '小时',
   },
   login: {
     smsLogin: '短信登录',
@@ -38,5 +40,11 @@ export default {
     courseDetail: '课程详情',
     myLearning: '我的学习',
     myLearningCourseDetail: '我的学习课程详情'
+  },
+  workflowTrade: {
+    signUpSuccess: '报名成功',
+    signUpFailed: '报名失败,请稍后重试',
+    publishSuccess: '发布成功',
   }
+
 }

+ 4 - 1
src/pages/WorkflowAdd.vue

@@ -139,6 +139,9 @@ const router = useRouter()
 import { getCategoryListTree } from '@/api/category.js'
 import { publishAdd } from '@/api/publish.js'
 
+  import { useI18n } from 'vue-i18n' 
+  const { t } = useI18n()
+
 // 从路由参数中获取 activePlatform
 const activePlatform = ref(route.query.activePlatform || '');
 
@@ -228,7 +231,7 @@ const submitForm = async () => {
     publishAdd(ruleForm).then(res => {
       console.log(res)
       if(res.code === 200){
-        DGTMessage.success('提交成功')
+        DGTMessage.success(t('workflowTrade.publishSuccess'))
         setTimeout(() => {
           goBack();
         }, 2000);

+ 4 - 4
src/pages/WorkflowDetail.vue

@@ -90,14 +90,14 @@
                 </div>
               </div>
               <div>
-                <div class="weiguanzhu cursor-pointer gap5">
+                <!-- <div class="weiguanzhu cursor-pointer gap5" v-if="true">
                   <img :src="weiguanzhuIcon" alt="" style="width: 16px; height: 16px;">
                   <span class="font_size14">关注</span>
                 </div>
-                <div class="yiguanzhu cursor-pointer gap5 contactInfo_bg">
+                <div class="yiguanzhu cursor-pointer gap5 contactInfo_bg" v-else>
                   <img :src="yiguanzhuIcon" alt="" style="width: 16px; height: 16px;">
-                  <span class="font_size14">关注</span>
-                </div>
+                  <span class="font_size14">关注</span>
+                </div> -->
               </div>
             </div>
           </div>

+ 32 - 36
src/pages/workflowTrade/workflowTrade.vue

@@ -45,10 +45,10 @@
              @click="searchFom.applyStatus = ''; getList('init');"
              :key="-1">全部</div>
             <div class="font_size14 typeItem"
-             :class="{'active':item.applyStatus === searchFom.applyStatus}"
-             @click="searchFom.applyStatus = item.applyStatus; getList('init');"
-             v-for="item in [{applyStatus:'1',applyStatusName:'报名中'},{applyStatus:'2',applyStatusName:'已结束'}]" :key="item.applyStatus">
-              {{item.applyStatusName}}
+             :class="{'active':key === searchFom.applyStatus}"
+             @click="searchFom.applyStatus = key; getList('init');"
+             v-for="(value,key) in applyStatusInfo" :key="key">
+              {{value}}
             </div>
           </div>
         </div>
@@ -74,50 +74,39 @@
       <div class="course-list mt20">
         <div class="font_size20 bold mb10">共 <span class="color_theme">{{listTotal}}</span> 条需求</div>
         <div class="padding16 bg_color_fff border_radius_16 mb20 box_shadow_card list_item_animation" 
-          v-for="item in 4" :key="item"
+          v-for="item in list" :key="item.questId"
         >
           <div  @click="goworkflowTradeDetail(item)" class="cursor-pointer">
             <div class="flex-between">
               <div class="gap10">
-                <el-button type="primary">免费</el-button>
-                <div class="bold font_size30">AI智能线索富集与多渠道通知系统</div>
+                <el-button type="primary" v-if="applyStatusInfo[item.applyStatus]">{{applyStatusInfo[item.applyStatus]}}</el-button>
+                <div class="bold font_size30">{{item.title}}</div>
               </div>
-              <div class="font_size24 color_price bold">¥2000.00-3000.00</div>
+              <div class="font_size24 color_price bold">¥{{item.budgetMin}}-{{item.budgetMax}}</div>
             </div>
-            <div class="gap20 mt10">
-              <el-button type="primary" size="large" plain>一级分类名称</el-button>
-              <el-button type="primary" size="large" plain>二级分类名称</el-button>
+            <div class="gap10 mt10">
+              <el-button type="primary" size="large" plain>{{item.categoryName1}}</el-button>
+              <el-button type="primary" size="large" plain>{{item.categoryName2}}</el-button>
+              <el-button type="primary" size="large" plain>{{item.categoryName3}}</el-button>
               <div class="gap5">
-                <img :src="yuangong" alt="员工" style="width: 16px; height: 16px;">
-                <span class="font_size14">张三</span>
+                <img :src="yuangong" alt="" style="width: 16px; height: 16px;">
+                <span class="font_size14">{{item.questCount || 0}} 人报名</span>
               </div>
               <div class="gap5">
-                <img :src="riliIcon" alt="员工" style="width: 16px; height: 16px;">
-                <span class="font_size14">2024-10-16</span>
+                <img :src="riliIcon" alt="" style="width: 16px; height: 16px;">
+                <span class="font_size14">报名截止时间:{{item.deadline}}</span>
               </div>
-              <!-- <div class="gap5">
-                <img :src="shiyongIcon" alt="员工" style="width: 16px; height: 16px;">
-                <span class="font_size14">2,456使用</span>
-              </div> -->
-              <!-- <div class="gap5">
-                <img :src="yunIcon" alt="员工" style="width: 16px; height: 16px;">
-                <span class="font_size14">n8n平台</span>
-              </div> -->
-              <!-- <div class="gap5">
-                <img :src="biaoqianIcon" alt="员工" style="width: 16px; height: 16px;">
-                <span class="font_size14">二级分类名称-三级分类名称</span>
-              </div> -->
             </div>
-            <div class="font_size16 gray mt10">此n8n工作流是一款强大的AI驱动的潜在客户富集与通知系统,专为市场营销和销售团队设计。它通过智能表单收集线索,利用OpenAI深度分析并丰富客户数据,自动识别关键信息。系统能根据预设条件,通过多种渠道即时发送个性化通知,显著提升线索质量、转化率及团队协作效率,是优化客户关系管理的关键工具。</div>
+            <div class="font_size16 gray mt10">{{item.requirements || ''}}</div>
           </div>
           <div class="mt20">
             <div class="flex-between">
               <div class="gap10">
                 <el-avatar :size="32" :src="appStore.avatar" />
-                <div class="font_size16 bold">张三</div>
-                <div class="font_size14 gray mt2">2024-10-16</div>
+                <div class="font_size16 bold">{{item.nickName}}</div>
+                <div class="font_size14 gray mt2">{{item.createTime}} 发布</div>
               </div>
-              <el-button type="primary" size="large">
+              <el-button type="primary" size="large" @click="submitSignUp({questId: item.questId,questUserId: item.questUserId},t)">
                 <img :src="zaixianbaomingIcon" alt="" class="mr10" style="width: 16px; height: 16px;">
                 <span class="font_size14">{{$t('common.zaixianbaoming')}}</span>
               </el-button>
@@ -147,13 +136,16 @@
   import yunIcon from '@/assets/imgs/yun.png'
   import biaoqianIcon from '@/assets/imgs/biaoqian.png'
   import zaixianbaomingIcon from '@/assets/imgs/zaixianbaoming.png'
+  import { submitSignUp } from '@/utils/util.js'
 
   import Pagination from '@/components/Pagination.vue'
   import { getQuestList } from '@/api/workflowTrade.js'
   import { getCategoryList } from '@/api/category.js'
+  
 
 
-
+  import { useI18n } from 'vue-i18n' 
+  const { t } = useI18n()
   import { useRouter, useRoute } from 'vue-router'
   const router = useRouter()
   const route = useRoute()
@@ -169,6 +161,10 @@
   const isChildRoute = computed(() => {
     return route.matched.length > 1
   });
+  const applyStatusInfo = {
+    '1': '报名中',
+    '2': '已结束',
+  }
   // 一级分类列表
   const CategoryList = ref([]);
 
@@ -204,8 +200,8 @@
     searchFom.publishTimeEnd = publishTime.value && publishTime.value[1] ? publishTime.value[1] : ''
     const res = await getQuestList(searchFom)
     if(res.code === 200){
-      // listTotal.value = res.data.total
-      // list.value = res.data.records
+      listTotal.value = res.total
+      list.value = res.rows
     }
   }
 
@@ -214,8 +210,8 @@
     router.push({
       path: `/workflow-trade/workflow-trade-detail`,
       query: {
-        id:2,
-        metaTitle: item.name || '详情'
+        questId: item.questId,
+        metaTitle: item.title || '详情'
       }
     })
   };

+ 3 - 2
src/pages/workflowTrade/workflowTradeAdd.vue

@@ -185,7 +185,8 @@ import DGTMessage from '@/utils/message'
 import { questAdd } from '@/api/workflowTrade.js'
 import { getCategoryListTree } from '@/api/category.js'
 
-
+import { useI18n } from 'vue-i18n' 
+const { t } = useI18n()
 import { useRouter, useRoute } from 'vue-router'
 const router = useRouter()
 const route = useRoute()
@@ -277,7 +278,7 @@ const submitForm = async () => {
     questAdd(ruleForm).then(res => {
       console.log(res)
       if(res.code === 200){
-        DGTMessage.success('发布成功')
+        DGTMessage.success(t('workflowTrade.publishSuccess'))
         goBack();
       }
     })

+ 65 - 20
src/pages/workflowTrade/workflowTradeDetail.vue

@@ -7,19 +7,21 @@
           <div class="cursor-pointer">
             <div class="flex-between">
               <div class="gap10">
-                <el-button type="primary">报名中</el-button>
-                <div class="bold font_size30">AI智能线索富集与多渠道通知系统</div>
+                <el-button type="primary">{{applyStatusInfo[ruleForm.applyStatus]}}</el-button>
+                <div class="bold font_size30">{{ruleForm.title}}</div>
               </div>
             </div>
-            <div class="font_size24 color_price bold mt10">¥2000.00-3000.00</div>
+            <div class="font_size24 color_price bold mt10">¥{{ruleForm.budgetMin}}-{{ruleForm.budgetMax}}</div>
             <div class="gap20 mt10">
               <div class="gap5">
-                <img :src="biaoqianIcon" alt="员工" style="width: 16px; height: 16px;">
-                <span class="font_size14">二级分类名称-三级分类名称</span>
+                <img :src="biaoqianIcon" alt="" style="width: 16px; height: 16px;">
+                <span class="font_size14">
+                  {{ruleForm.categoryName1}}-{{ruleForm.categoryName2}}-{{ruleForm.categoryName3}}
+                </span>
               </div>
               <div class="gap5">
                 <img :src="riliIcon" alt="员工" style="width: 16px; height: 16px;">
-                <span class="font_size14">报名截止时间:2024-10-16</span>
+                <span class="font_size14">报名截止时间:{{ruleForm.deadline}}</span>
               </div>
             </div>
           </div>
@@ -30,14 +32,14 @@
             <div class="font_size20 bold">需求背景</div>
           </div>
           <div class="gray font_size14 mt10">
-            我们是一家内容营销公司,每天需要为多个客户生成大量高质量的营销文章。目前我们依赖人工撰写,效率较低且成本较高。希望通过引入AI技术,建立一个自动化文章生成工作流,提高内容生产效率,同时保持文章质量。这个工作流需要能够根据给定的关键词或主题,自动生成符合品牌调性的营销文章,并支持多种输出格式,便于我们直接发布到不同平台。
+            {{ruleForm.background}}
           </div>
           <div class="gap10 mt10">
             <div class="line_vertical"></div>
             <div class="font_size20 bold">具体要求</div>
           </div>
           <div class="gray font_size14 mt10">
-            我们是一家内容营销公司,每天需要为多个客户生成大量高质量的营销文章。目前我们依赖人工撰写,效率较低且成本较高。希望通过引入AI技术,建立一个自动化文章生成工作流,提高内容生产效率,同时保持文章质量。这个工作流需要能够根据给定的关键词或主题,自动生成符合品牌调性的营销文章,并支持多种输出格式,便于我们直接发布到不同平台。
+            {{ruleForm.requirements}}
           </div>
           <div class="gap10 mt10">
             <div class="line_vertical"></div>
@@ -47,19 +49,19 @@
             <div class="flex_1 flex-center">
               <div class="gap10">
                 <img :src="dianhuaIcon" alt="员工" style="width: 24px; height: 24px;">
-                <div class="font_size16">电话:13925214105</div>
+                <div class="font_size16">电话:{{ruleForm.phone}}</div>
               </div>
             </div>
             <div class="flex_1 flex-center">
               <div class="gap10">
                 <img :src="weixinIcon" alt="员工" style="width: 24px; height: 24px;">
-                <div class="font_size16">微信:13925214105</div>
+                <div class="font_size16">微信:{{ruleForm.wechat}}</div>
               </div>
             </div>
             <div class="flex_1 flex-center">
               <div class="gap10">
                 <img :src="youxiangIcon" alt="员工" style="width: 24px; height: 24px;">
-                <div class="font_size16">邮箱:13925214105@qq.com</div>
+                <div class="font_size16">邮箱:{{ruleForm.email}}</div>
               </div>
             </div>
           </div>
@@ -68,17 +70,17 @@
       <div class="workflowTradeDetail_right detail_right">
         <div class="padding16 bg_color_fff border_radius_16 box_shadow_card">
           <div class="flex-column-center">
-            <el-avatar :size="80" :src="appStore.avatar" />
-            <div class="font_size16 bold mt10">张三</div>
+            <el-avatar :size="80" :src="ruleForm.userAvatar || appStore.avatarDefault" />
+            <div class="font_size16 bold mt10">{{ruleForm.nickName}}</div>
           </div>
           <div class="totalInfo mt20 flex-center-between contactInfo_bg">
             <div class="flex-column-center flex_1 border_right">
-              <div class="font_size24 bold">24</div>
+              <div class="font_size24 bold">{{ruleForm.questCount}}</div>
               <div class="font_size14 mt10">发布需求数</div>
             </div>
             <div class="flex-column-center flex_1">
-              <div class="font_size24 bold">24</div>
-              <div class="font_size14 mt10">发布需求数</div>
+              <div class="font_size24 bold">{{ruleForm.publishCount}}</div>
+              <div class="font_size14 mt10">创建工作流</div>
             </div>
           </div>
         </div>
@@ -92,16 +94,22 @@
               <img :src="yuangong" alt="员工" style="width: 20px; height: 20px;">
               <span class="font_size16">已报名人数</span>
             </div>
-            <div class="font_size16 bold">15人</div>
+            <div class="font_size16 bold">{{ruleForm.questApplyCount}}人</div>
           </div>
           <div class="flex-center-between mt10">
             <div class="gap10">
               <img :src="riliIcon" alt="员工" style="width: 20px; height: 20px;">
               <span class="font_size16">剩余时间</span>
             </div>
-            <div class="font_size16 bold color_price">2天3小时</div>
+            <div class="font_size16 bold color_price">
+              {{formatDeadline(ruleForm.deadline).diffDays}}
+              {{t('common.day') }}
+              {{formatDeadline(ruleForm.deadline).diffHours}}
+              {{t('common.hour') }}
+            </div>
           </div>
-          <el-button type="primary" size="large" style="width: 100%;" class="mt20">
+          <el-button type="primary" size="large" style="width: 100%;" class="mt20" 
+          @click="submitSignUp({questId: ruleForm.questId,questUserId: ruleForm.questUserId},t,getQuestDetailFn)">
             <img :src="zaixianbaomingIcon" alt="" class="mr10" style="width: 30px; height: 30px;">
             <span class="font_size18">{{$t('common.zaixianbaoming')}}</span>
           </el-button>
@@ -119,18 +127,55 @@
   import dianhuaIcon from '@/assets/imgs/dianhua.png'
   import weixinIcon from '@/assets/imgs/weixin.png'
   import youxiangIcon from '@/assets/imgs/youxiang.png'
+  import { submitSignUp,formatDeadline } from '@/utils/util.js'
 
+  // 引入api
+  import { getQuestDetail } from '@/api/workflowTrade.js'
+  import { useI18n } from 'vue-i18n' 
+  const { t } = useI18n()
 
   import { useRouter, useRoute } from 'vue-router'
   const router = useRouter()
   const route = useRoute()
   console.log(router,route)
-  import { ref, computed, reactive } from 'vue'
+  import { ref, computed, reactive, onMounted } from 'vue'
   import { useAppStore } from '@/pinia/appStore'
   const appStore = useAppStore()
 
+  const applyStatusInfo = {
+    '1': '报名中',
+    '2': '已结束',
+  }
+
   //获取参数
   const query = route.query;
+  const questId = ref(query.questId || '');
+  const ruleForm = ref({
+    title: '',
+    categoryId1: '',
+    categoryId2: '',
+    categoryId3: '',
+    background: '',
+    requirements: '',
+    budgetMin: '',
+    budgetMax: '',
+    deadline: '',
+    phone: '',
+    wechat: '',
+    email: '',
+  })
+
+  onMounted(() => {
+   getQuestDetailFn();
+  });
+  const getQuestDetailFn = async () => {
+     getQuestDetail({questId: questId.value}).then(res => {
+      if(res.code === 200){
+        console.log(res)
+        ruleForm.value = res.data || {};
+      }
+    })
+  };
 
 
 </script>

+ 34 - 1
src/utils/util.js

@@ -1,4 +1,5 @@
 import DGTMessage from '@/utils/message'//浏览器下载文件
+import { signUp } from '@/api/apply.js'
 export function downloadFile(url, fileName) {
   if (!url) {
     DGTMessage.error('文件地址不存在');
@@ -10,6 +11,38 @@ export function downloadFile(url, fileName) {
   document.body.appendChild(link);
   link.click();
   document.body.removeChild(link);
+}
 
+export async function submitSignUp(formData,t,callback) {
+  try {
+    const response = await signUp(formData);
+    if (response.code === 200) {
+      DGTMessage.success(t('workflowTrade.signUpSuccess'));
+      if(callback && typeof callback === 'function'){
+        callback();
+      }
+      return response.data;
+    } else {
+      DGTMessage.error(response.msg || t('workflowTrade.signUpFailed'));
+      return null;
+    }
+  } catch (error) {
+    return null;
+  }   
+}
+//截止日期转化为天数和小时
+export function formatDeadline(deadline) {
+  if (!deadline || isNaN(Date.parse(deadline))) {
+    return {diffDays:0,diffHours:0};
+  }
+  const now = new Date();
+  const deadlineDate = new Date(deadline);
+  const diffTime = deadlineDate - now;
+  //负数直接返回0
+  if(diffTime <=0){
+    return {diffDays:0,diffHours:0};
+  }
+  const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+  const diffHours = Math.ceil(diffTime / (1000 * 60 * 60)) % 24;
+  return {diffDays,diffHours};
 }
-