wlzboy
2025-12-30 a4b14a35a2209a30e53472e6333b13aa4a55b0eb
feat:增加创建任务
12个文件已修改
3个文件已添加
824 ■■■■■ 已修改文件
app/pages/task/create.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/HospitalSelector.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/store/modules/user.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/utils/constant.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/utils/request.js 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
prd/用户创建任务单权限功能说明.md 301 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
prd/用户创建任务权限前端控制功能说明.md 404 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/UserSyncDTO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserSyncServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/UserSyncMapper.xml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/add_can_create_task_to_sys_user.sql 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/create.vue
@@ -26,6 +26,8 @@
</template>
<script>
import { mapState } from 'vuex'
export default {
  data() {
    return {
@@ -70,8 +72,19 @@
      ]
    }
  },
  computed: {
    ...mapState({
      canCreateTask: state => state.user.canCreateTask
    })
  },
  methods: {
    selectTaskCategory(category) {
      // æ£€æŸ¥æ˜¯å¦æœ‰åˆ›å»ºä»»åŠ¡çš„æƒé™
      if (this.canCreateTask !== '1') {
        this.$modal.msgError('您没有权限创建任务')
        return
      }
      // è·³è½¬åˆ°å¯¹åº”的任务创建页面
      uni.navigateTo({
        url: `${category.page}?categoryName=${category.name}&categoryType=${category.type}&taskType=${category.taskType}`
app/pagesTask/components/HospitalSelector.vue
@@ -185,8 +185,9 @@
        this.searchResults = response.data || []
        this.showResults = true
      }).catch(error => {
        console.error('搜索医院失败:', error)
        // console.error('搜索医院失败:', error)
        this.searchResults = []
        // this.showResults = false
      })
    },
    
@@ -215,9 +216,9 @@
        this.searchResults = this.defaultHospitals
        this.showResults = true
        this.hasLoadedDefault = true
        console.log('加载默认医院列表,数量:', this.defaultHospitals.length)
        // console.log('加载默认医院列表,数量:', this.defaultHospitals.length)
      }).catch(error => {
        console.error('加载默认医院列表失败:', error)
        // console.error('加载默认医院列表失败:', error)
        this.defaultHospitals = []
      })
    },
@@ -303,7 +304,7 @@
          this.showAddressSuggestions = false
        }
      }).catch(error => {
        console.error('搜索地址失败:', error)
        // console.error('搜索地址失败:', error)
        this.addressSuggestions = []
        this.showAddressSuggestions = false
      })
app/store/modules/user.js
@@ -19,7 +19,8 @@
    deptId: storage.get(constant.deptId),
    branchCompanyId: storage.get(constant.branchCompanyId),
    branchCompanyName: storage.get(constant.branchCompanyName),
    oaUserId: storage.get(constant.oaUserId)
    oaUserId: storage.get(constant.oaUserId),
    canCreateTask: storage.get(constant.canCreateTask)
  },
  mutations: {
@@ -65,6 +66,10 @@
    SET_OA_USER_ID: (state, oaUserId) => {
      state.oaUserId = oaUserId
      storage.set(constant.oaUserId, oaUserId)
    },
    SET_CAN_CREATE_TASK: (state, canCreateTask) => {
      state.canCreateTask = canCreateTask
      storage.set(constant.canCreateTask, canCreateTask)
    }
  },
@@ -124,6 +129,7 @@
          commit('SET_BRANCH_COMPANY_ID', res.branchCompanyId)
          commit('SET_BRANCH_COMPANY_NAME', res.branchCompanyName)
          commit('SET_OA_USER_ID', res.oaUserId)
          commit('SET_CAN_CREATE_TASK', res.canCreateTask)
          resolve(res)
        }).catch(error => {
          reject(error)
app/utils/constant.js
@@ -8,7 +8,8 @@
   deptId: 'vuex_deptId',
   branchCompanyId: 'vuex_branchCompanyId',
   branchCompanyName: 'vuex_branchCompanyName',
   oaUserId: 'vuex_oaUserId'
   oaUserId: 'vuex_oaUserId',
   canCreateTask: 'vuex_canCreateTask'
 }
 export default constant
app/utils/request.js
@@ -54,9 +54,9 @@
          reject('无效的会话,或者会话已过期,请重新登录。')
        } else if (code === 500) {
          // checkDuplicate æŽ¥å£ä¸æ˜¾ç¤º toast,让业务代码自己处理
          if (!isCheckDuplicateApi) {
            toast(msg)
          }
          // if (!isCheckDuplicateApi) {
          //   toast(msg)
          // }
          // checkDuplicate æŽ¥å£è¿”回完整响应,不 reject
          if (isCheckDuplicateApi) {
            resolve(res.data)
@@ -65,9 +65,9 @@
          reject('500')
        } else if (code !== 200) {
          // checkDuplicate æŽ¥å£ä¸æ˜¾ç¤º toast,让业务代码自己处理
          if (!isCheckDuplicateApi) {
            toast(msg)
          }
          // if (!isCheckDuplicateApi) {
          //   toast(msg)
          // }
          // checkDuplicate æŽ¥å£è¿”回完整响应,不 reject
          if (isCheckDuplicateApi) {
            resolve(res.data)
@@ -86,7 +86,7 @@
        } else if (message.includes('Request failed with status code')) {
          message = '系统接口' + message.substr(message.length - 3) + '异常'
        }
        toast(message)
        // toast(message)
        reject(error)
      })
  })
prd/Óû§´´½¨ÈÎÎñµ¥È¨ÏÞ¹¦ÄÜ˵Ã÷.md
New file
@@ -0,0 +1,301 @@
# ç”¨æˆ·åˆ›å»ºä»»åŠ¡å•æƒé™åŠŸèƒ½è¯´æ˜Ž
## ðŸ“‹ åŠŸèƒ½æ¦‚è¿°
在 sys_user è¡¨ä¸­æ–°å¢ž `can_create_task` å­—段,用于标识用户是否具有创建任务单的权限。该字段从 OA ç³»ç»Ÿçš„ `OA_User` è¡¨ä¸­çš„ `OA_Power` å­—段同步而来。
## ðŸŽ¯ åŒæ­¥è§„则
当 `OA_Power` å­—段包含 `,020101,` æ—¶ï¼Œç”¨æˆ·å…·æœ‰åˆ›å»ºä»»åŠ¡å•æƒé™ã€‚
**SQL åˆ¤æ–­é€»è¾‘**:
```sql
CASE
    WHEN OA_Power LIKE '%,020101,%' THEN '1'
    ELSE '0'
END AS can_create_task
```
## ðŸ”¨ å®žçް内容
### 1. æ•°æ®åº“修改
#### æ·»åŠ å­—æ®µ
**文件**: `sql/add_can_create_task_to_sys_user.sql`
```sql
ALTER TABLE sys_user ADD COLUMN can_create_task CHAR(1) DEFAULT '0' COMMENT '是否可创建任务单(0否 1是)';
CREATE INDEX idx_can_create_task ON sys_user(can_create_task);
```
**字段说明**:
- **字段名**: `can_create_task`
- **类型**: `CHAR(1)`
- **默认值**: `'0'` (不可创建)
- **可选值**:
  - `'0'`: ä¸å¯åˆ›å»ºä»»åŠ¡å•
  - `'1'`: å¯ä»¥åˆ›å»ºä»»åŠ¡å•
- **索引**: å·²æ·»åŠ ç´¢å¼• `idx_can_create_task` æ–¹ä¾¿æŸ¥è¯¢
### 2. å®žä½“类修改
#### SysUser å®žä½“
**文件**: `ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java`
**新增字段**:
```java
/** æ˜¯å¦å¯åˆ›å»ºä»»åŠ¡å•ï¼ˆ0否 1是) */
@Excel(name = "可创建任务单", readConverterExp = "0=否,1=是")
private String canCreateTask;
public String getCanCreateTask() {
    return canCreateTask;
}
public void setCanCreateTask(String canCreateTask) {
    this.canCreateTask = canCreateTask;
}
```
### 3. DTO å±‚修改
#### UserSyncDTO
**文件**: `ruoyi-system/src/main/java/com/ruoyi/system/domain/UserSyncDTO.java`
**新增字段**:
```java
/** æ˜¯å¦å¯åˆ›å»ºä»»åŠ¡å•ï¼ˆ0否 1是) */
private String canCreateTask;
public String getCanCreateTask() {
    return canCreateTask;
}
public void setCanCreateTask(String canCreateTask) {
    this.canCreateTask = canCreateTask;
}
```
### 4. Mapper å±‚修改
#### UserSyncMapper.xml
**文件**: `ruoyi-system/src/main/resources/mapper/system/UserSyncMapper.xml`
**修改内容**:
1. åœ¨ resultMap ä¸­æ·»åŠ å­—æ®µæ˜ å°„
2. åœ¨ SQL æŸ¥è¯¢ä¸­æ·»åŠ  CASE åˆ¤æ–­é€»è¾‘
```xml
<result property="canCreateTask" column="can_create_task" />
<!-- SQL æŸ¥è¯¢æ·»åŠ  -->
CASE
    WHEN OA_Power LIKE '%,020101,%' THEN '1'
    ELSE '0'
END AS can_create_task
```
#### SysUserMapper.xml
**文件**: `ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml`
**修改内容**:
1. resultMap æ·»åŠ å­—æ®µæ˜ å°„
2. selectUserVo æŸ¥è¯¢æ·»åŠ å­—æ®µ
3. insertUser æ·»åŠ å­—æ®µæ’å…¥
4. updateUser æ·»åŠ å­—æ®µæ›´æ–°
### 5. Service å±‚修改
#### UserSyncServiceImpl
**文件**: `ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserSyncServiceImpl.java`
**修改内容**:
**更新已存在用户**:
```java
if (StringUtils.isNotEmpty(dto.getCanCreateTask())) {
    existingUser.setCanCreateTask(dto.getCanCreateTask());
}
```
**创建新用户**:
```java
if (StringUtils.isNotEmpty(dto.getCanCreateTask())) {
    newUser.setCanCreateTask(dto.getCanCreateTask());
}
```
## ðŸ“Š æ•°æ®æµè½¬è¿‡ç¨‹
```
OA_User (SQL Server)
    â†“
OA_Power LIKE '%,020101,%'
    â†“
SQL CASE åˆ¤æ–­
    â†“
UserSyncDTO.canCreateTask
    â†“
SysUser.canCreateTask
    â†“
sys_user.can_create_task
```
## ðŸš€ ä½¿ç”¨æ­¥éª¤
### 1. æ‰§è¡Œæ•°æ®åº“脚本
```bash
mysql -u root -p ry-vue < sql/add_can_create_task_to_sys_user.sql
```
### 2. åŒæ­¥ç”¨æˆ·æ•°æ®
通过后台管理界面或 API æ‰§è¡Œç”¨æˆ·åŒæ­¥ï¼š
```bash
POST http://localhost:8080/system/dept/sync/user
Headers:
  Authorization: Bearer {你的token}
```
### 3. éªŒè¯åŒæ­¥ç»“æžœ
```sql
-- æŸ¥çœ‹å…·æœ‰åˆ›å»ºä»»åŠ¡å•æƒé™çš„ç”¨æˆ·
SELECT
    user_name,
    nick_name,
    can_create_task,
    oa_user_id
FROM sys_user
WHERE can_create_task = '1'
  AND del_flag = '0'
ORDER BY create_time DESC;
-- ç»Ÿè®¡æƒé™åˆ†å¸ƒ
SELECT
    can_create_task,
    CASE can_create_task
        WHEN '1' THEN '可创建'
        ELSE '不可创建'
    END AS permission_label,
    COUNT(*) AS user_count
FROM sys_user
WHERE del_flag = '0'
GROUP BY can_create_task;
```
## ðŸ’¡ ä½¿ç”¨ç¤ºä¾‹
### åœºæ™¯1:查询有权限的用户
```java
// åœ¨ Service å±‚添加查询方法
public List<SysUser> getUsersWithCreateTaskPermission() {
    SysUser query = new SysUser();
    query.setCanCreateTask("1");
    return userMapper.selectUserList(query);
}
```
### åœºæ™¯2:前端权限控制
```javascript
// æ ¹æ®ç”¨æˆ·æƒé™æ˜¾ç¤º/隐藏创建任务按钮
if (user.canCreateTask === '1') {
    // æ˜¾ç¤ºåˆ›å»ºä»»åŠ¡æŒ‰é’®
    showCreateTaskButton();
} else {
    // éšè—åˆ›å»ºä»»åŠ¡æŒ‰é’®
    hideCreateTaskButton();
}
```
### åœºæ™¯3:后端接口权限验证
```java
@PreAuthorize("@ss.hasPermi('task:create')")
@PostMapping("/create")
public AjaxResult createTask(@RequestBody Task task) {
    SysUser user = SecurityUtils.getLoginUser().getUser();
    if (!"1".equals(user.getCanCreateTask())) {
        return AjaxResult.error("您没有创建任务单的权限");
    }
    // åˆ›å»ºä»»åŠ¡é€»è¾‘
    return taskService.createTask(task);
}
```
## ðŸ“ æ³¨æ„äº‹é¡¹
### é‡è¦æç¤º
1. **权限同步**: è¯¥å­—段从 OA ç³»ç»Ÿè‡ªåŠ¨åŒæ­¥ï¼Œè¯·å‹¿æ‰‹åŠ¨ä¿®æ”¹
2. **默认值**: æ–°åˆ›å»ºç”¨æˆ·é»˜è®¤ä¸º `'0'`(不可创建)
3. **同步更新**: æ¯æ¬¡ç”¨æˆ·åŒæ­¥æ—¶ä¼šè‡ªåŠ¨æ›´æ–°è¯¥å­—æ®µ
4. **权限粒度**: è¯¥å­—段仅控制是否可创建任务单,不影响其他权限
### ä¸Žå…¶ä»–权限字段的关系
- `can_view_all_consult`: æ˜¯å¦å¯æŸ¥çœ‹æ‰€æœ‰å’¨è¯¢å•(OA_Power LIKE '%,020112,%')
- `can_create_task`: æ˜¯å¦å¯åˆ›å»ºä»»åŠ¡å•ï¼ˆOA_Power LIKE '%,020101,%')
这两个字段互相独立,用户可以同时拥有或分别拥有这些权限。
## ðŸ” éªŒè¯æ–¹æ³•
### æŸ¥çœ‹åŽå°ç”¨æˆ·ç®¡ç†ç•Œé¢
1. ç™»å½•后台管理系统
2. è¿›å…¥ **系统管理** â†’ **用户管理**
3. åœ¨ç”¨æˆ·åˆ—表中查看 "可创建任务单" åˆ—
4. ç¼–辑用户时可看到该字段(只读,由同步控制)
### æ•°æ®åº“验证
```sql
-- æ£€æŸ¥å­—段是否存在
SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'sys_user'
  AND COLUMN_NAME = 'can_create_task';
-- æŸ¥çœ‹å…·ä½“用户的权限
SELECT
    u.user_name,
    u.nick_name,
    u.can_view_all_consult,
    u.can_create_task,
    u.oa_user_id
FROM sys_user u
WHERE u.oa_user_id IS NOT NULL
ORDER BY u.user_id;
```
## ðŸ“š ç›¸å…³æ–‡ä»¶æ¸…单
### SQL è„šæœ¬
- `sql/add_can_create_task_to_sys_user.sql` - æ·»åŠ å­—æ®µè„šæœ¬
### Java æ–‡ä»¶
- `SysUser.java`(修改) - æ·»åŠ å­—æ®µå®šä¹‰
- `UserSyncDTO.java`(修改) - æ·»åŠ  DTO å­—段
- `UserSyncServiceImpl.java`(修改) - æ·»åŠ åŒæ­¥é€»è¾‘
### Mapper æ–‡ä»¶
- `UserSyncMapper.xml`(修改) - æ·»åŠ  SQL æŸ¥è¯¢é€»è¾‘
- `SysUserMapper.xml`(修改) - æ·»åŠ å­—æ®µæ˜ å°„å’Œæ“ä½œ
### æ–‡æ¡£
- `prd/用户创建任务单权限功能说明.md` - æœ¬æ–‡æ¡£
## ðŸŽ¯ å¸¸è§é—®é¢˜
### Q1: å­—段值为什么是字符串而不是布尔值?
**A**: ä¸ºäº†ä¸Žè‹¥ä¾æ¡†æž¶çš„æ•°æ®å­—典规范保持一致,使用 `'0'` å’Œ `'1'` å­—符串表示布尔值。
### Q2: å¦‚何手动设置用户权限?
**A**: ä¸å»ºè®®æ‰‹åŠ¨ä¿®æ”¹ã€‚è¯¥å­—æ®µåº”é€šè¿‡ OA ç³»ç»Ÿçš„ `OA_Power` å­—段控制,通过用户同步自动更新。
### Q3: ç”¨æˆ·åŒæ­¥åŽæƒé™æ²¡æœ‰å˜åŒ–?
**A**: è¯·æ£€æŸ¥ï¼š
1. OA ç³»ç»Ÿä¸­ç”¨æˆ·çš„ `OA_Power` å­—段是否包含 `,020101,`
2. ç”¨æˆ·åŒæ­¥æ˜¯å¦æ‰§è¡ŒæˆåŠŸ
3. æŸ¥çœ‹åŒæ­¥æ—¥å¿—确认更新情况
### Q4: å¦‚何批量设置用户权限?
**A**: åœ¨ OA ç³»ç»Ÿä¸­æ‰¹é‡ä¿®æ”¹ç”¨æˆ·çš„ `OA_Power` å­—段,然后执行用户同步即可。
---
**版本**: v1.0
**更新时间**: 2025-12-29
**相关功能**: ç”¨æˆ·åŒæ­¥ã€æƒé™ç®¡ç†
prd/Óû§´´½¨ÈÎÎñȨÏÞǰ¶Ë¿ØÖƹ¦ÄÜ˵Ã÷.md
New file
@@ -0,0 +1,404 @@
# ç”¨æˆ·åˆ›å»ºä»»åŠ¡æƒé™å‰ç«¯æŽ§åˆ¶åŠŸèƒ½è¯´æ˜Ž
## åŠŸèƒ½æ¦‚è¿°
在用户登录后,系统会返回 `canCreateTask` å­—段到 `getInfo` æŽ¥å£å“åº”中,并存储到 APP çš„ Vuex store。当用户在任务创建页面点击任务类型时,会检查该权限字段,如果值为 `'0'`(无权限),则弹出提示"您没有权限创建任务",阻止用户进入任务创建表单页面。
## å®žçް内容
### 1. åŽç«¯æŽ¥å£ä¿®æ”¹
#### 1.1 getInfo æŽ¥å£è¿”回 canCreateTask
**文件**:`ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java`
**修改位置**:第 236-245 è¡Œ
**修改内容**:
```java
AjaxResult ajax = AjaxResult.success();
ajax.put("user", user);
ajax.put("roles", roles);
ajax.put("permissions", permissions);
ajax.put("branchCompanyId", branchCompanyId);
ajax.put("branchCompanyName", branchCompanyName);
ajax.put("branchCompanies", branchCompanies);
ajax.put("oaUserId", user.getOaUserId());
ajax.put("canCreateTask", user.getCanCreateTask());  // âœ… æ–°å¢ž
return ajax;
```
**说明**:在 getInfo æŽ¥å£è¿”回的 JSON ä¸­æ·»åŠ  `canCreateTask` å­—段,其值来自 `SysUser.canCreateTask`。
---
### 2. APP ç«¯å­˜å‚¨å±‚修改
#### 2.1 æ·»åŠ  constant å¸¸é‡
**文件**:`app/utils/constant.js`
**修改内容**:
```javascript
const constant = {
   userId: 'vuex_userId',
   avatar: 'vuex_avatar',
   name: 'vuex_name',
   nickName: 'vuex_nickName',
   roles: 'vuex_roles',
   permissions: 'vuex_permissions',
   deptId: 'vuex_deptId',
   branchCompanyId: 'vuex_branchCompanyId',
   branchCompanyName: 'vuex_branchCompanyName',
   oaUserId: 'vuex_oaUserId',
   canCreateTask: 'vuex_canCreateTask'  // âœ… æ–°å¢ž
}
```
**说明**:添加 `canCreateTask` çš„存储键名常量,用于在本地存储中保存该字段。
---
#### 2.2 Vuex State æ·»åŠ å­—æ®µ
**文件**:`app/store/modules/user.js`
**修改位置 1**:第 11-23 è¡Œ
```javascript
state: {
  token: getToken(),
  userId: storage.get(constant.userId),
  name: storage.get(constant.name),
  nickName: storage.get(constant.nickName),
  avatar: storage.get(constant.avatar),
  roles: storage.get(constant.roles),
  permissions: storage.get(constant.permissions),
  deptId: storage.get(constant.deptId),
  branchCompanyId: storage.get(constant.branchCompanyId),
  branchCompanyName: storage.get(constant.branchCompanyName),
  oaUserId: storage.get(constant.oaUserId),
  canCreateTask: storage.get(constant.canCreateTask)  // âœ… æ–°å¢ž
},
```
**说明**:在 Vuex state ä¸­æ·»åŠ  `canCreateTask` å­—段,从本地存储中读取初始值。
---
**修改位置 2**:第 65-72 è¡Œï¼ˆmutations)
```javascript
SET_OA_USER_ID: (state, oaUserId) => {
  state.oaUserId = oaUserId
  storage.set(constant.oaUserId, oaUserId)
},
SET_CAN_CREATE_TASK: (state, canCreateTask) => {  // âœ… æ–°å¢ž
  state.canCreateTask = canCreateTask
  storage.set(constant.canCreateTask, canCreateTask)
}
```
**说明**:添加 `SET_CAN_CREATE_TASK` mutation,用于更新 state å’Œæœ¬åœ°å­˜å‚¨ã€‚
---
**修改位置 3**:第 119-132 è¡Œï¼ˆGetInfo Action)
```javascript
commit('SET_USER_ID', userId)
commit('SET_NAME', username)
commit('SET_NICK_NAME', nickname)
commit('SET_AVATAR', avatar)
commit('SET_DEPT_ID', deptId)
commit('SET_BRANCH_COMPANY_ID', res.branchCompanyId)
commit('SET_BRANCH_COMPANY_NAME', res.branchCompanyName)
commit('SET_OA_USER_ID', res.oaUserId)
commit('SET_CAN_CREATE_TASK', res.canCreateTask)  // âœ… æ–°å¢ž
resolve(res)
```
**说明**:在 GetInfo action ä¸­æäº¤ `SET_CAN_CREATE_TASK` mutation,将接口返回的 `canCreateTask` å­˜å‚¨åˆ° Vuex。
---
### 3. APP ç«¯æƒé™æŽ§åˆ¶
#### 3.1 ä»»åŠ¡åˆ›å»ºé¡µé¢æƒé™æ£€æŸ¥
**文件**:`app/pages/task/create.vue`
**修改位置 1**:第 28-30 è¡Œï¼ˆå¼•å…¥ mapState)
```javascript
<script>
import { mapState } from 'vuex'  // âœ… æ–°å¢ž
export default {
```
**修改位置 2**:第 72-78 è¡Œï¼ˆæ·»åŠ  computed)
```javascript
computed: {
  ...mapState({
    canCreateTask: state => state.user.canCreateTask
  })
},
```
**说明**:使用 Vuex çš„ mapState è¾…助函数,将 `canCreateTask` æ˜ å°„到组件的计算属性。
---
**修改位置 3**:第 81-90 è¡Œï¼ˆæƒé™æ£€æŸ¥é€»è¾‘)
```javascript
methods: {
  selectTaskCategory(category) {
    // æ£€æŸ¥æ˜¯å¦æœ‰åˆ›å»ºä»»åŠ¡çš„æƒé™
    if (this.canCreateTask !== '1') {
      this.$modal.msgError('您没有权限创建任务')
      return
    }
    // è·³è½¬åˆ°å¯¹åº”的任务创建页面
    uni.navigateTo({
      url: `${category.page}?categoryName=${category.name}&categoryType=${category.type}&taskType=${category.taskType}`
    })
  }
}
```
**核心逻辑**:
1. åœ¨ç‚¹å‡»ä»»åŠ¡ç±»åž‹æ—¶ï¼Œé¦–å…ˆæ£€æŸ¥ `this.canCreateTask` çš„值
2. å¦‚果值不等于 `'1'`,则弹出错误提示"您没有权限创建任务",并 return é˜»æ­¢åŽç»­æ‰§è¡Œ
3. åªæœ‰å½“ `canCreateTask === '1'` æ—¶ï¼Œæ‰å…è®¸è·³è½¬åˆ°å…·ä½“的任务创建页面
---
## æ•°æ®æµå‘
```
SQL Server (OA_User.OA_Power)
      â†“
[用户同步] CASE WHEN OA_Power LIKE '%,020101,%' THEN '1' ELSE '0' END
      â†“
MySQL (sys_user.can_create_task)
      â†“
[登录后] getInfo æŽ¥å£
      â†“
{
  "user": {...},
  "canCreateTask": "1" or "0"
}
      â†“
[APP] Vuex Store (state.user.canCreateTask)
      â†“
[本地存储] vuex_canCreateTask
      â†“
[页面权限控制] create.vue æ£€æŸ¥æƒé™
      â†“
允许创建 or æç¤º"您没有权限创建任务"
```
---
## ä½¿ç”¨åœºæ™¯
### åœºæ™¯ 1:有权限的用户
1. ç”¨æˆ·ç™»å½•后,`canCreateTask` ä¸º `'1'`
2. ç‚¹å‡»åº•部 TabBar "创建任务" è¿›å…¥ä»»åŠ¡ç±»åž‹é€‰æ‹©é¡µé¢
3. ç‚¹å‡»ä»»ä½•一种任务类型(转运任务、维修保养、加油等)
4. **✅ æ­£å¸¸è·³è½¬**到对应的任务创建表单页面
### åœºæ™¯ 2:无权限的用户
1. ç”¨æˆ·ç™»å½•后,`canCreateTask` ä¸º `'0'` æˆ– `null`
2. ç‚¹å‡»åº•部 TabBar "创建任务" è¿›å…¥ä»»åŠ¡ç±»åž‹é€‰æ‹©é¡µé¢
3. ç‚¹å‡»ä»»ä½•一种任务类型
4. **❌ å¼¹å‡ºæç¤º**:"您没有权限创建任务"
5. **❌ é˜»æ­¢è·³è½¬**,停留在任务类型选择页面
---
## éªŒè¯æ–¹æ³•
### 1. åŽç«¯éªŒè¯
**查询用户权限**:
```sql
SELECT user_name, nick_name, can_create_task, oa_user_id
FROM sys_user
WHERE del_flag = '0'
ORDER BY can_create_task DESC;
```
**测试 getInfo æŽ¥å£**:
```bash
# ç™»å½•获取 token
POST http://localhost:8080/login
{
  "username": "test_user",
  "password": "password"
}
# èŽ·å–ç”¨æˆ·ä¿¡æ¯
GET http://localhost:8080/getInfo
Authorization: Bearer {token}
# å“åº”示例
{
  "code": 200,
  "msg": "操作成功",
  "user": {...},
  "canCreateTask": "1"  // âœ… ç¡®è®¤åŒ…含此字段
}
```
---
### 2. APP ç«¯éªŒè¯
**步骤 1**:登录 APP
**步骤 2**:打开开发者工具,查看 Vuex State
```javascript
// åœ¨æŽ§åˆ¶å°è¾“å…¥
console.log(this.$store.state.user.canCreateTask)
// è¾“出应为 "1" æˆ– "0"
```
**步骤 3**:点击底部 TabBar "创建任务"
**步骤 4**:在任务类型选择页面,点击任意任务类型
**预期结果**:
- æœ‰æƒé™ç”¨æˆ·ï¼ˆ`canCreateTask === '1'`):正常跳转到创建表单
- æ— æƒé™ç”¨æˆ·ï¼ˆ`canCreateTask !== '1'`):弹出提示"您没有权限创建任务"
---
## å¸¸è§é—®é¢˜
### Q1:新用户登录后 canCreateTask ä¸º null,如何处理?
**A**:需要先执行用户同步接口,从 OA ç³»ç»ŸåŒæ­¥æƒé™å­—段:
```bash
POST http://localhost:8080/system/dept/sync/user
```
同步后,`can_create_task` å­—段会根据 OA_Power è‡ªåŠ¨å¡«å……ã€‚
---
### Q2:如何给用户开通创建任务权限?
**A**:有两种方式:
**方式 1(推荐)**:在 OA ç³»ç»Ÿä¸­ä¿®æ”¹ç”¨æˆ·çš„ OA_Power å­—段
- ç¡®ä¿ OA_Power åŒ…含 `,020101,`(注意前后都有逗号)
- æ‰§è¡Œç”¨æˆ·åŒæ­¥æŽ¥å£
**方式 2**:直接修改数据库
```sql
UPDATE sys_user
SET can_create_task = '1'
WHERE user_name = '用户名';
```
**注意**:方式 2 çš„修改会在下次用户同步时被覆盖,建议使用方式 1。
---
### Q3:用户权限更新后,需要重新登录吗?
**A**:是的,有两种方式生效:
**方式 1**:用户退出登录,重新登录
- ç™»å½•时会调用 getInfo,获取最新的 `canCreateTask`
**方式 2**:前端主动刷新用户信息
```javascript
this.$store.dispatch('GetInfo')
```
---
### Q4:如何在前端其他页面判断用户是否有创建权限?
**A**:参考 `create.vue` çš„实现方式:
```javascript
import { mapState } from 'vuex'
export default {
  computed: {
    ...mapState({
      canCreateTask: state => state.user.canCreateTask
    })
  },
  methods: {
    checkPermission() {
      if (this.canCreateTask !== '1') {
        this.$modal.msgError('您没有权限创建任务')
        return false
      }
      return true
    }
  }
}
```
---
## æ³¨æ„äº‹é¡¹
1. **字段类型**:`canCreateTask` æ˜¯å­—符串类型 `CHAR(1)`,值为 `'0'` æˆ– `'1'`,比较时必须使用字符串
   - âœ… æ­£ç¡®ï¼š`canCreateTask !== '1'`
   - âŒ é”™è¯¯ï¼š`canCreateTask !== 1` æˆ– `!canCreateTask`
2. **null å€¼å¤„理**:未同步的用户 `canCreateTask` å¯èƒ½ä¸º `null`
   - åœ¨æƒé™åˆ¤æ–­æ—¶ï¼Œ`null !== '1'` è¿”回 `true`,会阻止操作,符合安全原则
3. **数据同步**:确保定期执行用户同步,保持权限字段与 OA ç³»ç»Ÿä¸€è‡´
4. **前端存储**:`canCreateTask` ä¼šåŒæ—¶å­˜å‚¨åœ¨ï¼š
   - Vuex State(内存)
   - LocalStorage(持久化)
   - é¡µé¢åˆ·æ–°æˆ–重启 APP æ—¶ä¼šä»Ž LocalStorage æ¢å¤
5. **安全性**:
   - å‰ç«¯æƒé™æ£€æŸ¥åªæ˜¯ç”¨æˆ·ä½“验优化,不影响后端安全
   - åŽç«¯ä»éœ€åœ¨åˆ›å»ºä»»åŠ¡çš„æŽ¥å£ä¸­è¿›è¡Œæƒé™éªŒè¯
---
## æŠ€æœ¯æ€»ç»“
本次修改实现了完整的**前端权限控制流程**:
1. âœ… **后端接口**:getInfo è¿”回 canCreateTask
2. âœ… **数据存储**:Vuex + LocalStorage åŒé‡å­˜å‚¨
3. âœ… **权限检查**:页面级权限拦截
4. âœ… **用户提示**:友好的错误提示信息
5. âœ… **数据同步**:与 OA ç³»ç»Ÿæƒé™ä¿æŒä¸€è‡´
**优点**:
- ç»Ÿä¸€çš„æƒé™æ•°æ®æºï¼ˆOA_Power)
- è‡ªåŠ¨åŒæ­¥æœºåˆ¶
- å‰ç«¯å¿«é€Ÿå“åº”,避免无效请求
- è‰¯å¥½çš„用户体验
**局限性**:
- å‰ç«¯æƒé™å¯è¢«ç»•过(开发者工具修改)
- éœ€è¦ä¾èµ–后端接口的二次验证
---
## ç›¸å…³æ–‡æ¡£
- [用户创建任务单权限功能说明](./用户创建任务单权限功能说明.md)
- [用户同步功能说明](./用户同步功能说明.md)
- [Vuex ç”¨æˆ·çŠ¶æ€å®Œæ•´æ€§ä¼˜åŒ–](./Vuex用户状态完整性优化.md)
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
@@ -241,6 +241,7 @@
        ajax.put("branchCompanyName", branchCompanyName);
        ajax.put("branchCompanies", branchCompanies);
        ajax.put("oaUserId", user.getOaUserId());
        ajax.put("canCreateTask", user.getCanCreateTask());
        return ajax;
    }
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java
@@ -1349,7 +1349,9 @@
            }
            JSONArray results = jsonResponse.getJSONArray("pois");
            if(results==null|| results.isEmpty()){
                return AjaxResult.error("未找到匹配的地址");
            }
            
            // æž„建返回结果
            List<Map<String, Object>> suggestions = new ArrayList<>();
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
@@ -113,6 +113,10 @@
    /** æ˜¯å¦å¯æŸ¥çœ‹æ‰€æœ‰å’¨è¯¢å•(0否 1是) */
    @Excel(name = "可查看所有咨询单", readConverterExp = "0=否,1=是")
    private String canViewAllConsult;
    /** æ˜¯å¦å¯åˆ›å»ºä»»åŠ¡å•ï¼ˆ0否 1是) */
    @Excel(name = "可创建任务单", readConverterExp = "0=否,1=是")
    private String canCreateTask;
@@ -403,6 +407,16 @@
    {
        this.canViewAllConsult = canViewAllConsult;
    }
    public String getCanCreateTask()
    {
        return canCreateTask;
    }
    public void setCanCreateTask(String canCreateTask)
    {
        this.canCreateTask = canCreateTask;
    }
    @Override
@@ -435,7 +449,7 @@
            .append("qyWechatUserId", getQyWechatUserId())
            .append("qyWechatUpdateTime", getQyWechatUpdateTime())
            .append("canViewAllConsult", getCanViewAllConsult())
            .append("canCreateTask", getCanCreateTask())
            .toString();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/UserSyncDTO.java
@@ -28,6 +28,9 @@
    
    /** æ˜¯å¦å¯æŸ¥çœ‹æ‰€æœ‰å’¨è¯¢å•(0否 1是) */
    private String canViewAllConsult;
    /** æ˜¯å¦å¯åˆ›å»ºä»»åŠ¡å•ï¼ˆ0否 1是) */
    private String canCreateTask;
    /** ç”¨æˆ·æ€§åˆ«ï¼ˆ0=男,1=女,2=未知) */
    private String sex;
@@ -137,6 +140,16 @@
    {
        this.canViewAllConsult = canViewAllConsult;
    }
    public String getCanCreateTask()
    {
        return canCreateTask;
    }
    public void setCanCreateTask(String canCreateTask)
    {
        this.canCreateTask = canCreateTask;
    }
    @Override
    public String toString()
@@ -152,6 +165,7 @@
                ", phonenumber='" + phonenumber + '\'' +
                ", oaOrderClass='" + oaOrderClass + '\'' +
                ", canViewAllConsult='" + canViewAllConsult + '\'' +
                ", canCreateTask='" + canCreateTask + '\'' +
                '}';
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/UserSyncServiceImpl.java
@@ -227,6 +227,10 @@
        {
            existingUser.setCanViewAllConsult(dto.getCanViewAllConsult());
        }
        if (StringUtils.isNotEmpty(dto.getCanCreateTask()))
        {
            existingUser.setCanCreateTask(dto.getCanCreateTask());
        }
        // åŒæ­¥ä¼ä¸šå¾®ä¿¡ç”¨æˆ·ID
        if (StringUtils.isNotEmpty(dto.getOaWeixinUserId()))
        {
@@ -253,6 +257,10 @@
        {
            newUser.setCanViewAllConsult(dto.getCanViewAllConsult());
        }
        if (StringUtils.isNotEmpty(dto.getCanCreateTask()))
        {
            newUser.setCanCreateTask(dto.getCanCreateTask());
        }
        
        // è®¾ç½®ä¼ä¸šå¾®ä¿¡ç”¨æˆ·ID
        if (StringUtils.isNotEmpty(dto.getOaWeixinUserId()))
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -31,6 +31,7 @@
        <result property="qyWechatUserId" column="qy_wechat_user_id" />
        <result property="qyWechatUpdateTime" column="qy_wechat_update_time" />
        <result property="canViewAllConsult" column="can_view_all_consult" />
        <result property="canCreateTask" column="can_create_task" />
        <association property="dept"    javaType="SysDept"         resultMap="deptResult" />
        <collection  property="roles"   javaType="java.util.List"  resultMap="RoleResult" />
    </resultMap>
@@ -55,7 +56,7 @@
    </resultMap>
    
    <sql id="selectUserVo">
        select u.user_id, u.dept_id, u.user_name,u.oa_user_id, u.oa_order_class, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.oa_user_id, u.create_by, u.create_time, u.remark,u.open_id,u.union_id,u.wechat_nickname,u.qy_wechat_user_id,u.qy_wechat_update_time,u.can_view_all_consult,
        select u.user_id, u.dept_id, u.user_name,u.oa_user_id, u.oa_order_class, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.oa_user_id, u.create_by, u.create_time, u.remark,u.open_id,u.union_id,u.wechat_nickname,u.qy_wechat_user_id,u.qy_wechat_update_time,u.can_view_all_consult,u.can_create_task,
        d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
        r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
        from sys_user u
@@ -173,6 +174,7 @@
             <if test="oaUserId != null">oa_user_id,</if>
             <if test="oaOrderClass != null and oaOrderClass != ''">oa_order_class,</if>
            <if test="canViewAllConsult != null and canViewAllConsult != ''">can_view_all_consult,</if>
            <if test="canCreateTask != null and canCreateTask != ''">can_create_task,</if>
             <if test="qyWechatUserId != null and qyWechatUserId != ''">qy_wechat_user_id,</if>
             <if test="qyWechatUpdateTime != null">qy_wechat_update_time,</if>
             <if test="createBy != null and createBy != ''">create_by,</if>
@@ -192,6 +194,7 @@
             <if test="oaUserId != null">#{oaUserId},</if>
             <if test="oaOrderClass != null and oaOrderClass != ''">#{oaOrderClass},</if>
             <if test="canViewAllConsult != null and canViewAllConsult != ''">#{canViewAllConsult},</if>
             <if test="canCreateTask != null and canCreateTask != ''">#{canCreateTask},</if>
             <if test="qyWechatUserId != null and qyWechatUserId != ''">#{qyWechatUserId},</if>
             <if test="qyWechatUpdateTime != null">#{qyWechatUpdateTime},</if>
             <if test="createBy != null and createBy != ''">#{createBy},</if>
@@ -214,6 +217,7 @@
             <if test="oaUserId != null">oa_user_id = #{oaUserId},</if>
             <if test="oaOrderClass != null">oa_order_class = #{oaOrderClass},</if>
             <if test="canViewAllConsult != null and canViewAllConsult != ''">can_view_all_consult = #{canViewAllConsult},</if>
             <if test="canCreateTask != null and canCreateTask != ''">can_create_task = #{canCreateTask},</if>
             <if test="qyWechatUserId != null and qyWechatUserId != ''">qy_wechat_user_id = #{qyWechatUserId},</if>
             <if test="qyWechatUpdateTime != null">qy_wechat_update_time = #{qyWechatUpdateTime},</if>
             <if test="openId != null and openId != ''">open_id = #{openId},</if>
ruoyi-system/src/main/resources/mapper/system/UserSyncMapper.xml
@@ -15,6 +15,7 @@
        <result property="phonenumber" column="phonenumber" />
        <result property="oaOrderClass" column="OA_OrderClass" />
        <result property="canViewAllConsult" column="can_view_all_consult" />
        <result property="canCreateTask" column="can_create_task" />
    </resultMap>
    <!-- æŸ¥è¯¢SQL Server中的OA用户列表 -->
@@ -33,7 +34,11 @@
            CASE 
                WHEN OA_Power LIKE '%,020112,%' THEN '1'
                ELSE '0'
            END AS can_view_all_consult
            END AS can_view_all_consult,
            CASE
                WHEN OA_Power LIKE '%,020101,%' THEN '1'
                ELSE '0'
            END AS can_create_task
        FROM OA_User
        WHERE OA_User IS NOT NULL 
          AND OA_Name IS NOT NULL
sql/add_can_create_task_to_sys_user.sql
New file
@@ -0,0 +1,16 @@
-- åœ¨sys_user表中添加是否可创建任务单字段
-- ä½œç”¨: æ ‡è¯†ç”¨æˆ·æ˜¯å¦æœ‰åˆ›å»ºä»»åŠ¡å•çš„æƒé™ï¼Œä»ŽOA系统同步
-- åŒæ­¥è§„则: OA_Power LIKE '%,020101,%' çš„用户可以创建任务单
-- æ·»åŠ å­—æ®µ
ALTER TABLE sys_user ADD COLUMN can_create_task CHAR(1) DEFAULT '0' COMMENT '是否可创建任务单(0否 1是)';
-- æ·»åŠ ç´¢å¼•ï¼ˆæ–¹ä¾¿æŸ¥è¯¢æœ‰æƒé™çš„ç”¨æˆ·ï¼‰
CREATE INDEX idx_can_create_task ON sys_user(can_create_task);
-- æŸ¥è¯¢éªŒè¯
SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'ry-vue'
  AND TABLE_NAME = 'sys_user'
  AND COLUMN_NAME = 'can_create_task';