3 Commits 973cb567fe ... e6b452593e

Auteur SHA1 Message Date
  steelwei e6b452593e Merge branch 'master' of http://8.152.4.3:3000/yp_other/pet-system-fulfiller-app il y a 1 mois
  steelwei 337452268e 1.完成app样式调整 il y a 1 mois
  steelwei d9b02029ae 1.完成app样式调整 il y a 1 mois

+ 124 - 0
.idea/workspace.xml

@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="AutoImportSettings">
+    <option name="autoReloadType" value="SELECTIVE" />
+  </component>
+  <component name="ChangeListManager">
+    <list default="true" id="e5f5f697-2bd4-4205-922a-fb106cdbbdf5" name="Changes" comment="1.完成app样式调整" />
+    <list id="6ae23f6a-53fe-4817-a1d7-106bcf184c18" name="New changelist" comment="不想提交的文件">
+      <change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+    </list>
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="Git.Settings">
+    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
+  </component>
+  <component name="KubernetesApiPersistence">{}</component>
+  <component name="KubernetesApiProvider">{
+  &quot;isMigrated&quot;: true
+}</component>
+  <component name="MavenImportPreferences">
+    <option name="generalSettings">
+      <MavenGeneralSettings>
+        <option name="customMavenHome" value="D:\apache-maven-3.3.9" />
+        <option name="localRepository" value="D:\maven\repository" />
+        <option name="mavenHomeTypeForPersistence" value="CUSTOM" />
+        <option name="userSettingsFile" value="D:\apache-maven-3.3.9\conf\settings.xml" />
+      </MavenGeneralSettings>
+    </option>
+  </component>
+  <component name="ProjectColorInfo">{
+  &quot;associatedIndex&quot;: 0
+}</component>
+  <component name="ProjectId" id="3ANmbshzjd8LoEqf228vl8RSHus" />
+  <component name="ProjectViewState">
+    <option name="hideEmptyMiddlePackages" value="true" />
+    <option name="showLibraryContents" value="true" />
+    <option name="showMembers" value="true" />
+  </component>
+  <component name="PropertiesComponent">{
+  &quot;keyToString&quot;: {
+    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
+    &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
+    &quot;kotlin-language-version-configured&quot;: &quot;true&quot;,
+    &quot;last_opened_file_path&quot;: &quot;D:/windsurfProject/petSystem/pet-system-fulfiller-app&quot;,
+    &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
+    &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
+    &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
+    &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
+    &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
+    &quot;settings.editor.selected.configurable&quot;: &quot;MavenSettings&quot;,
+    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
+  }
+}</component>
+  <component name="SharedIndexes">
+    <attachedChunks>
+      <set>
+        <option value="bundled-jdk-9823dce3aa75-a94e463ab2e7-intellij.indexing.shared.core-IU-243.26053.27" />
+        <option value="bundled-js-predefined-d6986cc7102b-1632447f56bf-JavaScript-IU-243.26053.27" />
+      </set>
+    </attachedChunks>
+  </component>
+  <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="e5f5f697-2bd4-4205-922a-fb106cdbbdf5" name="Changes" comment="" />
+      <changelist id="6ae23f6a-53fe-4817-a1d7-106bcf184c18" name="New changelist" comment="不想提交的文件" />
+      <created>1772441477194</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1772441477194</updated>
+      <workItem from="1772441478263" duration="4739000" />
+      <workItem from="1773048292053" duration="1121000" />
+      <workItem from="1773058426552" duration="87000" />
+    </task>
+    <task id="LOCAL-00001" summary="1.完成app端履约者入驻相关功能开发&#10;2.完成app端履约者登录功能开发&#10;3.完成履约者个人中心功能开发">
+      <option name="closed" value="true" />
+      <created>1772441589323</created>
+      <option name="number" value="00001" />
+      <option name="presentableId" value="LOCAL-00001" />
+      <option name="project" value="LOCAL" />
+      <updated>1772441589323</updated>
+    </task>
+    <task id="LOCAL-00002" summary="完成修改认证页面">
+      <option name="closed" value="true" />
+      <created>1772470465122</created>
+      <option name="number" value="00002" />
+      <option name="presentableId" value="LOCAL-00002" />
+      <option name="project" value="LOCAL" />
+      <updated>1772470465122</updated>
+    </task>
+    <task id="LOCAL-00003" summary="1.完成app登录调整">
+      <option name="closed" value="true" />
+      <created>1772706550000</created>
+      <option name="number" value="00003" />
+      <option name="presentableId" value="LOCAL-00003" />
+      <option name="project" value="LOCAL" />
+      <updated>1772706550000</updated>
+    </task>
+    <task id="LOCAL-00004" summary="1.完成app样式调整">
+      <option name="closed" value="true" />
+      <created>1773058319475</created>
+      <option name="number" value="00004" />
+      <option name="presentableId" value="LOCAL-00004" />
+      <option name="project" value="LOCAL" />
+      <updated>1773058319475</updated>
+    </task>
+    <option name="localTasksCounter" value="5" />
+    <servers />
+  </component>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="3" />
+  </component>
+  <component name="VcsManagerConfiguration">
+    <MESSAGE value="1.完成app端履约者入驻相关功能开发&#10;2.完成app端履约者登录功能开发&#10;3.完成履约者个人中心功能开发" />
+    <MESSAGE value="完成修改认证页面" />
+    <MESSAGE value="1.完成app登录调整" />
+    <MESSAGE value="1" />
+    <MESSAGE value="1.完成app样式调整" />
+    <option name="LAST_COMMIT_MESSAGE" value="1.完成app样式调整" />
+  </component>
+</project>

+ 8 - 0
App.vue

@@ -1,7 +1,15 @@
 <script>
+	import { isLoggedIn } from '@/utils/auth'
+
 	export default {
 		onLaunch: function() {
 			console.log('App Launch')
+			// 已登录用户直接跳转首页,无需再进登录页
+			if (isLoggedIn()) {
+				uni.switchTab({
+					url: '/pages/home/index'
+				})
+			}
 		},
 		onShow: function() {
 			console.log('App Show')

+ 12 - 0
api/fulfiller.js

@@ -28,6 +28,18 @@ export function submitAudit(data) {
   })
 }
 
+/**
+ * 获取服务项目列表(动态获取服务类型)
+ * @author steelwei
+ */
+export function getServiceTypes() {
+  return request({
+    url: '/fulfiller/app/service/list',
+    method: 'GET',
+    needToken: false
+  })
+}
+
 /**
  * 查询子级区域/站点列表(级联选择器用)
  * @param {number} parentId - 父级ID,0或不传查顶级

+ 1 - 1
pages/mine/index.vue

@@ -60,7 +60,7 @@
                     <image class="arrow-icon" src="/static/icons/chevron_right.svg"></image>
                 </view>
                 <view class="stat-value">
-                    <text class="num">{{ profile?.balance || 0 }}</text>
+                    <text class="num">{{ profile?.balance ? (profile.balance / 100).toFixed(2) : '0.00' }}</text>
                     <text class="unit">元</text>
                 </view>
                 <text class="sub-text">账户余额</text>

+ 66 - 35
pages/mine/settings/auth/edit.vue

@@ -53,12 +53,12 @@
         <view 
           class="service-item" 
           v-for="(service, index) in serviceOptions" 
-          :key="index"
+          :key="service.id"
           @click="toggleService(service)"
         >
-          <text class="service-name">{{ service }}</text>
-          <view class="check-icon" :class="{ active: selectedServices.includes(service) }">
-            <text v-if="selectedServices.includes(service)">✓</text>
+          <text class="service-name">{{ service.name }}</text>
+          <view class="check-icon" :class="{ active: selectedServices.includes(service.id) }">
+            <text v-if="selectedServices.includes(service.id)">✓</text>
           </view>
         </view>
       </view>
@@ -70,23 +70,23 @@
       <text class="section-subtitle">请上传对应服务的资质</text>
       
       <!-- 动态渲染每个服务类型的资质上传 -->
-      <view v-for="(service, index) in selectedServices" :key="index" class="qual-section">
-        <text class="qual-title">{{ service }}资质</text>
+      <view v-for="(serviceId, index) in selectedServices" :key="serviceId" class="qual-section">
+        <text class="qual-title">{{ getServiceName(serviceId) }}资质</text>
         
         <view class="qual-upload-row">
           <!-- 已上传的资质图片 -->
           <view 
             class="qual-item" 
-            v-for="(img, imgIndex) in qualifications[service]" 
+            v-for="(img, imgIndex) in qualifications[getServiceName(serviceId)]" 
             :key="imgIndex"
-            @click="previewImage(service, imgIndex)"
+            @click="previewImage(getServiceName(serviceId), imgIndex)"
           >
             <image :src="img" class="qual-img" mode="aspectFill"></image>
-            <view class="delete-btn" @click.stop="deleteQualImage(service, imgIndex)">×</view>
+            <view class="delete-btn" @click.stop="deleteQualImage(getServiceName(serviceId), imgIndex)">×</view>
           </view>
           
           <!-- 上传按钮 -->
-          <view class="qual-upload-btn" @click="chooseQualImage(service)">
+          <view class="qual-upload-btn" @click="chooseQualImage(getServiceName(serviceId))">
             <text class="plus-icon">+</text>
           </view>
         </view>
@@ -103,7 +103,7 @@
 </template>
 
 <script>
-import { getAuthInfo, uploadFile, updateAuthInfo } from '@/api/fulfiller'
+import { getAuthInfo, uploadFile, updateAuthInfo, getServiceTypes } from '@/api/fulfiller'
 
 export default {
   data() {
@@ -112,16 +112,28 @@ export default {
       idCardBack: '',
       idCardFrontOssId: '',
       idCardBackOssId: '',
-      serviceOptions: ['宠物接送', '上门喂遛', '上门洗护'],
+      serviceOptions: [],
       selectedServices: [],
       qualifications: {},
       qualOssIds: {}
     }
   },
-  onLoad() {
+  async onLoad() {
+    await this.loadServiceOptions()
     this.loadAuthInfo()
   },
   methods: {
+    async loadServiceOptions() {
+      try {
+        const res = await getServiceTypes()
+        this.serviceOptions = (res.data || []).map(item => ({
+          id: Number(item.id),
+          name: item.name
+        }))
+      } catch (e) {
+        console.error('加载服务类型失败', e)
+      }
+    },
     async loadAuthInfo() {
       try {
         uni.showLoading({ title: '加载中...' })
@@ -131,19 +143,33 @@ export default {
           this.idCardBack = res.data.idCardBackUrl || ''
           this.idCardFrontOssId = res.data.idCardFront || ''
           this.idCardBackOssId = res.data.idCardBack || ''
-          this.selectedServices = res.data.serviceTypeList || []
+          // 解析服务类型ID列表(逗号分隔,去重)
+          let serviceIds = []
+          if (res.data.serviceTypes) {
+            serviceIds = [...new Set(
+              res.data.serviceTypes.replace(/[\[\]"]/g, '').split(',')
+                .map(s => s.trim()).filter(s => s)
+                .map(Number)
+                .filter(id => !isNaN(id) && id > 0)
+            )]
+          }
+          this.selectedServices = serviceIds
           
-          this.selectedServices.forEach(service => {
-            this.qualifications[service] = []
-            this.qualOssIds[service] = []
-          })
+          // 解析资质图片URL和OSS ID
+          const qualUrlList = res.data.qualImageUrls ? res.data.qualImageUrls.split(',').filter(Boolean) : []
+          const qualOssIdList = res.data.qualImages ? res.data.qualImages.replace(/[\[\]"]/g, '').split(',').map(s => s.trim()).filter(Boolean) : []
           
-          if (res.data.qualImageUrls && res.data.qualImageUrls.length > 0) {
-            const firstService = this.selectedServices[0]
-            if (firstService) {
-              this.qualifications[firstService] = res.data.qualImageUrls
-            }
-          }
+          // 收集有效的服务名称列表
+          const validNames = serviceIds.map(sid => this.getServiceName(sid)).filter(Boolean)
+          
+          // 为每个已选服务类型初始化资质数据,并均匀分配已有图片
+          validNames.forEach((name, idx) => {
+            // 将已有资质图片按服务类型数量均匀分配
+            const start = Math.floor(idx * qualUrlList.length / validNames.length)
+            const end = Math.floor((idx + 1) * qualUrlList.length / validNames.length)
+            this.$set(this.qualifications, name, qualUrlList.slice(start, end))
+            this.$set(this.qualOssIds, name, qualOssIdList.slice(start, end))
+          })
         }
         uni.hideLoading()
       } catch (e) {
@@ -195,16 +221,20 @@ export default {
         this.idCardBackOssId = ''
       }
     },
+    getServiceName(serviceId) {
+      const found = this.serviceOptions.find(s => s.id === serviceId)
+      return found ? found.name : ''
+    },
     toggleService(service) {
-      const index = this.selectedServices.indexOf(service)
+      const index = this.selectedServices.indexOf(service.id)
       if (index > -1) {
         this.selectedServices.splice(index, 1)
-        delete this.qualifications[service]
-        delete this.qualOssIds[service]
+        this.$delete(this.qualifications, service.name)
+        this.$delete(this.qualOssIds, service.name)
       } else {
-        this.selectedServices.push(service)
-        this.qualifications[service] = []
-        this.qualOssIds[service] = []
+        this.selectedServices.push(service.id)
+        this.$set(this.qualifications, service.name, [])
+        this.$set(this.qualOssIds, service.name, [])
       }
       this.$forceUpdate()
     },
@@ -260,9 +290,10 @@ export default {
         return
       }
       
-      for (const service of this.selectedServices) {
-        if (!this.qualifications[service] || this.qualifications[service].length === 0) {
-          uni.showToast({ title: `请上传${service}资质`, icon: 'none' })
+      for (const serviceId of this.selectedServices) {
+        const name = this.getServiceName(serviceId)
+        if (!this.qualifications[name] || this.qualifications[name].length === 0) {
+          uni.showToast({ title: `请上传${name}资质`, icon: 'none' })
           return
         }
       }
@@ -286,8 +317,8 @@ export default {
       const submitData = {
         idCardFront: this.idCardFrontOssId,
         idCardBack: this.idCardBackOssId,
-        serviceTypes: JSON.stringify(this.selectedServices),
-        qualifications: JSON.stringify(allQualOssIds)
+        serviceTypes: this.selectedServices.join(','),  // 逗号分隔的服务类型ID
+        qualifications: allQualOssIds.join(',')  // 逗号分隔的资质图片OSS ID
       }
       
       try {

+ 1 - 1
pages/mine/settings/auth/index.vue

@@ -126,7 +126,7 @@ export default {
                         authId: res.data.authId || false,
                         authQual: res.data.authQual || false,
                         pendingAudit: res.data.pendingAudit || false,
-                        qualImages: res.data.qualImageUrls || []
+                        qualImages: res.data.qualImageUrls ? res.data.qualImageUrls.split(',').filter(Boolean) : []
                     }
                 }
             } catch (e) {

+ 3 - 3
pages/recruit/auth_logic.js

@@ -24,7 +24,7 @@ export default {
     onLoad(options) {
         if (options.services) {
             try {
-                this.serviceType = JSON.parse(options.services);
+                this.serviceType = JSON.parse(decodeURIComponent(options.services));
             } catch (e) {
                 console.error('Parse services failed', e);
             }
@@ -158,10 +158,10 @@ export default {
                 console.error('保存认证数据失败', e)
             }
 
-            // 传递数据
+            // 传递数据(服务类型对象数组 {id, name})
             const services = JSON.stringify(this.serviceType);
             uni.navigateTo({
-                url: `/pages/recruit/qualifications?services=${services}`
+                url: `/pages/recruit/qualifications?services=${encodeURIComponent(services)}`
             });
         }
     }

+ 3 - 3
pages/recruit/form.vue

@@ -94,11 +94,11 @@
         <view 
           class="type-btn" 
           v-for="(item, index) in serviceTypes" 
-          :key="index"
-          :class="{selected: formData.serviceType.includes(item)}"
+          :key="item.id"
+          :class="{selected: formData.serviceType.includes(item.id)}"
           @click="toggleService(item)"
         >
-          {{ item }}
+          {{ item.name }}
         </view>
       </view>
 

+ 22 - 6
pages/recruit/logic.js

@@ -1,5 +1,5 @@
 import { sendSmsCode } from '@/api/auth'
-import { getAreaChildren } from '@/api/fulfiller'
+import { getAreaChildren, getServiceTypes } from '@/api/fulfiller'
 
 export default {
     data() {
@@ -18,7 +18,7 @@ export default {
             },
             showPwd: false,
             isAgreed: false,
-            serviceTypes: ['宠物接送', '上门喂遛', '上门洗护'],
+            serviceTypes: [],
 
             // 验证码倒计时
             countDown: 0,
@@ -53,6 +53,7 @@ export default {
     },
     created() {
         this.initDateData();
+        this.loadServiceTypes();
     },
     beforeDestroy() {
         if (this.timer) clearInterval(this.timer);
@@ -119,12 +120,25 @@ export default {
             this.closePicker();
         },
 
+        async loadServiceTypes() {
+            try {
+                const res = await getServiceTypes();
+                this.serviceTypes = (res.data || []).map(item => ({
+                    id: item.id,
+                    name: item.name
+                }));
+            } catch (err) {
+                console.error('加载服务类型失败:', err);
+                this.serviceTypes = [];
+            }
+        },
+
         toggleService(item) {
-            const idx = this.formData.serviceType.indexOf(item);
+            const idx = this.formData.serviceType.indexOf(item.id);
             if (idx > -1) {
                 this.formData.serviceType.splice(idx, 1);
             } else {
-                this.formData.serviceType.push(item);
+                this.formData.serviceType.push(item.id);
             }
         },
 
@@ -287,9 +301,11 @@ export default {
             }
             // 暂存表单数据到本地,供后续页面组装提交
             uni.setStorageSync('recruit_form_data', JSON.stringify(this.formData));
-            const services = JSON.stringify(this.formData.serviceType);
+            // 传递选中的服务类型对象(id+name)给后续页面
+            const selectedServices = this.serviceTypes.filter(s => this.formData.serviceType.includes(s.id));
+            const services = JSON.stringify(selectedServices);
             uni.navigateTo({
-                url: `/pages/recruit/auth?services=${services}`
+                url: `/pages/recruit/auth?services=${encodeURIComponent(services)}`
             });
         }
     }

+ 6 - 6
pages/recruit/qualifications.vue

@@ -10,18 +10,18 @@
     </view>
 
     <!-- 动态渲染资质卡片 -->
-    <view class="qual-card" v-for="(type, index) in serviceTypes" :key="index">
-      <view class="card-title">{{ type }}服务资质</view>
+    <view class="qual-card" v-for="(item, index) in serviceTypes" :key="item.id">
+      <view class="card-title">{{ item.name }}服务资质</view>
       
       <view class="upload-wrapper">
          <!-- 已有图片列表 -->
-         <view class="img-item" v-for="(img, imgIndex) in qualifications[type]" :key="imgIndex">
-             <image :src="img" class="preview-img" mode="aspectFill" @click="previewImage(type, imgIndex)"></image>
-             <view class="delete-btn" @click.stop="deleteImage(type, imgIndex)">×</view>
+         <view class="img-item" v-for="(img, imgIndex) in qualifications[item.name]" :key="imgIndex">
+             <image :src="img" class="preview-img" mode="aspectFill" @click="previewImage(item.name, imgIndex)"></image>
+             <view class="delete-btn" @click.stop="deleteImage(item.name, imgIndex)">×</view>
          </view>
          
          <!-- 上传/添加按钮 -->
-         <view class="upload-box" @click="chooseImage(type)">
+         <view class="upload-box" @click="chooseImage(item.name)">
              <text class="plus-icon">+</text>
              <text class="upload-text">上传</text>
          </view>

+ 7 - 7
pages/recruit/qualifications_logic.js

@@ -11,11 +11,11 @@ export default {
     onLoad(options) {
         if (options.services) {
             try {
-                this.serviceTypes = JSON.parse(options.services);
-                // 初始化 qualifications 对象
-                this.serviceTypes.forEach(type => {
-                    this.qualifications[type] = [];
-                    this.qualOssIds[type] = [];
+                this.serviceTypes = JSON.parse(decodeURIComponent(options.services));
+                // 初始化 qualifications 对象(以服务名称为key)
+                this.serviceTypes.forEach(item => {
+                    this.qualifications[item.name] = [];
+                    this.qualOssIds[item.name] = [];
                 });
             } catch (e) {
                 console.error('Parse services failed', e);
@@ -93,7 +93,7 @@ export default {
                 password: recruitData.password || '',
                 gender: recruitData.gender === 1 ? '0' : '1',
                 birthday: recruitData.birthday || '',
-                serviceTypes: JSON.stringify(recruitData.serviceType || []),
+                serviceTypes: (recruitData.serviceType || []).join(','),  // 逗号分隔的服务类型ID
                 city: recruitData.city || '',
                 stationId: recruitData.stationId || null,
                 realName: recruitData.realName || '',
@@ -101,7 +101,7 @@ export default {
                 idValidDate: recruitData.expiryDate || '',
                 idCardFront: recruitData.idCardFrontOssId || null,
                 idCardBack: recruitData.idCardBackOssId || null,
-                qualifications: JSON.stringify(allQualOssIds)
+                qualifications: allQualOssIds.join(',')  // 逗号分隔的资质图片OSS ID
             }
 
             uni.showLoading({ title: '提交中...' })

+ 4 - 3
pages/recruit/style.css

@@ -140,13 +140,14 @@ page {
 
 .service-types {
     display: flex;
-    justify-content: space-between;
+    flex-wrap: wrap;
+    gap: 20rpx;
     margin-bottom: 30rpx;
 }
 
 .type-btn {
-    width: 30%;
-    /* 三等分 */
+    width: calc((100% - 40rpx) / 3);
+    /* 每行3个,间距20rpx */
     height: 80rpx;
     background-color: #F8F8F8;
     border-radius: 8rpx;