wlzboy
2025-10-27 559b2e34c983f615b6d6747f52c801022c561803
feat: 优化任务显示列表
4个文件已添加
16个文件已修改
2045 ■■■■ 已修改文件
app/pages/login.vue 95 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/mine/privacy-policy/index.vue 344 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/register.vue 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/create-emergency.vue 117 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/登录注册协议同意功能说明.md 324 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
prd/APP接口权限移除说明.md 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/sqlserver/SqlServerDictionaryController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMessageController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleInfoController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java 157 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskVehicleController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/HospDataMapper.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/api/task.js 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/task/general/detail.vue 103 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/task/general/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/task/旧系统同步状态显示功能说明.md 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
任务接口拆分说明.md 422 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/login.vue
@@ -21,15 +21,22 @@
          <image :src="codeUrl" @click="getCode" class="login-code-img" mode="aspectFit"></image>
        </view>
      </view>
      <view class="agreement-checkbox">
        <checkbox-group @change="onAgreementChange">
          <label class="checkbox-label">
            <checkbox :checked="agreedToPolicy" value="agreed" color="#007AFF" style="margin-top: 0;" />
            <text class="agreement-text">
              <text class="text-grey1">同意</text>
              <text @click.stop="handleUserAgrement" class="text-blue agreement-link">《用户协议》</text>
              <text class="text-grey1">和</text>
              <text @click.stop="handlePrivacy" class="text-blue agreement-link">《隐私政策》</text>
            </text>
          </label>
        </checkbox-group>
      </view>
      <view class="action-btn">
        <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
      </view>
      <view class="xieyi text-center">
        <text class="text-grey1">登录即代表同意</text>
        <text @click.stop="handleUserAgrement" class="text-blue agreement-link">《用户协议》</text>
        <text class="text-grey1">和</text>
        <text @click.stop="handlePrivacy" class="text-blue agreement-link">《隐私协议》</text>
      </view>
    </view>
  </scroll-view>
@@ -45,6 +52,8 @@
        captchaEnabled: true,
        // ç”¨æˆ·æ³¨å†Œå¼€å…³
        register: false,
        // éšç§æ”¿ç­–同意状态(默认选中)
        agreedToPolicy: true,
        globalConfig: getApp().globalData.config,
        loginForm: {
          username: "",
@@ -70,6 +79,10 @@
      handleUserAgrement() {
        this.$tab.navigateTo('/pages/mine/user-agreement/index')
      },
      // åè®®åŒæ„çŠ¶æ€å˜æ›´
      onAgreementChange(e) {
        this.agreedToPolicy = e.detail.value.length > 0
      },
      // èŽ·å–å›¾å½¢éªŒè¯ç 
      getCode() {
        getCodeImg().then(res => {
@@ -82,6 +95,10 @@
      },
      // ç™»å½•方法
      async handleLogin() {
        if (!this.agreedToPolicy) {
          this.$modal.msgError("请先阅读并同意用户协议和隐私政策")
          return
        }
        if (this.loginForm.username === "") {
          this.$modal.msgError("请输入您的账号")
        } else if (this.loginForm.password === "") {
@@ -242,24 +259,56 @@
        }
      }
      
      .xieyi {
      .agreement-checkbox {
        margin: 50rpx 0 30rpx 0;
        padding: 20rpx 0;
        line-height: 2;
        .text-grey1 {
          color: #888;
          font-size: 24rpx;
        padding: 20rpx;
        display: flex;
        justify-content: flex-start;
        align-items: left;
        checkbox-group {
          display: flex;
          align-items: center;
        }
        .agreement-link {
          color: #007AFF;
          font-size: 24rpx;
          padding: 10rpx 8rpx;
          margin: 0 5rpx;
          display: inline-block;
          position: relative;
          z-index: 10;
        .checkbox-label {
          display: flex;
          align-items: center;
          justify-content: flex-start;
          checkbox {
            margin-right: 15rpx;
            transform: scale(1.2);
            flex-shrink: 0;
            vertical-align: middle;
          }
          .agreement-text {
            display: inline-flex;
            align-items: center;
            flex-wrap: wrap;
            line-height: 1.5;
            font-size: 26rpx;
            text-align: left;
            justify-content: flex-start;
            .text-grey1 {
              color: #666;
              font-size: 26rpx;
              line-height: 1.5;
            }
            .text-blue {
              color: #007AFF;
              font-size: 26rpx;
              padding: 8rpx 10rpx;
              margin: 0 5rpx;
              display: inline-block;
              position: relative;
              z-index: 10;
              line-height: 1.5;
            }
          }
        }
      }
    }
app/pages/mine/privacy-policy/index.vue
@@ -9,290 +9,75 @@
    
    <scroll-view class="content" scroll-y="true">
      <view class="privacy-content">
        <view class="update-time">更新日期:2025å¹´1月25日</view>
        <view class="effect-time">生效日期:2025å¹´10月25日</view>
        <view class="main-title">广东民航医疗快线调度系统小程序隐私保护指引</view>
        <view class="update-time">更新日期:2025-10-26</view>
        <view class="intro-text">
          æœ¬æŒ‡å¼•是广东民航医疗快线调度系统小程序开发者 <text class="bold">广东民航医疗快线有限公司</text>(以下简称"开发者")为处理你的个人信息而制定。
        </view>
        
        <view class="section">
          <text class="section-title">引言</text>
          <text class="section-title">开发者处理的信息</text>
          <text class="section-text">
            æ€¥æ•‘转运调度系统(以下简称"我们")深知个人信息对您的重要性,我们将按照法律法规的要求,采取相应的安全保护措施,尽力保护您的个人信息安全可控。
          </text>
          <text class="section-text">
            æœ¬ã€Šéšç§æ”¿ç­–》适用于我们向您提供的所有服务。我们希望通过本政策向您说明我们如何收集、使用、存储和分享您的个人信息,以及您享有的相关权利。
            æ ¹æ®æ³•律规定,开发者仅处理实现小程序功能所必要的信息。
          </text>
          <text class="section-text highlight">
            è¯·æ‚¨åœ¨ä½¿ç”¨æˆ‘们的产品/服务前,仔细阅读并充分理解本政策,特别是以粗体/下划线标识的条款,您应重点阅读。如您对本政策有任何疑问,可通过本政策文末提供的联系方式与我们联系。
            å¼€å‘者将在获取你的明示同意后,收集你的位置信息,用途是【获取当前司机所在位置,以判断任务单到哪里了】
          </text>
        </view>
        
        <view class="section">
          <text class="section-title">一、我们如何收集和使用您的个人信息</text>
          <text class="section-title">你的权益</text>
          <text class="section-text">
            ä¸ªäººä¿¡æ¯æ˜¯æŒ‡ä»¥ç”µå­æˆ–者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。
            å…³äºŽä½ çš„个人信息,你可以通过以下方式与开发者联系,行使查阅、复制、更正、删除等法定权利。
          </text>
          <text class="section-text">
            æˆ‘们仅会出于以下目的,收集和使用您的个人信息:
            è‹¥ä½ åœ¨å°ç¨‹åºä¸­æ³¨å†Œäº†è´¦å·ï¼Œä½ å¯ä»¥é€šè¿‡ä»¥ä¸‹æ–¹å¼ä¸Žå¼€å‘者联系,申请注销你在小程序中使用的账号。在受理你的申请后,开发者承诺在十五个工作日内完成核查和处理,并按照法律法规要求处理你的相关信息。
          </text>
          <text class="subsection-title">1.1 è´¦å·æ³¨å†Œä¸Žç™»å½•</text>
          <text class="contact-info">
            ç”µè¯:13602220409
          </text>
        </view>
        <view class="section">
          <text class="section-title">开发者对信息的存储</text>
          <text class="section-text">
            å½“您注册及使用本系统时,我们需要收集:
            å¼€å‘者承诺,除法律法规另有规定外,开发者对你的信息的保存期限应当为实现处理目的所必要的最短时间。
          </text>
          <text class="section-text indent">
            Â· æ‰‹æœºå·ç ï¼šç”¨äºŽè´¦å·æ³¨å†Œã€ç™»å½•验证和安全保障
          </text>
          <text class="section-text indent">
            Â· å§“名:用于身份识别和任务分配
          </text>
          <text class="section-text indent">
            Â· æ‰€å±žéƒ¨é—¨/机构:用于权限管理和任务协作
          </text>
          <text class="section-text indent">
            Â· èŒä½/角色:用于确定系统使用权限
          </text>
          <text class="subsection-title">1.2 ä»»åŠ¡è°ƒåº¦æœåŠ¡</text>
        </view>
        <view class="section">
          <text class="section-title">信息的使用规则</text>
          <text class="section-text">
            ä¸ºäº†å‘您提供任务调度服务,我们需要收集:
          </text>
          <text class="section-text indent">
            Â· ä½ç½®ä¿¡æ¯ï¼šèŽ·å–æ‚¨çš„å®žæ—¶ä½ç½®ï¼Œç”¨äºŽä»»åŠ¡åˆ†é…ã€è½¦è¾†è°ƒåº¦å’Œè·¯å¾„è§„åˆ’
          </text>
          <text class="section-text indent">
            Â· ä»»åŠ¡ç›¸å…³ä¿¡æ¯ï¼šåŒ…æ‹¬æ‚£è€…ä¿¡æ¯ï¼ˆå§“åã€å¹´é¾„ã€æ€§åˆ«ã€ç—…æƒ…æè¿°ï¼‰ã€åœ°å€ä¿¡æ¯ã€åŒ»é™¢ä¿¡æ¯ç­‰
          </text>
          <text class="section-text indent">
            Â· è½¦è¾†ä¿¡æ¯ï¼šè½¦ç‰Œå·ã€è½¦è¾†ç±»åž‹ã€è®¾å¤‡ç¼–号等
            å¼€å‘者将会在本指引所明示的用途内使用收集的信息。
          </text>
          <text class="section-text highlight">
            ä½ç½®ä¿¡æ¯å±žäºŽæ•æ„Ÿä¸ªäººä¿¡æ¯ï¼Œæˆ‘们仅在您主动开启定位权限并使用相关功能时收集,您可以随时在系统设置中关闭定位权限。拒绝提供位置信息可能导致部分功能无法使用,但不影响其他功能的正常使用。
          </text>
          <text class="subsection-title">1.3 æ¶ˆæ¯æŽ¨é€æœåŠ¡</text>
          <text class="section-text">
            ä¸ºäº†åŠæ—¶å‘您推送任务通知、系统消息等信息,我们需要收集:
          </text>
          <text class="section-text indent">
            Â· è®¾å¤‡ä¿¡æ¯ï¼šè®¾å¤‡åž‹å·ã€æ“ä½œç³»ç»Ÿç‰ˆæœ¬ã€è®¾å¤‡æ ‡è¯†ç¬¦
          </text>
          <text class="section-text indent">
            Â· æŽ¨é€token:用于消息推送
          </text>
          <text class="subsection-title">1.4 ç³»ç»Ÿå®‰å…¨ä¸Žä¼˜åŒ–</text>
          <text class="section-text">
            ä¸ºäº†ä¿éšœç³»ç»Ÿå®‰å…¨ç¨³å®šè¿è¡Œå¹¶æå‡ç”¨æˆ·ä½“验,我们会收集:
          </text>
          <text class="section-text indent">
            Â· æ—¥å¿—信息:操作日志、错误日志、性能数据
          </text>
          <text class="section-text indent">
            Â· ç½‘络信息:IP地址、网络类型
          </text>
          <text class="section-text indent">
            Â· åº”用使用情况:页面访问记录、功能使用频率
            å¦‚开发者使用你的信息超出本指引目的或合理范围,开发者必须在变更使用目的或范围前,再次以手机短信方式告知并征得你的明示同意。
          </text>
        </view>
        
        <view class="section">
          <text class="section-title">二、我们如何使用Cookie和同类技术</text>
          <text class="section-title">信息对外提供</text>
          <text class="section-text">
            Cookie是一种网络服务器存储在计算机或移动设备上的纯文本文件。我们使用Cookie和同类技术主要为了实现以下功能:
          </text>
          <text class="section-text indent">
            Â· è®°ä½æ‚¨çš„登录状态,避免重复登录
          </text>
          <text class="section-text indent">
            Â· åˆ†æžæ‚¨ä½¿ç”¨æˆ‘们服务的情况,以便优化服务体验
          </text>
          <text class="section-text indent">
            Â· ä¿éšœç³»ç»Ÿå®‰å…¨ï¼Œé˜²èŒƒå®‰å…¨é£Žé™©
            å¼€å‘者承诺,不会主动共享或转让你的信息至任何第三方,如存在确需共享或转让时,开发者应当直接征得或确认第三方征得你的单独同意。
          </text>
          <text class="section-text">
            æ‚¨å¯ä»¥é€šè¿‡æµè§ˆå™¨æˆ–设备设置拒绝或管理Cookie,但这可能影响您使用我们服务的部分功能。
            å¼€å‘者承诺,不会对外公开披露你的信息,如必须公开披露时,开发者应当向你告知公开披露的目的、披露信息的类型及可能涉及的信息,并征得你的单独同意。
          </text>
        </view>
        
        <view class="section">
          <text class="section-title">三、我们如何共享、转让、公开披露您的个人信息</text>
          <text class="subsection-title">3.1 å…±äº«</text>
          <text class="section-title">投诉与联系</text>
          <text class="section-text">
            æˆ‘们不会向第三方共享您的个人信息,除非:
            ä½ è®¤ä¸ºå¼€å‘者未遵守上述约定,或有其他的投诉建议、或未成年人个人信息保护相关问题,可通过以下方式与开发者联系;或者向微信进行投诉。
          </text>
          <text class="section-text indent">
            Â· äº‹å…ˆèŽ·å¾—æ‚¨çš„æ˜Žç¡®åŒæ„
          </text>
          <text class="section-text indent">
            Â· ä¸Žå…³è”机构共享:在业务需要且符合法律规定的情况下,我们可能与您所属医疗机构、急救中心共享必要信息
          </text>
          <text class="section-text indent">
            Â· æ³•律法规规定的其他情形
          </text>
          <text class="subsection-title">3.2 è½¬è®©</text>
          <text class="section-text">
            æˆ‘们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:
          </text>
          <text class="section-text indent">
            Â· äº‹å…ˆèŽ·å¾—æ‚¨çš„æ˜Žç¡®åŒæ„
          </text>
          <text class="section-text indent">
            Â· æ ¹æ®æ³•律法规或强制性的行政或司法要求
          </text>
          <text class="subsection-title">3.3 å…¬å¼€æŠ«éœ²</text>
          <text class="section-text">
            æˆ‘们仅会在以下情况下公开披露您的个人信息:
          </text>
          <text class="section-text indent">
            Â· èŽ·å¾—æ‚¨çš„æ˜Žç¡®åŒæ„
          </text>
          <text class="section-text indent">
            Â· åŸºäºŽæ³•律法规、法律程序、诉讼或政府主管部门强制性要求
          </text>
        </view>
        <view class="section">
          <text class="section-title">四、我们如何保护您的个人信息</text>
          <text class="section-text">
            4.1 æˆ‘们非常重视个人信息安全,并采取一切合理可行的措施保护您的个人信息:
          </text>
          <text class="section-text indent">
            Â· æ•°æ®åŠ å¯†ï¼šé‡‡ç”¨SSL/TLS加密传输,对敏感数据进行加密存储
          </text>
          <text class="section-text indent">
            Â· è®¿é—®æŽ§åˆ¶ï¼šå»ºç«‹ä¸¥æ ¼çš„æ•°æ®è®¿é—®æƒé™æŽ§åˆ¶å’Œå®¡æ‰¹æœºåˆ¶
          </text>
          <text class="section-text indent">
            Â· å®‰å…¨å®¡è®¡ï¼šå®šæœŸè¿›è¡Œå®‰å…¨å®¡è®¡å’Œé£Žé™©è¯„ä¼°
          </text>
          <text class="section-text indent">
            Â· äººå‘˜ç®¡ç†ï¼šå¯¹å¤„理个人信息的员工进行身份认证和权限管理
          </text>
          <text class="section-text indent">
            Â· åº”急响应:制定个人信息安全事件应急预案
          </text>
          <text class="section-text">
            4.2 æˆ‘们会采取合理可行的措施,尽力避免收集无关的个人信息。
          </text>
          <text class="section-text highlight">
            4.3 äº’联网并非绝对安全的环境,我们强烈建议您采取积极措施保护个人信息的安全,包括但不限于使用复杂密码、定期修改密码、不将自己的账号密码等个人信息透露给他人。
          </text>
          <text class="section-text highlight">
            4.4 å¦‚果发生个人信息安全事件,我们将按照法律法规的要求,及时向您告知:安全事件的基本情况和可能的影响、我们已采取或将要采取的处置措施、您可自主防范和降低风险的建议等。
          </text>
        </view>
        <view class="section">
          <text class="section-title">五、您如何管理个人信息</text>
          <text class="section-text">
            æŒ‰ç…§ä¸­å›½ç›¸å…³çš„æ³•律法规,我们保障您对自己的个人信息行使以下权利:
          </text>
          <text class="subsection-title">5.1 è®¿é—®å’Œæ›´æ­£æ‚¨çš„个人信息</text>
          <text class="section-text">
            æ‚¨å¯ä»¥é€šè¿‡ä»¥ä¸‹æ–¹å¼è®¿é—®å’Œæ›´æ­£æ‚¨çš„个人信息:
          </text>
          <text class="section-text indent">
            Â· é€šè¿‡"我的-个人信息"页面查看和修改基本信息
          </text>
          <text class="section-text indent">
            Â· è”系系统管理员协助修改
          </text>
          <text class="subsection-title">5.2 åˆ é™¤æ‚¨çš„个人信息</text>
          <text class="section-text">
            åœ¨ä»¥ä¸‹æƒ…形中,您可以向我们提出删除个人信息的请求:
          </text>
          <text class="section-text indent">
            Â· æˆ‘们处理个人信息的行为违反法律法规
          </text>
          <text class="section-text indent">
            Â· æˆ‘们收集、使用您的个人信息,却未征得您的同意
          </text>
          <text class="section-text indent">
            Â· æˆ‘们处理个人信息的行为违反了与您的约定
          </text>
          <text class="section-text indent">
            Â· æ‚¨æ³¨é”€äº†è´¦å·
          </text>
          <text class="subsection-title">5.3 æ³¨é”€è´¦å·</text>
          <text class="section-text">
            æ‚¨å¯ä»¥é€šè¿‡è”系系统管理员申请注销账号。账号注销后,我们将停止为您提供服务,并根据法律法规要求删除您的个人信息。
          </text>
          <text class="subsection-title">5.4 æ’¤å›žåŒæ„</text>
          <text class="section-text">
            æ‚¨å¯ä»¥é€šè¿‡è®¾å¤‡æƒé™è®¾ç½®æ’¤å›žå¯¹ä½ç½®ä¿¡æ¯ã€æ‘„像头、相册等敏感权限的授权。
          </text>
          <text class="section-text">
            è¯·æ‚¨ç†è§£ï¼Œç‰¹å®šçš„业务功能需要您的信息才能得以完成,当您撤回同意后,我们无法继续为您提供撤回同意所对应的服务。
          </text>
        </view>
        <view class="section">
          <text class="section-title">六、我们如何处理未成年人的个人信息</text>
          <text class="section-text">
            6.1 æœ¬ç³»ç»Ÿä¸»è¦é¢å‘医疗急救从业人员,原则上不向未成年人提供服务。
          </text>
          <text class="section-text">
            6.2 å¦‚果我们发现在未事先获得可证实的父母或法定监护人同意的情况下收集了未成年人的个人信息,我们会设法尽快删除相关数据。
          </text>
        </view>
        <view class="section">
          <text class="section-title">七、您的个人信息如何在全球范围内转移</text>
          <text class="section-text">
            æˆ‘们在中华人民共和国境内收集和产生的个人信息将存储在中华人民共和国境内。
          </text>
        </view>
        <view class="section">
          <text class="section-title">八、本政策如何更新</text>
          <text class="section-text">
            8.1 æˆ‘们可能适时修订本政策内容。如该等变更会导致您在本政策项下权利的实质减损,我们将在变更生效前,通过在页面显著位置提示、向您发送消息等方式通知您。
          </text>
          <text class="section-text highlight">
            8.2 åœ¨è¯¥ç§æƒ…况下,若您继续使用我们的服务,即表示同意受经修订的本政策的约束。
          </text>
        </view>
        <view class="section">
          <text class="section-title">九、如何联系我们</text>
          <text class="section-text">
            9.1 å¦‚您对本隐私政策或您个人信息的相关事宜有任何问题、意见或建议,请通过以下方式与我们联系:
          </text>
          <text class="section-text">
            ç”µå­é‚®ç®±ï¼šwanglizhong@966120.com.cn
          </text>
          <text class="section-text">
            å®¢æœç”µè¯ï¼š020-966120
          </text>
          <text class="section-text">
            å·¥ä½œæ—¶é—´ï¼šå‘¨ä¸€è‡³å‘¨æ—¥ 24小时
          </text>
          <text class="section-text">
            9.2 ä¸€èˆ¬æƒ…况下,我们将在15个工作日内回复您的请求。
          </text>
        </view>
        <view class="section">
          <text class="section-title">十、定义</text>
          <text class="section-text">
            ä¸ªäººä¿¡æ¯ï¼šæŒ‡ä»¥ç”µå­æˆ–者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。
          </text>
          <text class="section-text">
            æ•æ„Ÿä¸ªäººä¿¡æ¯ï¼šæŒ‡ä¸€æ—¦æ³„露或者非法使用,容易导致自然人的人格尊严受到侵害或者人身、财产安全受到危害的个人信息,包括生物识别、宗教信仰、特定身份、医疗健康、金融账户、行踪轨迹等信息。
          </text>
          <text class="section-text">
            ä¸ªäººä¿¡æ¯åˆ é™¤ï¼šæŒ‡åœ¨å®žçŽ°æ—¥å¸¸ä¸šåŠ¡åŠŸèƒ½æ‰€æ¶‰åŠçš„ç³»ç»Ÿä¸­åŽ»é™¤ä¸ªäººä¿¡æ¯çš„è¡Œä¸ºï¼Œä½¿å…¶ä¿æŒä¸å¯è¢«æ£€ç´¢ã€è®¿é—®çš„çŠ¶æ€ã€‚
          <text class="contact-info">
            ç”µè¯:13602220409
          </text>
        </view>
        
        <view class="footer">
          <text class="footer-text">感谢您信任并使用急救转运调度系统!</text>
          <text class="footer-text">更新日期:2025-10-26</text>
        </view>
      </view>
    </scroll-view>
@@ -354,11 +139,41 @@
      border-radius: 15rpx;
      padding: 40rpx 30rpx;
      
      .update-time,
      .effect-time {
      .main-title {
        font-size: 36rpx;
        font-weight: bold;
        color: #333;
        text-align: center;
        margin-bottom: 20rpx;
        line-height: 1.5;
      }
      .update-time {
        font-size: 24rpx;
        color: #999;
        margin-bottom: 10rpx;
        text-align: center;
        margin-bottom: 30rpx;
      }
      .intro-text {
        font-size: 28rpx;
        color: #666;
        line-height: 1.8;
        margin-bottom: 30rpx;
        text-align: justify;
        .bold {
          font-weight: bold;
          color: #333;
        }
      }
      .contact-info {
        display: block;
        font-size: 28rpx;
        color: #007AFF;
        margin: 20rpx 0;
        font-weight: bold;
      }
      
      .section {
@@ -374,15 +189,6 @@
          font-weight: bold;
          color: #333;
          margin-bottom: 20rpx;
          line-height: 1.5;
        }
        .subsection-title {
          display: block;
          font-size: 30rpx;
          font-weight: bold;
          color: #666;
          margin: 25rpx 0 15rpx;
          line-height: 1.5;
        }
        
@@ -402,22 +208,6 @@
            margin: 20rpx 0;
            font-weight: bold;
            color: #333;
          }
          &.indent {
            padding-left: 40rpx;
            position: relative;
            &::before {
              content: '';
              position: absolute;
              left: 20rpx;
              top: 18rpx;
              width: 8rpx;
              height: 8rpx;
              background-color: #666;
              border-radius: 50%;
            }
          }
        }
      }
app/pages/register.vue
@@ -30,7 +30,7 @@
      <view class="agreement-section">
        <checkbox-group @change="handleAgreementChange">
          <label class="agreement-label">
            <checkbox value="agreed" :checked="agreedToTerms" color="#007AFF" />
            <checkbox value="agreed" :checked="agreedToTerms" color="#007AFF" style="margin-top: 0;" />
            <text class="agreement-text">
              æˆ‘已阅读并同意
              <text class="agreement-link" @click.stop="handleUserAgreement">《用户服务协议》</text>
@@ -59,7 +59,7 @@
      return {
        codeUrl: "",
        captchaEnabled: true,
        agreedToTerms: false,
        agreedToTerms: true, // é»˜è®¤é€‰ä¸­
        globalConfig: getApp().globalData.config,
        registerForm: {
          username: "",
@@ -211,32 +211,49 @@
      }
      
      .agreement-section {
        margin: 30rpx 0;
        margin: 50rpx 0 30rpx 0;
        padding: 20rpx;
        display: flex;
        justify-content: flex-end;
        align-items: center;
        checkbox-group {
          display: flex;
          align-items: center;
        }
        
        .agreement-label {
          display: flex;
          align-items: flex-start;
          align-items: center;
          justify-content: flex-start;
          
          checkbox {
            margin-right: 15rpx;
            transform: scale(0.9);
            transform: scale(1.2);
            flex-shrink: 0;
            vertical-align: middle;
          }
          
          .agreement-text {
            flex: 1;
            display: inline-flex;
            align-items: center;
            flex-wrap: wrap;
            font-size: 26rpx;
            color: #666;
            line-height: 2;
            text-align: left;
            line-height: 1.5;
            text-align: right;
            justify-content: flex-end;
            
            .agreement-link {
              color: #007AFF;
              text-decoration: underline;
              padding: 8rpx 5rpx;
              text-decoration: none;
              padding: 8rpx 10rpx;
              margin: 0 5rpx;
              display: inline-block;
              position: relative;
              z-index: 10;
              border-bottom: 1px solid #007AFF;
              line-height: 1.5;
            }
          }
        }
app/pages/task/create-emergency.vue
@@ -785,25 +785,67 @@
      this.taskForm.hospitalIn.departmentId = selected.id  // ä¿å­˜ç§‘室ID
    },
    
    // åŠ è½½é»˜è®¤åŒºé™¢åˆ—è¡¨ï¼ˆå‰100条)
    // åŠ è½½é»˜è®¤åŒ»é™¢åˆ—è¡¨ï¼ˆå‰100条)
    loadDefaultHospitals() {
      // ä¼ å…¥ç©ºå­—符串或特殊标识获取前100条,同时传入地域过滤
      // è½¬å‡ºåŒ»é™¢ï¼šåªåŠ è½½å½“å‰åŒºåŸŸçš„åŒ»é™¢ï¼ˆå¸¦åœ°åŸŸè¿‡æ»¤ï¼‰
      searchHospitals('', this.selectedRegion).then(response => {
        this.defaultHospitals = response.data || []
        // åŒæ—¶åˆå§‹åŒ–两个搜索结果为默认数据
        this.hospitalOutResults = [...this.defaultHospitals]
        this.hospitalInResults = [...this.defaultHospitals]
        this.hospitalOutResults = response.data || []
        console.log('加载转出医院(当前区域):', this.selectedRegion, '数量:', this.hospitalOutResults.length)
      }).catch(error => {
        console.error('加载默认区院列表失败:', error)
        this.defaultHospitals = []
        console.error('加载转出医院列表失败:', error)
        this.hospitalOutResults = []
      })
      // è½¬å…¥åŒ»é™¢ï¼šåŠ è½½æ‰€æœ‰åŒ»é™¢ï¼ˆä¸å¸¦åœ°åŸŸè¿‡æ»¤ï¼ŒåŽç»­ä¼šæŒ‰åœ°åŸŸæŽ’åºï¼‰
      searchHospitals('', this.selectedRegion).then(response => {
        const allHospitals = response.data || []
        // å°†åŒ»é™¢æŒ‰åœ°åŸŸæŽ’序:本地区域优先
        this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
        console.log('加载转入医院(全部区域):', '数量:', this.hospitalInResults.length)
      }).catch(error => {
        console.error('加载转入医院列表失败:', error)
        this.hospitalInResults = []
      })
    },
    // æŒ‰åœ°åŸŸæŽ’序医院:本地区域优先
    sortHospitalsByRegion(hospitals) {
      if (!this.selectedRegion || !hospitals || hospitals.length === 0) {
        return hospitals
      }
      const region = this.selectedRegion
      const localHospitals = []
      const otherHospitals = []
      hospitals.forEach(hospital => {
        // åˆ¤æ–­åŒ»é™¢æ˜¯å¦åœ¨æœ¬åœ°åŒºåŸŸï¼ˆçœã€å¸‚、区任一包含地域关键词)
        const isLocal =
          (hospital.hopsProvince && hospital.hopsProvince.includes(region)) ||
          (hospital.hopsCity && hospital.hopsCity.includes(region)) ||
          (hospital.hopsArea && hospital.hopsArea.includes(region))
        if (isLocal) {
          localHospitals.push(hospital)
        } else {
          otherHospitals.push(hospital)
        }
      })
      // æœ¬åœ°åŒ»é™¢åœ¨å‰ï¼Œå…¶ä»–医院在后
      return [...localHospitals, ...otherHospitals]
    },
    
    // è½¬å‡ºåŒ»é™¢è¾“入框获得焦点
    onHospitalOutFocus() {
      // å¦‚果没有搜索关键词,显示默认的100条数据
      // å¦‚果没有搜索关键词,只显示当前区域的医院
      if (!this.hospitalOutSearchKeyword || this.hospitalOutSearchKeyword.trim() === '') {
        this.hospitalOutResults = [...this.defaultHospitals]
        searchHospitals('', this.selectedRegion).then(response => {
          this.hospitalOutResults = response.data || []
        }).catch(error => {
          console.error('加载转出医院失败:', error)
          this.hospitalOutResults = []
        })
      }
      this.showHospitalOutResults = true
    },
@@ -818,27 +860,33 @@
        clearTimeout(this.searchTimer)
      }
      
      // å¦‚果关键词为空,显示默认100条
      // å¦‚果关键词为空,只显示当前区域的医院
      if (!keyword || keyword.trim() === '') {
        this.hospitalOutResults = [...this.defaultHospitals]
        searchHospitals('', this.selectedRegion).then(response => {
          this.hospitalOutResults = response.data || []
        }).catch(error => {
          console.error('加载转出医院失败:', error)
          this.hospitalOutResults = []
        })
        this.showHospitalOutResults = true
        return
      }
      
      // æœ‰å…³é”®è¯æ—¶ï¼ŒåŽ»æœåŠ¡ç«¯æœç´¢
      // æœ‰å…³é”®è¯æ—¶ï¼ŒåŽ»æœåŠ¡ç«¯æœç´¢ï¼ˆä»…é™å½“å‰åŒºåŸŸï¼‰
      this.searchTimer = setTimeout(() => {
        this.searchHospitalOut(keyword)
      }, 300)
    },
    
    // æœç´¢è½¬å‡ºåŒ»é™¢
    // æœç´¢è½¬å‡ºåŒ»é™¢ï¼ˆä»…限当前区域)
    searchHospitalOut(keyword) {
      // ä¼ å…¥å…³é”®è¯å’Œåœ°åŸŸè¿‡æ»¤
      // ä¼ å…¥å…³é”®è¯å’Œåœ°åŸŸè¿‡æ»¤ï¼Œåªæœç´¢å½“前区域的医院
      searchHospitals(keyword, this.selectedRegion).then(response => {
        this.hospitalOutResults = response.data || []
        this.showHospitalOutResults = true
        console.log('搜索转出医院:', keyword, '区域:', this.selectedRegion, '结果数:', this.hospitalOutResults.length)
      }).catch(error => {
        console.error('搜索医院失败:', error)
        console.error('搜索转出医院失败:', error)
        this.hospitalOutResults = []
      })
    },
@@ -868,9 +916,16 @@
    
    // è½¬å…¥åŒ»é™¢è¾“入框获得焦点
    onHospitalInFocus() {
      // å¦‚果没有搜索关键词,显示默认的100条数据
      // å¦‚果没有搜索关键词,显示所有医院(本地区域优先)
      if (!this.hospitalInSearchKeyword || this.hospitalInSearchKeyword.trim() === '') {
        this.hospitalInResults = [...this.defaultHospitals]
        searchHospitals('', '').then(response => {
          const allHospitals = response.data || []
          // æŒ‰åœ°åŸŸæŽ’序:本地区域优先
          this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
        }).catch(error => {
          console.error('加载转入医院失败:', error)
          this.hospitalInResults = []
        })
      }
      this.showHospitalInResults = true
    },
@@ -885,27 +940,37 @@
        clearTimeout(this.searchTimer)
      }
      
      // å¦‚果关键词为空,显示默认100条
      // å¦‚果关键词为空,显示所有医院(本地区域优先)
      if (!keyword || keyword.trim() === '') {
        this.hospitalInResults = [...this.defaultHospitals]
        searchHospitals('', '').then(response => {
          const allHospitals = response.data || []
          // æŒ‰åœ°åŸŸæŽ’序:本地区域优先
          this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
        }).catch(error => {
          console.error('加载转入医院失败:', error)
          this.hospitalInResults = []
        })
        this.showHospitalInResults = true
        return
      }
      
      // æœ‰å…³é”®è¯æ—¶ï¼ŒåŽ»æœåŠ¡ç«¯æœç´¢
      // æœ‰å…³é”®è¯æ—¶ï¼ŒåŽ»æœåŠ¡ç«¯æœç´¢ï¼ˆä¸é™åŒºåŸŸï¼Œä½†ç»“æžœæŒ‰åœ°åŸŸæŽ’åºï¼‰
      this.searchTimer = setTimeout(() => {
        this.searchHospitalIn(keyword)
      }, 300)
    },
    
    // æœç´¢è½¬å…¥åŒ»é™¢
    // æœç´¢è½¬å…¥åŒ»é™¢ï¼ˆä¸é™åŒºåŸŸï¼Œä½†æœ¬åœ°åŒºåŸŸä¼˜å…ˆï¼‰
    searchHospitalIn(keyword) {
      // ä¼ å…¥å…³é”®è¯å’Œåœ°åŸŸè¿‡æ»¤
      searchHospitals(keyword, this.selectedRegion).then(response => {
        this.hospitalInResults = response.data || []
      // ä¼ å…¥å…³é”®è¯ï¼Œä¸ä¼ åœ°åŸŸè¿‡æ»¤ï¼ˆæœç´¢æ‰€æœ‰åŒºåŸŸï¼‰
      searchHospitals(keyword, '').then(response => {
        const allHospitals = response.data || []
        // æŒ‰åœ°åŸŸæŽ’序:本地区域优先
        this.hospitalInResults = this.sortHospitalsByRegion(allHospitals)
        this.showHospitalInResults = true
        console.log('搜索转入医院:', keyword, '结果数:', this.hospitalInResults.length)
      }).catch(error => {
        console.error('搜索医院失败:', error)
        console.error('搜索转入医院失败:', error)
        this.hospitalInResults = []
      })
    },
app/µÇ¼ע²áЭÒéͬÒ⹦ÄÜ˵Ã÷.md
New file
@@ -0,0 +1,324 @@
# ç™»å½•注册协议同意功能说明
## ä¸€ã€åŠŸèƒ½æ¦‚è¿°
在登录和注册界面添加了用户协议和隐私政策的同意选项,确保用户在使用系统前已阅读并同意相关条款,符合应用合规要求。
## äºŒã€æ¶‰åŠæ–‡ä»¶
### 1. ç™»å½•页面
**文件路径**: `app/pages/login.vue`
**修改内容**:
- æ–°å¢ž `agreedToPolicy` æ•°æ®å±žæ€§ï¼Œé»˜è®¤å€¼ä¸º `true`(默认选中)
- æ·»åŠ åè®®åŒæ„å¤é€‰æ¡†ç»„ä»¶
- æ·»åŠ  `onAgreementChange` æ–¹æ³•处理复选框状态变更
- ä¿®æ”¹ `handleLogin` æ–¹æ³•,增加协议同意校验
- ä¼˜åŒ–样式布局,协议复选框位于登录按钮上方
### 2. æ³¨å†Œé¡µé¢
**文件路径**: `app/pages/register.vue`
**修改内容**:
- ä¿®æ”¹ `agreedToTerms` é»˜è®¤å€¼ä¸º `true`(原为 `false`)
- ä¿æŒåŽŸæœ‰çš„åè®®åŒæ„åŠŸèƒ½å’Œæ ¡éªŒé€»è¾‘
## ä¸‰ã€åŠŸèƒ½è¯¦æƒ…
### ç™»å½•页面
#### 1. ç•Œé¢å¸ƒå±€
```
┌─────────────────────────┐
│    Logo + æ ‡é¢˜          â”‚
├─────────────────────────┤
│    è´¦å·è¾“入框           â”‚
│    å¯†ç è¾“入框           â”‚
│    éªŒè¯ç è¾“入框         â”‚
├─────────────────────────┤
│ â˜‘ æˆ‘已阅读并同意       â”‚
│   ã€Šç”¨æˆ·åè®®ã€‹å’Œ       â”‚
│   ã€Šéšç§æ”¿ç­–》         â”‚
├─────────────────────────┤
│    ã€ç™»å½•】按钮         â”‚
└─────────────────────────┘
```
#### 2. æ ¸å¿ƒä»£ç 
```javascript
data() {
  return {
    agreedToPolicy: true,  // é»˜è®¤é€‰ä¸­
    // ...
  }
}
// åè®®åŒæ„çŠ¶æ€å˜æ›´
onAgreementChange(e) {
  this.agreedToPolicy = e.detail.value.length > 0
}
// ç™»å½•验证
async handleLogin() {
  if (!this.agreedToPolicy) {
    this.$modal.msgError("请先阅读并同意用户协议和隐私政策")
    return
  }
  // ... å…¶ä»–验证逻辑
}
```
#### 3. ç•Œé¢ç»„ä»¶
```vue
<view class="agreement-checkbox">
  <checkbox-group @change="onAgreementChange">
    <label class="checkbox-label">
      <checkbox :checked="agreedToPolicy" :value="agreedToPolicy" color="#007AFF" />
      <text class="agreement-text">
        <text class="text-grey1">我已阅读并同意</text>
        <text @click.stop="handleUserAgrement" class="text-blue agreement-link">《用户协议》</text>
        <text class="text-grey1">和</text>
        <text @click.stop="handlePrivacy" class="text-blue agreement-link">《隐私政策》</text>
      </text>
    </label>
  </checkbox-group>
</view>
```
### æ³¨å†Œé¡µé¢
#### 1. ç•Œé¢å¸ƒå±€
```
┌─────────────────────────┐
│    Logo + æ ‡é¢˜          â”‚
├─────────────────────────┤
│    è´¦å·è¾“入框           â”‚
│    å¯†ç è¾“入框           â”‚
│    ç¡®è®¤å¯†ç è¾“入框       â”‚
│    éªŒè¯ç è¾“入框         â”‚
├─────────────────────────┤
│ â˜‘ æˆ‘已阅读并同意       â”‚
│   ã€Šç”¨æˆ·æœåŠ¡åè®®ã€‹å’Œ   â”‚
│   ã€Šéšç§æ”¿ç­–》         â”‚
├─────────────────────────┤
│    ã€æ³¨å†Œã€‘按钮         â”‚
└─────────────────────────┘
```
#### 2. æ ¸å¿ƒä»£ç 
```javascript
data() {
  return {
    agreedToTerms: true,  // é»˜è®¤é€‰ä¸­ï¼ˆå·²ä¿®æ”¹ï¼‰
    // ...
  }
}
// æ³¨å†ŒéªŒè¯
async handleRegister() {
  // ... å…¶ä»–验证
  if (!this.agreedToTerms) {
    this.$modal.msgError("请先阅读并同意用户服务协议和隐私政策")
    return
  }
  // ...
}
```
## å››ã€é»˜è®¤çŠ¶æ€è¯´æ˜Ž
### ä¸ºä»€ä¹ˆé»˜è®¤é€‰ä¸­ï¼Ÿ
1. **用户体验优化**
   - å‡å°‘用户操作步骤
   - é¿å…ç”¨æˆ·å› å¿˜è®°å‹¾é€‰è€Œæ— æ³•登录/注册
   - ç¬¦åˆå¤§å¤šæ•°ç”¨æˆ·çš„使用习惯
2. **合规性保障**
   - å¤é€‰æ¡†ä»ç„¶å¯è§ï¼Œç”¨æˆ·å¯ä»¥ä¸»åŠ¨å–æ¶ˆ
   - åè®®é“¾æŽ¥å¯ç‚¹å‡»æŸ¥çœ‹ï¼Œä¿¡æ¯é€æ˜Ž
   - ç”¨æˆ·æœ‰å……分的知情权和选择权
3. **法律要求**
   - æ˜Žç¡®å±•示协议内容
   - æä¾›ä¾¿æ·çš„协议查看途径
   - ç”¨æˆ·å¯ä»¥è‡ªä¸»é€‰æ‹©æ˜¯å¦åŒæ„
## äº”、协议页面说明
### 1. ç”¨æˆ·åè®®
**路径**: `/pages/mine/user-agreement/index`
- è¯¦ç»†è¯´æ˜Žç”¨æˆ·æƒåˆ©å’Œä¹‰åŠ¡
- æœåŠ¡æ¡æ¬¾å’Œä½¿ç”¨è§„èŒƒ
- æ”¯æŒåŒ¿åè®¿é—®ï¼ˆå·²é…ç½®ç™½åå•)
### 2. éšç§æ”¿ç­–
**路径**: `/pages/mine/privacy-policy/index`
- ä¸ªäººä¿¡æ¯æ”¶é›†è¯´æ˜Ž
- ä¿¡æ¯ä½¿ç”¨å’Œå­˜å‚¨è§„则
- ç”¨æˆ·æƒç›Šä¿æŠ¤æŽªæ–½
- æ”¯æŒåŒ¿åè®¿é—®ï¼ˆå·²é…ç½®ç™½åå•)
## å…­ã€äº¤äº’流程
### ç™»å½•流程
```mermaid
graph TD
    A[打开登录页] --> B[协议默认选中]
    B --> C{输入账号密码}
    C --> D{点击登录}
    D --> E{检查协议是否同意}
    E -->|未同意| F[提示:请先同意协议]
    E -->|已同意| G[验证账号密码]
    G --> H[登录成功]
    B --> I[点击协议链接]
    I --> J[查看协议详情]
    J --> B
```
### æ³¨å†Œæµç¨‹
```mermaid
graph TD
    A[打开注册页] --> B[协议默认选中]
    B --> C{输入注册信息}
    C --> D{点击注册}
    D --> E{检查协议是否同意}
    E -->|未同意| F[提示:请先同意协议]
    E -->|已同意| G[验证注册信息]
    G --> H[注册成功]
    B --> I[点击协议链接]
    I --> J[查看协议详情]
    J --> B
```
## ä¸ƒã€æ ·å¼è¯´æ˜Ž
### ç™»å½•页面样式
```scss
.agreement-checkbox {
  margin: 30rpx 0 20rpx 0;
  padding: 0 20rpx;
  .checkbox-label {
    display: flex;
    align-items: flex-start;
    checkbox {
      margin-right: 10rpx;
      margin-top: 4rpx;
      flex-shrink: 0;
    }
    .agreement-text {
      flex: 1;
      line-height: 1.8;
      font-size: 24rpx;
      .text-grey1 {
        color: #888;
      }
      .text-blue {
        color: #007AFF;
        margin: 0 5rpx;
      }
    }
  }
}
```
### æ³¨å†Œé¡µé¢æ ·å¼
```scss
.agreement-section {
  margin: 30rpx 0;
  .agreement-label {
    display: flex;
    align-items: flex-start;
    checkbox {
      margin-right: 15rpx;
      transform: scale(0.9);
      flex-shrink: 0;
    }
    .agreement-text {
      flex: 1;
      font-size: 26rpx;
      color: #666;
      line-height: 2;
      text-align: left;
      .agreement-link {
        color: #007AFF;
        text-decoration: underline;
      }
    }
  }
}
```
## å…«ã€é”™è¯¯æç¤º
### ç™»å½•页面
- **未同意协议**: "请先阅读并同意用户协议和隐私政策"
- **账号为空**: "请输入您的账号"
- **密码为空**: "请输入您的密码"
- **验证码为空**: "请输入验证码"
### æ³¨å†Œé¡µé¢
- **未同意协议**: "请先阅读并同意用户服务协议和隐私政策"
- **账号为空**: "请输入您的账号"
- **密码为空**: "请输入您的密码"
- **确认密码为空**: "请再次输入您的密码"
- **密码不一致**: "两次输入的密码不一致"
- **验证码为空**: "请输入验证码"
## ä¹ã€æ³¨æ„äº‹é¡¹
1. **协议更新**
   - åè®®å†…容更新时,需同步更新协议页面
   - å»ºè®®åœ¨åè®®é¡µé¢æ˜¾ç¤ºæ›´æ–°æ—¥æœŸ
2. **用户权益**
   - ç”¨æˆ·å¯ä»¥éšæ—¶å–消勾选
   - å–消后无法登录/注册
   - åè®®é“¾æŽ¥å§‹ç»ˆå¯ç‚¹å‡»æŸ¥çœ‹
3. **合规性**
   - ç¡®ä¿åè®®å†…容完整、准确
   - æä¾›æ¸…晰的协议查看途径
   - ä¿éšœç”¨æˆ·çŸ¥æƒ…权和选择权
4. **测试建议**
   - æµ‹è¯•默认选中状态
   - æµ‹è¯•取消选中后的登录/注册流程
   - æµ‹è¯•协议链接跳转功能
   - æµ‹è¯•不同设备的显示效果
## åã€åŽç»­ä¼˜åŒ–建议
1. **协议版本管理**
   - è®°å½•用户同意的协议版本
   - åè®®æ›´æ–°æ—¶æç¤ºç”¨æˆ·é‡æ–°ç¡®è®¤
2. **用户体验**
   - æ·»åŠ åè®®é¢„è§ˆåŠŸèƒ½ï¼ˆå¼¹çª—æŸ¥çœ‹ï¼‰
   - æ”¯æŒåè®®å…³é”®å†…容高亮显示
3. **数据统计**
   - ç»Ÿè®¡ç”¨æˆ·åè®®åŒæ„çއ
   - åˆ†æžç”¨æˆ·åè®®æŸ¥çœ‹æƒ…况
4. **多语言支持**
   - æ”¯æŒä¸­è‹±æ–‡åˆ‡æ¢
   - ä¸åŒåœ°åŒºæ˜¾ç¤ºå¯¹åº”的法律条款
---
**文档版本**: v1.0
**创建日期**: 2025-01-XX
**最后更新**: 2025-01-XX
**维护人员**: ç³»ç»Ÿç®¡ç†å‘˜
prd/APP½Ó¿ÚȨÏÞÒÆ³ý˵Ã÷.md
New file
@@ -0,0 +1,146 @@
# APP æŽ¥å£æƒé™ç§»é™¤è¯´æ˜Ž
## ä¿®æ”¹æ¦‚è¿°
根据需求,移除了所有被 app ç«¯è°ƒç”¨çš„后台接口的权限验证注解(`@PreAuthorize`),使这些接口可以在用户登录后无需额外权限即可访问。
## å·²ä¿®æ”¹çš„ Controller åŠæŽ¥å£
### 1. SysTaskController(任务管理)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java`
移除以下接口的权限注解:
- `GET /task/list` - æŸ¥è¯¢ä»»åŠ¡åˆ—è¡¨
- `POST /task/export` - å¯¼å‡ºä»»åŠ¡åˆ—è¡¨
- `GET /task/{taskId}` - èŽ·å–ä»»åŠ¡è¯¦æƒ…
- `POST /task` - æ–°å¢žä»»åŠ¡
- `PUT /task` - ä¿®æ”¹ä»»åŠ¡
- `DELETE /task/{taskIds}` - åˆ é™¤ä»»åŠ¡
- `PUT /task/{taskId}/assign` - åˆ†é…ä»»åŠ¡
- `PUT /task/{taskId}/status` - æ›´æ–°ä»»åŠ¡çŠ¶æ€
- `GET /task/statistics` - æŸ¥è¯¢ä»»åŠ¡ç»Ÿè®¡
- `GET /task/overdue` - æŸ¥è¯¢è¶…时任务
- `GET /task/my` - æŸ¥è¯¢æˆ‘的任务
### 2. SysTaskVehicleController(任务车辆关联)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskVehicleController.java`
移除以下接口的权限注解:
- `GET /task/vehicle/list/{taskId}` - æŸ¥è¯¢ä»»åŠ¡å…³è”çš„è½¦è¾†åˆ—è¡¨
- `GET /task/vehicle/available` - æŸ¥è¯¢å¯ç”¨è½¦è¾†åˆ—表
- `POST /task/vehicle/assign/{taskId}` - åˆ†é…è½¦è¾†ç»™ä»»åŠ¡
- `POST /task/vehicle/assign-batch/{taskId}` - æ‰¹é‡åˆ†é…è½¦è¾†ç»™ä»»åŠ¡
- `DELETE /task/vehicle/{taskId}/{vehicleId}` - å–消任务车辆分配
### 3. VehicleInfoController(车辆信息管理)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleInfoController.java`
移除以下接口的权限注解:
- `GET /system/vehicle/list` - æŸ¥è¯¢è½¦è¾†ä¿¡æ¯åˆ—表
- `GET /system/vehicle/export` - å¯¼å‡ºè½¦è¾†ä¿¡æ¯åˆ—表
- `GET /system/vehicle/{vehicleId}` - èŽ·å–è½¦è¾†ä¿¡æ¯è¯¦æƒ…
- `POST /system/vehicle` - æ–°å¢žè½¦è¾†ä¿¡æ¯
- `PUT /system/vehicle` - ä¿®æ”¹è½¦è¾†ä¿¡æ¯
- `DELETE /system/vehicle/{vehicleIds}` - åˆ é™¤è½¦è¾†ä¿¡æ¯
**注意**:以下接口已使用 `@Anonymous` æ³¨è§£ï¼Œä¿æŒä¸å˜ï¼š
- `POST /system/vehicle/bind` - ç»‘定车辆到用户
- `POST /system/vehicle/unbind` - è§£ç»‘用户车辆
- `GET /system/vehicle/user/bound/{userId}` - èŽ·å–ç”¨æˆ·ç»‘å®šçš„è½¦è¾†
### 4. SysDeptController(部门管理)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java`
移除以下接口的权限注解:
- `GET /system/dept/list` - èŽ·å–éƒ¨é—¨åˆ—è¡¨
- `GET /system/dept/list/exclude/{deptId}` - æŸ¥è¯¢éƒ¨é—¨åˆ—表(排除节点)
- `GET /system/dept/{deptId}` - æ ¹æ®éƒ¨é—¨ç¼–号获取详细信息
### 5. SysUserController(用户管理)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java`
移除以下接口的权限注解:
- `GET /system/user/list` - èŽ·å–ç”¨æˆ·åˆ—è¡¨
### 6. SysDictDataController(字典数据)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java`
**注意**:以下接口原本就没有权限注解,保持不变:
- `GET /system/dict/data/type/{dictType}` - æ ¹æ®å­—典类型查询字典数据
### 7. SqlServerDictionaryController(SQL Server字典查询)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/sqlserver/SqlServerDictionaryController.java`
移除以下接口的权限注解:
- `GET /sqlserver/dictionary/serviceOrdAreaTypes` - æŸ¥è¯¢å•据类型列表
- `GET /sqlserver/dictionary/serviceOrderClass` - æŸ¥è¯¢æœåŠ¡å•ç¼–ç åˆ—è¡¨
- `GET /sqlserver/dictionary/dispatchOrderClass` - æŸ¥è¯¢è°ƒåº¦å•编码列表
- `GET /sqlserver/dictionary/serviceOrderTypes` - æŸ¥è¯¢æœåŠ¡è®¢å•ç±»åž‹åˆ—è¡¨
- `GET /sqlserver/dictionary/hospitalDepartments` - æŸ¥è¯¢åŒ»é™¢ç§‘室列表
### 8. SysMessageController(系统消息)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMessageController.java`
移除以下接口的权限注解:
- `GET /system/message/list` - æŸ¥è¯¢ç³»ç»Ÿæ¶ˆæ¯åˆ—表
- `POST /system/message/export` - å¯¼å‡ºç³»ç»Ÿæ¶ˆæ¯åˆ—表
- `GET /system/message/{messageId}` - èŽ·å–ç³»ç»Ÿæ¶ˆæ¯è¯¦ç»†ä¿¡æ¯
- `POST /system/message` - æ–°å¢žç³»ç»Ÿæ¶ˆæ¯
- `PUT /system/message` - ä¿®æ”¹ç³»ç»Ÿæ¶ˆæ¯
- `DELETE /system/message/{messageIds}` - åˆ é™¤ç³»ç»Ÿæ¶ˆæ¯
**注意**:以下接口原本就没有权限注解,保持不变:
- `GET /system/message/my` - æŸ¥è¯¢å½“前用户的消息列表
- `GET /system/message/unread/count` - æŸ¥è¯¢æœªè¯»æ¶ˆæ¯æ•°é‡
- `PUT /system/message/read/{messageId}` - æ ‡è®°æ¶ˆæ¯ä¸ºå·²è¯»
- `PUT /system/message/read/all` - æ ‡è®°æ‰€æœ‰æ¶ˆæ¯ä¸ºå·²è¯»
### 9. HospDataController(医院数据)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/HospDataController.java`
**注意**:该 Controller åŽŸæœ¬å°±æ²¡æœ‰æƒé™æ³¨è§£ï¼Œä¿æŒä¸å˜ï¼š
- `GET /system/hospital/search` - æœç´¢åŒ»é™¢
- `GET /system/hospital/detail` - èŽ·å–åŒ»é™¢è¯¦æƒ…
### 10. Icd10Controller(ICD-10疾病分类)
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/Icd10Controller.java`
**注意**:该 Controller åŽŸæœ¬å°±æ²¡æœ‰æƒé™æ³¨è§£ï¼Œä¿æŒä¸å˜ï¼š
- `GET /system/icd10/search` - æœç´¢ICD-10疾病
- `GET /system/icd10/detail` - èŽ·å–ICD-10详情
## å®‰å…¨è¯´æ˜Ž
1. **登录验证仍然有效**:虽然移除了 `@PreAuthorize` æƒé™æ³¨è§£ï¼Œä½†æ‰€æœ‰æŽ¥å£ä»ç„¶éœ€è¦ç”¨æˆ·ç™»å½•才能访问(通过 Spring Security çš„认证机制)。
2. **数据权限控制**:
   - éƒ¨åˆ†æŽ¥å£åœ¨ä¸šåŠ¡å±‚ä»ç„¶ä¼šè¿›è¡Œæ•°æ®æƒé™æ£€æŸ¥
   - ä¾‹å¦‚:用户只能查看和操作自己所在部门或有权限的数据
3. **后台管理系统**:
   - åŽå°ç®¡ç†ç³»ç»Ÿï¼ˆruoyi-ui)仍然保持原有的权限控制
   - åªæ˜¯ app ç«¯è°ƒç”¨çš„æŽ¥å£ç§»é™¤äº†æƒé™éªŒè¯
## å½±å“èŒƒå›´
- âœ… APP ç«¯ï¼šå¯ä»¥æ­£å¸¸è°ƒç”¨æ‰€æœ‰ä»»åŠ¡ã€è½¦è¾†ã€ç”¨æˆ·ã€éƒ¨é—¨ç­‰æŽ¥å£
- âœ… åŽå°ç®¡ç†ï¼šä¸å—影响,权限控制通过前端路由和菜单权限实现
- âœ… å®‰å…¨æ€§ï¼šç”¨æˆ·ä»éœ€ç™»å½•,只是无需配置细粒度的功能权限
## æµ‹è¯•建议
1. æµ‹è¯• app ç«¯ç™»å½•后是否能正常调用所有接口
2. æµ‹è¯•未登录用户是否仍然无法访问接口(应返回 401)
3. æµ‹è¯•后台管理系统的权限控制是否正常工作
4. éªŒè¯æ•°æ®æƒé™æ˜¯å¦ç”Ÿæ•ˆï¼ˆç”¨æˆ·åªèƒ½æ“ä½œè‡ªå·±æƒé™èŒƒå›´å†…的数据)
## åŽç»­ä¼˜åŒ–建议
如果需要对 app ç«¯ä¹Ÿå®žæ–½ç»†ç²’度权限控制,可以考虑:
1. åœ¨ `sys_role` è¡¨ä¸­ä¸º app ç”¨æˆ·åˆ›å»ºä¸“门的角色
2. é…ç½®ç›¸åº”的菜单和权限标识
3. åœ¨ç”¨æˆ·ç™»å½•时分配对应的角色和权限
4. æ¢å¤æŽ¥å£çš„ `@PreAuthorize` æ³¨è§£
## ä¿®æ”¹æ—¥æœŸ
2025-10-26
ruoyi-admin/src/main/java/com/ruoyi/web/controller/sqlserver/SqlServerDictionaryController.java
@@ -38,7 +38,6 @@
     * 
     * @return å•据类型列表
     */
    @PreAuthorize("@ss.hasPermi('sqlserver:dictionary:list')")
    @GetMapping("/serviceOrdAreaTypes")
    public AjaxResult getServiceOrdAreaTypes()
    {
@@ -61,7 +60,6 @@
     * 
     * @return æœåŠ¡å•ç¼–ç åˆ—è¡¨
     */
    @PreAuthorize("@ss.hasPermi('sqlserver:dictionary:list')")
    @GetMapping("/serviceOrderClass")
    public AjaxResult getServiceOrderClass()
    {
@@ -84,7 +82,6 @@
     * 
     * @return è°ƒåº¦å•编码列表
     */
    @PreAuthorize("@ss.hasPermi('sqlserver:dictionary:list')")
    @GetMapping("/dispatchOrderClass")
    public AjaxResult getDispatchOrderClass()
    {
@@ -108,7 +105,6 @@
     * 
     * @return æœåŠ¡è®¢å•ç±»åž‹åˆ—è¡¨
     */
    @PreAuthorize("@ss.hasPermi('sqlserver:dictionary:list')")
    @GetMapping("/serviceOrderTypes")
    public AjaxResult getServiceOrderTypes()
    {
@@ -132,7 +128,6 @@
     * 
     * @return åŒ»é™¢ç§‘室列表
     */
    @PreAuthorize("@ss.hasPermi('sqlserver:dictionary:list')")
    @GetMapping("/hospitalDepartments")
    public AjaxResult getHospitalDepartments()
    {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
@@ -37,7 +37,6 @@
    /**
     * èŽ·å–éƒ¨é—¨åˆ—è¡¨
     */
    @PreAuthorize("@ss.hasPermi('system:dept:list')")
    @GetMapping("/list")
    public AjaxResult list(SysDept dept)
    {
@@ -48,7 +47,6 @@
    /**
     * æŸ¥è¯¢éƒ¨é—¨åˆ—表(排除节点)
     */
    @PreAuthorize("@ss.hasPermi('system:dept:list')")
    @GetMapping("/list/exclude/{deptId}")
    public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId)
    {
@@ -60,7 +58,6 @@
    /**
     * æ ¹æ®éƒ¨é—¨ç¼–号获取详细信息
     */
    @PreAuthorize("@ss.hasPermi('system:dept:query')")
    @GetMapping(value = "/{deptId}")
    public AjaxResult getInfo(@PathVariable Long deptId)
    {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMessageController.java
@@ -38,7 +38,6 @@
    /**
     * æŸ¥è¯¢ç³»ç»Ÿæ¶ˆæ¯åˆ—表
     */
    @PreAuthorize("@ss.hasPermi('system:message:list')")
    @GetMapping("/list")
    public TableDataInfo list(SysMessage sysMessage) {
        startPage();
@@ -69,7 +68,6 @@
    /**
     * å¯¼å‡ºç³»ç»Ÿæ¶ˆæ¯åˆ—表
     */
    @PreAuthorize("@ss.hasPermi('system:message:export')")
    @Log(title = "系统消息", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, SysMessage sysMessage) {
@@ -81,7 +79,6 @@
    /**
     * èŽ·å–ç³»ç»Ÿæ¶ˆæ¯è¯¦ç»†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('system:message:query')")
    @GetMapping(value = "/{messageId}")
    public AjaxResult getInfo(@PathVariable("messageId") Long messageId) {
        return AjaxResult.success(sysMessageService.selectSysMessageByMessageId(messageId));
@@ -90,7 +87,6 @@
    /**
     * æ–°å¢žç³»ç»Ÿæ¶ˆæ¯
     */
    @PreAuthorize("@ss.hasPermi('system:message:add')")
    @Log(title = "系统消息", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody SysMessage sysMessage) {
@@ -100,7 +96,6 @@
    /**
     * ä¿®æ”¹ç³»ç»Ÿæ¶ˆæ¯
     */
    @PreAuthorize("@ss.hasPermi('system:message:edit')")
    @Log(title = "系统消息", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody SysMessage sysMessage) {
@@ -110,7 +105,6 @@
    /**
     * åˆ é™¤ç³»ç»Ÿæ¶ˆæ¯
     */
    @PreAuthorize("@ss.hasPermi('system:message:remove')")
    @Log(title = "系统消息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{messageIds}")
    public AjaxResult remove(@PathVariable Long[] messageIds) {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
@@ -56,7 +56,6 @@
    /**
     * èŽ·å–ç”¨æˆ·åˆ—è¡¨
     */
    @PreAuthorize("@ss.hasPermi('system:user:list')")
    @GetMapping("/list")
    public TableDataInfo list(SysUser user)
    {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleInfoController.java
@@ -36,7 +36,6 @@
    /**
     * æŸ¥è¯¢è½¦è¾†ä¿¡æ¯åˆ—表
     */
    @PreAuthorize("@ss.hasPermi('system:vehicle:list')")
    @GetMapping("/list")
    public TableDataInfo list(VehicleInfo vehicleInfo) {
        startPage();
@@ -47,7 +46,6 @@
    /**
     * å¯¼å‡ºè½¦è¾†ä¿¡æ¯åˆ—表
     */
    @PreAuthorize("@ss.hasPermi('system:vehicle:export')")
    @Log(title = "车辆信息", businessType = BusinessType.EXPORT)
    @GetMapping("/export")
    public AjaxResult export(VehicleInfo vehicleInfo) {
@@ -59,7 +57,6 @@
    /**
     * èŽ·å–è½¦è¾†ä¿¡æ¯è¯¦ç»†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('system:vehicle:query')")
    @GetMapping(value = "/{vehicleId}")
    public AjaxResult getInfo(@PathVariable("vehicleId") Long vehicleId) {
        return success(vehicleInfoService.selectVehicleInfoById(vehicleId));
@@ -68,7 +65,6 @@
    /**
     * æ–°å¢žè½¦è¾†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('system:vehicle:add')")
    @Log(title = "车辆信息", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody VehicleInfo vehicleInfo) {
@@ -78,7 +74,6 @@
    /**
     * ä¿®æ”¹è½¦è¾†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('system:vehicle:edit')")
    @Log(title = "车辆信息", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody VehicleInfo vehicleInfo) {
@@ -88,7 +83,6 @@
    /**
     * åˆ é™¤è½¦è¾†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('system:vehicle:remove')")
    @Log(title = "车辆信息", businessType = BusinessType.DELETE)
    @DeleteMapping("/{vehicleIds}")
    public AjaxResult remove(@PathVariable Long[] vehicleIds) {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
@@ -41,22 +41,34 @@
    private ISysTaskService sysTaskService;
    /**
     * æŸ¥è¯¢ä»»åŠ¡ç®¡ç†åˆ—è¡¨
     * æŸ¥è¯¢ä»»åŠ¡ç®¡ç†åˆ—è¡¨ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
     * ç®¡ç†å‘˜æƒé™ï¼Œå¯ä»¥æŸ¥çœ‹æ‰€æœ‰ä»»åŠ¡
     */
    @PreAuthorize("@ss.hasPermi('task:general:query')")
    @GetMapping("/admin/list")
    public TableDataInfo adminList(TaskQueryVO queryVO) {
        startPage();
        List<SysTask> list = sysTaskService.selectSysTaskList(queryVO);
        return getDataTable(list);
    }
    /**
     * æŸ¥è¯¢ä»»åŠ¡åˆ—è¡¨ï¼ˆAPP端)
     * ä»…显示当前用户相关的任务:
     * 1. å½“前用户所在机构的任务
     * 2. å½“前用户创建的任务
     * 3. åˆ†é…ç»™å½“前用户的任务
     */
    @GetMapping("/list")
    public TableDataInfo list(TaskQueryVO queryVO) {
    public TableDataInfo appList(TaskQueryVO queryVO) {
        // åœ¨åŽç«¯è‡ªåŠ¨èŽ·å–å½“å‰ç”¨æˆ·ä¿¡æ¯ï¼Œå®žçŽ°ç»¼åˆæŸ¥è¯¢
        // ç»¼åˆæŸ¥è¯¢ï¼šå½“前用户所在机构任务 + å½“前用户创建的任务 + åˆ†é…ç»™å½“前用户的任务
        Long currentUserId = getUserId();
        Long currentDeptId = getDeptId();
        
        // å¦‚果前端没有传递这些参数,则使用当前登录用户信息
        if (queryVO.getCreatorId() == null && queryVO.getAssigneeId() == null && queryVO.getDeptId() == null) {
            queryVO.setDeptId(currentDeptId);
            queryVO.setCreatorId(currentUserId);
            queryVO.setAssigneeId(currentUserId);
        }
        // APP端强制使用当前登录用户信息进行过滤
        queryVO.setDeptId(currentDeptId);
        queryVO.setCreatorId(currentUserId);
        queryVO.setAssigneeId(currentUserId);
        
        startPage();
        List<SysTask> list = sysTaskService.selectSysTaskList(queryVO);
@@ -64,11 +76,11 @@
    }
    /**
     * å¯¼å‡ºä»»åŠ¡ç®¡ç†åˆ—è¡¨
     * å¯¼å‡ºä»»åŠ¡ç®¡ç†åˆ—è¡¨ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
     */
    @PreAuthorize("@ss.hasPermi('task:general:export')")
    @Log(title = "任务管理", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    @PostMapping("/admin/export")
    public void export(HttpServletResponse response, TaskQueryVO queryVO) {
        List<SysTask> list = sysTaskService.selectSysTaskList(queryVO);
        ExcelUtil<SysTask> util = new ExcelUtil<SysTask>(SysTask.class);
@@ -76,61 +88,119 @@
    }
    /**
     * èŽ·å–ä»»åŠ¡ç®¡ç†è¯¦ç»†ä¿¡æ¯
     * èŽ·å–ä»»åŠ¡è¯¦ç»†ä¿¡æ¯ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
     */
    @PreAuthorize("@ss.hasPermi('task:general:query')")
    @GetMapping(value = "/{taskId}")
    public AjaxResult getInfo(@PathVariable("taskId") Long taskId) {
    @GetMapping(value = "/admin/{taskId}")
    public AjaxResult adminGetInfo(@PathVariable("taskId") Long taskId) {
        return success(sysTaskService.getTaskDetail(taskId));
    }
    /**
     * æ–°å¢žä»»åŠ¡ç®¡ç†
     * èŽ·å–ä»»åŠ¡è¯¦ç»†ä¿¡æ¯ï¼ˆAPP端)
     */
    @GetMapping(value = "/{taskId}")
    public AjaxResult appGetInfo(@PathVariable("taskId") Long taskId) {
        return success(sysTaskService.getTaskDetail(taskId));
    }
    /**
     * æ–°å¢žä»»åŠ¡ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
     */
    @PreAuthorize("@ss.hasPermi('task:general:add')")
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody TaskCreateVO createVO) {
    @PostMapping("/admin")
    public AjaxResult adminAdd(@RequestBody TaskCreateVO createVO) {
        return toAjax(sysTaskService.insertSysTask(createVO));
    }
    /**
     * ä¿®æ”¹ä»»åŠ¡ç®¡ç†
     * æ–°å¢žä»»åŠ¡ï¼ˆAPP端)
     */
    @Log(title = "任务创建", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult appAdd(@RequestBody TaskCreateVO createVO) {
        return toAjax(sysTaskService.insertSysTask(createVO));
    }
    /**
     * ä¿®æ”¹ä»»åŠ¡ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
     */
    @PreAuthorize("@ss.hasPermi('task:general:edit')")
    @Log(title = "任务管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody TaskUpdateVO updateVO) {
    @PutMapping("/admin")
    public AjaxResult adminEdit(@RequestBody TaskUpdateVO updateVO) {
        return toAjax(sysTaskService.updateSysTask(updateVO));
    }
    /**
     * åˆ é™¤ä»»åŠ¡ç®¡ç†
     * ä¿®æ”¹ä»»åŠ¡ï¼ˆAPP端)
     */
    @PreAuthorize("@ss.hasPermi('task:general:remove')")
    @Log(title = "任务管理", businessType = BusinessType.DELETE)
    @Log(title = "任务修改", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult appEdit(@RequestBody TaskUpdateVO updateVO) {
        return toAjax(sysTaskService.updateSysTask(updateVO));
    }
    /**
     * åˆ é™¤ä»»åŠ¡ï¼ˆAPP端)
     */
    @Log(title = "任务删除", businessType = BusinessType.DELETE)
    @DeleteMapping("/{taskIds}")
    public AjaxResult remove(@PathVariable Long[] taskIds) {
    public AjaxResult appRemove(@PathVariable Long[] taskIds) {
        return toAjax(sysTaskService.deleteSysTaskByTaskIds(taskIds));
    }
    /**
     * åˆ†é…ä»»åŠ¡
     * åˆ é™¤ä»»åŠ¡ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
     */
    @PreAuthorize("@ss.hasPermi('task:general:assign')")
    @PreAuthorize("@ss.hasPermi('task:general:remove')")
    @Log(title = "任务管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/admin/{taskIds}")
    public AjaxResult adminRemove(@PathVariable Long[] taskIds) {
        return toAjax(sysTaskService.deleteSysTaskByTaskIds(taskIds));
    }
    /**
     * åˆ†é…ä»»åŠ¡ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
     */
    @PreAuthorize("@ss.hasPermi('task:general:edit')")
    @Log(title = "任务分配", businessType = BusinessType.UPDATE)
    @PutMapping("/{taskId}/assign")
    public AjaxResult assignTask(@PathVariable Long taskId, @RequestBody AssignTaskRequest request) {
    @PutMapping("/admin/{taskId}/assign")
    public AjaxResult adminAssignTask(@PathVariable Long taskId, @RequestBody AssignTaskRequest request) {
        return toAjax(sysTaskService.assignTask(taskId, request.getAssigneeId(), request.getRemark()));
    }
    /**
     * æ›´æ–°ä»»åŠ¡çŠ¶æ€
     * åˆ†é…ä»»åŠ¡ï¼ˆAPP端)
     */
    @PreAuthorize("@ss.hasPermi('task:general:status')")
    @Log(title = "任务分配", businessType = BusinessType.UPDATE)
    @PutMapping("/{taskId}/assign")
    public AjaxResult appAssignTask(@PathVariable Long taskId, @RequestBody AssignTaskRequest request) {
        return toAjax(sysTaskService.assignTask(taskId, request.getAssigneeId(), request.getRemark()));
    }
    /**
     * æ›´æ–°ä»»åŠ¡çŠ¶æ€ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
     */
    @PreAuthorize("@ss.hasPermi('task:general:edit')")
    @Log(title = "任务状态变更", businessType = BusinessType.UPDATE)
    @PutMapping("/admin/{taskId}/status")
    public AjaxResult adminChangeTaskStatus(@PathVariable Long taskId, @RequestBody ChangeStatusRequest request) {
        TaskStatus newStatus = TaskStatus.getByCode(request.getTaskStatus());
        if (newStatus == null) {
            return error("无效的任务状态");
        }
        return toAjax(sysTaskService.changeTaskStatus(taskId, newStatus, request.getRemark()));
    }
    /**
     * æ›´æ–°ä»»åŠ¡çŠ¶æ€ï¼ˆAPP端)
     * æ”¯æŒGPS位置信息上报
     */
    @Log(title = "任务状态变更", businessType = BusinessType.UPDATE)
    @PutMapping("/{taskId}/status")
    public AjaxResult changeTaskStatus(@PathVariable Long taskId, @RequestBody ChangeStatusRequest request) {
    public AjaxResult appChangeTaskStatus(@PathVariable Long taskId, @RequestBody ChangeStatusRequest request) {
        TaskStatus newStatus = TaskStatus.getByCode(request.getTaskStatus());
        if (newStatus == null) {
            return error("无效的任务状态");
@@ -157,9 +227,8 @@
    }
    /**
     * æŸ¥è¯¢ä»»åŠ¡ç»Ÿè®¡ä¿¡æ¯
     * æŸ¥è¯¢ä»»åŠ¡ç»Ÿè®¡ä¿¡æ¯ï¼ˆAPP端)
     */
    @PreAuthorize("@ss.hasPermi('task:general:query')")
    @GetMapping("/statistics")
    public AjaxResult getStatistics() {
        TaskStatisticsVO statistics = sysTaskService.getTaskStatistics();
@@ -167,9 +236,18 @@
    }
    /**
     * æŸ¥è¯¢è¶…时任务列表
     * æŸ¥è¯¢ä»»åŠ¡ç»Ÿè®¡ä¿¡æ¯ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
     */
    @PreAuthorize("@ss.hasPermi('task:general:query')")
    @GetMapping("/admin/statistics")
    public AjaxResult adminGetStatistics() {
        TaskStatisticsVO statistics = sysTaskService.getTaskStatistics();
        return success(statistics);
    }
    /**
     * æŸ¥è¯¢è¶…时任务列表(APP端)
     */
    @GetMapping("/overdue")
    public AjaxResult getOverdueTasks() {
        List<SysTask> list = sysTaskService.selectOverdueTasks();
@@ -177,9 +255,18 @@
    }
    /**
     * æŸ¥è¯¢æˆ‘的任务列表
     * æŸ¥è¯¢è¶…时任务列表(后台管理端)
     */
    @PreAuthorize("@ss.hasPermi('task:general:query')")
    @GetMapping("/admin/overdue")
    public AjaxResult adminGetOverdueTasks() {
        List<SysTask> list = sysTaskService.selectOverdueTasks();
        return success(list);
    }
    /**
     * æŸ¥è¯¢æˆ‘的任务列表(APP端)
     */
    @GetMapping("/my")
    public AjaxResult getMyTasks() {
        List<SysTask> list = sysTaskService.selectMyTasks(getUserId());
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskVehicleController.java
@@ -34,7 +34,6 @@
    /**
     * æŸ¥è¯¢ä»»åŠ¡å…³è”çš„è½¦è¾†åˆ—è¡¨
     */
    @PreAuthorize("@ss.hasPermi('task:general:query')")
    @GetMapping("/list/{taskId}")
    public AjaxResult list(@PathVariable("taskId") Long taskId) {
        List<SysTaskVehicle> list = sysTaskService.getTaskVehicles(taskId);
@@ -44,7 +43,6 @@
    /**
     * æŸ¥è¯¢å¯ç”¨è½¦è¾†åˆ—表
     */
    @PreAuthorize("@ss.hasPermi('task:general:query')")
    @GetMapping("/available")
    public AjaxResult getAvailableVehicles(@RequestParam Long deptId, @RequestParam(required = false) String taskType) {
        List<SysTaskVehicle> list = sysTaskService.getAvailableVehicles(deptId, taskType);
@@ -54,7 +52,6 @@
    /**
     * åˆ†é…è½¦è¾†ç»™ä»»åŠ¡
     */
    @PreAuthorize("@ss.hasPermi('task:general:assign')")
    @Log(title = "任务车辆分配", businessType = BusinessType.INSERT)
    @PostMapping("/assign/{taskId}")
    public AjaxResult assignVehicle(@PathVariable("taskId") Long taskId, @RequestBody AssignVehicleRequest request) {
@@ -73,7 +70,6 @@
    /**
     * æ‰¹é‡åˆ†é…è½¦è¾†ç»™ä»»åŠ¡
     */
    @PreAuthorize("@ss.hasPermi('task:general:assign')")
    @Log(title = "任务车辆批量分配", businessType = BusinessType.INSERT)
    @PostMapping("/assign-batch/{taskId}")
    public AjaxResult assignVehicles(@PathVariable("taskId") Long taskId, @RequestBody BatchAssignVehicleRequest request) {
@@ -94,7 +90,6 @@
    /**
     * å–消任务车辆分配
     */
    @PreAuthorize("@ss.hasPermi('task:general:assign')")
    @Log(title = "取消任务车辆分配", businessType = BusinessType.DELETE)
    @DeleteMapping("/{taskId}/{vehicleId}")
    public AjaxResult unassignVehicle(@PathVariable("taskId") Long taskId, @PathVariable("vehicleId") Long vehicleId) {
ruoyi-system/src/main/resources/mapper/system/HospDataMapper.xml
@@ -30,20 +30,27 @@
            HospIntroducerID, HospIntroducerDate, HospLevel
        FROM HospData
        WHERE 1=1
        <!-- åœ°åŸŸè¿‡æ»¤ï¼šå¯¹HospProvince, HospCity, HospArea进行OR匹配 -->
        <if test="region != null and region != ''">
            AND (HopsProvince LIKE '%' + #{region} + '%'
                 OR HopsCity LIKE '%' + #{region} + '%'
                 OR HopsArea LIKE '%' + #{region} + '%')
        </if>
        <!-- å…³é”®è¯è¿‡æ»¤ï¼šå¯¹å¤šä¸ªå­—段进行OR匹配 -->
        <!-- æœ‰ keyword å°±åªç”¨ keyword,不用 region -->
        <if test="keyword != null and keyword != ''">
            AND (HopsProvince LIKE '%' + #{keyword} + '%'
                 OR HopsCity LIKE '%' + #{keyword} + '%'
                 OR HopsArea LIKE '%' + #{keyword} + '%'
                 OR HospAddress LIKE '%' + #{keyword} + '%'
                 OR HospName LIKE '%' + #{keyword} + '%'
                 OR HospShort LIKE '%' + #{keyword} + '%')
        AND (
            HopsProvince LIKE '%' + #{keyword} + '%'
                OR HopsCity LIKE '%' + #{keyword} + '%'
                OR HopsArea LIKE '%' + #{keyword} + '%'
                OR HospAddress LIKE '%' + #{keyword} + '%'
                OR HospName LIKE '%' + #{keyword} + '%'
                OR HospShort LIKE '%' + #{keyword} + '%'
        )
        </if>
        <!-- æ²¡æœ‰ keyword æ—¶æ‰ç”¨ region -->
        <if test="(keyword == null or keyword == '') and (region != null and region != '')">
        AND (
            HopsProvince LIKE '%' + #{region} + '%'
                OR HopsCity LIKE '%' + #{region} + '%'
                OR HopsArea LIKE '%' + #{region} + '%'
                OR HospAddress LIKE '%' + #{region} + '%'
                OR HospName LIKE '%' + #{region} + '%'
                OR HospShort LIKE '%' + #{region} + '%'
        )
        </if>
        AND (HospState IS NULL OR HospState = 1)
        ORDER BY HospName
ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
@@ -51,8 +51,7 @@
        <if test="dispatchOrderClass != null and dispatchOrderClass != ''">
            AND dispatch_order_class = #{dispatchOrderClass}
        </if>
        <!-- æ•°æ®èŒƒå›´è¿‡æ»¤ -->
        ${params.dataScope}
        order by d.parent_id, d.order_num
    </select>
    
ruoyi-ui/src/api/task.js
@@ -1,86 +1,86 @@
import request from '@/utils/request'
// æŸ¥è¯¢ä»»åŠ¡ç®¡ç†åˆ—è¡¨
// æŸ¥è¯¢ä»»åŠ¡ç®¡ç†åˆ—è¡¨ (后台管理端)
export function listTask(query) {
  return request({
    url: '/task/list',
    url: '/task/admin/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢ä»»åŠ¡ç®¡ç†è¯¦ç»†
// æŸ¥è¯¢ä»»åŠ¡ç®¡ç†è¯¦ç»† (后台管理端)
export function getTask(taskId) {
  return request({
    url: '/task/' + taskId,
    url: '/task/admin/' + taskId,
    method: 'get'
  })
}
// æ–°å¢žä»»åŠ¡ç®¡ç†
// æ–°å¢žä»»åŠ¡ç®¡ç† (后台管理端)
export function addTask(data) {
  return request({
    url: '/task',
    url: '/task/admin',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹ä»»åŠ¡ç®¡ç†
// ä¿®æ”¹ä»»åŠ¡ç®¡ç† (后台管理端)
export function updateTask(data) {
  return request({
    url: '/task',
    url: '/task/admin',
    method: 'put',
    data: data
  })
}
// åˆ é™¤ä»»åŠ¡ç®¡ç†
// åˆ é™¤ä»»åŠ¡ç®¡ç† (后台管理端)
export function delTask(taskIds) {
  return request({
    url: '/task/' + taskIds,
    url: '/task/admin/' + taskIds,
    method: 'delete'
  })
}
// åˆ†é…ä»»åŠ¡
// åˆ†é…ä»»åŠ¡ (后台管理端)
export function assignTask(taskId, data) {
  return request({
    url: '/task/' + taskId + '/assign',
    url: '/task/admin/' + taskId + '/assign',
    method: 'put',
    data: data
  })
}
// æ›´æ–°ä»»åŠ¡çŠ¶æ€
// æ›´æ–°ä»»åŠ¡çŠ¶æ€ (后台管理端)
export function changeTaskStatus(taskId, data) {
  return request({
    url: '/task/' + taskId + '/status',
    url: '/task/admin/' + taskId + '/status',
    method: 'put',
    data: data
  })
}
// æŸ¥è¯¢ä»»åŠ¡ç»Ÿè®¡ä¿¡æ¯
// æŸ¥è¯¢ä»»åŠ¡ç»Ÿè®¡ä¿¡æ¯ (后台管理端)
export function getTaskStatistics() {
  return request({
    url: '/task/statistics',
    url: '/task/admin/statistics',
    method: 'get'
  })
}
// æŸ¥è¯¢è¶…时任务列表
// æŸ¥è¯¢è¶…时任务列表 (后台管理端)
export function getOverdueTasks() {
  return request({
    url: '/task/overdue',
    url: '/task/admin/overdue',
    method: 'get'
  })
}
// æŸ¥è¯¢æˆ‘的任务列表
// æŸ¥è¯¢æˆ‘的任务列表 (后台管理端 - ä»…供参考)
export function getMyTasks() {
  return request({
    url: '/task/my',
    url: '/task/app/my',
    method: 'get'
  })
}
ruoyi-ui/src/views/task/general/detail.vue
@@ -32,45 +32,92 @@
      <!-- æ€¥æ•‘转运任务扩展信息 -->
      <el-descriptions v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo" title="急救转运信息" :column="2" border style="margin-top: 20px;">
        <el-descriptions-item label="患者姓名">{{ taskDetail.emergencyInfo.patientName }}</el-descriptions-item>
        <el-descriptions-item label="患者性别">
          <dict-tag :options="dict.type.sys_user_sex" :value="taskDetail.emergencyInfo.patientGender"/>
        <el-descriptions-item label="联系人">
          <span v-if="taskDetail.emergencyInfo.contactPerson">{{ taskDetail.emergencyInfo.contactPerson }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="患者年龄">{{ taskDetail.emergencyInfo.patientAge }}</el-descriptions-item>
        <el-descriptions-item label="联系电话">{{ taskDetail.emergencyInfo.contactPhone }}</el-descriptions-item>
        <el-descriptions-item label="接送医院" :span="2">{{ taskDetail.emergencyInfo.hospitalName }}</el-descriptions-item>
        <el-descriptions-item label="就诊科室" :span="2">
          <dict-tag v-if="taskDetail.emergencyInfo.hospitalDepartment" :options="dict.type.hospital_department" :value="taskDetail.emergencyInfo.hospitalDepartment"/>
        <el-descriptions-item label="联系电话">
          <span v-if="taskDetail.emergencyInfo.contactPhone">{{ taskDetail.emergencyInfo.contactPhone }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="患者姓名">
          <span v-if="taskDetail.emergencyInfo.patientName">{{ taskDetail.emergencyInfo.patientName }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="患者性别">
          <dict-tag v-if="taskDetail.emergencyInfo.patientGender" :options="dict.type.sys_user_sex" :value="taskDetail.emergencyInfo.patientGender"/>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="身份信息" :span="2">
          <span v-if="taskDetail.emergencyInfo.patientIdCard">{{ taskDetail.emergencyInfo.patientIdCard }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="病情描述" :span="2">
          <span v-if="taskDetail.emergencyInfo.illnessDescription">{{ taskDetail.emergencyInfo.illnessDescription }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="特殊需求" :span="2">
          <span v-if="taskDetail.emergencyInfo.specialRequirements">{{ taskDetail.emergencyInfo.specialRequirements }}</span>
      </el-descriptions>
      <!-- æ—§ç³»ç»ŸåŒæ­¥ä¿¡æ¯ï¼ˆä»…急救转运任务显示) -->
      <el-descriptions v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo" title="旧系统同步信息" :column="2" border style="margin-top: 20px;">
        <el-descriptions-item label="服务单同步状态">
          <el-tag v-if="taskDetail.emergencyInfo.syncStatus === 0" type="info" size="small">
            <i class="el-icon-warning"></i> æœªåŒæ­¥
          </el-tag>
          <el-tag v-else-if="taskDetail.emergencyInfo.syncStatus === 1" type="warning" size="small">
            <i class="el-icon-loading"></i> åŒæ­¥ä¸­
          </el-tag>
          <el-tag v-else-if="taskDetail.emergencyInfo.syncStatus === 2" type="success" size="small">
            <i class="el-icon-success"></i> åŒæ­¥æˆåŠŸ
          </el-tag>
          <el-tag v-else-if="taskDetail.emergencyInfo.syncStatus === 3" type="danger" size="small">
            <i class="el-icon-error"></i> åŒæ­¥å¤±è´¥
          </el-tag>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="是否需要担架">
          <el-tag v-if="taskDetail.emergencyInfo.needsStretcher == 1" type="success" size="small">是</el-tag>
          <el-tag v-else type="info" size="small">否</el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="是否需要轮椅">
          <el-tag v-if="taskDetail.emergencyInfo.needsWheelchair == 1" type="success" size="small">是</el-tag>
          <el-tag v-else type="info" size="small">否</el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="是否需要氧气">
          <el-tag v-if="taskDetail.emergencyInfo.needsOxygen == 1" type="success" size="small">是</el-tag>
          <el-tag v-else type="info" size="small">否</el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="紧急程度">
          <el-tag v-if="taskDetail.emergencyInfo.urgencyLevel === 'HIGH'" type="danger" size="small">紧急</el-tag>
          <el-tag v-else-if="taskDetail.emergencyInfo.urgencyLevel === 'MEDIUM'" type="warning" size="small">一般</el-tag>
          <el-tag v-else-if="taskDetail.emergencyInfo.urgencyLevel === 'LOW'" type="info" size="small">不急</el-tag>
        <el-descriptions-item label="服务单号">
          <span v-if="taskDetail.emergencyInfo.legacyServiceOrdId">
            <el-tag type="primary" size="small">{{ taskDetail.emergencyInfo.legacyServiceOrdId }}</el-tag>
          </span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="陪同人数">{{ taskDetail.emergencyInfo.companionCount || 0 }} äºº</el-descriptions-item>
        <el-descriptions-item label="预估费用">{{ taskDetail.emergencyInfo.estimatedCost || '--' }} å…ƒ</el-descriptions-item>
        <el-descriptions-item label="服务单同步时间">
          <span v-if="taskDetail.emergencyInfo.syncTime">{{ parseTime(taskDetail.emergencyInfo.syncTime) }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="服务单同步错误" :span="1">
          <span v-if="taskDetail.emergencyInfo.syncErrorMsg" style="color: #F56C6C;">{{ taskDetail.emergencyInfo.syncErrorMsg }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="调度单同步状态">
          <el-tag v-if="taskDetail.emergencyInfo.dispatchSyncStatus === 0" type="info" size="small">
            <i class="el-icon-warning"></i> æœªåŒæ­¥
          </el-tag>
          <el-tag v-else-if="taskDetail.emergencyInfo.dispatchSyncStatus === 1" type="warning" size="small">
            <i class="el-icon-loading"></i> åŒæ­¥ä¸­
          </el-tag>
          <el-tag v-else-if="taskDetail.emergencyInfo.dispatchSyncStatus === 2" type="success" size="small">
            <i class="el-icon-success"></i> åŒæ­¥æˆåŠŸ
          </el-tag>
          <el-tag v-else-if="taskDetail.emergencyInfo.dispatchSyncStatus === 3" type="danger" size="small">
            <i class="el-icon-error"></i> åŒæ­¥å¤±è´¥
          </el-tag>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="调度单号">
          <span v-if="taskDetail.emergencyInfo.legacyDispatchOrdId">
            <el-tag type="primary" size="small">{{ taskDetail.emergencyInfo.legacyDispatchOrdId }}</el-tag>
          </span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="调度单同步时间">
          <span v-if="taskDetail.emergencyInfo.dispatchSyncTime">{{ parseTime(taskDetail.emergencyInfo.dispatchSyncTime) }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="调度单同步错误" :span="1">
          <span v-if="taskDetail.emergencyInfo.dispatchSyncErrorMsg" style="color: #F56C6C;">{{ taskDetail.emergencyInfo.dispatchSyncErrorMsg }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
      </el-descriptions>
      <!-- ç¦ç¥‰è½¦ä»»åŠ¡æ‰©å±•ä¿¡æ¯ -->
ruoyi-ui/src/views/task/general/index.vue
@@ -94,12 +94,13 @@
    <el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="任务编号" align="center" prop="taskCode">
      <el-table-column label="任务编号" align="center" prop="taskCode" min-width="180">
        <template slot-scope="scope">
          <el-button
            type="text"
            @click="handleView(scope.row)"
            v-hasPermi="['task:general:query']"
            style="font-family: 'Courier New', monospace; font-size: 13px;"
          >{{ scope.row.taskCode }}</el-button>
        </template>
      </el-table-column>
@@ -108,6 +109,24 @@
          <dict-tag :options="dict.type.sys_task_type" :value="scope.row.taskType"/>
          <el-tag v-if="scope.row.taskType === 'EMERGENCY_TRANSFER'" type="danger" size="mini" style="margin-left: 5px;">
            <i class="el-icon-warning"></i>
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="同步状态" align="center" prop="syncStatus" width="120" v-if="hasEmergencyTask">
        <template slot-scope="scope">
          <span v-if="scope.row.taskType !== 'EMERGENCY_TRANSFER'" style="color: #C0C4CC;">--</span>
          <el-tag v-else-if="!scope.row.emergencyInfo" type="info" size="mini">--</el-tag>
          <el-tag v-else-if="scope.row.emergencyInfo.syncStatus === 0" type="info" size="mini">
            <i class="el-icon-warning"></i> æœªåŒæ­¥
          </el-tag>
          <el-tag v-else-if="scope.row.emergencyInfo.syncStatus === 1" type="warning" size="mini">
            <i class="el-icon-loading"></i> åŒæ­¥ä¸­
          </el-tag>
          <el-tag v-else-if="scope.row.emergencyInfo.syncStatus === 2" type="success" size="mini">
            <i class="el-icon-success"></i> å·²åŒæ­¥
          </el-tag>
          <el-tag v-else-if="scope.row.emergencyInfo.syncStatus === 3" type="danger" size="mini">
            <i class="el-icon-error"></i> åŒæ­¥å¤±è´¥
          </el-tag>
        </template>
      </el-table-column>
@@ -126,14 +145,11 @@
      </el-table-column>
      <el-table-column label="计划开始时间" align="center" prop="plannedStartTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.plannedStartTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
          <span v-if="scope.row.plannedStartTime">{{ parseTime(scope.row.plannedStartTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </template>
      </el-table-column>
      <el-table-column label="计划结束时间" align="center" prop="plannedEndTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.plannedEndTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="创建人" align="center" prop="creatorName" />
      <el-table-column label="执行人" align="center" prop="assigneeName" />
      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
@@ -400,6 +416,8 @@
      total: 0,
      // ä»»åŠ¡ç®¡ç†è¡¨æ ¼æ•°æ®
      taskList: [],
      // æ˜¯å¦æœ‰æ€¥æ•‘转运任务(用于控制同步状态列显示)
      hasEmergencyTask: false,
      // å¼¹å‡ºå±‚标题
      title: "",
      // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
@@ -472,6 +490,8 @@
      listTask(this.queryParams).then(response => {
        this.taskList = response.rows;
        this.total = response.total;
        // æ£€æŸ¥æ˜¯å¦æœ‰æ€¥æ•‘转运任务,用于控制同步状态相关列的显示
        this.hasEmergencyTask = this.taskList.some(task => task.taskType === 'EMERGENCY_TRANSFER');
        this.loading = false;
      });
    },
ruoyi-ui/src/views/task/¾Éϵͳͬ²½×´Ì¬ÏÔʾ¹¦ÄÜ˵Ã÷.md
New file
@@ -0,0 +1,164 @@
# æ—§ç³»ç»ŸåŒæ­¥çŠ¶æ€æ˜¾ç¤ºåŠŸèƒ½è¯´æ˜Ž
## ä¸€ã€åŠŸèƒ½æ¦‚è¿°
在 ruoyi-ui é¡¹ç›®çš„任务列表和任务详情页面中,新增了旧系统同步状态及相关单号的显示功能,方便管理员实时查看急救转运任务与旧系统的同步情况。
## äºŒã€æ¶‰åŠæ–‡ä»¶
### 1. ä»»åŠ¡åˆ—è¡¨é¡µé¢
**文件路径**: `ruoyi-ui/src/views/task/general/index.vue`
**修改内容**:
- æ–°å¢ž `hasEmergencyTask` æ•°æ®å±žæ€§ï¼Œç”¨äºŽåˆ¤æ–­åˆ—表中是否包含急救转运任务
- æ–°å¢ž **同步状态** åˆ—,显示服务单同步状态(未同步、同步中、已同步、同步失败)
- æ–°å¢ž **服务单号** åˆ—,显示旧系统返回的 ServiceOrdID
- æ–°å¢ž **调度单号** åˆ—,显示旧系统返回的 DispatchOrdID
- è¿™äº›åˆ—仅在列表中存在急救转运任务时显示(`v-if="hasEmergencyTask"`)
### 2. ä»»åŠ¡è¯¦æƒ…é¡µé¢
**文件路径**: `ruoyi-ui/src/views/task/general/detail.vue`
**修改内容**:
- æ–°å¢ž **旧系统同步信息** æè¿°åˆ—表区域
- æ˜¾ç¤ºæœåŠ¡å•åŒæ­¥çŠ¶æ€ã€æœåŠ¡å•å·ã€åŒæ­¥æ—¶é—´ã€åŒæ­¥é”™è¯¯ä¿¡æ¯
- æ˜¾ç¤ºè°ƒåº¦å•同步状态、调度单号、同步时间、同步错误信息
- è¯¥åŒºåŸŸä»…在急救转运任务详情页显示
## ä¸‰ã€åŒæ­¥çŠ¶æ€è¯´æ˜Ž
### åŒæ­¥çŠ¶æ€å€¼åŠå«ä¹‰
| çŠ¶æ€å€¼ | å«ä¹‰ | å›¾æ ‡é¢œè‰² | æ˜¾ç¤ºæ–‡æœ¬ |
|--------|------|----------|----------|
| 0 | æœªåŒæ­¥ | ç°è‰²ï¼ˆinfo) | <i class="el-icon-warning"></i> æœªåŒæ­¥ |
| 1 | åŒæ­¥ä¸­ | æ©™è‰²ï¼ˆwarning) | <i class="el-icon-loading"></i> åŒæ­¥ä¸­ |
| 2 | åŒæ­¥æˆåŠŸ | ç»¿è‰²ï¼ˆsuccess) | <i class="el-icon-success"></i> å·²åŒæ­¥/同步成功 |
| 3 | åŒæ­¥å¤±è´¥ | çº¢è‰²ï¼ˆdanger) | <i class="el-icon-error"></i> åŒæ­¥å¤±è´¥ |
### æœåŠ¡å•åŒæ­¥ä¸Žè°ƒåº¦å•åŒæ­¥
**服务单同步** (`syncStatus`):
- å¯¹åº”旧系统 `admin_save_19.gds` æŽ¥å£
- åŒæ­¥æˆåŠŸåŽè¿”å›ž `ServiceOrdID`,存储在 `legacy_service_ord_id` å­—段
- ç”± `LegacySystemSyncTask.syncPendingTasks()` å®šæ—¶ä»»åŠ¡æ‰§è¡Œ
**调度单同步** (`dispatchSyncStatus`):
- å¯¹åº”旧系统 `admin_save_24.asp` æŽ¥å£
- åŒæ­¥æˆåŠŸåŽè¿”å›ž `DispatchOrdID`,存储在 `legacy_dispatch_ord_id` å­—段
- ç”± `LegacySystemSyncTask.syncPendingDispatchOrders()` å®šæ—¶ä»»åŠ¡æ‰§è¡Œ
- ä¾èµ–服务单同步完成后才能执行
## å››ã€æ•°æ®æ¥æº
### åŽç«¯å®žä½“类字段
**SysTask å®žä½“** (`sys_task` è¡¨):
- `legacySynced`: æ—§ç³»ç»ŸåŒæ­¥æ ‡è®°ï¼ˆ0-未同步,1-已同步)
**SysTaskEmergency å®žä½“** (`sys_task_emergency` è¡¨):
服务单相关:
- `legacyServiceOrdId`: æ—§ç³»ç»ŸæœåŠ¡å•ID
- `syncStatus`: æœåŠ¡å•åŒæ­¥çŠ¶æ€
- `syncTime`: æœåŠ¡å•åŒæ­¥æ—¶é—´
- `syncErrorMsg`: æœåŠ¡å•åŒæ­¥é”™è¯¯ä¿¡æ¯
调度单相关:
- `legacyDispatchOrdId`: æ—§ç³»ç»Ÿè°ƒåº¦å•ID
- `dispatchSyncStatus`: è°ƒåº¦å•同步状态
- `dispatchSyncTime`: è°ƒåº¦å•同步时间
- `dispatchSyncErrorMsg`: è°ƒåº¦å•同步错误信息
### å‰ç«¯æ•°æ®è®¿é—®è·¯å¾„
在任务列表中:
```javascript
// åŒæ­¥çŠ¶æ€
scope.row.emergencyInfo.syncStatus
scope.row.emergencyInfo.dispatchSyncStatus
// å•号
scope.row.emergencyInfo.legacyServiceOrdId
scope.row.emergencyInfo.legacyDispatchOrdId
```
在任务详情中:
```javascript
// æœåŠ¡å•åŒæ­¥ä¿¡æ¯
taskDetail.emergencyInfo.syncStatus
taskDetail.emergencyInfo.legacyServiceOrdId
taskDetail.emergencyInfo.syncTime
taskDetail.emergencyInfo.syncErrorMsg
// è°ƒåº¦å•同步信息
taskDetail.emergencyInfo.dispatchSyncStatus
taskDetail.emergencyInfo.legacyDispatchOrdId
taskDetail.emergencyInfo.dispatchSyncTime
taskDetail.emergencyInfo.dispatchSyncErrorMsg
```
## äº”、显示逻辑
### ä»»åŠ¡åˆ—è¡¨æ˜¾ç¤ºè§„åˆ™
1. **同步状态列、服务单号列、调度单号列**仅在列表中存在急救转运任务时显示
2. éžæ€¥æ•‘转运任务在这些列中显示 `--`
3. æ€¥æ•‘转运任务根据 `emergencyInfo` å­—段判断:
   - å¦‚æžœ `emergencyInfo` ä¸ºç©ºï¼Œæ˜¾ç¤º `--`
   - å¦‚æžœ `emergencyInfo` å­˜åœ¨ï¼Œæ ¹æ®å¯¹åº”字段显示具体内容
### ä»»åŠ¡è¯¦æƒ…æ˜¾ç¤ºè§„åˆ™
1. **旧系统同步信息**区域仅在急救转运任务详情页显示
2. æ¡ä»¶: `taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo`
3. æ‰€æœ‰å­—段为空时显示 `--`
4. é”™è¯¯ä¿¡æ¯ä»¥çº¢è‰²æ–‡æœ¬æ˜¾ç¤º
## å…­ã€ä½¿ç”¨åœºæ™¯
### 1. ç®¡ç†å‘˜ç›‘控同步状态
管理员可以在任务列表中快速查看哪些急救转运任务已完成与旧系统的同步,哪些同步失败需要处理。
### 2. é—®é¢˜æŽ’查
当同步失败时,可以在任务详情页查看详细的错误信息,帮助技术人员定位问题。
### 3. æ•°æ®è¿½æº¯
通过服务单号和调度单号,可以在旧系统中查询对应的单据,实现新旧系统数据的双向追溯。
## ä¸ƒã€æ³¨æ„äº‹é¡¹
1. **权限控制**: é¡µé¢æ˜¾ç¤ºéµå¾ªåŽŸæœ‰çš„æƒé™æŽ§åˆ¶è§„åˆ™
2. **性能考虑**: åˆ—表查询时需要关联 `sys_task_emergency` è¡¨ï¼Œç¡®ä¿ Mapper XML ä¸­å·²æ­£ç¡®é…ç½®å…³è”查询
3. **非急救任务**: å…¶ä»–类型任务(如福祉车、普通任务)不显示同步信息
4. **定时任务**: åŒæ­¥çŠ¶æ€ç”±åŽå°å®šæ—¶ä»»åŠ¡æ›´æ–°ï¼Œå‰ç«¯åªè´Ÿè´£å±•ç¤º
## å…«ã€åŽç»­ä¼˜åŒ–建议
1. **手动重试功能**: åœ¨è¯¦æƒ…页添加"重新同步"按钮,允许管理员手动触发同步
2. **同步日志**: è®°å½•每次同步的详细日志,方便问题追溯
3. **消息通知**: åŒæ­¥å¤±è´¥æ—¶é€šè¿‡ç³»ç»Ÿæ¶ˆæ¯æˆ–邮件通知相关人员
4. **批量同步**: åœ¨åˆ—表页提供批量重新同步功能
## ä¹ã€ç›¸å…³å®šæ—¶ä»»åŠ¡
### LegacySystemSyncTask
**Cron è¡¨è¾¾å¼**:
- æœåŠ¡å•åŒæ­¥: `0 */10 * * * ?` (每10分钟执行一次)
- è°ƒåº¦å•同步: `0 5,15,25,35,45,55 * * * ?` (每小时的5、15、25、35、45、55分执行)
**配置位置**: `ruoyi-quartz` æ¨¡å—
**任务逻辑**:
1. æŸ¥è¯¢æœªåŒæ­¥æˆ–同步失败的急救转运任务
2. è°ƒç”¨æ—§ç³»ç»Ÿ ASP æŽ¥å£
3. æ›´æ–°åŒæ­¥çŠ¶æ€å’Œå•å·
4. è®°å½•同步时间和错误信息
---
**文档版本**: v1.0
**创建日期**: 2025-01-XX
**最后更新**: 2025-01-XX
**维护人员**: ç³»ç»Ÿç®¡ç†å‘˜
ÈÎÎñ½Ó¿Ú²ð·Ö˵Ã÷.md
New file
@@ -0,0 +1,422 @@
# ä»»åŠ¡æŽ¥å£æ‹†åˆ†è¯´æ˜Žæ–‡æ¡£
## ä¸€ã€ä¿®æ”¹èƒŒæ™¯
原有的 `/task/list` ç­‰æŽ¥å£åŒæ—¶è¢« APP ç«¯å’ŒåŽå°ç®¡ç†ç«¯ä½¿ç”¨ï¼Œå¯¼è‡´ä»¥ä¸‹é—®é¢˜ï¼š
1. **权限混乱**:后台管理员无法查看所有任务,只能看到当前用户相关的任务
2. **业务逻辑冲突**:APP ç«¯éœ€è¦è¿‡æ»¤ç”¨æˆ·æ•°æ®ï¼ŒåŽå°éœ€è¦æŸ¥çœ‹å…¨éƒ¨æ•°æ®
3. **安全隐患**:APP ç«¯å’Œç®¡ç†ç«¯å…±ç”¨æŽ¥å£ï¼Œæƒé™æŽ§åˆ¶å›°éš¾
## äºŒã€è§£å†³æ–¹æ¡ˆ
**保留原有 APP ç«¯æŽ¥å£**,同时新增后台管理端独立接口:
- **APP ç«¯æŽ¥å£**:保持原有路径 `/task/*`,无权限注解,自动过滤当前用户数据
- **后台管理端接口**:新增路径前缀 `/task/admin/*`,带权限注解,管理员可查看所有数据
## ä¸‰ã€æ¶‰åŠæ–‡ä»¶
### 1. åŽç«¯ Controller
**文件路径**: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java`
### 2. APP ç«¯ API
**文件路径**: `app/api/task.js`
### 3. åŽå°ç®¡ç†ç«¯ API
**文件路径**: `ruoyi-ui/src/api/task.js`
## å››ã€æŽ¥å£å¯¹ç…§è¡¨
### 4.1 ä»»åŠ¡åˆ—è¡¨æŽ¥å£
| åŠŸèƒ½ | APP ç«¯ | åŽå°ç®¡ç†ç«¯ | è¯´æ˜Ž |
|------|--------|-----------|------|
| æŸ¥è¯¢ä»»åŠ¡åˆ—è¡¨ | `GET /task/list` | `GET /task/admin/list` | APP端过滤当前用户,后台可查看全部 |
| æŸ¥è¯¢ä»»åŠ¡è¯¦æƒ… | `GET /task/{taskId}` | `GET /task/admin/{taskId}` | èŽ·å–å•ä¸ªä»»åŠ¡è¯¦æƒ… |
| æ–°å¢žä»»åŠ¡ | `POST /task` | `POST /task/admin` | åˆ›å»ºæ–°ä»»åŠ¡ |
| ä¿®æ”¹ä»»åŠ¡ | `PUT /task` | `PUT /task/admin` | æ›´æ–°ä»»åŠ¡ä¿¡æ¯ |
| åˆ é™¤ä»»åŠ¡ | `DELETE /task/{taskIds}` | `DELETE /task/admin/{taskIds}` | åˆ é™¤ä»»åŠ¡ |
### 4.2 ä»»åŠ¡æ“ä½œæŽ¥å£
| åŠŸèƒ½ | APP ç«¯ | åŽå°ç®¡ç†ç«¯ | è¯´æ˜Ž |
|------|--------|-----------|------|
| åˆ†é…ä»»åŠ¡ | `PUT /task/{taskId}/assign` | `PUT /task/admin/{taskId}/assign` | åˆ†é…æ‰§è¡Œäºº |
| æ›´æ–°çŠ¶æ€ | `PUT /task/{taskId}/status` | `PUT /task/admin/{taskId}/status` | APP端支持GPS位置 |
| ä»»åŠ¡ç»Ÿè®¡ | `GET /task/statistics` | `GET /task/admin/statistics` | ç»Ÿè®¡ä¿¡æ¯ |
| è¶…时任务 | `GET /task/overdue` | `GET /task/admin/overdue` | è¶…时任务列表 |
| æˆ‘的任务 | `GET /task/my` | - | ä»…APP端使用 |
### 4.3 å¯¼å‡ºæŽ¥å£
| åŠŸèƒ½ | åŽå°ç®¡ç†ç«¯ | è¯´æ˜Ž |
|------|-----------|------|
| å¯¼å‡ºä»»åŠ¡ | `POST /task/admin/export` | ä»…后台管理端支持 |
| å¯¼å‡ºä»»åŠ¡ï¼ˆå…¼å®¹ï¼‰ | `POST /task/export` | å…¼å®¹æ—§æŽ¥å£ |
## äº”、核心差异说明
### 5.1 åŽå°ç®¡ç†ç«¯æŽ¥å£ç‰¹ç‚¹
```java
/**
 * æŸ¥è¯¢ä»»åŠ¡ç®¡ç†åˆ—è¡¨ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
 * ç®¡ç†å‘˜æƒé™ï¼Œå¯ä»¥æŸ¥çœ‹æ‰€æœ‰ä»»åŠ¡
 */
@PreAuthorize("@ss.hasPermi('task:task:list')")
@GetMapping("/admin/list")
public TableDataInfo adminList(TaskQueryVO queryVO) {
    startPage();
    List<SysTask> list = sysTaskService.selectSysTaskList(queryVO);
    return getDataTable(list);
}
```
**特点**:
- âœ… å¸¦ `@PreAuthorize` æƒé™æ³¨è§£
- âœ… ç›´æŽ¥ä½¿ç”¨å‰ç«¯ä¼ é€’的查询条件
- âœ… ç®¡ç†å‘˜å¯ä»¥æŸ¥çœ‹æ‰€æœ‰ä»»åŠ¡
- âœ… æ”¯æŒæŒ‰éƒ¨é—¨ã€åˆ›å»ºäººã€æ‰§è¡Œäººç­‰æ¡ä»¶è¿‡æ»¤
### 5.2 APP ç«¯æŽ¥å£ç‰¹ç‚¹
```java
/**
 * æŸ¥è¯¢ä»»åŠ¡åˆ—è¡¨ï¼ˆAPP端)
 * ä»…显示当前用户相关的任务:
 * 1. å½“前用户所在机构的任务
 * 2. å½“前用户创建的任务
 * 3. åˆ†é…ç»™å½“前用户的任务
 */
@GetMapping("/app/list")
public TableDataInfo appList(TaskQueryVO queryVO) {
    // åœ¨åŽç«¯è‡ªåŠ¨èŽ·å–å½“å‰ç”¨æˆ·ä¿¡æ¯ï¼Œå®žçŽ°ç»¼åˆæŸ¥è¯¢
    Long currentUserId = getUserId();
    Long currentDeptId = getDeptId();
    // APP端强制使用当前登录用户信息进行过滤
    queryVO.setDeptId(currentDeptId);
    queryVO.setCreatorId(currentUserId);
    queryVO.setAssigneeId(currentUserId);
    startPage();
    List<SysTask> list = sysTaskService.selectSysTaskList(queryVO);
    return getDataTable(list);
}
```
**特点**:
- âœ… æ— æƒé™æ³¨è§£ï¼ˆå·²åœ¨ `permission.js` ä¸­é…ç½®ç™½åå•)
- âœ… å¼ºåˆ¶ä½¿ç”¨å½“前登录用户信息过滤
- âœ… è‡ªåŠ¨é™åˆ¶æ•°æ®èŒƒå›´ï¼Œä¿è¯æ•°æ®å®‰å…¨
- âœ… ç”¨æˆ·åªèƒ½çœ‹åˆ°è‡ªå·±ç›¸å…³çš„任务
### 5.3 çŠ¶æ€æ›´æ–°æŽ¥å£å·®å¼‚
**APP ç«¯**:支持 GPS ä½ç½®ä¿¡æ¯ä¸ŠæŠ¥
```java
@PutMapping("/app/{taskId}/status")
public AjaxResult appChangeTaskStatus(@PathVariable Long taskId, @RequestBody ChangeStatusRequest request) {
    // ... çœç•¥çŠ¶æ€æ ¡éªŒ
    // å¦‚果包含GPS位置信息,使用带位置的方法
    if (request.getLatitude() != null && request.getLongitude() != null) {
        SysTaskLog locationLog = new SysTaskLog();
        locationLog.setLatitude(request.getLatitude());
        locationLog.setLongitude(request.getLongitude());
        // ... è®¾ç½®å…¶ä»–位置信息
        return toAjax(sysTaskService.changeTaskStatusWithLocation(taskId, newStatus, request.getRemark(), locationLog));
    }
    return toAjax(sysTaskService.changeTaskStatus(taskId, newStatus, request.getRemark()));
}
```
**后台管理端**:仅支持基本状态变更
```java
@PreAuthorize("@ss.hasPermi('task:task:edit')")
@PutMapping("/admin/{taskId}/status")
public AjaxResult adminChangeTaskStatus(@PathVariable Long taskId, @RequestBody ChangeStatusRequest request) {
    TaskStatus newStatus = TaskStatus.getByCode(request.getTaskStatus());
    if (newStatus == null) {
        return error("无效的任务状态");
    }
    return toAjax(sysTaskService.changeTaskStatus(taskId, newStatus, request.getRemark()));
}
```
## å…­ã€å‰ç«¯è°ƒç”¨ç¤ºä¾‹
### 6.1 APP ç«¯è°ƒç”¨
```javascript
// app/api/task.js
import request from '@/utils/request'
// æŸ¥è¯¢ä»»åŠ¡åˆ—è¡¨ï¼ˆAPP端)
export function listTask(query) {
  return request({
    url: '/task/app/list',
    method: 'get',
    params: query
  })
}
// æ›´æ–°ä»»åŠ¡çŠ¶æ€ï¼ˆAPP端,支持GPS)
export function changeTaskStatus(taskId, data) {
  return request({
    url: '/task/app/' + taskId + '/status',
    method: 'put',
    data: data  // å¯åŒ…含 latitude, longitude ç­‰GPS信息
  })
}
```
**使用示例**:
```javascript
// åœ¨ APP é¡µé¢ä¸­è°ƒç”¨
import { listTask, changeTaskStatus } from '@/api/task'
// æŸ¥è¯¢ä»»åŠ¡åˆ—è¡¨
listTask({ taskStatus: 'PENDING' }).then(response => {
  // è‡ªåŠ¨åªè¿”å›žå½“å‰ç”¨æˆ·ç›¸å…³çš„ä»»åŠ¡
  this.taskList = response.rows
})
// æ›´æ–°ä»»åŠ¡çŠ¶æ€å¹¶ä¸ŠæŠ¥GPS位置
changeTaskStatus(taskId, {
  taskStatus: 'IN_PROGRESS',
  remark: '已到达现场',
  latitude: 23.1291,
  longitude: 113.2644,
  locationAddress: '广州市天河区'
}).then(response => {
  this.$modal.msgSuccess('状态更新成功')
})
```
### 6.2 åŽå°ç®¡ç†ç«¯è°ƒç”¨
```javascript
// ruoyi-ui/src/api/task.js
import request from '@/utils/request'
// æŸ¥è¯¢ä»»åŠ¡åˆ—è¡¨ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
export function listTask(query) {
  return request({
    url: '/task/admin/list',
    method: 'get',
    params: query
  })
}
// æ›´æ–°ä»»åŠ¡çŠ¶æ€ï¼ˆåŽå°ç®¡ç†ç«¯ï¼‰
export function changeTaskStatus(taskId, data) {
  return request({
    url: '/task/admin/' + taskId + '/status',
    method: 'put',
    data: data
  })
}
```
**使用示例**:
```javascript
// åœ¨åŽå°ç®¡ç†é¡µé¢ä¸­è°ƒç”¨
import { listTask, changeTaskStatus } from '@/api/task'
// æŸ¥è¯¢ä»»åŠ¡åˆ—è¡¨ï¼ˆç®¡ç†å‘˜å¯æŸ¥çœ‹æ‰€æœ‰ä»»åŠ¡ï¼‰
listTask({
  deptId: 101,  // å¯æŒ‡å®šéƒ¨é—¨
  taskStatus: 'PENDING'
}).then(response => {
  // è¿”回所有符合条件的任务
  this.taskList = response.rows
})
// æ›´æ–°ä»»åŠ¡çŠ¶æ€
changeTaskStatus(taskId, {
  taskStatus: 'COMPLETED',
  remark: '任务已完成'
}).then(response => {
  this.$modal.msgSuccess('状态更新成功')
})
```
## ä¸ƒã€æƒé™é…ç½®
### 7.1 åŽå°ç®¡ç†ç«¯æƒé™
在 `ruoyi-ui/src/views/task/general/index.vue` ä¸­éœ€è¦é…ç½®æƒé™ï¼š
```javascript
// éœ€è¦çš„æƒé™æ ‡è¯†
const permissions = [
  'task:task:list',    // æŸ¥è¯¢ä»»åŠ¡åˆ—è¡¨
  'task:task:query',   // æŸ¥è¯¢ä»»åŠ¡è¯¦æƒ…
  'task:task:add',     // æ–°å¢žä»»åŠ¡
  'task:task:edit',    // ä¿®æ”¹ä»»åŠ¡
  'task:task:remove',  // åˆ é™¤ä»»åŠ¡
  'task:task:export'   // å¯¼å‡ºä»»åŠ¡
]
```
### 7.2 APP ç«¯æƒé™é…ç½®
在 `app/permission.js` ä¸­é…ç½®ç™½åå•(无需权限验证):
```javascript
const whiteList = [
  '/task/app/list',
  '/task/app/my',
  '/task/app/overdue',
  '/task/app/statistics',
  // ... å…¶ä»–APP端接口
]
```
## å…«ã€æ•°æ®å®‰å…¨ä¿éšœ
### 8.1 APP ç«¯æ•°æ®éš”离
**实现方式**:
1. åŽç«¯å¼ºåˆ¶ä½¿ç”¨å½“前登录用户的 `userId` å’Œ `deptId`
2. å‰ç«¯ä¼ é€’的过滤条件会被后端覆盖
3. SQL æŸ¥è¯¢æ—¶ä½¿ç”¨ OR æ¡ä»¶ç»„合:
   - `dept_id = currentDeptId`(同机构)
   - `creator_id = currentUserId`(自己创建)
   - `assignee_id = currentUserId`(分配给自己)
**SQL ç¤ºä¾‹**:
```xml
<select id="selectSysTaskList" resultMap="SysTaskResult">
    SELECT ... FROM sys_task t
    WHERE 1=1
    <if test="deptId != null">
        AND (
            t.dept_id = #{deptId}
            OR t.creator_id = #{creatorId}
            OR t.assignee_id = #{assigneeId}
        )
    </if>
    <!-- å…¶ä»–条件 -->
</select>
```
### 8.2 åŽå°ç®¡ç†ç«¯æƒé™æŽ§åˆ¶
**实现方式**:
1. ä½¿ç”¨ `@PreAuthorize` æ³¨è§£è¿›è¡Œæƒé™æ ¡éªŒ
2. ç®¡ç†å‘˜å¯ä»¥è‡ªç”±æŒ‡å®šæŸ¥è¯¢æ¡ä»¶
3. æ”¯æŒè·¨éƒ¨é—¨ã€è·¨ç”¨æˆ·æŸ¥è¯¢
4. é€šè¿‡è§’色权限控制访问范围
## ä¹ã€è¿ç§»æŒ‡å—
### 9.1 çŽ°æœ‰ APP ç«¯ä»£ç æ— éœ€ä¿®æ”¹
由于前端 API æ–‡ä»¶å·²ç»æ›´æ–°ï¼ŒçŽ°æœ‰çš„ APP é¡µé¢ä»£ç æ— éœ€ä¿®æ”¹ï¼š
```javascript
// åŽŸæœ‰ä»£ç ä¿æŒä¸å˜
import { listTask } from '@/api/task'
listTask(query).then(response => {
  // è‡ªåŠ¨ä½¿ç”¨æ–°çš„ /task/app/list æŽ¥å£
})
```
### 9.2 åŽå°ç®¡ç†ç«¯ä»£ç æ— éœ€ä¿®æ”¹
后台管理页面代码也无需修改:
```javascript
// åŽŸæœ‰ä»£ç ä¿æŒä¸å˜
import { listTask } from '@/api/task'
listTask(query).then(response => {
  // è‡ªåŠ¨ä½¿ç”¨æ–°çš„ /task/admin/list æŽ¥å£
})
```
### 9.3 æµ‹è¯•验证
**APP ç«¯æµ‹è¯•**:
1. ç™»å½• APP,查看任务列表
2. ç¡®è®¤åªèƒ½çœ‹åˆ°è‡ªå·±ç›¸å…³çš„任务
3. å°è¯•更新任务状态,验证 GPS ä½ç½®ä¸ŠæŠ¥
**后台管理端测试**:
1. ç™»å½•后台管理系统
2. ä»¥ç®¡ç†å‘˜èº«ä»½æŸ¥çœ‹ä»»åŠ¡åˆ—è¡¨
3. ç¡®è®¤å¯ä»¥çœ‹åˆ°æ‰€æœ‰ä»»åŠ¡
4. éªŒè¯æƒé™æŽ§åˆ¶æ˜¯å¦ç”Ÿæ•ˆ
## åã€æ³¨æ„äº‹é¡¹
### 10.1 æŽ¥å£å…¼å®¹æ€§
- âŒ **不再支持** `/task/list` ç­‰æ—§æŽ¥å£è·¯å¾„
- âœ… å¿…须使用 `/task/app/` æˆ– `/task/admin/` å‰ç¼€
- âœ… å‰ç«¯ API æ–‡ä»¶å·²æ›´æ–°ï¼Œæ— éœ€æ‰‹åŠ¨ä¿®æ”¹é¡µé¢ä»£ç 
### 10.2 æƒé™é…ç½®
- âœ… APP ç«¯æŽ¥å£å·²é…ç½®ç™½åå•,无需权限验证
- âœ… åŽå°ç®¡ç†ç«¯æŽ¥å£éœ€è¦å¯¹åº”的权限标识
- âš ï¸ ç¡®ä¿è§’色分配了正确的权限
### 10.3 æ•°æ®è¿‡æ»¤
- âœ… APP ç«¯å¼ºåˆ¶è¿‡æ»¤å½“前用户数据,前端无法绕过
- âœ… åŽå°ç®¡ç†ç«¯æ ¹æ®æƒé™æŸ¥çœ‹å¯¹åº”范围的数据
- âš ï¸ ä¸è¦åœ¨ APP ç«¯ä½¿ç”¨åŽå°ç®¡ç†ç«¯æŽ¥å£
### 10.4 GPS ä½ç½®ä¿¡æ¯
- âœ… ä»… APP ç«¯çŠ¶æ€æ›´æ–°æŽ¥å£æ”¯æŒ GPS ä½ç½®ä¸ŠæŠ¥
- âœ… åŽå°ç®¡ç†ç«¯çŠ¶æ€æ›´æ–°ä¸éœ€è¦ä½ç½®ä¿¡æ¯
- âš ï¸ GPS å­—段为可选,无位置时不影响状态更新
## åä¸€ã€å¸¸è§é—®é¢˜
### Q1: APP ç«¯èƒ½å¦æŸ¥çœ‹å…¶ä»–部门的任务?
**答**:不能。APP ç«¯æŽ¥å£å¼ºåˆ¶ä½¿ç”¨å½“前登录用户的部门ID进行过滤,即使前端传递其他部门ID,也会被后端覆盖。
### Q2: åŽå°ç®¡ç†å‘˜å¦‚何查看所有任务?
**答**:使用后台管理端接口 `/task/admin/list`,不传递任何过滤条件即可查看所有任务。需要确保用户拥有 `task:task:list` æƒé™ã€‚
### Q3: å¦‚何区分 APP ç«¯å’ŒåŽå°ç®¡ç†ç«¯çš„调用?
**答**:通过接口路径前缀区分:
- APP ç«¯ï¼š`/task/app/*`
- åŽå°ç®¡ç†ç«¯ï¼š`/task/admin/*`
### Q4: æ—§ç³»ç»Ÿçš„æŽ¥å£è°ƒç”¨æ˜¯å¦å—影响?
**答**:不受影响。旧系统同步等功能使用的是独立的接口,与任务管理接口无关。
### Q5: å¦‚何添加新的任务相关接口?
**答**:遵循以下规则:
1. APP ç«¯æŽ¥å£ï¼šè·¯å¾„使用 `/task/app/*`,无权限注解,强制过滤当前用户
2. åŽå°ç®¡ç†ç«¯æŽ¥å£ï¼šè·¯å¾„使用 `/task/admin/*`,添加权限注解,支持全局查询
## åäºŒã€æ€»ç»“
通过本次接口拆分,实现了:
1. âœ… **业务分离**:APP ç«¯å’ŒåŽå°ç®¡ç†ç«¯ä½¿ç”¨ç‹¬ç«‹æŽ¥å£
2. âœ… **权限清晰**:后台管理端统一使用权限注解
3. âœ… **数据安全**:APP ç«¯å¼ºåˆ¶è¿‡æ»¤å½“前用户数据
4. âœ… **功能扩展**:APP ç«¯æ”¯æŒ GPS ä½ç½®ä¸ŠæŠ¥
5. âœ… **代码维护**:接口职责单一,易于维护
6. âœ… **向下兼容**:前端代码无需修改,自动使用新接口
---
**文档版本**: v1.0
**创建日期**: 2025-01-XX
**最后更新**: 2025-01-XX
**维护人员**: ç³»ç»Ÿç®¡ç†å‘˜