| | |
| | | } |
| | | List<AppRegisterRegionPickerOptionVO> currentDistrictOptions = new ArrayList<AppRegisterRegionPickerOptionVO>(districts.size()); |
| | | for (SysRegion district : districts) { |
| | | if (shouldHideInAppPicker(district)) { |
| | | continue; |
| | | } |
| | | currentDistrictOptions.add(toPickerOption(district, 3)); |
| | | } |
| | | currentAreaOptions.add(currentDistrictOptions); |
| | |
| | | } |
| | | |
| | | /** |
| | | * APP 地区选择器不展示行政区划占位节点。 |
| | | */ |
| | | private boolean shouldHideInAppPicker(SysRegion region) { |
| | | return region.getName() != null && region.getName().contains("市辖区"); |
| | | } |
| | | |
| | | /** |
| | | * 将海诊通诊所地区编码或名称归一化为本地三级行政区划。 |
| | | */ |
| | | public RegionSelection resolveRegionSelection(String provinceCode, |
| | |
| | | clinic.setBusinessLicenseUrl(trimToNull(request.getBusinessLicenseUrl())); |
| | | clinic.setInviteCode(generateInviteCode()); |
| | | clinic.setStatus(1); |
| | | |
| | | clinic.setAgentId(4L); // 绑定到默认的省代上面 |
| | | |
| | | clinicMapper.insert(clinic); |
| | | |
| | | AppUser user = new AppUser(); |
| | |
| | | user.setClinicId(clinic.getId()); |
| | | userMapper.insert(user); |
| | | |
| | | // Integer provinceCode = parseRegionCode(clinic.getProvinceCode(), "省编码"); |
| | | // Integer cityCode = parseRegionCode(clinic.getCityCode(), "市编码"); |
| | | // Integer areaCode = parseRegionCode(clinic.getDistrictCode(), "区县编码"); |
| | | // log.info("调用微赞创建门店,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}", |
| | | // clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone()); |
| | | // try { |
| | | // JsonNode vzanResponse = vzanHttpUtil.createStore(clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone()); |
| | | // log.info("微赞创建门店成功,出参={}", vzanResponse); |
| | | // } catch (RuntimeException ex) { |
| | | // log.error("微赞创建门店失败,本地事务将回滚,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}", |
| | | // clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone(), ex); |
| | | // throw new IllegalStateException("微赞创建门店失败", ex); |
| | | // } |
| | | Integer provinceCode = parseRegionCode(clinic.getProvinceCode(), "省编码"); |
| | | Integer cityCode = parseRegionCode(clinic.getCityCode(), "市编码"); |
| | | Integer areaCode = parseRegionCode(clinic.getDistrictCode(), "区县编码"); |
| | | log.info("调用微赞创建门店,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}" |
| | | , clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone()); |
| | | try { |
| | | JsonNode vzanResponse = vzanHttpUtil.createStore(clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone()); |
| | | log.info("微赞创建门店成功,出参={}", vzanResponse); |
| | | // 注册阶段暂未绑定区域代理,parentMobile 传空,由微赞按一级代理处理。 |
| | | log.info("调用微赞创建一级代理,入参 agentMobile={}, agentName={}, levelCode=1, storeName={}, parentMobile=18510939636", |
| | | clinic.getContactPhone(), clinic.getName(), clinic.getName()); |
| | | JsonNode vzanAgentResponse = vzanHttpUtil.createAgent( |
| | | clinic.getContactPhone(), clinic.getName(), 1, "18510939636", clinic.getName()); |
| | | log.info("微赞创建一级代理成功,出参={}", vzanAgentResponse); |
| | | } catch (RuntimeException ex) { |
| | | log.error("微赞创建门店或一级代理失败,本地事务将回滚,clinicId={}", clinic.getId(), ex); |
| | | throw new IllegalStateException("微赞门店或一级代理创建失败", ex); |
| | | } |
| | | |
| | | clearDraft(request.getRegisterTicket()); |
| | | return createLoginResponse(user); |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | @Component |
| | | //@Component |
| | | public class DataInitializer implements ApplicationRunner { |
| | | |
| | | private final SysUserService sysUserService; |
| | |
| | | @RequestParam(required = false) String regionCode, |
| | | @RequestParam(required = false) String name, |
| | | @RequestParam(required = false) String phone, |
| | | @RequestParam(required = false) String agentName, |
| | | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdFrom, |
| | | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdTo, |
| | | @RequestParam(required = false) Integer status) { |
| | | return ApiResponse.ok(clinicAppService.page(page, pageSize, regionCode, name, phone, createdFrom, createdTo, status)); |
| | | return ApiResponse.ok(clinicAppService.page(page, pageSize, regionCode, name, phone, agentName, createdFrom, createdTo, status)); |
| | | } |
| | | |
| | | @GetMapping("/clinics/clinic-import-template") |
| | |
| | | @RequestParam(required = false) String regionCode, |
| | | @RequestParam(required = false) String name, |
| | | @RequestParam(required = false) String phone, |
| | | @RequestParam(required = false) String agentName, |
| | | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdFrom, |
| | | @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createdTo, |
| | | @RequestParam(required = false) Integer status) throws IOException { |
| | | clinicAppService.export(regionCode, name, phone, createdFrom, createdTo, status, response); |
| | | clinicAppService.export(regionCode, name, phone, agentName, createdFrom, createdTo, status, response); |
| | | } |
| | | |
| | | @GetMapping("/clinics/regions") |
| | |
| | | @Param("regionCode") String regionCode, |
| | | @Param("name") String name, |
| | | @Param("phone") String phone, |
| | | @Param("agentName") String agentName, |
| | | @Param("createdFrom") LocalDateTime createdFrom, |
| | | @Param("createdTo") LocalDateTime createdTo, |
| | | @Param("status") Integer status); |
| | |
| | | List<ClinicListRow> selectClinicList(@Param("regionCode") String regionCode, |
| | | @Param("name") String name, |
| | | @Param("phone") String phone, |
| | | @Param("agentName") String agentName, |
| | | @Param("createdFrom") LocalDateTime createdFrom, |
| | | @Param("createdTo") LocalDateTime createdTo, |
| | | @Param("status") Integer status); |
| | |
| | | private String phone; |
| | | private String regionName; |
| | | private Integer status; |
| | | private String agentName; |
| | | private Integer userCount; |
| | | private LocalDateTime createdAt; |
| | | private String businessLicenseUrl; |
| | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.tcm.training.admin.agent.entity.AgentRegion; |
| | | import com.tcm.training.admin.agent.entity.ProvincialAgent; |
| | | import com.tcm.training.admin.agent.excel.ClinicAgentImportRow; |
| | |
| | | import com.tcm.training.admin.member.vo.ClinicDetailVO; |
| | | import com.tcm.training.admin.member.vo.ClinicListItemVO; |
| | | import com.tcm.training.admin.member.vo.ClinicRegionOptionVO; |
| | | import com.tcm.training.admin.utils.VzanHttpUtil; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.jdbc.datasource.DataSourceTransactionManager; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | private final ProvincialAgentMapper provincialAgentMapper; |
| | | private final UserMapper userMapper; |
| | | private final SysRegionMapper sysRegionMapper; |
| | | private final VzanHttpUtil vzanHttpUtil; |
| | | private final DataSourceTransactionManager transactionManager; |
| | | |
| | | public ClinicAppService(ClinicQueryMapper clinicQueryMapper, |
| | |
| | | ProvincialAgentMapper provincialAgentMapper, |
| | | UserMapper userMapper, |
| | | SysRegionMapper sysRegionMapper, |
| | | VzanHttpUtil vzanHttpUtil, |
| | | DataSourceTransactionManager transactionManager) { |
| | | this.clinicQueryMapper = clinicQueryMapper; |
| | | this.clinicMapper = clinicMapper; |
| | | this.provincialAgentMapper = provincialAgentMapper; |
| | | this.userMapper = userMapper; |
| | | this.sysRegionMapper = sysRegionMapper; |
| | | this.vzanHttpUtil = vzanHttpUtil; |
| | | this.transactionManager = transactionManager; |
| | | } |
| | | |
| | |
| | | String regionCode, |
| | | String name, |
| | | String phone, |
| | | String agentName, |
| | | LocalDateTime createdFrom, |
| | | LocalDateTime createdTo, |
| | | Integer status) { |
| | |
| | | trimToNull(regionCode), |
| | | trimToNull(name), |
| | | trimToNull(phone), |
| | | trimToNull(agentName), |
| | | createdFrom, |
| | | createdTo, |
| | | status |
| | |
| | | public void export(String regionCode, |
| | | String name, |
| | | String phone, |
| | | String agentName, |
| | | LocalDateTime createdFrom, |
| | | LocalDateTime createdTo, |
| | | Integer status, |
| | |
| | | trimToNull(regionCode), |
| | | trimToNull(name), |
| | | trimToNull(phone), |
| | | trimToNull(agentName), |
| | | createdFrom, |
| | | createdTo, |
| | | status |
| | |
| | | throw e; |
| | | } |
| | | |
| | | if (request.getAgentId() == null) { |
| | | return detail(id); |
| | | } |
| | | |
| | | // 事务提交后,调用微赞接口(不回滚本地事务) |
| | | try { |
| | | Clinic clinic = clinicMapper.selectById(id); |
| | | |
| | | // 调用微赞创建门店 |
| | | Integer provinceCode = parseRegionCode(clinic.getProvinceCode(), "省编码"); |
| | | Integer cityCode = parseRegionCode(clinic.getCityCode(), "市编码"); |
| | | Integer areaCode = parseRegionCode(clinic.getDistrictCode(), "区县编码"); |
| | | log.info("调用微赞创建门店,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}", |
| | | clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone()); |
| | | JsonNode vzanStoreResponse = vzanHttpUtil.createStore(clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone()); |
| | | log.info("微赞创建门店成功,出参={}", vzanStoreResponse); |
| | | |
| | | // 查询代理信息获取parentMobile |
| | | ProvincialAgent agent = provincialAgentMapper.selectById(request.getAgentId()); |
| | | |
| | | String parentMobile = agent.getContactInfo(); |
| | | if (parentMobile == null || parentMobile.trim().isEmpty()) { |
| | | throw new IllegalArgumentException("代理联系方式为空,agentId=" + request.getAgentId()); |
| | | } |
| | | |
| | | // 调用微赞创建代理 |
| | | log.info("调用微赞创建代理,入参 agentMobile={}, agentName={}, levelCode=1, storeName={}, parentMobile={}", |
| | | clinic.getContactPhone(), clinic.getName(), clinic.getName(), parentMobile); |
| | | JsonNode vzanAgentResponse = vzanHttpUtil.createAgent( |
| | | clinic.getContactPhone(), // agentMobile传门店的手机号 |
| | | clinic.getName(), // agentName传门店的名称 |
| | | 1, // levelCode传1 |
| | | parentMobile, // parentMobile传代理的contact_info |
| | | clinic.getName() // storeName传门店的name |
| | | ); |
| | | log.info("微赞创建代理成功,出参={}", vzanAgentResponse); |
| | | |
| | | } catch (Exception e) { |
| | | // 捕获微赞接口异常,不回滚本地事务 |
| | | log.error("调用微赞接口失败,但本地数据已保存成功,clinicId={}", id, e); |
| | | throw new IllegalStateException("调用微赞失败", e); |
| | | } |
| | | |
| | | return detail(id); |
| | | } |
| | | |
| | | private Integer parseRegionCode(String code, String fieldName) { |
| | | if (code == null || code.trim().isEmpty()) { |
| | | throw new IllegalArgumentException(fieldName + "不能为空"); |
| | | } |
| | | try { |
| | | return Integer.parseInt(code); |
| | | } catch (NumberFormatException e) { |
| | | throw new IllegalArgumentException(fieldName + "格式错误: " + code); |
| | | } |
| | | } |
| | | |
| | | private void addCode(Set<String> codes, String code) { |
| | |
| | | item.setPhone(row.getPhone() == null ? "-" : PhoneMasker.mask(row.getPhone())); |
| | | item.setRegionName(defaultText(row.getRegionName())); |
| | | item.setStatus(row.getStatus()); |
| | | item.setAgentName(defaultText(row.getAgentName())); |
| | | item.setUserCount(row.getUserCount() == null ? 0 : row.getUserCount()); |
| | | item.setCreatedAt(row.getCreatedAt()); |
| | | item.setBusinessLicenseUrl(trimToNull(row.getBusinessLicenseUrl())); |
| | |
| | | private String phone; |
| | | private String regionName; |
| | | private Integer status; |
| | | private String agentName; |
| | | private Integer userCount; |
| | | private LocalDateTime createdAt; |
| | | private String businessLicenseUrl; |
| | |
| | | <if test="phone != null and phone.trim() != ''"> |
| | | AND c.contact_phone LIKE CONCAT('%', #{phone}, '%') |
| | | </if> |
| | | <if test="agentName != null and agentName.trim() != ''"> |
| | | AND pa.agent_name LIKE CONCAT('%', #{agentName}, '%') |
| | | </if> |
| | | <if test="createdFrom != null"> |
| | | AND c.created_at >= #{createdFrom} |
| | | </if> |
| | |
| | | ELSE NULL |
| | | END AS regionName, |
| | | c.status, |
| | | pa.agent_name AS agentName, |
| | | COUNT(u.id) AS userCount, |
| | | c.created_at AS createdAt, |
| | | c.business_license_url AS businessLicenseUrl |
| | | FROM tcm_clinic c |
| | | LEFT JOIN tcm_provincial_agent pa ON pa.id = c.agent_id AND pa.deleted = 0 |
| | | LEFT JOIN tcm_app_user u ON u.clinic_id = c.id AND u.deleted = 0 |
| | | <include refid="ClinicWhere"/> |
| | | GROUP BY |
| | |
| | | c.city_name, |
| | | c.district_name, |
| | | c.status, |
| | | pa.agent_name, |
| | | c.created_at, |
| | | c.business_license_url |
| | | ORDER BY c.id DESC |
| | |
| | | ELSE NULL |
| | | END AS regionName, |
| | | c.status, |
| | | pa.agent_name AS agentName, |
| | | COUNT(u.id) AS userCount, |
| | | c.created_at AS createdAt, |
| | | c.business_license_url AS businessLicenseUrl |
| | | FROM tcm_clinic c |
| | | LEFT JOIN tcm_provincial_agent pa ON pa.id = c.agent_id AND pa.deleted = 0 |
| | | LEFT JOIN tcm_app_user u ON u.clinic_id = c.id AND u.deleted = 0 |
| | | <include refid="ClinicWhere"/> |
| | | GROUP BY |
| | |
| | | c.city_name, |
| | | c.district_name, |
| | | c.status, |
| | | pa.agent_name, |
| | | c.created_at, |
| | | c.business_license_url |
| | | ORDER BY c.id DESC |
| | |
| | | |
| | | import com.tcm.training.admin.app.register.entity.SysRegion; |
| | | import com.tcm.training.admin.app.register.mapper.SysRegionMapper; |
| | | import com.tcm.training.admin.app.register.vo.AppRegisterRegionDatasetVO; |
| | | import com.tcm.training.admin.app.register.vo.AppRegisterRegionOptionVO; |
| | | import org.junit.jupiter.api.BeforeEach; |
| | | import org.junit.jupiter.api.Test; |
| | |
| | | import org.mockito.Mock; |
| | | import org.mockito.junit.jupiter.MockitoExtension; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * APP 完整地区数据集应过滤区县层级的市辖区占位节点。 |
| | | */ |
| | | @Test |
| | | void buildRegionDatasetShouldHideMunicipalDistrictPlaceholder() { |
| | | SysRegion province = buildRegion(1L, "江西省", "360000", 0L); |
| | | SysRegion city = buildRegion(2L, "吉安市", "360800", 1L); |
| | | SysRegion placeholder = buildRegion(3L, "吉安市市辖区", "360801", 2L); |
| | | SysRegion district = buildRegion(4L, "吉州区", "360802", 2L); |
| | | when(sysRegionMapper.selectAllOrdered()).thenReturn( |
| | | Arrays.asList(province, city, placeholder, district) |
| | | ); |
| | | |
| | | AppRegisterRegionDatasetVO dataset = appRegisterRegionService.buildRegionDataset(); |
| | | |
| | | assertEquals(1, dataset.getAreas().size()); |
| | | assertEquals(1, dataset.getAreas().get(0).size()); |
| | | assertEquals(1, dataset.getAreas().get(0).get(0).size()); |
| | | assertEquals("吉州区", dataset.getAreas().get(0).get(0).get(0).getLabel()); |
| | | assertEquals("360802", dataset.getAreas().get(0).get(0).get(0).getValue()); |
| | | } |
| | | |
| | | /** |
| | | * 海诊通仅返回地区名称时,应通过 sys_region 解析出完整编码。 |
| | | */ |
| | | @Test |
| | |
| | | component: './member/user/redirect', |
| | | }, |
| | | { |
| | | name: '用户管理', |
| | | path: '/member/user/manage', |
| | | access: 'member:user:view', |
| | | component: './member/user', |
| | | name: '省代用户', |
| | | path: '/member/user/agent', |
| | | access: 'member:agent:view', |
| | | component: './member/agent', |
| | | }, |
| | | { |
| | | name: '诊所管理', |
| | | name: '诊所用户', |
| | | path: '/member/user/clinic', |
| | | access: 'member:clinic:view', |
| | | component: './member/clinic', |
| | | }, |
| | | { |
| | | name: '省代理管理', |
| | | path: '/member/user/agent', |
| | | access: 'member:agent:view', |
| | | component: './member/agent', |
| | | name: '普通用户', |
| | | path: '/member/user/manage', |
| | | access: 'member:user:view', |
| | | component: './member/user', |
| | | }, |
| | | { |
| | | name: '会员用户', |
| | | path: '/member/user/member-users', |
| | | access: 'member:user:member:view', |
| | | component: './member/user/member-users', |
| | | }, |
| | | { |
| | | name: '编辑诊所', |
| | |
| | | hideInMenu: true, |
| | | access: 'member:clinic:view', |
| | | component: './member/clinic/detail', |
| | | }, |
| | | { |
| | | name: '会员用户', |
| | | path: '/member/user/member-users', |
| | | access: 'member:user:member:view', |
| | | component: './member/user/member-users', |
| | | }, |
| | | { |
| | | name: '会员用户详情', |
| | |
| | | }, |
| | | { |
| | | path: '/live', |
| | | name: '课程与学习', |
| | | name: '内容', |
| | | icon: 'read', |
| | | access: 'canViewLiveModule', |
| | | routes: [ |
| | | { |
| | | path: '/live', |
| | | redirect: '/live/course', |
| | | redirect: '/live/course/broadcast', |
| | | }, |
| | | { |
| | | path: '/live/expert', |
| | | redirect: '/live/course/expert', |
| | | }, |
| | | { |
| | | path: '/live/course', |
| | | name: '课程管理', |
| | | name: '类直播', |
| | | routes: [ |
| | | { |
| | | path: '/live/course', |
| | | redirect: '/live/course/broadcast', |
| | | }, |
| | | { |
| | | name: '养生直播', |
| | | path: '/live/course/broadcast', |
| | |
| | | component: './live/broadcast/edit', |
| | | }, |
| | | { |
| | | name: '微赞直播间设置', |
| | | path: '/live/course/live-room-setting', |
| | | access: 'live:room-setting:view', |
| | | component: './live/live-room-setting', |
| | | }, { |
| | | name: '直播预约记录', |
| | | name: '预约记录', |
| | | path: '/live/course/reserve-records', |
| | | access: 'live:reserve:view', |
| | | component: './live/reserve-records', |
| | | }, |
| | | { |
| | | name: '直播观看记录', |
| | | name: '观看记录', |
| | | path: '/live/course/watch-records', |
| | | access: 'live:watch:view', |
| | | component: './live/watch-records', |
| | | }, |
| | | { |
| | | name: '分类管理', |
| | | name: '直播预约记录跳转', |
| | | path: '/live/course/broadcast/:courseId/sessions/:sessionId/reserves', |
| | | hideInMenu: true, |
| | | access: 'live:reserve:view', |
| | | component: './live/record-redirect', |
| | | }, |
| | | { |
| | | name: '直播观看记录跳转', |
| | | path: '/live/course/broadcast/:courseId/sessions/:sessionId/watch-records', |
| | | hideInMenu: true, |
| | | access: 'live:watch:view', |
| | | component: './live/record-redirect', |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | name: '微赞直播', |
| | | routes: [ |
| | | { |
| | | name: '直播设置', |
| | | path: '/live/course/live-room-setting', |
| | | access: 'live:room-setting:view', |
| | | component: './live/live-room-setting', |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | name: '课程', |
| | | routes: [ |
| | | { |
| | | name: '添加分类', |
| | | path: '/live/course/category', |
| | | access: 'live:category:view', |
| | | component: './live/category', |
| | | }, |
| | | { |
| | | name: '养生课程', |
| | | name: '添加专家', |
| | | path: '/live/course/expert', |
| | | access: 'live:expert:view', |
| | | component: './live/expert', |
| | | }, |
| | | { |
| | | name: '添加课堂', |
| | | path: '/live/course/health', |
| | | access: 'live:health-course:view', |
| | | component: './live/health', |
| | | }, |
| | | { |
| | | name: '视频与课程资源管理', |
| | | name: '添加视频', |
| | | path: '/live/course/video-resources', |
| | | access: 'live:health-course:view', |
| | | component: './live/video-resources', |
| | |
| | | access: 'live:health-course:video:edit', |
| | | component: './live/health/video-manage', |
| | | }, |
| | | { |
| | | name: '直播预约记录跳转', |
| | | path: '/live/course/broadcast/:courseId/sessions/:sessionId/reserves', |
| | | hideInMenu: true, |
| | | access: 'live:reserve:view', |
| | | component: './live/record-redirect', |
| | | }, |
| | | { |
| | | name: '直播观看记录跳转', |
| | | path: '/live/course/broadcast/:courseId/sessions/:sessionId/watch-records', |
| | | hideInMenu: true, |
| | | access: 'live:watch:view', |
| | | component: './live/record-redirect', |
| | | }, |
| | | { |
| | | name: '授课专家管理', |
| | | path: '/live/course/expert', |
| | | access: 'live:expert:view', |
| | | component: './live/expert', |
| | | }, |
| | | { |
| | | path: '/live/course/project-introduction/create', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/project-introduction/create', |
| | | }, |
| | | { |
| | | path: '/live/course/project-introduction', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/project-introduction', |
| | | }, |
| | | { |
| | | path: '/live/course/project-introduction-password', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/project-introduction-password', |
| | | }, |
| | | { |
| | | path: '/live/course/health-popularization/create', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/health-popularization/create', |
| | | }, |
| | | { |
| | | path: '/live/course/health-popularization', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/health-popularization', |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: '/live/promotion', |
| | | name: '宣传知识', |
| | | name: '科普', |
| | | routes: [ |
| | | { |
| | | path: '/live/promotion', |
| | | redirect: '/live/promotion/project-introduction', |
| | | }, |
| | | { |
| | | name: '健康知识', |
| | | path: '/live/promotion/health-popularization', |
| | |
| | | access: 'live:health-promotion:edit', |
| | | component: './live/health-promotion/manage', |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | name: '项目', |
| | | routes: [ |
| | | { |
| | | name: '项目密码', |
| | | path: '/live/promotion/project-introduction-password', |
| | | access: 'live:project-introduction:view', |
| | | component: './live/project-introduction-password', |
| | | }, |
| | | { |
| | | name: '项目介绍', |
| | | path: '/live/promotion/project-introduction', |
| | |
| | | component: './live/project-introduction/manage', |
| | | }, |
| | | { |
| | | name: '项目介绍密码', |
| | | path: '/live/promotion/project-introduction-password', |
| | | access: 'live:project-introduction:view', |
| | | component: './live/project-introduction-password', |
| | | path: '/live/course/project-introduction/create', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/project-introduction/create', |
| | | }, |
| | | { |
| | | path: '/live/course/project-introduction', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/project-introduction', |
| | | }, |
| | | { |
| | | path: '/live/course/project-introduction-password', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/project-introduction-password', |
| | | }, |
| | | { |
| | | path: '/live/course/health-popularization/create', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/health-popularization/create', |
| | | }, |
| | | { |
| | | path: '/live/course/health-popularization', |
| | | hideInMenu: true, |
| | | redirect: '/live/promotion/health-popularization', |
| | | }, |
| | | ], |
| | | }, |
| | |
| | | import type { Settings as LayoutSettings } from '@ant-design/pro-components'; |
| | | import type { MenuDataItem, Settings as LayoutSettings } from '@ant-design/pro-components'; |
| | | import { SettingDrawer } from '@ant-design/pro-components'; |
| | | import type { RunTimeLayoutConfig } from '@umijs/max'; |
| | | import { history } from '@umijs/max'; |
| | |
| | | loading?: boolean; |
| | | }; |
| | | |
| | | const ensureMenuKeys = (menuData: MenuDataItem[], parentKey = 'menu'): MenuDataItem[] => |
| | | menuData.map((item, index) => { |
| | | const key = item.key || item.path || `${parentKey}-${String(item.name || index)}`; |
| | | |
| | | return { |
| | | ...item, |
| | | key, |
| | | children: item.children ? ensureMenuKeys(item.children, String(key)) : item.children, |
| | | }; |
| | | }); |
| | | |
| | | /** |
| | | * @see https://umijs.org/zh-CN/plugins/plugin-initial-state |
| | |
| | | ); |
| | | }, |
| | | ...initialState?.settings, |
| | | menuDataRender: ensureMenuKeys, |
| | | pageTitleRender: () => '管理后台', |
| | | }; |
| | | }; |
| | |
| | | <Space size={16} className="live-broadcast-table__actions"> |
| | | {canDetail ? ( |
| | | <button type="button" onClick={() => history.push(`/live/course/broadcast/${record.id}`)}> |
| | | 详情 |
| | | 类直播 |
| | | </button> |
| | | ) : null} |
| | | {canEdit ? ( |
| | |
| | | regionCode?: string[]; |
| | | name?: string; |
| | | phone?: string; |
| | | agentName?: string; |
| | | status?: number; |
| | | createdRange?: Dayjs[]; |
| | | }; |
| | |
| | | phone?: string; |
| | | regionName?: string; |
| | | status: number; |
| | | agentName?: string; |
| | | userCount: number; |
| | | createdAt: string; |
| | | businessLicenseUrl?: string; |
| | |
| | | regionCode: getSelectedRegionCode(values.regionCode), |
| | | name: values.name?.trim() || undefined, |
| | | phone: values.phone?.trim() || undefined, |
| | | agentName: values.agentName?.trim() || undefined, |
| | | status: values.status, |
| | | createdFrom: values.createdRange?.[0] |
| | | ? dayjs(values.createdRange[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss') |
| | |
| | | render: (value) => (value === 1 ? '启用' : value === 2 ? '注销' : '禁用'), |
| | | }, |
| | | { |
| | | title: '省代', |
| | | dataIndex: 'agentName', |
| | | width: 140, |
| | | render: (value) => value || '-', |
| | | }, |
| | | { |
| | | title: '注册时间', |
| | | dataIndex: 'createdAt', |
| | | width: 180, |
| | |
| | | ]} |
| | | /> |
| | | </Form.Item> |
| | | <Form.Item label="省代" name="agentName"> |
| | | <Input placeholder="请输入" allowClear /> |
| | | </Form.Item> |
| | | <Form.Item label="注册时间" name="createdRange"> |
| | | <DatePicker.RangePicker className="member-clinic-search-form__range" /> |
| | | </Form.Item> |
| | |
| | | import { cacheInviteCode } from '@/utils/invite-cache.js' |
| | | import { initJPushLifecycle } from '@/utils/push.js' |
| | | import { |
| | | checkAppVersionUpdate, |
| | | resumeForceUpdatePrompt |
| | | } from '@/utils/app-version.js' |
| | | import { |
| | | getOpeninstallInstallData, |
| | | initOpeninstall, |
| | | registerOpeninstallWakeUp |
| | |
| | | initOpeninstall() |
| | | registerOpeninstallWakeUp(handleOpeninstallPayload) |
| | | getOpeninstallInstallData(handleOpeninstallPayload) |
| | | checkAppVersionUpdate() |
| | | // #endif |
| | | }) |
| | | |
| | |
| | | */ |
| | | onShow(() => { |
| | | handleOpenByScheme() |
| | | // #ifdef APP-PLUS |
| | | resumeForceUpdatePrompt() |
| | | // #endif |
| | | }) |
| | | |
| | | /** |
| | |
| | | // BASE_URL: 'http://221.182.45.100:48080', |
| | | BASE_URL: 'https://haizhentong.com', |
| | | OPENINSTALL_URL: 'https://m.malink.cn/s/7bIzeu', |
| | | APP_STORE_URL: '', |
| | | uploadUrl: "https://haizhentong.com/api/open/file/upload", |
| | | imageUrl: 'http://221.182.45.100:11224/images/' |
| | | }; |
| | |
| | | "name" : "海诊康道", |
| | | "appid" : "__UNI__7B5B0CA", |
| | | "description" : "", |
| | | "versionName" : "1.0.23", |
| | | "versionCode" : 122, |
| | | "versionName" : "1.0.0", |
| | | "versionCode" : 100, |
| | | "transformPx" : false, |
| | | /* 5+App特有相关 */ |
| | | "app-plus" : { |
| | |
| | | |
| | | <!-- 内容区域 --> |
| | | <view class="content-area"> |
| | | <text class="content-title">{{ detail.title || '健康科普详情' }}</text> |
| | | <text v-if="isVideoType" class="content-title">{{ detail.title || '健康科普详情' }}</text> |
| | | <text v-if="isVideoType && videoSubtitle" class="content-subtitle">{{ videoSubtitle }}</text> |
| | | <rich-text v-if="!isVideoType" :nodes="contentNodes" class="rich-content"></rich-text> |
| | | <view v-if="loading" class="status-text">加载中...</view> |
| | |
| | | } |
| | | |
| | | .rich-content { |
| | | margin-top: 24rpx; |
| | | width: 100%; |
| | | font-size: 28rpx; |
| | | line-height: 1.8; |
| | |
| | | <!-- 导航栏 --> |
| | | <view class="nav-bar"> |
| | | <image class="back-icon" src="/static/image/home/left.png" mode="aspectFit" @tap="goBack"></image> |
| | | <text class="nav-title">{{ detail.title || '详情' }}</text> |
| | | <text class="nav-title">详情</text> |
| | | <view class="nav-right-placeholder"></view> |
| | | </view> |
| | | |
| | |
| | | |
| | | <!-- 图文内容 --> |
| | | <view v-if="detail.contentType === 1"> |
| | | <!-- 标题 --> |
| | | <view class="detail-title">{{ detail.title }}</view> |
| | | <!-- 内容 --> |
| | | <view v-if="detail.content" class="html-content"> |
| | | <rich-text :nodes="detail.content"></rich-text> |
| | |
| | | const userAgent = res?.data?.ua?.trim(); |
| | | console.log('[goWeiZanLive] 解析目标URL:', targetUrl); |
| | | |
| | | if (res?.code !== 0 || !targetUrl || !userAgent) { |
| | | if (res?.code !== 0 || !targetUrl) { |
| | | console.log('[goWeiZanLive] 获取跳转地址失败,code:', res?.code, 'msg:', res?.msg); |
| | | uni.showToast({ title: res?.msg || '获取直播间地址失败', icon: 'none' }); |
| | | return; |
| | |
| | | |
| | | .course-info { |
| | | flex: 1; |
| | | min-width: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | |
| | | .course-top { |
| | | display: flex; |
| | | min-width: 0; |
| | | align-items: center; |
| | | margin-top: 4rpx; |
| | | |
| | |
| | | font-weight: bold; |
| | | color: #333; |
| | | flex: 1; |
| | | min-width: 0; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | |
| | | }) |
| | | |
| | | /** |
| | | * 关闭登录页,优先返回上一页,没有页面栈时回首页。 |
| | | * 关闭登录页统一回到首页,避免从需要登录的 tabbar 页面返回后再次触发登录拦截。 |
| | | */ |
| | | const handleClose = () => { |
| | | if (getCurrentPages().length > 1) { |
| | | uni.navigateBack() |
| | | return |
| | | } |
| | | uni.switchTab({ |
| | | url: '/pages/home/home' |
| | | }) |
| | |
| | | </view> |
| | | |
| | | <view class="list-section card-box"> |
| | | <view class="list-item" v-for="(item, index) in bottomMenuList" :key="`${item.title}-${index}`" @tap="goPage(item)"> |
| | | <view class="list-item" v-for="(item, index) in bottomMenuList" :key="`${item.title}-${index}`" @tap="handleBottomMenuClick(item)"> |
| | | <view class="list-left"> |
| | | <image class="list-icon" :class="item.iconClass" :src="item.icon" mode="aspectFit"></image> |
| | | <text class="list-title">{{ item.title }}</text> |
| | |
| | | import { onLoad, onShow } from '@dcloudio/uni-app' |
| | | import { getNoticeList } from '../notice/service' |
| | | import { getUserCenter } from './service' |
| | | import { getRuntimeVersionName } from '@/utils/app-version.js' |
| | | import { hasStoredSession } from '@/utils/auth-session.js' |
| | | import manifestConfig from '../../manifest.json' |
| | | |
| | |
| | | const statusBarHeight = ref(0) |
| | | const unreadNoticeTotal = ref(0) |
| | | const manifestVersionName = String(manifestConfig.versionName || '') |
| | | const appVersionText = ref(manifestVersionName) |
| | | const appVersionText = manifestVersionName |
| | | const userCenter = ref<UserCenterData>(createDefaultUserCenter()) |
| | | const userRole = computed<UserRole>(() => { |
| | | return isDoctorLikeUser(userCenter.value) ? 'doctor' : 'personal' |
| | |
| | | onLoad(() => { |
| | | const systemInfo = uni.getSystemInfoSync() |
| | | statusBarHeight.value = systemInfo.statusBarHeight || 20 |
| | | resolveAppVersion() |
| | | syncCachedUserInfo() |
| | | }) |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 处理底部普通菜单跳转。 |
| | | */ |
| | | async function handleBottomMenuClick(item: MenuItem) { |
| | | goPage(item) |
| | | } |
| | | |
| | | /** |
| | | * 统一跳转个人中心相关页面。 |
| | | */ |
| | | function goPage(target: string | MenuItem) { |
| | |
| | | url: '/pages/login/login', |
| | | }) |
| | | }, 200) |
| | | } |
| | | |
| | | /** |
| | | * APP 环境读取当前安装包版本,非 APP 环境使用 manifest 版本兜底。 |
| | | */ |
| | | function resolveAppVersion() { |
| | | getRuntimeVersionName(manifestVersionName).then((versionName) => { |
| | | appVersionText.value = versionName || manifestVersionName |
| | | }).catch(() => { |
| | | appVersionText.value = manifestVersionName |
| | | }) |
| | | } |
| | | |
| | | /** |
| | |
| | | color: #1A1A1A; |
| | | margin-left: 22rpx; |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |